feat: ADR-280/281/282 — durable RVF metadata, role-aware embeddings, nightly research quality gate - #774
Merged
Merged
Conversation
…3-275 # Conflicts: # npm/package-lock.json # npm/packages/ruvector/package.json
…beddings, nightly research quality gate - ADR-280: RVF durable self-contained metadata (rvf-types metadata.rs, runtime options/store/filter/safety_net/vector_slab wiring, crash-safety and durability integration tests) - ADR-281: role-aware embedding APIs in ruvector-core (embeddings.rs, agenticdb.rs, error taxonomy across core/hailo/hailo-cluster, role test) - ADR-282: nightly research quality gate (research-gate scripts + schemas, research-* GitHub workflows, CODEOWNERS, sota-bench metaharness harness with scorePolicy + darwin.ts, gate override + promote pipelines) ADRs renumbered from 273-275 to 280-282 after PR #752 took 273-279. Co-Authored-By: claude-flow <ruv@ruv.net>
… overshoot - cache_hits_skip_transport_after_first_call: capacity 16 gives per-shard capacity 1 (16 shards), so distinct texts whose keys hash to one shard evict each other — the embedding_space_id cache-key change surfaced this. Capacity 32 makes the test independent of hash layout. - ttl_insert_refreshes_timestamp: 20ms TTL with a 5ms margin fails deterministically on macOS where thread::sleep overshoots; widened to 100ms TTL with ~30ms margins. Pre-existing on main. Co-Authored-By: claude-flow <ruv@ruv.net>
…ild repair, allocation clamp, durability ordering - Metadata commits now emit generation deltas against the previous committed state (full snapshot on generation 1 and every 32 deltas), replacing full-snapshot-per-commit which made artifacts permanently unopenable past the replay ceiling (600 commits: META payload 5.4MB -> 241KB, reopen 10.2ms, compact() -> single 127KB META segment) - restore_metadata walks newest-first to the nearest full snapshot; tolerates corrupt superseded generations and torn newest (falls back) - compact() rewrites one fresh snapshot instead of preserving stale META segments (which could reference removed vectors) - derive() children no longer persist parent metadata for vectors they cannot resolve; child metadata_generation resets to 0 - rvf-types metadata decode clamps Vec preallocation to what remaining input can hold (80-byte blob claiming 16.7M records no longer drives a 512MB allocation) - delete/set_file_metadata now fsync payload before manifest (matching ingest); compact propagates dir-fsync errors - crash tests extended to delete/set_file_metadata paths; fixed the crash-test segment walker which was finding no boundaries at all - fix rvf-launch compile break from SearchResult.metadata field Co-Authored-By: claude-flow <ruv@ruv.net>
…late fidelity, working legacy constructors - Legacy constructors (LatticeEmbedding::from_pretrained/with_model, HailoEmbedder::open, HailoClusterEmbedder::new) derive identities from the model registry instead of failing unconditionally — worker daemon and all ten unmigrated call sites work again; hailo overwrites registry hash defaults with digests of on-disk artifacts - Prompt templates now come from the attested identity (verified against prompt_template_sha256) instead of hardcoded query:/passage: strings; PrefixPolicy::Custom and hash mismatches fail closed - AgenticDB persists the embedding-space identity and enforces EmbeddingSpaceAccess::for_identities on reopen — same-dimension different-model corpora now error with EmbeddingSpaceMismatch instead of silently mixing spaces; missing stored identity adopts-with-warning so pre-ADR data still opens - Artifact provenance verification runs in every build and fails closed on unrecognized layouts or missing declared artifacts - Model registry rewritten as pinned per-model entries; adds all-mpnet-base-v2, multilingual-e5-large, paraphrase-multilingual- MiniLM-L12-v2 and missing e5 aliases Co-Authored-By: claude-flow <ruv@ruv.net>
…ly run, and fails closed - Candidate checkout diff no longer dies on shallow clone: base commit is fetched from the sibling base checkout on disk (no network); the gate had never completed a single run - Fixed jq quoting that made the authorized-red override branch dead code (exit 3 on every invocation) - Check-run/status queries paginate and cross-check total_count, fail closed on mismatch — on a real main commit 8 of 22 failures (Build, Test, cargo-audit among them) were invisible to the old single-page query and a red base could be certified green - Schemas are now load-bearing: offline-registry jsonschema validation (pinned jsonschema==4.23.0) runs before the hand-rolled checks in every gate validator; report.json is schema-validated and its headline values re-bound to the trusted evaluation (ADR-282 acceptance criterion 13, previously unimplemented) - Override requires approver != dispatcher; promote workflow moves html_url into env; deletions now included in preflight scan (a candidate could previously delete Cargo.lock undetected); pycache untracked and ignored Co-Authored-By: claude-flow <ruv@ruv.net>
Seconds-scale TTL with an elapsed-time guard: the hit is only asserted when get() is reached within the TTL of the refresh, so scheduler stalls skip rather than false-fail. The 100ms version still flaked under parallel cargo builds. Co-Authored-By: claude-flow <ruv@ruv.net>
…isted-view deltas, chain recovery, flush errors - delete() snapshots and restores deletion_bitmap, membership_filter, metadata, chain state, segment_dir, epoch, and witness hash as one unit — a failed delete can no longer publish tombstones for records the META chain still carries (previously bricked the artifact) - Delta bases now diff against the persisted META chain view (committed_metadata), not in-memory state — records that become committable after being filtered are persisted in the next generation instead of silently lost; base advances only after manifest fsync - restore_metadata recovers the longest valid prefix of a damaged chain instead of failing (up to 32 single points of failure before); new RvfStore::metadata_recovery() reports served generation and dropped ones - Explicit BufWriter flush at all ten block-scoped writer sites — drop was discarding flush errors before sync_all (acknowledged writes lost near-full volume; reproduced) - Write-time chain budget: MetadataChainState forces a snapshot when the decoded-bytes budget would be exceeded and refuses commits that open could not replay - delete() error paths now roll back like ingest_batch Each regression test verified to fail with only its fix reverted. Co-Authored-By: claude-flow <ruv@ruv.net>
…per-proof identity storage, closed template bypasses - embedding_space_id no longer hashes the crate version: dedicated EMBEDDING_SPACE_FORMAT_REVISION (bump only on semantic change) replaces CARGO_PKG_VERSION in runtime_revision — a routine release bump no longer bricks every persisted corpus and cache key. Golden-id test pins the current id; blindness test proves version changes leave ids byte-identical while template changes move them - Identity persists in the vector store's own config table (authoritative) with the .agentic sidecar as a healed mirror — deleting the sidecar no longer downgrades the space check; populated guard consults the actual vector store - ApiEmbedding (default feature) now applies the identity's prompt template and fails closed at construction on Custom/tampered policies — same guarantee as the ONNX path, proven against a local capture server - Removed hashed hardcoded ort version string (was both stale and an id-instability vector); provider-class revision + informational build_revision() accessor instead (wire schema unchanged: 17 fields) - Lattice: fail-closed assertion that lattice-embed's internal prompts match the registry templates the identity attests (it owns prefixing; double-prefixing avoided) - Hailo: artifact_digests() helper so attested identities can verify; fallback identity keys off artifact digest, not directory basename - EmbeddingCache: documented small-capacity shard degeneracy Co-Authored-By: claude-flow <ruv@ruv.net>
…on, hashed dependency closure, exact evidence set - actions/setup-python (SHA-pinned v5.6.0, python 3.12) before all five pip installs — avoids PEP 668 externally-managed failures on ubuntu-24.04 and makes the trusted interpreter deterministic - requirements.in declares both direct imports (jsonschema, referencing); requirements.txt is a uv-compiled fully-hashed universal lock (126 hashes incl. platform wheels); installs use --require-hashes (tamper-tested: zeroed hash → pip refuses) - validate_artifact_index requires the evidence root to equal the indexed set exactly — unindexed files can no longer ride into the attested bundle; symlinked dirs reported by name, never followed Co-Authored-By: claude-flow <ruv@ruv.net>
…opped deletes - After a truncated recovery, restore_metadata retains only the applied META entries in segment_dir and sets needs_snapshot, so the next write re-anchors the chain with a full snapshot. Previously every write after a recovery was silently discarded forever (the forward-apply loop could never reach a delta appended past orphaned newer generations) and only compact() repaired it, losing everything written in between. - Post-replay validation now distinguishes intact from truncated chains: on an intact chain a deleted/absent vector for a restored record is still a hard InvalidMetadata, but on a truncated chain it is an expected consequence of the damage (deletion state comes from the newest manifest, metadata from an older prefix) — those records are dropped and counted in MetadataRecovery::dropped_records instead of bricking the artifact. Same hole fixed in restore_cow_state. - Recovery is now visible: rvf-cli warns on stderr at all nine open sites naming the recovered generation, counts, and the compact remedy; RvfStore::open documents that a successful open does not imply all committed metadata is present. Regression tests now WRITE after recovering — read-only reopen is what let both defects through review. Co-Authored-By: claude-flow <ruv@ruv.net>
Round-4's recovery fix split one operation across two writers: recovery pruned orphaned META entries from segment_dir (persisted by write_manifest) while only write_metadata_generation consumed needs_snapshot. Any mutation that rewrote the manifest without emitting a metadata generation — ingest_batch(.., None), freeze(), the embed_* paths — committed the pruning alone, so the next open saw no damaged generation, misclassified the truncated chain as intact, and hard-errored forever with no repair path (compact was unreachable because open itself failed). rvf-cli printed the 'run rvf compact' warning and then destroyed the artifact five lines later. Recovery now records unreachable META offsets without touching segment_dir; write_metadata_generation drops them in the same call that emits the re-anchoring snapshot. A manifest-only write publishes neither half, so the chain stays byte-identical and the next open recovers identically. Also: delete/set_file_metadata rollback snapshots the full segment directory (truncate could not restore middle removals once pruning existed); dropped_generations counts every lost generation instead of stopping at one; the recovery warning is wired into all 14 rvf-cli open sites (freeze and the embed_* commands — the ones that triggered the brick — had none). Tests: an intact-chain control pinning the fatal-inconsistency check (nulling it previously left every test green), and a manifest-only-write after truncated recovery covering both ingest_batch(.., None) and freeze(). Co-Authored-By: claude-flow <ruv@ruv.net>
…ness harness/test/research.test.ts shells out to research_gate.py, which now schema-validates its inputs (ADR-282 hardening) and therefore needs jsonschema + referencing. The research-* workflows install them; this one never did, so the harness job failed with ModuleNotFoundError. Uses the same SHA-pinned setup-python and the same --require-hashes lock. Also picks up a rustfmt miss in metadata_durability.rs. Co-Authored-By: claude-flow <ruv@ruv.net>
…mory ids (2.6.2) The JS wrapper read snake_case fields from the napi-rs native module, but napi-rs camelCases Rust struct fields on the way out — so query() silently dropped requestId, contextSize and latencyMs, route() dropped contextSize and topP, and stats() dropped every field. The type declarations also described the native shape as snake_case, which is why TypeScript never caught it. Both spellings are now accepted, camelCase first. Also: - searchMemory: native reports `distance` (smaller is closer) but MemoryResult documents `score` as a similarity — convert rather than passing a distance under a similarity's name. - similarity(): f32 accumulation returns 1.0000001 for an identical pair, which makes Math.acos(sim) NaN downstream. Clamp to [-1, 1]. - addMemory/MemoryResult.id: native returns UUID strings now; the public type said number. Widened to a documented MemoryId = string | number and updated the test that encoded the old assumption. Carries the previously unpublished ESM fix from bb36eb1 (2.6.1 was never released). 110/110 tests pass; esm smoke and extension verification green. Co-Authored-By: claude-flow <ruv@ruv.net>
26 packages had npm ahead of main's source — main declared versions that were never what users install, because packages were published from branches that did not merge back. Version fields now match the registry. This aligns version NUMBERS only. At least @ruvector/rvf-node has real content drift too: published 0.2.3 ships index.js at 9191 bytes with a different digest than main's 4510-byte copy, and declares a different `files` list. Version alignment makes the drift visible rather than hiding it behind a stale number; reconciling the source is separate work tracked in its own issue. All 26 edits touch only the version line (verified: 26 files, 26 insertions, 26 deletions). Every package.json still parses, and every exact intra-repo dependency pin now agrees with the local version it names — the ruvllm platform packages in particular were pinned at 2.0.1 while main declared 2.0.0. Co-Authored-By: claude-flow <ruv@ruv.net>
…an silently returns a stale manifest An external implementation attempt (rvQR) built a reader from ADR-009 as the normative spec and it did not work on real files. Root cause: two writer paths. rvf-wire/rvf-manifest emit a 4096-byte Level-0 root at the tail and pad segments to 64-byte boundaries; rvf-runtime — the crate behind rvf-cli, which produces the files people actually have — emits neither, writing header and payload back to back and terminating in a MANIFEST segment. rvf-runtime depends on neither wire crate. The failure was worse than the spec merely being incomplete: find_latest_manifest's 64-byte-stride backward scan cannot reach a runtime container's newest manifest (offset 2092 % 64 = 44) but does reach offset 0, so it returns Ok with the stale epoch-0 manifest reporting zero vectors. A conformant reader opens the file, reports an empty store, and raises no error. - 2.1 describes both shapes as first-class, marks the root manifest optional, notes containers under 4096 bytes cannot carry one, and specifies a two-path reader (tail root, else byte-wise scan for the newest MANIFEST segment) - 5 drops 'validate 64-byte alignment of the segment start', which rejected every runtime segment - 2.4 adds rvf-runtime as a normative source and states that sources 3 and 4 genuinely disagree, each normative for what it produces - acceptance criterion 3 splits into 3 (wire) and 3a (runtime); adds 7 - research docs corrected: the tail-scan pseudocode in binary-layout.md was the failing reader verbatim Also documents a third divergence: runtime hashes with a CRC32 rotation labelled checksum_algo=0 while rvf-wire reads 0 as XXH3-128, so cross-path hash verification fails. The amended algorithm was implemented literally from the ADR text and returns offset 2092, epoch 1, 24 vectors on a real container. Refs #775 Co-Authored-By: claude-flow <ruv@ruv.net>
Both jobs were failing. The clippy failure is ours; the audit failure predates this branch (red on main since at least 2026-05-16). clippy -D warnings: the hailo-cluster-as-provider example still called EmbeddingProvider::embed, which ADR-281 deprecated, and -D warnings makes a deprecation fatal. Migrated its three call sites to the role-aware API and documented why the similarity check still reads ~1.0 (the cluster provider declares a symmetric identity, so both roles encode identically; against an asymmetric model doc[0] would rank first but well below 1.0). cargo audit: five new advisories. event-listener 5.4.1 is the only one with a fix (RUSTSEC-2026-0221, unsound: StackSlot unconditionally implements Send/Sync, letting a !Send tag cross threads) — patched in 5.4.2, so it is bumped rather than ignored, per this file's own rule that anything fixable gets fixed. The other four carry patched = [] upstream: pqcrypto-traits, pqcrypto-internals, proc-macro-error2 and ttf-parser, each ignored with a justification. Co-Authored-By: claude-flow <ruv@ruv.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements three ADRs, hardened across five rounds of adversarial review.
ADR-280 — RVF durable self-contained metadata
Metadata is now persisted in the container with delta-encoded generations (full snapshot every 32 deltas). The first implementation wrote a full snapshot per commit and replayed every historical one at open — 600 commits produced a 725 MiB file that could no longer be opened, with no repair path. Now: 241 KB of META payload for the same workload, opens in ~4 ms,
compact()collapses to a single segment.Also fixed during review: derive-child containers that could not be reopened; an 80-byte crafted file that drove a 512 MiB allocation;
delete()rollback that left in-memory tombstones behind and bricked the artifact; acknowledged writes lost because tenBufWritersites discarded flush errors beforesync_all; corrupt mid-chain deltas making a file unopenable (now recovers the longest valid prefix); and an ordering bug where recovery pruning was committed without its re-anchoring snapshot, sorvf ingestprinted a repair warning and then destroyed the file.ADR-281 — Role-aware embedding APIs
Query/passage role routing with attested embedding-space identity. Review found the space id hashed
CARGO_PKG_VERSION, so a routine version bump would have rejected every persisted corpus and invalidated every cache key — with the test suite structurally blind to it (fixtures used a literal version). Now keyed on a dedicated format revision, with a golden-id test pinning the value and a blindness test proving version changes don't move it.Also: three constructors that failed unconditionally with ten callers never migrated (the worker daemon could not start); prompt templates now applied from the attested identity rather than hardcoded strings, fail-closed on mismatch; the identity check moved into the vector store's own config table so deleting a sidecar no longer downgrades it;
ApiEmbedding(a default feature) no longer bypasses templating.ADR-282 — Nightly research quality gate
Sandboxed candidate evaluation with attested evidence. Review found the gate had never completed a single run: the candidate checkout was shallow so its
git diffalways failed, and a jq quoting bug made the authorized-override path dead code. Check-run queries were also unpaginated — on a real main commit, 8 of 22 failures (including Build, Test, cargo-audit) were invisible, so a red base could be certified green.Now: schemas are load-bearing (offline registry, pinned + fully hashed dependency closure, tamper-tested), the evidence root must match the attested index exactly, deletions are scanned, and report headlines are re-bound to the trusted evaluation.
Verification
~1,100 Rust tests across both workspaces, 25 Python gate tests,
cargo check --workspace --all-targetsclean. Review verdict: SHIP, with both new regression tests confirmed load-bearing by mutation testing.Follow-ups filed, none blocking: #770, #771, #772.
🤖 Generated with claude-flow