Skip to content

feat(oplog): make a control v2 cut's pre-cut manifest durable - #1364

Merged
skakri merged 4 commits into
mainfrom
feat/1311-manifest-durability
Sep 17, 2026
Merged

skakri merged 4 commits into
mainfrom
feat/1311-manifest-durability

Conversation

@skakri

@skakri skakri commented Sep 16, 2026

Copy link
Copy Markdown
Member

A control v2 revocation names its evidence as a detached manifest, by digest. Nothing stored a manifest and nothing handed one to the executor, so every revocation parked on ParkCause::Manifest and a pinned account could apply enrolments and nothing else. This makes that evidence durable, which is what stands between the merged engine and a working upgrade path.

A manifest is an ordinary annex entry

The annex log (log 3) already exists for signed, authority-inert artifacts, and its module header reserved tag 1 for the next one. A manifest takes it: same account_ingest driver, same insert_candidate writer, same account_entries table. Zero DDL, zero wire change, and replication is inherited — account_entries_for_sync/_for_enrollment select every log, frames carry opaque bytes, and the session never inspects log id or entry type. An un-upgraded peer stores and relays a revocation's evidence it cannot itself apply, because an unknown annex tag is storable, relayed and RetainedUnfolded, and the closed-tag quarantine is CONTROL_LOG-scoped.

The payload is the manifest CBOR verbatim, so sha256(stored payload) is the digest the cut signed — on author, re-ingest and replication alike. execute_held now takes manifests and derive_pinned_projection supplies held_view_manifests(rows).

The first commit renames account::snapshot to account::annex: the module header already called it the annex log, and leaving a manifest in snapshot::ops would have been the misnomer becoming load-bearing. No shims — every call site migrated in the same change.

Evidence is never gated on its carrier's authority

A snapshot is a claim, so usable_snapshots scoping it to a live incarnation is right. A manifest asserts nothing; its integrity is the digest the cut signed. Copying that gate would make a revoked author's manifest vanish and re-park a cut that already applied — un-revoking a device with the very revocation that removed its author. The manifest entry carries no parent_ref and no authority_ref, which removes the field a future reader would be tempted to gate on.

A floor keeps evidence admissible

Cut and manifest compete for the same grow-only budget and the cut can land first, so an insider could fill the budget and leave honest manifests permanently unadmitted — parking those cuts forever, leaving their devices un-revoked, in a state capacity never drains out of.

A slice of the per-account budget is therefore reachable only by a manifest: 64 entries and 1 MiB, with ordinary traffic capped just below at 4032 and 15 MiB. The cost is real and deliberate — a 1.6% and 6.25% reduction for every account, pinned or not — and it is not silent: candidate_capacity_headroom reports the ordinary caps, so enrollment preflight measures against the budget admission will actually apply. ENROLLMENT_HELD_ENTRY_HASHES_MAX stays at the full 4096, since manifests can still reach that ceiling and the total per-account count is unchanged.

Using account_candidate_reservations was considered and rejected. That table charges a time-bounded obligation keyed by id, and the pruner deletes solely on expiry — so a non-expiring per-cut row keyed by the cited digest is unreapable by construction, and a few hundred cuts naming bogus digests would pin the byte budget permanently, converting insider-only starvation into an attacker-triggered one. A standing per-account row cannot express "this reserve belongs to one entry class", so admission would still need the class-aware branch. Gating the floor on pinned-ness fails for a different reason: the attack is to fill the budget before the pin lands.

Bounds

MAX_ENTRIES/MAX_BYTES remain the bundle bounds and now carry compile-time assertions that they equal the store's per-account caps — the property that lets a driver pass the entire held set to plan_replay without selecting. MAX_VIEW_ENTRIES = 1_900 is the new per-view bound, enforced on encode, on decode and in the length precheck, so a view between the two is neither authorable nor admittable.

That bound is measured, not assumed: the payload is 64,661 bytes, leaving 670 bytes spare on a device's first manifest and 621 on a worst-case chained one. The test asserts both sides on the header production actually writes, with the lower bound on the chained shape — the upper bound alone would not have caught a bound raised toward the ceiling, because the origin shape still had margin when the chained one did not.

