Skip to content

research(agent-memory): signed eviction-witness anchoring (ADR-346) - #974

Draft
ruvnet wants to merge 6 commits into
mainfrom
claude/focused-darwin-qrtfle
Draft

ruvnet wants to merge 6 commits into
mainfrom
claude/focused-darwin-qrtfle

Conversation

@ruvnet

@ruvnet ruvnet commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Hypothesis

Continues the 2026-09-05 nightly run's ("mincut-gated forgetting", ADR-345) "Next Research" item 4: wire an Ed25519 WitnessSigner into ruvector-agent-memory's eviction-witness chain so eviction receipts are signed, not just FNV-1a hash-chained. Both crate::ops's own module docs and ADR-134 name this as an explicit follow-up gate ("MUST land before WP8 cross-repo anchoring makes this log load-bearing").

Given an eviction-witness chain of N = 4,096 real compact_witnessed records,
when the chain head is signed with Ed25519 (rvf_types, already a dependency,
no new signature scheme) either every record (candidate_a) or periodically
every K records (candidate_b), vs. baseline (FNV-1a chaining only),
then a real relink attack (mutate + recompute every downstream hash, incl.
the head commitment) is caught 100% of the time by any covering signed
anchor and 0% of the time by baseline's verify_chain() alone,
subject to zero false positives on anchors predating the tamper, and
amortized signing cost dropping materially as K grows.

Full formal statement: ADR-346.

Architecture

  • New module ruvector-agent-memory::eviction_witness_signing: signs/verifies a (sequence, chain_head) commitment via rvf_types::ed25519_sign/ed25519_verify (already an unconditional crate dependency, used elsewhere for AtomicObservation, ADR-320 — no new signature scheme).
  • Reuses the interval/staleness anchor-policy shape ruvector-retrieval-receipt::state_anchor (ADR-342) already proved out for a different chain.
  • Includes relink_tampered_suffix, a real attack-simulation utility (not asserted-only) used by both unit tests and the benchmark.
  • No Cargo.toml changes, no new feature flag.

Files Changed

  • crates/ruvector-agent-memory/src/eviction_witness_signing.rs (new module, 7 unit tests)
  • crates/ruvector-agent-memory/src/lib.rs (registers + re-exports the module)
  • crates/ruvector-agent-memory/examples/signed_eviction_witness_bench.rs (new benchmark)
  • docs/adr/ADR-346-signed-eviction-witness-anchoring.md
  • docs/research/nightly/2026-09-09-signed-eviction-witness-anchoring/README.md (full methodology + raw output)
  • docs/research/nightly/2026-09-09-signed-eviction-witness-anchoring/gist.md

Benchmark Command

cargo run --release -p ruvector-agent-memory --example signed_eviction_witness_bench

Real Benchmark Results (4 independent runs)

  • Signing cost (candidate_a, K=1): 77,536 / 78,399 / 81,694 / 80,325 ns/anchor (software Ed25519; per-call SigningKey re-derivation in rvf_types::ed25519_sign is the likely dominant cost — flagged as next research, not fixed here).
  • Verify cost: ~48,200–49,000 ns/op, all 4 runs.
  • Memory overhead: K=1 → 137.5% of the raw chain (anchors bigger than what they protect); K=16 → 8.6%; K=4096 → 0.034%.
  • Adversarial detection (K=16, 111 sampled real relink-attack positions): 111/111 (100%) detected once a covering anchor exists; 0 false positives on anchors predating the tamper. Baseline (FNV-1a only): the same relinked, fully-consistent tampered log still reports verify_chain() == true.

Acceptance Result

ACCEPT — both fixed, pre-registered thresholds (100% detection once covered; 0 false positives) held on all 4 runs.

Darwin Result

