Skip to content

fix(catalog): retain compacted capture sources - #597

Closed
forhappy wants to merge 1 commit into
codex/catalog-capture-lifetimefrom
codex/catalog-source-retention
Closed

forhappy wants to merge 1 commit into
codex/catalog-capture-lifetimefrom
codex/catalog-source-retention

Conversation

@forhappy

@forhappy forhappy commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

A catalog session can capture immutable source ranges, then lose them when ref-frontier compaction replaces those runs and repository GC collects them. Optional browse readiness does not protect ordinary catalog sessions. The new GC regression reproduces this failure on the parent revision.

Publish per-ref source-lifetime history before making a compacted frontier visible. GC follows these authenticated roots using its existing history enumeration; fsck verifies the original run proofs. Explicit history pruning advances a persistent repository generation floor and trims per-ref histories, preventing expired publishers from restoring discarded source bodies.

  • Shared publication and coordinated repair use the same retention path; ordinary non-compacting publications add no lifetime I/O.
  • Catalog readers remain read-only. Capsule, checkpoint and catalog encodings remain unchanged.
  • New records have bounded control reads, authenticated immutable segments, CAS publication and aggregate resource limits. The new metadata module owns this lifecycle independently of optional browse indexes; it adds no catalog lookup path or authoritative-format migration.

Stacked on #595 (base codex/catalog-capture-lifetime). This PR contains only the source-lifetime fix and focused regression coverage. The user-owned advisor plan and the two large-file audit reports are excluded.

Validation:

  • Parent revision: real RustFS reproduction with a cold canonical catalog session, 532 pushes, source compaction and repository GC with an explicit one-hour grace period. GC removed the original body; the first selected lookup returned NotFound. This is a confirmed baseline failure, not a passing release gate.
  • Ten storage-only metadata tests pass, covering integrity/resource bounds, idempotence, independent refs, pruning, expired CAS and first-head/pruning races.
  • Twenty-two publication tests pass, including coordinated repair, exact replacement-source retention and the unchanged fewer-than-ten-requests-per-push bound. The 32-run compaction costs five writes: the previous three plus a lifetime segment and head.
  • Strict release Clippy passes for all targets of crab-metadata, crab-storage and crab-write.
  • Twenty-five release CLI GC/fsck/history regressions pass, including the cold catalog capture across compaction/GC and retained-source fsck checks. Exact source hashes stayed unchanged during qualification.
  • cargo fmt --all --check and git diff --check pass. Latest origin/main (cc2e4e25f46) is already an ancestor.
  • Format and staged-diff checks pass; dependency and lockfiles are unchanged.

Release gates still open: combined-stack 10 GiB qualification, broader explicit-pruning/concurrency/fault coverage, hosted-S3 latency, and rollout for previously compacted unreleased repositories. The completed six-case 10 GiB matrix qualifies the earlier staging/catalog revision; it is not evidence for this new change.

Evidence map for review:

  • Changed surface/owner: crates/crab-metadata/src/capsule_protocol/source_retention.rs owns lifetime records; crates/crab-storage/src/layout.rs owns their paths.
  • Entry point and caller: publication preparation and coordinated repair in crates/crab-write/src/capsule_protocol.rs call prepare_ref_successor, which records sources before publishing the replacement ref state.
  • Callees/dependency contract: bounded verified immutable reads, strict create and ETag-conditional update from crab-storage; no unconditional mutable-head writes.
  • Siblings: repository GC retains these roots, fsck verifies original runs, and explicit history pruning advances the generation floor under the existing exclusive GC lease. Optional browse retention remains independent.
  • Tests: cold canonical catalog capture across real run compaction and aged-object GC; source integrity, CAS/prune races, publication/repair, fsck and history coverage.
  • Current main lacks this source-lifetime path. The parent RustFS reproduction demonstrates the missing catalog-only protection without relying on browse readiness.

Is this the best fix? Publisher-owned source retention keeps catalog sessions read-only and protects original physical ranges at the replacement boundary. It avoids adding reader leases, TTLs or an eager catalog scan. Rollout/backfill and combined-source qualification remain explicit open gates; this draft is not ready to merge.

Exact-source RustFS qualification (9348511):

  • A fresh owned fixture passed 1,616 commands / 92 checks. A canonical catalog session captured before its first catalog-page read, then remained cold through 532 actual publications and frontier compaction.
  • The original source was absent from active root/ref frontiers, with no checkpoint history or browse roots masking its lifetime. Its provider age was 3,854 seconds at actual repository GC with an explicit 3,600-second grace period. The authenticated source-retirement chain retained it, and the pinned reader answered both ordered duplicate batches afterward; warm lookup used zero backend operations/bytes, with no query writes and drained operations.
  • An independent empty-cache clone/hydrate reproduced all seeded 64 MiB bytes. Deleting the owned retired source caused native fsck to report its exact missing path; restoring the identical bytes made native fsck pass again. An independent acceptance audit checked raw GC/fsck output, the seed digest, source/binary hashes, and terminal process state.
  • This is the exact source-retention component, not combined-stack or 10 GiB proof. The prior isolated immediate-restore failure remains preserved and unexplained; this successful fresh run does not erase it.
  • The fixed catalog query stayed at seven ranges / 48,159 bytes for 0, 32, 500 and 532 publications. Opening grew from 9 to 1,079 operations (7,736 to 1,946,634 bytes), so end-to-end opening/history scaling remains an observed performance gap.

Publish per-ref source lifetime before replacing compacted frontiers. Wire repository GC, fsck and explicit history pruning to the same authenticated retention records.
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.

1 participant