diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f0d82b..386a9c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -624,6 +624,30 @@ after its public API and format compatibility policies are established. ### Fixed +- The version-one ledger discloses the pending repository-task sealed-stage + capability escape (#146) while preserving the immutability requirement (#69). + +- The version-one segment ledger points to the existing filesystem-stage + regression module for exclusive staging and dropped-stage evidence (#69). + +- The version-one recovery overview links its implemented transitive + candidate-view admission to the next-head boundary and requirement anchors (#69). + +- Version-one publication documentation assigns unchecked publisher + construction to the crash harness and fault injection to its decorators (#69). + +- Version-one crash documentation identifies its 105 cases as a subset of + the complete command, which also executes version-two migration cases (#69). + +- Version-one recovery documentation distinguishes streamed inventory + fingerprint evidence from caller-supplied classifier bytes and the segment + resumer's separate materialization (#69). + +- Living version-1 format pages describe implemented initialization, + platform admission, publication, restart, recovery, and process-death + evidence rather than completed issue-era plans (#69). Historical issue + references and existing requirement/test anchors remain available. + - The migration transition-ledger guard rejects noncanonical line endings, extra rows, misplaced discard claims, and counterfeit completion postures. Recovery posture and namespace interruption checks use their exact columns. diff --git a/docs/formats/segment-store-v1/README.md b/docs/formats/segment-store-v1/README.md index 06feafe4..0194600e 100644 --- a/docs/formats/segment-store-v1/README.md +++ b/docs/formats/segment-store-v1/README.md @@ -7,8 +7,10 @@ visibility, and recovery as one contract. ADR-0005 records the cross-cutting decision. These pages are a protocol commitment. Segment writing and verified reading are implemented in issue #15. Catalog generation, writer-locked publication, and immutable restart snapshots -are implemented in issue #16. Store initialization and complete executable -crash and recovery evidence remain owned by issue #17. +are implemented in issue #16. Store initialization, platform admission, +explicit recovery, and the 105-case process-death crash matrix are implemented +in issue #17. Every page below describes the behaviour of `main`; the +[requirements ledger](requirements.md) names the test behind each claim. ## Core law diff --git a/docs/formats/segment-store-v1/publication.md b/docs/formats/segment-store-v1/publication.md index 3783be79..2d18302b 100644 --- a/docs/formats/segment-store-v1/publication.md +++ b/docs/formats/segment-store-v1/publication.md @@ -127,10 +127,17 @@ existing store root plus `staging`, `segments`, and `catalogs`. Both operations perform blocking filesystem I/O. Neither operation repairs, enumerates, or removes protocol state. -Issue #16 defines the proof type but deliberately exposes no public producer. -The filesystem transition suite uses a crate-private, test-only unchecked proof -to exercise publication mechanics. Issue #17 must implement initialization and -the platform contract before production callers can obtain admission. +`FilesystemPlatformAdmission` has private fields, so only Keep's own +initialization and platform-admission boundary can produce a production value. +A new store obtains one through `FilesystemPlatformAdmission::initialize`, +which runs the ordered initialization protocol (`KEEP-RECOVERY-002`); a +published store reacquires one through `FilesystemPlatformAdmission::reopen`, +which mutates nothing and admits the production platform +(`KEEP-RECOVERY-003`). Both are described in +[Recovery and platform contract](recovery.md). The crash matrix harness opens +an unchecked publisher only behind the `repository-tasks` Cargo feature, +then wraps the publisher in fault-injecting decorators. This bypass is +reserved for repository harnesses rather than production admission. `publish_catalog_generation` performs complete semantic preflight before the first storage transition. With `FilesystemCatalogPublisher`, it then executes @@ -161,12 +168,13 @@ head-selected coordinates, refuses symbolic links and nonregular artifacts, checks every length before allocation, and reconstructs logical bindings only after all canonical bytes and physical coordinates verify. -Issue #16 does not implement store-root initialization, platform admission, or -explicit recovery. A future admission producer must prove the exact canonical -directories and persistent lock file before opening a publisher. Any retained -`head.next` or `current.cat`, and any `current.seg` not owned by the selected -staged segment, causes publication to refuse before mutation and requires issue -recovery under #17. When `HEAD` is absent, the publisher probes both immutable pools +Publication never initializes, admits, or recovers on its own. Admission +proves the exact canonical directories and the persistent lock file before a +publisher opens. Any retained `head.next` or `current.cat`, and any +`current.seg` not owned by the selected staged segment, causes publication to +refuse before mutation; the explicit recovery boundaries in +[Recovery and platform contract](recovery.md) classify and resolve that +residue. When `HEAD` is absent, the publisher probes both immutable pools and admits first publication only when both are empty; any entry is preserved as recovery evidence and refuses the operation. An already-current retry refuses every fixed-name stage. diff --git a/docs/formats/segment-store-v1/recovery.md b/docs/formats/segment-store-v1/recovery.md index 20c3402e..1891d36c 100644 --- a/docs/formats/segment-store-v1/recovery.md +++ b/docs/formats/segment-store-v1/recovery.md @@ -55,14 +55,24 @@ exact truncation only when every available segment- or record-header framing byte remains canonical. It preserves proven partial-framing and complete-looking corruption as typed refusals. Catalog- and next-head-stage classifiers apply the same available-fixed-framing rule before -distinguishing exact truncation from complete canonical bytes. Transitive -publication-view admission and filesystem-streaming semantic classification -remain unimplemented. +distinguishing exact truncation from complete canonical bytes. Every +classifier consumes complete, caller-supplied protocol-bounded stage bytes; +the ledger's classification rows (`KEEP-RECOVERY-010`, `KEEP-RECOVERY-011`) +are whole-byte by design, and no classifier streams from a filesystem handle. +The inventory reader returns fingerprint evidence without retaining stage +bytes. The filesystem segment resumer separately materializes its pinned +writable stage before re-admission and continuation. `admit_recovery_stage_bytes` first requires the canonical-name stage, exact length, and recomputed stage fingerprint to match prior observation evidence; only `assess_recovery_stage` may dispatch those admitted bytes to a semantic classifier. Matching evidence does not convert corrupt bytes into lawful content. + +Transitive publication-view admission for a candidate `head.next` is +implemented at [Leftover next head](#leftover-next-head). Its planner and +filesystem finalizer require the complete candidate catalog snapshot under +`KEEP-RECOVERY-017` and `KEEP-RECOVERY-018` before head replacement. + `plan_recovery_stage_discard` admits only an exact truncation assessment and retains both its evidence and typed truncation reason. The semantic `execute_recovery_stage_discard` port refuses evidence drift before mutation, @@ -117,8 +127,10 @@ Run the repository-owned matrix: cargo xtask durability-crash-matrix ``` -The command executes the three ordered positions for each stable -`KEEP-CRASH-001`–`KEEP-CRASH-035` point: 105 canonical cases. Each case owns a +The 105 version-one cases are a subset of the complete command, which also +executes version-two migration cases. The version-one subset executes the +three ordered positions for each stable `KEEP-CRASH-001`–`KEEP-CRASH-035` +point. Each version-one case owns a fresh filesystem store and an isolated child process group. The child retains the writer lock and any open staged artifact while it executes the production initialization, segment-writing, catalog-publication, or recovery-discard diff --git a/docs/formats/segment-store-v1/requirements.md b/docs/formats/segment-store-v1/requirements.md index fe544a28..d7acbd69 100644 --- a/docs/formats/segment-store-v1/requirements.md +++ b/docs/formats/segment-store-v1/requirements.md @@ -47,11 +47,11 @@ retention, or garbage collection. | `KEEP-SEGMENT-002` | Staged and sealed writer states are distinct, consuming types | `tests/segment_writer.rs` | Implemented in #15 | | `KEEP-SEGMENT-003` | Short, interrupted, zero-progress, invalid-count, storage, permission, flush, and synchronization failures retain exact phases and offsets | `tests/segment_writer/write_contract_laws.rs`, `tests/segment_writer/refusal_laws.rs`, `tests/segment_writer/durability_laws.rs` | Implemented in #15 | | `KEEP-SEGMENT-004` | Complete-segment admission verifies bounds, framing, checksums, logical identities, duplicate refusal, terminal state, and physical digest before exposure | `tests/segment.rs`, `tests/segment/identity_laws.rs`, `tests/segment/framing_laws.rs` | Implemented in #15 | -| `KEEP-SEGMENT-005` | The public sealed receipt exposes no mutable stage handle | `src/adapters/sealed_segment.rs` | Implemented in #15 | +| `KEEP-SEGMENT-005` | The public sealed receipt exposes no mutable stage handle | `src/adapters/sealed_segment.rs` | Production API implemented in #15; repository-task escape tracked in #146 | | `KEEP-SEGMENT-006` | Malformed, unsupported, partial, conflicting, and corrupt input returns boundary-typed errors | `tests/segment_header/mutation_laws.rs`, `tests/segment_record_header/framing_laws.rs`, `tests/segment_seal/framing_laws.rs`, `tests/segment/identity_laws.rs` | Implemented in #15 | | `KEEP-SEGMENT-007` | Record, nested-layout, segment-length, and temporary identity-index allocation remain explicitly bounded | `tests/segment_memory.rs`, `tests/segment_record_memory.rs`, `tests/segment_seal_memory.rs` | Implemented in #15 | -| `KEEP-SEGMENT-008` | Filesystem staging uses exclusive fixed-name creation and never enumerates storage as a content index | `tests/segment_filesystem_stage.rs`, `src/adapters/filesystem_segment_stage.rs` | Implemented in #15 | -| `KEEP-SEGMENT-009` | Every implemented write and durability phase has deterministic fault injection, while dropped unsealed stages preserve recovery evidence | `tests/segment_writer/`, `tests/segment_filesystem_stage.rs` | Implemented in #15 | +| `KEEP-SEGMENT-008` | Filesystem staging uses exclusive fixed-name creation and never enumerates storage as a content index | `src/adapters/filesystem_segment_stage_tests.rs`, `src/adapters/filesystem_segment_stage.rs` | Implemented in #15 | +| `KEEP-SEGMENT-009` | Every implemented write and durability phase has deterministic fault injection, while dropped unsealed stages preserve recovery evidence | `tests/segment_writer/`, `src/adapters/filesystem_segment_stage_tests.rs` | Implemented in #15 | | `KEEP-SEGMENT-010` | Every public segment-format parser boundary is fuzzed from canonical deterministic seeds | `fuzz/fuzz_targets/segment_format.rs`, `xtask/src/fuzz_seed_corpus/segment_seeds.rs` | Implemented in #15 | @@ -62,7 +62,8 @@ Issue #16 implements catalog-generation admission, writer-locked filesystem publication mechanics, and immutable reader snapshots. Production publisher construction requires `FilesystemPlatformAdmission`, whose platform-checked producer is implemented as the initialization slice of issue #17. Explicit -recovery remains separate work. +recovery is implemented in issue #17; its evidence is the recovery table +below. @@ -103,7 +104,12 @@ may now authorize a transition-checked finalization through a semantic storage port, and the filesystem finalizer binds that transition to pinned writer-authorized storage. These slices now include reusable-stage continuation and the complete process-death crash matrix. Retention, compaction, garbage -collection, and host-power-loss simulation remain outside issue #17. +collection, and host-power-loss simulation remain outside version 1: +retention belongs to +[`keep.segment-store/v2`](../segment-store-v2/README.md), compaction and +garbage collection are tracked in issue #21. The process-death matrix does +not establish host-power-loss behavior; that requires separate filesystem and +device fault evidence. @@ -185,8 +191,9 @@ segment corpus and adds parser fuzzing and corruption evidence. Issue #16 matches the catalog and publication-head corpus, executes the documented publication order through a real filesystem adapter, reconstructs exact immutable restart snapshots, and adds deterministic transition-model and -seeded parser-fuzz evidence. Crash-injection and explicit recovery remain -owned by issue #17. +seeded parser-fuzz evidence. Issue #17 adds initialization, platform +admission, explicit recovery, and the 105-case process-death crash matrix +(`KEEP-RECOVERY-001`–`KEEP-RECOVERY-021`). The format-local tradeoffs are recorded in the [colocated rationale](rationale.md). diff --git a/xtask/tests/segment_store_implementation_documentation.rs b/xtask/tests/segment_store_implementation_documentation.rs index cb616aff..89deaac5 100644 --- a/xtask/tests/segment_store_implementation_documentation.rs +++ b/xtask/tests/segment_store_implementation_documentation.rs @@ -4,8 +4,18 @@ const ROOT_README: &str = include_str!("../../README.md"); const FORMAT_REGISTRY: &str = include_str!("../../docs/formats/README.md"); const FORMAT_README: &str = include_str!("../../docs/formats/segment-store-v1/README.md"); const REQUIREMENTS: &str = include_str!("../../docs/formats/segment-store-v1/requirements.md"); +const PUBLICATION: &str = include_str!("../../docs/formats/segment-store-v1/publication.md"); +const RECOVERY: &str = include_str!("../../docs/formats/segment-store-v1/recovery.md"); const CORPUS_README: &str = include_str!("../../conformance/segment-store/v1/README.md"); +#[test] +fn version_one_crash_evidence_is_distinguished_from_the_complete_command() { + assert!( + RECOVERY.contains("105 version-one cases are a subset"), + "the unqualified crash command also executes version-two migration cases" + ); +} + #[test] fn living_documentation_names_the_implemented_segment_boundary() { for (document, claim) in [ @@ -32,3 +42,32 @@ fn living_documentation_names_the_implemented_segment_boundary() { } assert!(!ROOT_README.contains("Durable segment storage, retention")); } + +#[test] +fn recovery_documentation_does_not_assign_materialization_to_the_inventory_reader() { + assert!( + !RECOVERY.contains("bytes that the inventory\nreader materializes"), + "inventory fingerprinting returns evidence, not materialized stage bytes" + ); +} + +#[test] +fn living_v1_pages_no_longer_assign_shipped_recovery_to_a_future_issue() { + for (document, stale_claim) in [ + (FORMAT_README, "remain owned by issue #17"), + (PUBLICATION, "Issue #17 must implement initialization"), + ( + PUBLICATION, + "Issue #16 does not implement store-root initialization", + ), + (PUBLICATION, "A future admission producer"), + (RECOVERY, "remain unimplemented"), + (REQUIREMENTS, "Explicit\nrecovery remains separate work"), + (REQUIREMENTS, "remain\nowned by issue #17"), + ] { + assert!( + !document.contains(stale_claim), + "stale issue-era claim survives: {stale_claim:?}" + ); + } +}