Candidate archive store v1

September 10, 2026 ยท View on GitHub

Status: implemented bounded Unix store; HOSTED GREEN under the v0.4.0 release baseline.

Audience: embedding hosts, compiler contributors, and agent workflow integrators.

This store persists one complete, source-backed ProjectCandidateArchive as an immutable file under an explicitly selected host root. It is separate from the Project Revision Store and changes neither its format nor its authority. No root is discovered from a manifest, current directory, environment, archive, candidate, or protocol request. Stored bytes are recovery inputs, never trusted HIR, source-commit approval, or evidence of current source admission.

The additive typed-draft persistence APIs share this root format and its aggregate limits. A root may contain complete candidate and draft archives, but each selected loader admits only its own exact archive kind. Existing candidate APIs and replay rules are unchanged.

Public API

pub fn persist(
    root: &Path,
    archive: &ProjectCandidateArchive,
) -> Result<CandidateArchiveStoreReceipt, Vec<Diagnostic>>;

pub fn load(
    root: &Path,
    expected_archive: &str,
    expected_candidate: &str,
) -> Result<ProjectCandidate, Vec<Diagnostic>>;

The functions are exposed through semaprax::candidate_archive_store. CandidateArchiveStoreReceipt has private fields and only borrowed archive_digest(), candidate_digest(), and base_revision() getters. It contains no path, open handle, root, approval, or reusable authority; it is not serde data, Clone, or Default. The archive's identities can be retained before publication to resolve an uncertain outcome with ordinary load.

persist independently restores the typed archive and checks its original base before opening a filesystem root. It prepares the receipt before publication. load independently reconstructs the embedded canonical original Project manifest and source inventory, replays complete candidate intentions, and checks exact archive/candidate identities and canonical bytes through ProjectCandidateArchive::restore. It does not read the original source paths. Removal of those original files does not prevent archive restoration. A restored candidate still requires a separately admitted current base and independent host authority before any eventual source commit.

Root authority and inventory

The root must already exist as an exact 0700 directory owned by the current effective user. Its spelling must be absolute and normalized: no relative, empty, filesystem-root-only, dot, parent, repeated-separator, or trailing-slash spelling. Paths are bounded to 4,096 bytes and 64 normal components. Every component is opened with directory and no-follow flags; each ancestor descriptor and its identity remain held through the operation. Before returning or publishing, the implementation checks held component identities, parent-child links, and an independently reopened absolute chain.

The host must exclude uncooperative mutation of this root, its ancestors, and the selected/staged file by another process acting as the same principal for the whole invocation. Mode checks do not inspect ACLs or revoke other existing handles. On hosts with ACL authority beyond Unix mode bits, the host guarantee must exclude that authority as well. A nonblocking advisory lock on the held root serializes cooperating store writers and readers. It is not protection against a same-user peer ignoring the lock, filesystem administrator, malicious mount replacement, or kernel compromise.

The complete root inventory consists only of:

<64 lowercase archive-digest hex digits>.json    # 0โ€“32 completed archives
.stage-<64 lowercase archive-digest hex digits>  # at most one inert failed stage

The sha256: prefix is omitted from filenames. Each entry must be a current effective-user-owned regular file with exact 0600 permissions and exactly one hard link. Symlinks, directories, device/FIFO/socket objects, unexpected names, extra stages, and excess entries reject. Completed files must be nonempty. All file sizes are bounded by MAX_PROJECT_CANDIDATE_ARCHIVE_BYTES (128 MiB); an inert stage may be empty or incomplete. Root enumeration and metadata checks are bounded to 32 completed entries plus one stage.

Unselected completed entries are checked for name, identity, owner, mode, single-link status, and bounded size only. Their content is not read, hashed, parsed, or semantically authenticated. A successful selected load does not attest to the contents of every file in the store. This distinction avoids replaying up to 32 unrelated archives on each operation; it is not a full-store integrity claim or a bound on all retained compiler memory.

Publication and load behavior

Under the nonblocking exclusive root lock, persist rejects any existing stage or selected destination. Even byte-identical existing destinations are not adopted. It requires a free completed-entry slot, creates exactly one stage with create-new/no-follow flags, and authenticates its empty held file and root inventory before writing. There is no implicit root creation or repair of permissions masked by the caller's umask.

It writes the archive's exact canonical bytes, syncs the file, reads back and compares every selected byte, and checks the root chain, inventory, and selected file identity again. After syncing the directory and repeating the selected readback, a no-replace rename publishes the stage under the digest filename. The store then syncs the root, checks the published path against the held stage identity, rechecks exact bytes and inventory, and releases the lock. It never overwrites, appends to, adopts, removes, or renames another completed entry.

load takes a nonblocking shared lock and permits at most one structurally valid inert stage. It opens only the selected completed file with no-follow and nonblocking flags, checks the held metadata, and reads at most its bounded declared size plus one byte. The independent archive restoration runs while the root lock and descriptors remain held. Before returning a candidate it checks the root/inventory and rereads the selected bytes for exact equality. The operation does not delete or resume a failed stage.

Before successful rename, failures leave any created stage as it stands. A stage blocks later persist calls but does not hide otherwise valid completed entries from load. After successful rename, every settlement, observation, readback, or lock-release failure returns SPX-I361: publication may have occurred. Neither branch performs cleanup, rollback, adoption, retry, eviction, or garbage collection. An uncertain publication must be inspected through exact load using the independently retained archive and candidate identities; callers must not treat the error as permission to retry publication blindly.

Sync calls express the local write/rename ordering. They do not establish power loss guarantees, network/NFS/overlay filesystem semantics, crash recovery, or durability across every storage device. The implementation is enabled on the same Unix target families as the existing handle-relative no-replace store (Linux, Android, Apple, Redox). Other targets, including Windows, fail closed without opening or mutating a store. This is not Windows store support.

Diagnostics and authored evidence

CodeMeaning
SPX-G300Invalid digest syntax, root spelling, or fixed path bounds.
SPX-G301Completed-entry or selected-file capacity exceeded.
SPX-G302Root/file/inventory binding disagreement, busy lock, missing entry, failed-stage blocker, or existing destination.
SPX-I360Filesystem failure before confirmed publication, or load/unlock failure.
SPX-I361Failure after successful publication rename; outcome must be resolved explicitly.

Independent archive/compiler diagnostics are preserved. They are not converted to a claim that stored source or a candidate is valid.

src/candidate_archive_store/tests.rs covers real filesystem cases for exact persistence/restoration after original-source removal; no adoption; wrong candidate binding and same-length tampering; root permissions/spelling/links; cooperative lock contention; selected symlinks/hard links and foreign entries; bounded metadata-only inventory; retained failed stages; hostile pre-publication stage substitution; and a post-publication observation failure resolved through exact load. Fault callbacks are private test seams around real filesystem operations. The post-pivot case performs the real rename before an injected observation error; it does not claim a physical crash or fsync fault was induced.

The implemented regressions have HOSTED GREEN v0.4.0 release evidence. The earlier authoring pass did not execute them locally; that record does not leave the released implementation unverified. Compilation for a Unix target family is not evidence of physical execution on every host in that family.

No semantic cache speedup, serialization of trusted HIR, source publication, network, build, or approval authority is provided. Typed-draft storage and automatic candidate/draft retention are implemented additive contracts, not missing functionality of this store. The graph-operational full goal remains Partial.