Repository navigation
Integrate backlog step 4: one storage format, macOS port, and expected state on open (#204) - #220
Draft
flyingrobots wants to merge 37 commits into
Draft
flyingrobots wants to merge 37 commits into
flyingrobots wants to merge 37 commits into
Conversation
Extract the pure candidate and snapshot admission from publish_catalog_generation, and split head execution into staging and replacement, so a gated publication route can run the same protocol and insert a check before HEAD replacement. Version-1 behavior is unchanged.
Add publish_retained_catalog_generation over the RetainedCatalogPublicationStorage port. Under writer authority it admits the current retention head, its manifest, and every manifest root, verifies each root closure against the candidate before the first catalog storage call, and rereads the retention head after head.next verification, refusing HEAD replacement if it changed. The receipt binds the preserved manifest generation and digest. Catalog stages are reachable only with a gate-issued RetainedClosureAdmission. Record the decision, crash states, and recovery obligation in the v2 format rationale; update closure, retention-publication, README, ledger, and changelog.
…198) Move the pinned staging, segment-pool and catalog-pool directories, the retained writable stages and the stage-authority token into one FilesystemCatalogStages value. It is the single CatalogPublicationStorage implementation; FilesystemCatalogPublisher owns it beside its writer lock and forwards every transition unchanged. Segment stage creation and selection move with it. No behavior change: this lets a version-two writer authority run the same stage code.
…ity (#198) The gate's evidence admission now reads through a crate-internal RetainedEvidenceSource. Forward publication adapts its RetainedCatalogPublicationStorage to it without exposing catalog stages, so recovery finalization can run the same admission later. No behavior change.
FilesystemVersionTwoCatalogPublisher implements RetainedCatalogPublicationStorage under FilesystemVersionTwoAdmission. Its Stages type, FilesystemVersionTwoCatalogStages, wraps the shared FilesystemCatalogStages and has no public constructor, so it is reachable only through the retained-closure gate. Retention evidence is read no-follow and bounded; an absent head is reported only when no fixed retention stage and no root or manifest pool entry contradicts it. Publication takes no reader fence; readers now admit a retained catalog head.next, which version 2 preserves and readers never select. FilesystemVersionTwoRecoveryAdmission admits a migrated root that may retain head.next. The inventory reader, discarder, completer, segment resumer and next-head finalizer gain version-two constructors over the same stage code, classify_version_two_recovery_names classifies every version-two root entry as known, and version-two next-head finalization runs the forward gate's retained-closure admission immediately before every candidate verification that can precede HEAD replacement. Version-one entry points keep refusing version-two roots and version-two entry points refuse version-one roots. Supported envelope: one staged segment per publication.
…198) Add the version-two-catalog crash sequence, KEEP-CRASH-074 through 077: segment link, catalog-stage creation, head-stage synchronization and HEAD replacement of the production version-two writer, each at before, during and after. The child installs an observer on the writer's own stage type and blocks there for process death. Restart requires the exact stage residue, recovers it with the public version-two recovery tools (segment completion, catalog discard, gated head finalization), republishes where the candidate never reached HEAD, retains the ingested blob and reads both blobs through DurableStore.
Add the catalog-publication page: supported envelope (one staged segment per publication, no rollover), authority, the ten-step forward protocol, every crash state with its recovery, version-two recovery admission and classification, gated next-head finalization, typed cross-version refusal, and the KEEP-CRASH-074..077 rows with 078..080 planned. Route the version-two README, namespace, retention-publication and rationale pages to it, record the shared-stage decision, add KEEP-RETENTION-012..015 and update 011.
The library's own interruption laws use the observer without the repository-tasks feature, so the hidden types must be reachable there too; clippy --no-default-features --all-targets otherwise denies them as unreachable pub items.
…#198) Move populated-store construction out of the golden worldline and version-two writer fixtures into tests/support/populated_store.rs and tests/support/successor_catalog.rs. The builder creates a store through the public writer (version-one initialization, the first catalog through FilesystemCatalogPublisher, migration), publishes retention roots, and publishes successor catalogs through FilesystemVersionTwoCatalogPublisher and the retained-closure gate. Admission is a typed route, so unit tests outside the platform profile reuse the same steps. durable_fixture and writer_fixture keep their names and signatures and delegate, so every law's construction and assertions are unchanged. When direct initialization replaces migration, only populated_store::create changes.
…198) The src retention fixture and the tmpfs reader-profile fixture wrote the frozen one-zero bundle segment, catalog, and HEAD by hand into an initialized version-one root and then migrated it. Both now ingest the one-zero blob through the shared builder under an unchecked test admission route. The retention laws prepare against the frozen bundle snapshot and root, so populate_bundle requires each pool to hold exactly the frozen entry and HEAD to equal the frozen head; a drifting writer refuses at construction. Staging the layout record before the chunk makes it refuse with "writer-built segments pool is not exactly the expected entry".
The retention and version-two catalog crash rows wrote the frozen bundle bytes by hand into an initialized root and migrated it. A new production_protocol/populated_store.rs creates the bundle store through FilesystemCatalogPublisher and migration, then requires the pools to equal the frozen bundle before any crash row runs. The crash rows, their coordinates, and restart verifiers are unchanged. xtask cannot include integration-test support, so this module is the matrix's counterpart of tests/support/populated_store.rs over the same public entry points. The version-two crash blob becomes CrashBlob, which also identifies the one-zero bundle blob; the unused initialized-lock helper is removed.
…ormat (#198) ADR-0010 makes keep.segment-store/v2 the only format and requires initialization to replace migration in the same change that deletes it. FilesystemVersionTwoAdmission::initialize creates a store under exclusive writer authority as a recoverable state machine: it classifies the root before creating writer.lock and again under the lock, makes FORMAT.next durable before any directory exists, creates the reader fence and the nine protocol directories with a parent synchronization each, links FORMAT without replacement, and commits with the root synchronization after the link. Every admission refuses FORMAT.next, and the stage is removed only after the commit synchronization, so a store never admits before its commit point. Interrupted states resume idempotently; every other nonempty root, and a committed one, refuses with typed evidence before mutation. An initialized store has no catalog HEAD: writers admit it, readers refuse it as Unpublished, version-2 recovery classification no longer requires HEAD, and the version-2 writer publishes the first catalog through the retained-closure gate. The test builders and the reader-fence fixture now initialize and publish through that writer. Migration is deleted: src/adapters/store_migration, its public API, tests, fuzz target, crash rows KEEP-CRASH-053..073, golden records and source tables, and format pages. The persisted root device-and-inode binding goes with it; live lock, fence, and directory checks remain. The FORMAT codec moves to src/adapters/store_format. Removing the migration rows from definition.tsv changes the definition digest to f1a0ce10...361d; the marker golden is regenerated from the handwritten oracle. The initialization crash matrix runs 41 cases at KEEP-CRASH-081..091 and recovers, publishes, retains, and reconstructs after each. Retired identifiers are mapped in docs/formats/segment-store-v2/retired-identifiers.md. Retires #108.
…acceptance (#198) tests/single_format.rs drives the production entry points under the admitted ext4 profile. Initialization laws: an initialized store reopens but refuses readers as Unpublished, version-1 admission refuses it, a committed store refuses re-initialization unchanged, an uncommitted root refuses writers and readers, and a first publication refuses a stray retention root without a head, leaving the store unchanged. The five ADR-0010 portability laws copy a quiescent store byte for byte into a new directory: the copy opens and reads; corrupt and incomplete copies refuse exactly as the same damage to the original; a replaced writer lock and retention directory still refuse; a copy and its original take independent locks, diverge, and keep the shared segment bytes; and a backup reopens with the original gone, then recovers an interrupted write. The acceptance sequence (ADR-0010 step 4) initializes, ingests, reopens, reconstructs exact bytes, retains, publishes a second catalog, interrupts a third publication before HEAD replacement, recovers through gated finalization, reads both retained blobs, and refuses a lossy candidate with every byte intact. Requirement rows KEEP-INITIALIZATION-006 and KEEP-PORTABILITY-001..005 record the evidence.
KEEP-CRASH-001..030 name the segment, catalog, head, and recovery-discard boundaries of the shared catalog stages, which the single-format writer executes unchanged. Their crash children now create the store by direct initialization and publish generation one through FilesystemVersionTwoCatalogPublisher and the retained-closure gate instead of the version-1 publisher. The driver observes the writer's own segment-stage port and catalog transitions; the version-1 CatalogPublicationStorage decorator is deleted. A `during` catalog or head write writes the same strict prefix into the stage the writer created and dies before the writer's own write. The recovery-discard rows open the discarder through version-2 recovery admission. Their head precondition is now a writer publication interrupted at the head write, so both pools hold the published segment and catalog. Restart inspection expects the committed single-format inventory, including the corpus FORMAT bytes, and admits every residue through version-2 recovery admission in place of a bare writer-lock reacquire. The rows keep their identifiers because each boundary is the same code.
…tores (#198) Every law below constrains code the single format runs unchanged, so it moves rather than retires. Each fixture now creates its store through direct initialization instead of hand-writing writer.lock, staging, segments, and catalogs: - the four recovery fixtures (stage completion, stage discard, segment continuation, next-head finalization) open their tools through FilesystemVersionTwoRecoveryAdmission; the planted crash residue (stages, pool entries, head.next) is unchanged. Writer exclusion is now observed where single-format recovery takes the lock: a second recovery admission refuses with WriterLock Busy; - the recovery-inventory and stage-fingerprint laws open the reader through version-2 admission; the symlinked-staging law now refuses at admission with WrongKind { Directory, Symlink }; - catalog_filesystem_publication runs through FilesystemVersionTwoCatalogPublisher and the retained-closure gate. A leftover head.next and a non-directory or linked protocol directory refuse at writer admission with exact namespace refusals; two new laws keep the stages' own no-follow checks for a namespace replaced after admission; - segment-stage, observed-stage, and catalog-restart laws use the single-format writer; catalog_restart's one-zero store is now published by the writer and required to equal the frozen corpus bytes; - name-classification laws classify the single-format root. Laws that plant a link target in the store root plant it after admission, which refuses unknown root entries, so each tool's own no-follow open remains the boundary under test. Test-only version-1 openers left without callers are removed.
ADR-0010 step 5. The single format now does everything version 1 did, so
version 1 is deleted and every open path refuses a root without the
format's FORMAT marker.
Deleted: FilesystemPlatformAdmission, the version-1
FilesystemCatalogPublisher and its ungated port impl, the version-1
initializer (initialize_store, StoreInitialization{Error,Phase,Receipt,
Storage}, RepositoryInitializationStorage, the filesystem initializer and
its storage), the version-1 namespace laws (admit, admit_published,
admit_recoverable), the version-1 platform-profile open, the version-1
recovery openers (open(store_root) on the completer, discarder, resumer,
finalizer, and inventory reader, and the unchecked variants),
classify_recovery_names, and the Platform and WriterLock variants of the
four recovery open errors, which no remaining constructor produces.
Next-head finalization always runs the retained-closure gate; the
version-1 branch that skipped it is gone.
FilesystemCatalogSnapshot::load now admits the exact namespace (an
in-flight head.next allowed) and the FORMAT marker before opening any
catalog artifact, refusing with the new CatalogRestartError::Admission.
Tests: the version-1 initializer, platform-admission, and cross-version
laws are deleted with their subject. format_marker_laws requires every
open path (writer, recovery, inventory, catalog restart, retention and
durable readers) to refuse a published, retained store whose FORMAT was
removed as MissingFormatMarker, unchanged; a new single-format law refuses
initialization on tmpfs before mutation (the obligation of the deleted
catalog_platform_admission law); compile_fail doctests now refuse a bare
FilesystemWriterLock instead of the deleted type. The replaced-retention
portability law displaces the directory outside the root, since catalog
restart now refuses unknown root entries.
Crash matrix: the version-1 initialization sequence and KEEP-CRASH-031..035
are retired; the retention restart model derives its phase from
WriteRootStage's position instead of a hard-coded index. KEEP-STORE-020,
KEEP-RECOVERY-002/003, and KEEP-RETENTION-014 are retired in
docs/formats/segment-store-v2/retired-identifiers.md with their last
evidence and successors; format and corpus pages describe the single
format's admission.
With version 1 deleted there is one format, so "VersionTwo" no longer
distinguishes anything and reads as if a second format existed. Refactor
only: no behavior or test law changes; Display messages that said
"version-two" now name the store instead.
Public API:
- FilesystemVersionTwoAdmission -> FilesystemStoreAdmission
- FilesystemVersionTwoRecoveryAdmission -> FilesystemRecoveryAdmission
- FilesystemVersionTwoCatalogPublisher -> FilesystemCatalogPublisher
- FilesystemVersionTwoCatalogStages -> FilesystemRetainedCatalogStages
- VersionTwoInitialization{,Boundary,Directory,Error,Observer,Phase,Prefix,
Refusal} -> StoreInitialization{...}
- VersionTwoRecordRefusal -> StoreRecordRefusal
- FilesystemPlatformAdmissionError -> FilesystemStoreAdmissionError
- classify_version_two_recovery_names -> classify_recovery_names
- open_version_two{,_unchecked_for_repository_tasks} -> open{,...} on the
recovery tools and inventory reader
Kept: StoreFormatDefinitionDigest::VERSION_TWO and
CanonicalStoreFormatMarker::version_two(), which name the format's own
version number (keep.segment-store/v2, marker version 2), and the "v2"
record-grammar and corpus names.
Modules and files follow the types (filesystem_store_admission,
filesystem_recovery_admission, filesystem_catalog_publisher,
filesystem_retained_catalog_stages, filesystem_store_namespace,
store_initialization/, tests/catalog_writer, tests/store_admission_contract).
Crash-matrix sequences version-two-initialization and version-two-catalog
become initialization and retained-catalog, and points VersionTwo* become
Retained*; their KEEP-CRASH identifiers are unchanged. Comments and living
docs drop "version-two" where it meant "the store"; changelog, ADRs, and
testing-evidence records keep their historical names.
ADR-0010 asks for one normative set of pages for the one store format. docs/formats/segment-store-v1 and segment-store-v2 become docs/formats/store: - segment.md, catalog.md, and publication.md move unchanged in grammar; their intros name the store format and the record-grammar version their own magic fields carry; - the version-1 recovery page becomes stage-recovery.md, its rationale catalog-protocol-rationale.md, and its ledger catalog-protocol-requirements.md, beside the store's own recovery.md, rationale.md, and requirements.md (merging the ledgers would exceed the 300-line page threshold); - one README replaces the two indexes and keeps both core-law sections and every route; the format registry has one row and states that keep.segment-store/v1 is retired as a store format. Every heading other pages link to is kept, including recovery.md#root-identity-across-restart. Links across the repository, including historical evidence pages, point to the new paths; the retirement ledger keeps the old paths it records as history. The conformance corpora keep their v1/v2 directory names, which are the record-grammar versions in the artifacts' own bytes, so definition.tsv and the definition digest are unchanged. Prose that used "version 2" to mean "the store beside version 1" now names the store. The compatibility section follows ADR-0010 (no in-place conversion; an upgrade tool after the first release) and states that definition.tsv does not yet bind the segment, catalog, and head grammars. KEEP-CRASH-078, reserved for discard under version-2 recovery admission and never implemented, is retired: KEEP-CRASH-027..030 now run under recovery admission. The changelog records the unit.
The deleted cross-version law showed that classification refuses a former version-1 inventory as missing reader.lock. Generalize it: removing any one of the nine root entries of an initialized store refuses as exactly that missing required entry, so a classifier that stops requiring FORMAT or the reader fence is caught.
Resolves the stack against macOS support (#216). Every store path goes through the native platform adapter and FilesystemDirectory, including the new initialization, namespace, marker and admission code, so the exact-name check covers them. Nested initialization lookups now traverse one entry at a time. The persisted root binding is gone, so the live directory coordinate keeps only device and mount, and Darwin's restart coordinate domain is removed. A reattached volume must still open; that law now uses an initialized store. Platform-neutral laws from the stack also run on macOS.
ADR-0010 removes the persisted root binding, which never detected a swapped or rolled-back store. Each public open now takes a StoreExpectation with no default: an exact catalog refuses anything else with expected and observed values, and NoExpectation states the absence of a check at the call site. Enumerated laws cover the two copies ADR-0010 requires before migration deletion: every mix of two published states and every partly copied file refuses or returns exact bytes, and a restored older snapshot refuses newer expected state.
The namespace check refuses a foreign spelling first, so the public laws do not witness the lookup check. These laws call the marker, head and protocol-directory lookups directly, and the public laws add the writer and initialization routes.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Initialization delegates every root synchronization to one synchronizer, which production binds to the real call. Two laws substitute it: one requires the commit synchronization after the marker link and before stage removal, while both names exist; the other fails it and requires an unadmitted, resumable store. The law previously named for a failed commit synchronization failed its observer before the synchronization ran. It is renamed for what it does. A mutant that made the synchronization a no-op passed the complete suite before this change. These laws witness that the protocol requests the synchronization. That the production call reaches stable storage is still established by reading it.
The module was compiled only on Linux. Its three laws pass natively on default and case-sensitive APFS, so the gate goes.
The guards could pass on the two whole states, any error counted as a refusal, and the names claimed more than the laws enumerate. Refusals must now be a typed absent-or-malformed cause, the guards count only real mixes, empty files are skipped as untorn, and the names and header say what is and is not covered.
Two process-group laws failed under a long temporary directory because their Unix socket path exceeded macOS's 104-byte sun_path bound, and the child panicked before readiness. Their sibling already used short names. With short names both pass natively; ProcessGroup itself was not at fault.
FilesystemCatalogSnapshot::load was a public open that took no StoreExpectation, so a caller could lose the check by using it. It now takes one and refuses with CatalogRestartError::Expectation. Verification loads still name none. An expectation can now be kept across processes. ExpectedCatalog is the caller's claim: from_recorded rebuilds it from a generation and 32 digest bytes, and digest_bytes returns the bytes to record. It no longer hands out a CatalogDigest, which would have let a claim pass as a verified digest. The observed side of a mismatch is the new ObservedCatalog, and the mismatch's Display reports both. The retained-catalog crash sequence now requires the catalog recorded before an interrupted publication to admit while generation one is published and to refuse, reporting generation two, once HEAD moves. Keep cannot tell that successor from another store, so accepting it stays the caller's decision. BREAKING CHANGE: FilesystemCatalogSnapshot::load takes a StoreExpectation. ExpectedCatalog::digest is replaced by digest_bytes, and StoreExpectationMismatch::observed returns Option<ObservedCatalog>.
The review of PR #220 found statements that claimed more than the code and laws establish. This corrects them: which opens take an expectation and which routes take none, how an expectation is kept and what a crash does to it, what the torn-copy and commit-point laws witness, the current crash-row counts, the retirement ledger, the changelog, and the platform pages. It records two owner decisions of 2026-11-05: KEEP-CRASH-079 and -080 follow version 1's deletion and block the first release in #221, and the GC salvage renumbers from KEEP-CRASH-092. The mutation record holds the six hand-run mutants and what they showed. The protocol contract law that pinned the old case-sensitivity wording now pins the rule that replaced it.
18 tasks
FilesystemCatalogSnapshot::load compared after loading the whole store, so a store that published another catalog and was also damaged, or over the caller's limits, reported that instead of the mismatch. The writer's reopen reported the mismatch for the same store. The load now compares what the checksummed HEAD names before it reads the catalog or any segment, and a law pins that order. Two assertions in the expectation laws were looser than their comments: one did not pin the head phase, and one flipped a bit in a digest that would have refused unflipped.
) The refusal list accepted every segment error, including policy and resource limits, which its own comment excluded. It now accepts only errors about the segment's bytes. A mix that behaves as the older whole state could satisfy both guards, because a blob that state never held refuses there too. The laws now also require a refusal at an absent or malformed artifact.
…198) The recorded generation-one catalog was only shown to admit on the republish path, after recovery had cleaned the stage, which is six of the twelve retained-catalog cases. Restart now requires it to admit before any recovery in every case that left HEAD at generation one, beside whatever stage the publication left.
) An independent read of the repair commits found wording that claimed slightly more than the code. Only the lock phase and the three root-synchronization phases go through the injected synchronizer; the fence, the directory admissions and the stage discard synchronize their parent directly. Five repository-task entry points take no expectation, not one. One of the six mutants was a test edit. The docs also follow the three preceding commits.
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.
Backlog step 4 of landing the open PRs: merges #204, which contains #200–#203, onto main with a real merge commit, and adds what ADR-0010 and #216 require on top. Backlog steps 1 to 3 were #208, #211 and #215. ("Backlog step" is this landing sequence. ADR-0010 has its own numbered steps, which are a different list.)
Problem
Main still has two formats, version-2 stores are created only by migration, and a migrated store cannot ingest. #204's stack fixes that, but it was written before two later changes:
A plain merge would have bypassed the exact-name check on the new store paths, and removing the binding would have left wrong-store and rollback detection with no replacement.
Before you merge
fb355d66. The case file is on thecases/open-prs-integrationbranch underdocs/cases/2026-11-04-step-4-single-format/. The ten commits afterfb355d66are the repairs: six from the review, then four from an independent read of those six, which found nothing blocking.FilesystemCatalogSnapshot::loadtakes an expectation.KEEP-CRASH-079and-080follow version 1's deletion. Crash-test recovery stage completion and gated finalization (KEEP-CRASH-079, -080) #221 tracks them and blocks the first release.KEEP-CRASH-092.FilesystemCatalogPublisherpublishes one segment and one caller-supplied catalog per call.git show --remerge-diff 5360a804shows the conflict resolutions in 91 files, separately from the stack. First-parent order after the merge is the expectation (9a44159c), the exact-name laws (cfdf6737), two fixes, then the repairs.7c03b179says initialization delegates every root synchronization; only the lock phase and the three root-synchronization phases are delegated.0037255fdescribes the crash check more broadly than it implemented;33a99772makes that true. The code comments and docs are correct at this head, and the branch is not rewritten.Invariant affected
Approach
filesystem_platform_profileselects the native adapter over the nine protocol directories.FilesystemRootIdentitybecomesFilesystemDirectoryCoordinate, holding device and mount only, used within one operation and never persisted. Darwin's restart coordinate domain and the lenient Linux mount policy go away with the binding.FilesystemDirectory. Nested initialization lookups traverse one entry at a time.StoreExpectation: five opens require it, with no default:FilesystemStoreAdmission::reopen,FilesystemCatalogSnapshot::load,FilesystemRetentionSnapshot::load,DurableSnapshot::openandDurableStore::open, which checks when a snapshot is pinned.Catalognames an exact generation and digest, andNoExpectationstates that there is no check.ExpectedCatalogis the caller's claim.from_recordedrebuilds it in a later process from a generation and 32 digest bytes, anddigest_bytesreturns the bytes to record. Keep stores nothing for the caller. The observed side of a mismatch isObservedCatalog.KEEP-STORE-022and-023.Alternatives rejected
NoExpectation. Callers would lose the check by omission, which the owner rejected.FilesystemCatalogSnapshot::loadas an "observational" read. Cheaper, but it concedes that a public read can skip the check, which is the thing the owner rejected.CatalogDigestconstructor for recorded values. Its type means a verified digest, so a recorded claim stays insideExpectedCatalog.Catalogpromises. A variant that accepts the direct successor is a possible follow-up in Follow-ups from the review of PR #220 (single storage format) #222.DurableViewas the expected state. It also covers retention, but it would make writer admission depend on the durable read layer.Failure modes
DurableStoreholding one refuses after the store moves on.Catalogdoes not detect a rollback that leaves the catalog unchanged, such as restored retention state alone. A law pins this.Tests
Native Rust 1.96 on macOS at
0ab39da6, throughscripts/native/check.sh:--all-features, root package)bea75ae9's source;retained-catalogsequence exit 0 at this headbea75ae9's sourceThe crash matrix is silent on success, so it reports no row count. The full local matrix last ran before the final four commits, which changed only the
retained-catalogrestart check among crash-matrix code; that sequence was re-run locally, and CI runs the full matrix at this head.CI at this head: run 42005696619 passes all five jobs: Rust quality gates on Linux, native macOS storage, documentation and workflow integrity, runtime fuzz smoke, and dependency policy. CodeRabbit skips drafts.
Mutants, run by hand and never committed (
docs/testing-evidence/single-format-mutants.md):fb355d66: a no-op commit synchronization survived the whole suite, and so did removing the lock replacement from the portability law. Removing the gate's re-entry atHEADreplacement was killed.loadare each killed by the law that names that behavior. The last also fails the retained-catalog crash sequence at its first case.cargo test -p xtasklocally: 419 passed, 7 failed. The seven failures are caused by the local environment, and none has been re-run in a clean one:b3sum_oraclelaw:b3sumis not installed.cli_contractlaws: the runner sets ambientCARGO_*build settings that the tool refuses. One message says so; the other four say only that a command failed.documentation_cli_contractlaw:PermissionDeniedunder the runner's sandbox.Two
process_grouplaws that failed here before now pass. Their Unix socket path exceeded macOS's 104-bytesun_pathbound under the runner's long scratch path. Linux CI passing never cleared that, andProcessGroupwas not at fault.Also worth knowing:
tests/single_format/compile only with therepository-tasksfeature, so a plaincargo testskips them silently.Not run locally:
documentation-integrity-check,conformance-checkandgolden-file-worldline-check(they need pinned tools orb3sum), Linux compile and tests, and the reattach law, which needs CI's images. Markdown lint and relative links were checked by hand on the changed files.Benchmark impact
None measured.
NoExpectationadds no I/O. WithCatalog, the readers compare the view they already collected andloadcompares theHEADit already read, so neither adds I/O. The writer's reopen adds a directory clone, a stat and open ofHEADand its 128-byte read, plus one listing scan of the root on macOS for the exact-name check.Format/API compatibility
Breaking API:
StoreExpectation.ExpectedCatalog::digestis replaced bydigest_bytes, andStoreExpectationMismatch::observedreturnsOption<ObservedCatalog>.CatalogRestartErrorgainsExpectation.This PR also carries the stack's format change: the definition digest changes, and version 1 and migration are deleted. Nothing is released, and the owner confirmed no stores of value. The expectation adds nothing to any durable format.
Recovery implications
Removal of migration recovery and the moved stage recovery are the stack's, described in #203 and #204. The expected-state check adds no write protocol.
KEEP-CRASH-079(stage completion under recovery admission) and-080(gated finalization under recovery admission) are not implemented. The plan placed them before version 1's deletion, and the owner accepted on 2026-11-05 that they follow it in #221. Until then, process death during recovery is untested, and so is the gate running again when a finalization is retried after a crash.Security implications
The exact-name port prevents a foreign spelling from answering for a protocol name on default APFS. Removing the binding widens no filesystem authority. Rollback and wrong-store protection exist only for callers that pass
Catalog, only as far as the catalog, and only as well as the caller protects its own record: a record an attacker can rewrite protects nothing.