Verification

  • cargo nextest run -p rag-rat-oplog — 1153 passed
  • cargo nextest run -p rag-rat-sync — 239 passed, 2 skipped
  • cargo clippy -D warnings on both crates, cargo check --workspace --all-targets, cargo +nightly fmt --check — clean

Eight mutants, each confirmed to compile with the expected test failing by name: supplying no manifests; gating manifests on a live incarnation; removing the capacity floor; widening the per-view bound; re-framing the payload instead of carrying it verbatim; routing the manifest tag to the unknown-tag retain branch; a manifest signed by an uncertified device; and raising the per-view bound until the envelope margin vanishes. A ninth proves the bundle-bound assertion by failing the build.

Not done

Support gate semantics and their ~84 call sites are untouched — the gate flip is a later slice. Pin install stays test-only and no production cadence authors a manifest, matching author_snapshot_in_tx. The same-author invariant (a manifest must come from its cut's device, or it parks in the evicting pre-verify queue and its cut can re-park after applying) is documented at the authoring seam but not mechanically enforced, because there is no cut-authoring path to bind it to yet; a_view_manifest_from_an_uncertified_device_is_only_parked pins the consequence.

Refs #1311.

…first tag

The log at `log_id = 3` carries authority-inert bookkeeping artifacts and is
named for that class; its op module, ops enum and ingest validator were named
for the snapshot, the only tag defined so far. Rename `account::snapshot` to
`account::annex`, `SnapshotOp` to `AnnexOp` and
`validate_storable_snapshot_payload` to `validate_storable_annex_payload`.
The snapshot-specific projection, selection, verification and authoring keep
their names.
A v2 revocation names its evidence as a detached manifest, by digest.
Nothing stored one and nothing handed one to the executor, so every
revocation parked on `ParkCause::Manifest` and a pinned account applied
v2 enrolments only.

A manifest is now an ordinary annex-log entry at tag 1, carrying the
planner's own canonical encoding verbatim — the digest a cut signed is
the digest of the stored payload, so nothing can re-frame it out of
reach. That needs no schema change and no wire change, and it replicates
through the existing account-entry selectors. A pinned refold reads the
held manifests back and hands them to the executor as the cut's evidence,
which is what lets a revocation reach `Applied` and close its subject's
roster seat.

Three constraints shape it:

- A manifest must be authored by the device that authors its cut. An
  unknown signer parks in `account_pre_verify`, which is capped per
  account and evicts oldest-first, and evidence a cut depends on for as
  long as it exists cannot live somewhere it can be evicted from.
- One view is carried by one 64 KiB envelope, so a view names at most
  `MAX_VIEW_ENTRIES` (1,900) identities rather than the planner's bundle
  bound of 4,096. Splitting a view across entries is not the alternative:
  a cut names one digest over one payload.
- A slice of the per-account candidate budget is reachable only by a
  manifest. Cut and manifest compete for the same grow-only budget and
  the cut can land first, so without the floor an exhausted account parks
  its revocations permanently, leaving the devices they name un-revoked.

A manifest is deliberately not gated the way `usable_snapshots` gates a
snapshot: its integrity is the digest the cut signed, not its carrier's
live authority, and gating it would make a revoked author's manifest
vanish and re-park a cut that had already applied.

The planner's declared evidence budget and the candidate store's
per-account budget are equal by design, which is what lets a refold hand
the executor every row it holds; that equality is now asserted at compile
time rather than left to a comment.
…he header production writes

The view-entry ceiling test measured a hand-built header and asserted only an
upper bound on the slack, so it could answer neither of the two questions that
matter: whether a real chained manifest still signs, and whether the margin has
been eaten. Both had to be settled by probing the code by hand.

Measure the header `author_view_manifest_in_tx` actually writes, in both shapes
it takes -- a device's first manifest and a chained one, whose `prev_hash` and
grown `seq`/`auth_len` are what bind in production -- and assert the slack from
both sides. The lower bound is the margin a new header field would have to eat
before `MAX_VIEW_ENTRIES` became unsignable; a change that eats past it has to
bring the bound down with it.
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

SCIP oracle — resolution report

Heuristic→compiler edge resolution per corpus. Δ compares resolved-after to the main baseline (only when the corpus profile + tool version match).

corpus tool edges resolved (heuristic → compiler) precision recall monikers Δ vs main
c-libuv scip-clang 12894 37.9% → 48.6% 90.2% 55.3% 679 +0.0pp
cpp-yaml scip-clang 9423 43.2% → 72.9% 82.7% 40.5% 1055 +0.0pp
py-django scip-python 12937 28.8% → 73.5% 96.7% 84.1% 3701 +0.0pp
py-rich scip-python 8383 27.6% → 92.3% 95.0% 81.3% 1196 +0.0pp
rust-time rust-analyzer 11040 37.6% → 56.9% 82.2% 83.6% 574 +0.0pp
ts-rxjs scip-typescript 7875 39.6% → 94.9% 87.3% 77.4% 631 +0.0pp

resolved = Exact/Syntactic + compiler upgrades + resolved-external, over edge candidates with a callee range. precision/recall are the oracle eval metrics.

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Projectrag-rat
Branchfeat/1311-manifest-durability
Testbedhetzner-bigmem

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkEstimated Cyclescycles x 1e6Instructionsinstructions x 1e6L1 Hitshits x 1e6LL Hitshits x 1e6RAM Hitshits x 1e3Total read+writereads/writes x 1e6
rag_pipeline::pipeline::index cargo_resolver:resolver_config()📈 view plot
⚠️ NO THRESHOLD
1,762.88 x 1e6📈 view plot
⚠️ NO THRESHOLD
1,098.03 x 1e6📈 view plot
⚠️ NO THRESHOLD
1,537.51 x 1e6📈 view plot
⚠️ NO THRESHOLD
36.23 x 1e6📈 view plot
⚠️ NO THRESHOLD
1,264.05 x 1e3📈 view plot
⚠️ NO THRESHOLD
1,575.00 x 1e6
rag_pipeline::pipeline::query_cold cargo_resolver:resolver_built_config()📈 view plot
⚠️ NO THRESHOLD
265.22 x 1e6📈 view plot
⚠️ NO THRESHOLD
175.64 x 1e6📈 view plot
⚠️ NO THRESHOLD
249.80 x 1e6📈 view plot
⚠️ NO THRESHOLD
2.86 x 1e6📈 view plot
⚠️ NO THRESHOLD
31.76 x 1e3📈 view plot
⚠️ NO THRESHOLD
252.70 x 1e6
rag_pipeline::pipeline::query_warm cargo_resolver:resolver_index()📈 view plot
⚠️ NO THRESHOLD
238.25 x 1e6📈 view plot
⚠️ NO THRESHOLD
157.33 x 1e6📈 view plot
⚠️ NO THRESHOLD
224.44 x 1e6📈 view plot
⚠️ NO THRESHOLD
2.54 x 1e6📈 view plot
⚠️ NO THRESHOLD
31.29 x 1e3📈 view plot
⚠️ NO THRESHOLD
227.02 x 1e6
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Projectrag-rat
Branchfeat/1311-manifest-durability
Testbedhetzner-bigmem
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
seconds (s)
(Result Δ%)
Upper Boundary
seconds (s)
(Limit %)
index_time/full_rebuild_cargo📈 view plot
🚷 view threshold
6.52 s
(+13.40%)Baseline: 5.75 s
7.08 s
(92.12%)
🐰 View full continuous benchmarking report in Bencher

Comment thread crates/rag-rat-oplog/src/account/storage/tests.rs Fixed
Comment thread crates/rag-rat-oplog/src/account/storage/tests.rs Fixed
@codecov

codecov Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.28099% with 9 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/rag-rat-oplog/src/account/annex/author.rs 94.50% 5 Missing ⚠️
crates/rag-rat-oplog/src/account/annex/ops.rs 94.28% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

…f formatting the outcome

The capacity-floor assertions interpolated the `IngestOutcome` that
`account_ingest` returned into their panic message. Formatting a returned
value into a panic message is the shape a cleartext-logging scan reads as
writing that value to a log, and an assertion does not need the value to say
what happened: it needs to name which branch the ingest took.

Map the outcome to a stable token through a closed match on the variants and
assert on that. The failure still names the branch it got instead of the one it
wanted, and a new variant breaks the match rather than going unnamed.
@skakri
skakri merged commit 2684019 into main Sep 17, 2026
27 checks passed
@skakri
skakri deleted the feat/1311-manifest-durability branch September 17, 2026 00:25
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