Not executed. No applicable, wired Darwin CLI was found for this crate/domain (verified, not assumed — see the research README's "Capabilities Actually Discovered" table). A 12-point exhaustive interval sweep (K = 1..4096) served the analogous parameter-search role and is reported as a benchmark result, not an evolutionary promotion, to avoid overclaiming.

Flywheel Result

No automated Flywheel tooling was invoked (same reason as Darwin — verified not present/applicable for this task shape in this environment). This PR itself is the retained evidence: hypothesis, sources, decisions, benchmark config/output, and rejected alternatives are all in the ADR and research README, continuing the previous run's named gap rather than starting a new island.

Security Review

Threat model: a log-writing adversary without the Ed25519 secret key. Domain-separated signed statements prevent cross-purpose replay. Key management (who holds the signing key, rotation, public-key distribution) is explicitly unaddressed — this ships the primitive, not a deployment (see ADR-346 Open Questions). No existing test, gate, or acceptance threshold was weakened to reach ACCEPT.

Main Limitations

  • Single corpus size (N=4,096) tested end-to-end.
  • 111 sampled tamper positions (fixed stride), not exhaustive over all 4,096.
  • Signing-cost attribution to per-call key re-derivation is inference from reading rvf_types, not isolated by a dedicated micro-benchmark this run.
  • Not wired into any production call site (compact_witnessed doesn't call this yet) — deliberately deferred pending a key-management decision.

Production Recommendation

Module: ship as additive, default-compiled-in code (done, this PR). Production integration: not yet — first needs (1) a key-management decision and (2) ideally closing the signing-cost gap (Open Question 2) before running on any hot eviction path. K≈16 recommended as the interval once integration happens.

Research Document

docs/research/nightly/2026-09-09-signed-eviction-witness-anchoring/README.md

ADR

docs/adr/ADR-346-signed-eviction-witness-anchoring.md

Gist

docs/research/nightly/2026-09-09-signed-eviction-witness-anchoring/gist.md


🤖 Generated with claude-flow

https://claude.ai/code/session_01DBMdqRntfcDC5Hwhvota67


Generated by Claude Code

claude and others added 6 commits September 9, 2026 07:30
Closes the adversarial gap named in crate::ops's own docs and the
2026-09-05 nightly run's Next Research: the ADR-345 eviction-witness
chain's FNV-1a linking is tamper-evident against accidental corruption
only, not against a log-writing adversary who can relink the whole
chain. EvictionAnchorLog periodically signs the chain head with
rvf_types' Ed25519 (already an unconditional dependency of this crate,
no new signature scheme), reusing the interval/staleness anchor shape
ADR-342 proved out for a different chain.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DBMdqRntfcDC5Hwhvota67
…bench

Sweeps 12 anchor intervals over a real 4,096-record compact_witnessed
chain, measuring signing/verify cost and memory overhead, and runs a
real relink-attack simulation (111 sampled tamper positions) proving
100% detection once a covering anchor exists vs. 0% for FNV-1a chaining
alone. Raw output reproduced in the accompanying nightly research doc
and ADR-346.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DBMdqRntfcDC5Hwhvota67
CI's clippy runs --all-targets (test code included), unlike the --lib
--examples scope validated locally before the initial push. Rewrite
`max_seen <= interval - 1` as the equivalent `max_seen < interval`.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01DBMdqRntfcDC5Hwhvota67

ruvnet commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

CI status on 713ccf08b: every check passed except Tests (core-and-rest), which shows cancelled.

Looked at its log: it was still compiling workspace dependencies (last line before cancellation was Compiling ruvector-temporal-tensor-wasm) when it was cancelled ~4 hours after starting, with The operation was canceled. — no compiler error, no test assertion failure, never reached any test body. This PR's diff touches only ruvector-agent-memory (a leaf library crate, no changes to anything this shard's dependency graph would newly pull in), so a 4-hour mid-compile timeout on this large monorepo's slowest shard reads as infra/runner-timeout, not something this diff caused.

Re-ran the failed job via rerun_failed_jobs (this PR's one allowed re-run for a job that died before any test body ran). Watching for the result.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants