From 1ffdf5aa22bc1c0b4e77c472e5cc552d0dfb4fae Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 12:18:37 -0700 Subject: [PATCH 1/4] Test: reproduce catalog authority on refused platform (#150) --- tests/catalog_platform_admission.rs | 49 +++++++++++++++++++ .../unsupported_directory.rs | 33 +++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tests/catalog_platform_admission.rs create mode 100644 tests/catalog_platform_admission/unsupported_directory.rs diff --git a/tests/catalog_platform_admission.rs b/tests/catalog_platform_admission.rs new file mode 100644 index 00000000..a21ab6fe --- /dev/null +++ b/tests/catalog_platform_admission.rs @@ -0,0 +1,49 @@ +//! Public platform-admission laws; medium Linux filesystem evidence for #150. +//! Oracle: KEEP-CATALOG-007 requires platform admission independently of lock ownership. + +#![cfg(all(target_os = "linux", feature = "repository-tasks"))] + +#[path = "catalog_platform_admission/unsupported_directory.rs"] +pub mod unsupported_directory; + +use std::error::Error; +use std::fs; +use std::io::ErrorKind; + +use keep::{ + CatalogRestartByteLimit, CatalogRestartPolicy, FilesystemCatalogPublisher, + FilesystemPlatformAdmission, FilesystemWriterLock, LayoutEntryLimit, SegmentReadPolicy, + SegmentRecordLimit, StoreInitializationError, StoreInitializationPhase, +}; + +#[test] +fn a_writer_lock_cannot_mint_publication_authority_on_a_refused_platform() +-> Result<(), Box> { + let directory = unsupported_directory::UnsupportedDirectory::create()?; + let refusal = match FilesystemPlatformAdmission::initialize(directory.path()) { + Ok(_admitted) => return Err("probe filesystem unexpectedly admitted".into()), + Err(error) => error, + }; + assert!( + matches!(refusal, StoreInitializationError::Io { phase, ref source } + if phase == StoreInitializationPhase::AdmitPlatform + && source.kind() == ErrorKind::Unsupported) + ); + fs::write(directory.path().join("writer.lock"), [])?; + for name in ["staging", "segments", "catalogs"] { + fs::create_dir(directory.path().join(name))?; + } + let lock = FilesystemWriterLock::try_acquire(directory.path())?; + let policy = CatalogRestartPolicy::new( + SegmentReadPolicy::new(SegmentRecordLimit::MAXIMUM, LayoutEntryLimit::MAXIMUM), + CatalogRestartByteLimit::new(1_048_576)?, + ); + let error = match FilesystemCatalogPublisher::open_unchecked_for_repository_tasks(lock, policy) + { + Ok(_publisher) => return Err("refused platform acquired public publisher authority".into()), + Err(error) => error, + }; + assert_eq!(error.kind(), ErrorKind::Unsupported); + directory.remove()?; + Ok(()) +} diff --git a/tests/catalog_platform_admission/unsupported_directory.rs b/tests/catalog_platform_admission/unsupported_directory.rs new file mode 100644 index 00000000..8f763170 --- /dev/null +++ b/tests/catalog_platform_admission/unsupported_directory.rs @@ -0,0 +1,33 @@ +//! This module owns per-process tmpfs scratch for unsupported-platform refusal laws. + +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; + +/// Private scratch on the Linux runner's explicitly unsupported tmpfs mount. +pub(super) struct UnsupportedDirectory { + path: PathBuf, +} + +impl UnsupportedDirectory { + pub(super) fn create() -> io::Result { + let path = + Path::new("/dev/shm").join(format!("keep-catalog-platform-{}", std::process::id())); + fs::create_dir(&path)?; + Ok(Self { path }) + } + + pub(super) fn path(&self) -> &Path { + &self.path + } + + pub(super) fn remove(self) -> io::Result<()> { + fs::remove_dir_all(&self.path) + } +} + +impl Drop for UnsupportedDirectory { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.path); + } +} From f7956c0405141cda7c4e93d257ace897b272e082 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 12:23:56 -0700 Subject: [PATCH 2/4] Fix: admit repository catalog publishers through production profile (#150) --- CHANGELOG.md | 2 ++ docs/formats/segment-store-v1/publication.md | 7 ++--- docs/formats/segment-store-v1/rationale.md | 4 +++ docs/formats/segment-store-v1/requirements.md | 2 +- .../catalog-platform-admission.md | 31 +++++++++++++++++++ src/adapters/filesystem_catalog_publisher.rs | 11 +++++-- src/adapters/filesystem_platform_admission.rs | 17 +++++----- src/adapters/filesystem_platform_profile.rs | 15 +++++++++ .../directory_laws.rs | 18 ----------- tests/catalog_platform_admission.rs | 25 +++++++++++++++ .../production_protocol/initialization.rs | 10 +++--- 11 files changed, 105 insertions(+), 37 deletions(-) create mode 100644 docs/testing-evidence/catalog-platform-admission.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c23e7c27..2f9f5081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ after its public API and format compatibility policies are established. ## [Unreleased] +- Repository-task catalog publisher construction now enforces the production filesystem profile even when given an existing writer lock; catalog publication crash campaigns use ordinary platform admission (#150). + - Retention recovery execution errors report the exact failed boundary, original typed cause, known namespace effects and uncertain effect/durability; retries freshly observe the store. Observed stage identity remains binding across reopening, and cleanup preserves verified pool evidence rather than promising the removed pathname survives (#99). - Retention recovery now preserves incomplete stages and requires explicit disposition before any recovery mutation or publication retry; automatic incomplete-stage disposal is deferred by maintainer decision (#99). diff --git a/docs/formats/segment-store-v1/publication.md b/docs/formats/segment-store-v1/publication.md index 2d18302b..9b3aa295 100644 --- a/docs/formats/segment-store-v1/publication.md +++ b/docs/formats/segment-store-v1/publication.md @@ -134,10 +134,9 @@ 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. +[Recovery and platform contract](recovery.md). + +The crash matrix opens its catalog publisher through ordinary production initialization and admission, then wraps that publisher in fault-injecting decorators. Publication campaigns therefore require admitted Linux ext4 scratch storage. The feature-gated legacy `open_unchecked_for_repository_tasks` entry point now checks the same production filesystem profile against its retained root capability before constructing authority; its name no longer denotes a platform bypass. It reopens that pinned directory with a readable descriptor for profile ioctls, checks each existing protocol directory, and requires strict root identity. It does not re-admit an ambient pathname or claim to establish the historical spelling used to acquire the lock. Private unit-test construction remains separate and is not callable by external consumers. `publish_catalog_generation` performs complete semantic preflight before the first storage transition. With `FilesystemCatalogPublisher`, it then executes diff --git a/docs/formats/segment-store-v1/rationale.md b/docs/formats/segment-store-v1/rationale.md index 7c03e0e1..fb1e98fd 100644 --- a/docs/formats/segment-store-v1/rationale.md +++ b/docs/formats/segment-store-v1/rationale.md @@ -176,6 +176,10 @@ filesystem whose individual operations returned success. Each would let an unsupported platform manufacture the authority that the proof is meant to represent. +## Repository-task publisher admission + +The repository-tasks feature must preserve the platform-admission invariant for public catalog publisher authority (#150). Its legacy locked-root constructor performs actual production profile checks before creating an admission proof; a retained lock and lenient identity probe are insufficient. Keeping the existing method signature avoids an unnecessary source compatibility break, while its documentation explicitly corrects the former bypass behavior. The crash harness uses ordinary production initialization for its publisher so that unsupported scratch platforms fail honestly. A separate unchecked public publisher or a caller-provided admission flag would recreate the defect. Other repository adapters have separate admission boundaries and are not certified by this catalog-specific change. + ## Observation before recovery Store opening performs no repair. It produces either one verified reader diff --git a/docs/formats/segment-store-v1/requirements.md b/docs/formats/segment-store-v1/requirements.md index 526fd25c..0005562c 100644 --- a/docs/formats/segment-store-v1/requirements.md +++ b/docs/formats/segment-store-v1/requirements.md @@ -75,7 +75,7 @@ below. | `KEEP-CATALOG-004` | Every catalog location equals a verified top-level record span in the exact named segment; construction and admission require every supplied segment to be referenced, and admission scans each referenced segment once | Bounded grouped lookup plan and golden artifacts | `tests/catalog_encoding.rs`, `tests/catalog_locations.rs` | Implemented in #16 | | `KEEP-CATALOG-005` | Publication admits only the exact expected successor and reports expected and observed generation and digest on staleness | Generation transition model | `tests/catalog_transition.rs` | Implemented in #16 | | `KEEP-CATALOG-006` | A reader retains one complete catalog generation and never combines it with a concurrent head | Immutable snapshot model | `tests/catalog_snapshot.rs` | Implemented in #16 | -| `KEEP-CATALOG-007` | Retained kernel locks on the pinned store root and persistent writer file exclude a second cooperative writer even if the directory entry is replaced; neither lock is deleted on release, and lock ownership alone cannot construct a publisher without platform admission | Multi-handle lock model, replacement fixture, and construction architecture law | `tests/catalog_writer_lock.rs`, `tests/catalog_filesystem_publication/directory_laws.rs` | Implemented in #16; replacement-hardened in #17 | +| `KEEP-CATALOG-007` | Retained kernel locks on the pinned store root and persistent writer file exclude a second cooperative writer even if the directory entry is replaced; neither lock is deleted on release, and lock ownership alone cannot construct a publisher without platform admission | Multi-handle lock model, replacement fixture, and public unsupported-platform refusal | `tests/catalog_writer_lock.rs`, `tests/catalog_platform_admission.rs`; [admission evidence](../../testing-evidence/catalog-platform-admission.md) | Implemented in #16; replacement-hardened in #17; repository-task admission corrected for #150 | | `KEEP-CATALOG-008` | Segment, catalog, and head publication follows the documented synchronization order; retained fixed-name recovery state refuses before mutation; an absent head requires empty immutable pools; retry of an already-current candidate performs no publication mutation and re-synchronizes the root | Fault-recording port and filesystem fixtures | `tests/catalog_publication.rs`, `src/adapters/filesystem_catalog_publisher_tests.rs` | Implemented in #16 | | `KEEP-CATALOG-009` | Restart loading refuses corrupt, unsupported, noncanonical, dangling, and conflicting catalog state | Corruption matrix | `tests/catalog_restart.rs` | Implemented in #16 | | `KEEP-CATALOG-010` | Model-based transitions and lookups agree with a deterministic `BTreeMap` catalog | Boring reference catalog | `tests/catalog_model.rs` | Implemented in #16 | diff --git a/docs/testing-evidence/catalog-platform-admission.md b/docs/testing-evidence/catalog-platform-admission.md new file mode 100644 index 00000000..4359cf02 --- /dev/null +++ b/docs/testing-evidence/catalog-platform-admission.md @@ -0,0 +1,31 @@ +# Catalog publisher platform admission + +Change kind: bug fix for #150, under completed-roadmap audit #131. Owner: `@flyingrobots`. Public catalog publisher authority must preserve production platform admission with `repository-tasks` enabled. Main `6051abb25a9fd33ae7ee0de5614514b709a4d82a` admitted a publisher from a writer lock even after ordinary initialization refused the same tmpfs root. The exact regression is committed in `1ffdf5a`; the following fix keeps its expected outcome unchanged. + +## Public outcome and oracle + +The medium-size Linux integration law `a_writer_lock_cannot_mint_publication_authority_on_a_refused_platform` creates owned scratch on `/dev/shm`, requires ordinary initialization to return `StoreInitializationError::Io` at `AdmitPlatform` with `Unsupported`, constructs the canonical namespace, acquires a real writer lock, and calls the alternate public publisher constructor. The specified oracle is KEEP-CATALOG-007: its result must also refuse with `Unsupported`. The parent instead returned a publisher and failed with `refused platform acquired public publisher authority`. This is an observed runtime RED, not a compilation or source-text check. + +The positive law `repository_publisher_preserves_staging_on_an_admitted_platform` initializes actual ext4 storage, reacquires writer authority, opens through the same alternate constructor, writes a stage, and observes the exact retained bytes after closing the handles. It prevents an unconditional-refusal implementation from satisfying the negative case. The bytes are independently supplied by the test; no production normalization computes the expectation. + +A separate copied production mutation reported successful writes without persisting any bytes. The positive law executed and failed its exact-byte assertion with an empty observed file; the negative law still passed. Its dedicated build directory was separate from both parent and fixed candidates. This calibration mutation was not committed. + +Both laws pass in debug and release after the fix. The first attempted strict probe correctly exposed an adapter descriptor mismatch: writer directories may use `O_PATH`, while profile ioctls need readable descriptors. That attempt returned `EBADF` and failed both laws. The correction opens `.` relative to the retained directory capability before profile inspection; it neither reopens an ambient path nor changes the lock or storage identity. + +## Finite capability inventory + +`FilesystemCatalogPublisher::open` consumes a privately constructed `FilesystemPlatformAdmission`. Its ordinary producers remain production initialization and published-store reopen. The alternate public `open_unchecked_for_repository_tasks` now runs the same Linux root and protocol-directory profile checks plus strict root identity before creating that proof; on unsupported operating systems it refuses. Its legacy method name and signature remain for source compatibility, with the corrected semantics documented explicitly. Private unit-test construction remains gated by `cfg(test)` and is unavailable to downstream callers. + +The publisher's fields remain private to its adapter module; no other external constructor produces this authority. Version-two admission, migration authority and recovery-stage-discard adapters are distinct public boundaries, retain their existing implementations, and are not certified by this probe. The pinned-root route establishes the properties of that retained capability; it cannot attest the spelling or alias history of the earlier path used to acquire it. No claim of inode-conditional pathname mutation or arbitrary out-of-band namespace isolation is added. + +## Harness integration and preservation + +Catalog crash execution now opens its publisher using `FilesystemPlatformAdmission::initialize` and the ordinary publisher constructor. Its ordered publication phases, fault decorators and restart oracles are unchanged. Full debug and optimized process-death matrices pass on actual ext4 scratch storage. The generic initialization fault port and other protocols retain their separate fault-injection boundaries. The campaign now honestly requires supported scratch storage for production catalog publication; an unsupported development filesystem cannot silently receive production publisher authority. + +The removed source-string test `publisher_has_no_unadmitted_production_constructor` survived the demonstrated bypass because it inspected only one constructor signature and declarations in one file. Deletion criterion: failed calibration against the exact claim, replaced by the public runtime refusal and positive capability laws above. Existing no-follow namespace, writer exclusion, canonical publication, corruption and restart laws remain. No test-case count is treated as a storage oracle. + +## Replay and limitations + +Replay `cargo test --test catalog_platform_admission --all-features --locked` in debug and with `--release`. Execute both `cargo xtask durability-crash-matrix` and `cargo run --quiet --release --locked --package xtask -- durability-crash-matrix`. Copy-isolated Linux aarch64 Docker validation uses pinned Rust 1.96.0, dedicated build output, tmpfs for the negative fixture and admitted ext4 for the positive fixture and crash campaign. Source SHAs, final required checks and review outcome are recorded on the PR. No host Rust tests, writable checkout mount or fabricated platform proof is used. + +The fixtures own per-process directories. Missing or differently configured `/dev/shm` fails setup visibly; it does not skip the refusal law. The schedules are deterministic and use no sleeps or random input. The negative law's RED proves detection of the reported bug; the positive law failed under the descriptor-defective implementation and passes with supported staging. This is not a new parser, performance or physical-power-loss claim, and no on-disk format changes. Resource ceilings and ordinary CI enforcement gaps remain those recorded in the testing enforcement profile; no new compliant-sandbox or latency-distribution claim is made. Retire these laws only if the public authority route is removed or stronger public evidence demonstrably replaces them. diff --git a/src/adapters/filesystem_catalog_publisher.rs b/src/adapters/filesystem_catalog_publisher.rs index 16999a26..b54b27f2 100644 --- a/src/adapters/filesystem_catalog_publisher.rs +++ b/src/adapters/filesystem_catalog_publisher.rs @@ -75,14 +75,19 @@ impl FilesystemCatalogPublisher { }) } - /// Opens a publisher without the production platform-profile proof. + /// Admits an already locked root and opens a publisher for repository tasks. /// /// Repository process-death tests use this after executing the production /// initialization protocol through [`crate::RepositoryInitializationStorage`]. + /// Despite the legacy name, this method now checks the full production + /// platform profile, including protocol-directory mount/device identity. + /// The pinned root is the authority; this does not re-admit an ambient path. + /// Unsupported filesystems cannot obtain publication authority through it. /// /// # Errors /// - /// Returns the same pinned-directory admission failures as [`Self::open`]. + /// Returns platform-profile or required root-identity failures before + /// constructing authority, then the same directory failures as [`Self::open`]. #[cfg(feature = "repository-tasks")] #[doc(hidden)] pub fn open_unchecked_for_repository_tasks( @@ -90,7 +95,7 @@ impl FilesystemCatalogPublisher { policy: CatalogRestartPolicy, ) -> io::Result { Self::open( - FilesystemPlatformAdmission::unchecked_for_repository_tasks(lock)?, + FilesystemPlatformAdmission::from_repository_writer_lock(lock)?, policy, ) } diff --git a/src/adapters/filesystem_platform_admission.rs b/src/adapters/filesystem_platform_admission.rs index 35fce4c6..556360ad 100644 --- a/src/adapters/filesystem_platform_admission.rs +++ b/src/adapters/filesystem_platform_admission.rs @@ -30,10 +30,13 @@ impl FilesystemPlatformAdmission { } #[cfg(feature = "repository-tasks")] - pub(super) fn unchecked_for_repository_tasks( - lock: FilesystemWriterLock, - ) -> std::io::Result { - Self::unchecked(lock) + pub(super) fn from_repository_writer_lock(lock: FilesystemWriterLock) -> std::io::Result { + // Writer capabilities may be O_PATH; profile ioctls require a readable + // descriptor of that same pinned directory, without an ambient reopen. + let pinned = lock.clone_directory()?; + let directory = super::sync_capable_directory::open(&pinned, ".")?; + let root_identity = super::filesystem_platform_profile::admit_locked_root(&directory)?; + Ok(Self::initialized(lock, root_identity)) } pub(super) fn into_lock(self) -> FilesystemWriterLock { @@ -44,10 +47,10 @@ impl FilesystemPlatformAdmission { (self.lock, self.root_identity) } - /// Grants authority without platform admission for tests and repository - /// tasks; the identity probe tolerates a kernel that reports no mount + /// Grants authority without platform admission only for private unit + /// tests; the identity probe tolerates a kernel that reports no mount /// identity so the bypass does not require `STATX_MNT_ID`. - #[cfg(any(test, feature = "repository-tasks"))] + #[cfg(test)] fn unchecked(lock: FilesystemWriterLock) -> std::io::Result { let directory = lock.clone_directory()?; let root_identity = super::filesystem_platform_profile::root_identity_lenient(&directory)?; diff --git a/src/adapters/filesystem_platform_profile.rs b/src/adapters/filesystem_platform_profile.rs index aea9e2c9..9998520e 100644 --- a/src/adapters/filesystem_platform_profile.rs +++ b/src/adapters/filesystem_platform_profile.rs @@ -56,6 +56,21 @@ pub(super) fn open(store_root: &Path) -> io::Result { Ok(directory) } +/// Applies the production profile to the root already pinned by writer authority. +#[cfg(all(target_os = "linux", feature = "repository-tasks"))] +pub(super) fn admit_locked_root(directory: &Dir) -> io::Result { + admit_linux_profile(directory, &PROTOCOL_DIRECTORIES)?; + root_identity(directory) +} + +#[cfg(all(not(target_os = "linux"), feature = "repository-tasks"))] +pub(super) fn admit_locked_root(_directory: &Dir) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "catalog publication requires the admitted Linux ext4 profile", + )) +} + /// Opens one version-two store root under the admitted Linux profile. /// /// Identical to [`open`], but every version-two protocol directory that diff --git a/tests/catalog_filesystem_publication/directory_laws.rs b/tests/catalog_filesystem_publication/directory_laws.rs index c570c584..1d7302d3 100644 --- a/tests/catalog_filesystem_publication/directory_laws.rs +++ b/tests/catalog_filesystem_publication/directory_laws.rs @@ -6,24 +6,6 @@ use crate::{FilesystemCatalogPublisher, FilesystemWriterLock}; use super::{StoreFixture, restart_policy}; -#[test] -fn publisher_has_no_unadmitted_production_constructor() -> Result<(), Box> { - let publisher = include_str!("../../src/adapters/filesystem_catalog_publisher.rs"); - let admission = include_str!("../../src/adapters/filesystem_platform_admission.rs"); - if !publisher.contains("pub fn open(\n admission: FilesystemPlatformAdmission,") { - return Err("publisher construction does not require platform admission".into()); - } - let public_items = admission - .lines() - .map(str::trim_start) - .filter(|line| line.starts_with("pub ")) - .collect::>(); - if public_items != ["pub struct FilesystemPlatformAdmission {"] { - return Err("platform admission exposes an unverified public producer".into()); - } - Ok(()) -} - #[test] fn publisher_refuses_a_non_directory_protocol_namespace() -> Result<(), Box> { let store = StoreFixture::create("catalog-filesystem-nondirectory")?; diff --git a/tests/catalog_platform_admission.rs b/tests/catalog_platform_admission.rs index a21ab6fe..3e6b35fd 100644 --- a/tests/catalog_platform_admission.rs +++ b/tests/catalog_platform_admission.rs @@ -3,12 +3,15 @@ #![cfg(all(target_os = "linux", feature = "repository-tasks"))] +#[path = "segment_filesystem_stage/sandbox.rs"] +pub mod admitted_directory; #[path = "catalog_platform_admission/unsupported_directory.rs"] pub mod unsupported_directory; use std::error::Error; use std::fs; use std::io::ErrorKind; +use std::io::Write; use keep::{ CatalogRestartByteLimit, CatalogRestartPolicy, FilesystemCatalogPublisher, @@ -47,3 +50,25 @@ fn a_writer_lock_cannot_mint_publication_authority_on_a_refused_platform() directory.remove()?; Ok(()) } + +#[test] +fn repository_publisher_preserves_staging_on_an_admitted_platform() -> Result<(), Box> { + let directory = admitted_directory::TestDirectory::create("catalog-admitted-platform")?; + drop(FilesystemPlatformAdmission::initialize(directory.path())?); + let lock = FilesystemWriterLock::try_acquire(directory.path())?; + let policy = CatalogRestartPolicy::new( + SegmentReadPolicy::new(SegmentRecordLimit::MAXIMUM, LayoutEntryLimit::MAXIMUM), + CatalogRestartByteLimit::new(1_048_576)?, + ); + let publisher = FilesystemCatalogPublisher::open_unchecked_for_repository_tasks(lock, policy)?; + let mut stage = publisher.create_segment_stage()?; + stage.write_all(b"retained stage evidence")?; + drop(stage); + drop(publisher); + assert_eq!( + fs::read(directory.path().join("staging/current.seg"))?, + b"retained stage evidence" + ); + directory.remove()?; + Ok(()) +} diff --git a/xtask/src/durability_crash_matrix/production_protocol/initialization.rs b/xtask/src/durability_crash_matrix/production_protocol/initialization.rs index 32c81178..298ef994 100644 --- a/xtask/src/durability_crash_matrix/production_protocol/initialization.rs +++ b/xtask/src/durability_crash_matrix/production_protocol/initialization.rs @@ -3,8 +3,9 @@ use std::path::Path; use keep::{ - CatalogRestartByteLimit, CatalogRestartPolicy, FilesystemCatalogPublisher, LayoutEntryLimit, - RepositoryInitializationStorage, SegmentReadPolicy, SegmentRecordLimit, initialize_store, + CatalogRestartByteLimit, CatalogRestartPolicy, FilesystemCatalogPublisher, + FilesystemPlatformAdmission, LayoutEntryLimit, RepositoryInitializationStorage, + SegmentReadPolicy, SegmentRecordLimit, initialize_store, }; use super::control::CrashControl; @@ -41,8 +42,9 @@ pub(super) fn initialized_lock( pub(super) fn publisher( store_root: &Path, ) -> Result { - let lock = initialized_lock(store_root)?; - FilesystemCatalogPublisher::open_unchecked_for_repository_tasks(lock, restart_policy()?) + let admission = FilesystemPlatformAdmission::initialize(store_root) + .map_err(|source| verification("admit crash catalog publisher platform", source))?; + FilesystemCatalogPublisher::open(admission, restart_policy()?) .map_err(|source| DurabilityCrashMatrixError::io("open crash catalog publisher", source)) } From fa06adfde89b70be5aaf7356206b7d8c1fbce169 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 2 Oct 2026 12:26:21 -0700 Subject: [PATCH 3/4] Docs: format catalog admission contract paragraphs (#150) --- docs/formats/segment-store-v1/publication.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/formats/segment-store-v1/publication.md b/docs/formats/segment-store-v1/publication.md index 9b3aa295..e2d64879 100644 --- a/docs/formats/segment-store-v1/publication.md +++ b/docs/formats/segment-store-v1/publication.md @@ -136,7 +136,20 @@ which mutates nothing and admits the production platform (`KEEP-RECOVERY-003`). Both are described in [Recovery and platform contract](recovery.md). -The crash matrix opens its catalog publisher through ordinary production initialization and admission, then wraps that publisher in fault-injecting decorators. Publication campaigns therefore require admitted Linux ext4 scratch storage. The feature-gated legacy `open_unchecked_for_repository_tasks` entry point now checks the same production filesystem profile against its retained root capability before constructing authority; its name no longer denotes a platform bypass. It reopens that pinned directory with a readable descriptor for profile ioctls, checks each existing protocol directory, and requires strict root identity. It does not re-admit an ambient pathname or claim to establish the historical spelling used to acquire the lock. Private unit-test construction remains separate and is not callable by external consumers. +The crash matrix opens its catalog publisher through ordinary production +initialization and admission, then wraps that publisher in fault-injecting +decorators. Publication campaigns therefore require admitted Linux ext4 scratch +storage. + +The feature-gated legacy `open_unchecked_for_repository_tasks` entry point now +checks the same production filesystem profile against its retained root +capability before constructing authority; its name no longer denotes a platform +bypass. It reopens that pinned directory with a readable descriptor for profile +ioctls, checks each existing protocol directory, and requires strict root identity. + +The pinned-root route does not re-admit an ambient pathname or claim to establish +the historical spelling used to acquire the lock. Private unit-test construction +remains separate and is not callable by external consumers. `publish_catalog_generation` performs complete semantic preflight before the first storage transition. With `FilesystemCatalogPublisher`, it then executes From 3626f6e2677a1d6d3af08b88245584800596ff55 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 3 Oct 2026 12:25:24 -0700 Subject: [PATCH 4/4] Docs: describe current catalog admission callers (#150) --- src/adapters/filesystem_catalog_publisher.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/adapters/filesystem_catalog_publisher.rs b/src/adapters/filesystem_catalog_publisher.rs index b54b27f2..6034f662 100644 --- a/src/adapters/filesystem_catalog_publisher.rs +++ b/src/adapters/filesystem_catalog_publisher.rs @@ -77,8 +77,9 @@ impl FilesystemCatalogPublisher { /// Admits an already locked root and opens a publisher for repository tasks. /// - /// Repository process-death tests use this after executing the production - /// initialization protocol through [`crate::RepositoryInitializationStorage`]. + /// This legacy route accepts an already retained writer lock. The catalog + /// crash campaign instead uses ordinary platform initialization and + /// [`Self::open`]. /// Despite the legacy name, this method now checks the full production /// platform profile, including protocol-directory mount/device identity. /// The pinned root is the authority; this does not re-admit an ambient path.