diff --git a/CLAUDE.md b/CLAUDE.md index 05670c5..d39b52f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,10 +4,10 @@ Neutral, agent-agnostic state fabric: delegation manifests binding four lineages (state, authority, behavior, trace), snapshot-backed undo for owned state, brokered capabilities for external effects, human-ratified compilation loops (skills up, caveats down). Read `docs/agent-state-fabric-brief.md` (why/what, v0.2) and -`docs/asf-schema-spec.md` (the constitution, v0.9) before writing any code. +`docs/asf-schema-spec.md` (the constitution, v0.10) before writing any code. The spec wins over this file wherever they disagree. Spec ambiguities found while implementing go to `docs/spec-issues.md` (never silently interpret); -SI-1…SI-21 are resolved (SI-20 → A20/M8 in v0.5; SI-21 → A21/M7 in v0.6); SI-22 is interpreted (gate replay clock, W-2); SI-24 is resolved (A22/§5.4 in v0.7 — capability early closure; implemented by W-8 under its gated matrix: A22 two-sided contract, targeted `a22_*` mutation lane, W-9 verdict-invariance reproduced bit-for-bit); SI-23 (actuation vs approval surfaces) is OPEN — no actuation-scoped tool may register before SI-23 resolves. SI-25 is resolved (A23/§6.2 in v0.8 — authenticated global order: signed per-home global_seq layer 1 normative now, external anchor layer 2 graduation-gated; W-15 implements; ratified via W-20 determinations D1–D7/S1–S5 in ADR 0006). SI-26…SI-30 remain OPEN from the 2026-07-12 cryptographic mechanism audit (signed type/domain, key lifecycle, AEAD envelope, post-shred generations, redaction commitments). SI-31/SI-33/SI-34 are resolved (A24–A26 in v0.9 — owned-state transition protocol §5.3, consumed authority §5.5, approval candidate binding §6; W-20 retro-ratification of the merged W-14 protocols via PR #48, determinations D31/D33/D34 plus seven rounds of review adjustments R1–R25 (round 7 scoped) and an internal pre-round-6 review in ADR 0007 — round 2 added the recovery capture record and positional freshness, round 3 hardened them, round 4 extended temporal binding to both consumption consumers, rounds 4–6 surfaced as-built defects and completed canonical-entry identity and the recovery path-state domain; spec-code deltas tracked as RF-35–RF-37/RF-39 (adjusted beyond as-built) and RF-40 (as-built fs-pipeline defects), carried by W-15/W-22; G13 files the outstanding negatives; unenforced clauses bind at their carrier gates per the ledger). SI-32 (store publication/filesystem attacker model) is OPEN from the same 2026-07-13 PR #43 review-cycle analysis and is next in W-20's batch. SI-35 (workboard domain labels vs domain-taxonomy governance, recovered from codex/workboard-dogfood at the W-21 revival decision), SI-37 (TracePosition agreement predicate; W-15 enforces fail-closed from day one), and SI-38 (TracePosition genesis representation; W-15 implements a bootstrap-event floor provisionally) are OPEN; SI-36 (mid-run "actually do Y" amendments, §3.1/M1/M5) merged via PR #46. SI-39 (recovery-window divergence: automated multi-window preservation; filed by PR #48's round-3 review, the narrow-and-file remedy ratified as R14) is OPEN. New issues start at SI-40. +SI-1…SI-21 are resolved (SI-20 → A20/M8 in v0.5; SI-21 → A21/M7 in v0.6); SI-22 is interpreted (gate replay clock, W-2); SI-24 is resolved (A22/§5.4 in v0.7 — capability early closure; implemented by W-8 under its gated matrix: A22 two-sided contract, targeted `a22_*` mutation lane, W-9 verdict-invariance reproduced bit-for-bit); SI-23 (actuation vs approval surfaces) is OPEN — no actuation-scoped tool may register before SI-23 resolves. SI-25 is resolved (A23/§6.2 in v0.8 — authenticated global order: signed per-home global_seq layer 1 normative now, external anchor layer 2 graduation-gated; W-15 implements; ratified via W-20 determinations D1–D7/S1–S5 in ADR 0006). SI-26…SI-30 remain OPEN from the 2026-07-12 cryptographic mechanism audit (signed type/domain, key lifecycle, AEAD envelope, post-shred generations, redaction commitments). SI-31/SI-33/SI-34 are resolved (A24–A26 in v0.9 — owned-state transition protocol §5.3, consumed authority §5.5, approval candidate binding §6; W-20 retro-ratification of the merged W-14 protocols via PR #48, determinations D31/D33/D34 plus seven rounds of review adjustments R1–R25 (round 7 scoped) and an internal pre-round-6 review in ADR 0007 — round 2 added the recovery capture record and positional freshness, round 3 hardened them, round 4 extended temporal binding to both consumption consumers, rounds 4–6 surfaced as-built defects and completed canonical-entry identity and the recovery path-state domain; spec-code deltas tracked as RF-35–RF-37/RF-39 (adjusted beyond as-built) and RF-40 (as-built fs-pipeline defects), carried by W-15/W-22; G13 files the outstanding negatives; unenforced clauses bind at their carrier gates per the ledger). SI-32 is resolved (A27/§5.3 in v0.10 — the three-tier storage adversary model: T1 offline tampering defeated by verify-on-read-back with the trust-root/unsigned-index/rollback carve-outs, T2 same-uid writer answered by topology never pathname checks — every T2 claim carries "holds under COOP; requires W-4 containment at G-ADVERSARIAL" — T3 human edit answered by attribution; staged-bytes normative; ratified 2026-07-15 via W-20 determinations D32-1…D32-10 in ADR 0008; RF-41 (unsigned meta rows → W-15a/b, the W-15 split in PR #52), RF-42/RF-43 (symlink/exclusion gaps → the RF-40 mechanical bugfix lane) file the gaps; SI-40 files the active-publication-window edit, its topology arm foreclosed by D32-4). SI-35 (workboard domain labels vs domain-taxonomy governance, recovered from codex/workboard-dogfood at the W-21 revival decision), SI-37 (TracePosition agreement predicate; W-15 enforces fail-closed from day one), and SI-38 (TracePosition genesis representation; W-15 implements a bootstrap-event floor provisionally) are OPEN; SI-36 (mid-run "actually do Y" amendments, §3.1/M1/M5) merged via PR #46. SI-39 (recovery-window divergence: automated multi-window preservation; filed by PR #48's round-3 review, the narrow-and-file remedy ratified as R14) and SI-40 (a human edit inside a live promotion/revert apply window is overwritten uncaptured; filed by the SI-32 ratification, P29-bounded, preservation-protocol candidate) are OPEN. New issues start at SI-41. ## Built — Stages 1–3 (kernel, spine, promotion gate). Current queue: docs/roadmap.md diff --git a/docs/adr/0008-filesystem-attacker-model.md b/docs/adr/0008-filesystem-attacker-model.md new file mode 100644 index 0000000..7da8257 --- /dev/null +++ b/docs/adr/0008-filesystem-attacker-model.md @@ -0,0 +1,559 @@ +# ADR 0008 — Filesystem attacker model for store publication and read-back + +**Status: ACCEPTED — ratified 2026-07-15 (operator session; W-20 item 3). +SI-32 is RESOLVED. The determinations for the ten decision points are +recorded in the ratification addendum at the end of this document +(D32-1…D32-10) and integrated into the schema spec as amendment A27 +(v0.10, §5.3). The body below is preserved as the candidate the +determinations judged; where the addendum adjusts it, the addendum wins. +Per the review-battery stopping rule (adopted 2026-07-15, PR #52): one +delta-scoped external round on the ratification text; continuation only +on blocking findings.** + +## Context + +The spec assumes content-addressed preparation and coherent restore but never +defines the filesystem adversary those operations run against. RF-20's +remediation (PR #43) implemented a publication discipline — retained verified +bytes, exclusive randomized no-follow siblings, rehash through the retained +handle, same-inode non-symlink recheck immediately before rename, fsync of +file and parent — **without a written threat model saying what those steps +must defeat**. Each review therefore re-derives the attacker and finds a new +residue (the RF-20 → RF-40 chain is five rounds of exactly this). SI-32 asks: +define the attacker in tiers, label every guarantee with the tier it holds +under, and state which publication-safety claims require containment before +they hold. + +This candidate is grounded in a complete inventory of every filesystem +publication and authority-bearing read-back site (the appendix at the end of +this ADR, with file:line anchors). The finding that motivates a written model: +the code already has one gold-standard publisher (`write_atomic_verified`, +every guard present) and three verifying read-backs — CAS and payload rehash +content, the recovery journal verifies its Ed25519 signature — but eleven other +sites carry partial guard sets whose *sufficiency cannot be judged without +knowing which tier they must survive*. Genuine gaps this model surfaces: R7 +(unsigned-meta trust, proposed RF-41), the registered-SQLite symlink gap +(proposed RF-42), and the T2-unwinnable class; the rest are tier-appropriate +and this model is what lets us say so. + +## The three-tier attacker model (candidate) + +Each tier names a distinct adversary against fabric-home storage; every +guarantee in the spec and the code is labeled with the *weakest* tier under +which it still holds. T1 and T2 are cumulative in capability (T2 is T1 plus a +live descriptor/race); T3 is **orthogonal to both** — a legitimate edit is not +a weaker attacker but a non-attacker whose window (idle, between roots, or +*inside a publication*) cuts across T1/T2 (external review round 1, finding 1). +The honest answers do not share a mechanism: T1 is defeated by cryptography +(content-addressing for substitution, A23 for rollback), T2 by topology, T3 by +attribution — never by treating it as an attack. + +**Out of scope (the trust boundary, stated so the tiers are not read as +total).** Two different-principal cases, correctly distinguished (external +review round 1, finding 3): a **local root / privileged-host** adversary is +strictly more capable than T2 and defeats its uid-scoped answer by +construction — **out of scope**; the trust boundary is the Unix account (P7), +and nothing here defends against root. An **ordinary (unprivileged) +different-uid** process is the opposite — it is *blocked* by the enforced +0700/0600 home permissions (the posture ledger's G-MULTITENANT boundary), so it +is *defended*, not out of scope, exactly as long as those permissions hold. The +out-of-scope line is root, not "any other uid"; naming it is what keeps +"distinct adversary / cumulative" from reading as a completeness claim it does +not make. + +**T1 — offline storage tampering between processes.** CAS blobs, branch +files, the SQLite databases, key material, or the recovery journal are +changed, substituted, or replanted while no fabric process holds them open +— the backup/restore case, the copied-home case, and the +same-uid-writes-while-idle case. The honest answer for **substitution, +corruption, and truncation** is **content-address verification on every +authority-bearing read-back**: a substituted or truncated byte string fails +its rehash and degrades to a denial, never an authority bypass. T1 is where +"verify what you read, never trust the pathname" wins, with **three +carve-outs** where content-addressing is not the answer: (a) the read target +is the trust root itself (keys — nothing to verify against); (b) an unsigned +index the code still trusts (the R7 gap); and (c) **rollback/replay to a +prior, genuinely-signed, genuinely-content-addressed whole-home state** — +where every blob rehashes correctly and every signature verifies, because the +old state was legitimately produced, so content-addressing is silent by +construction. Rollback's answer is **A23, not content-addressing**, and its +domain must be stated precisely (external review round 1, finding 2): layer 1's +authenticated monotonic `global_seq`/`VerifiedPrefix` detects only rollback +that is **inconsistent relative to a non-rolled-back expected terminal** — an +interior gap, or a reset that leaves a surviving caller pin ahead of it. A +*coherent suffix regression* — rolling `fabric.db` (and with it `global_seq` +and the local checkpoint) back to an internally-consistent older copy while no +external head or pin survives — verifies cleanly at layer 1; that is +**layer 2's** job (the external anchor / caller-pinned expected head), which +content-addressing and single-machine layer-1 both structurally cannot see. Under the +current **SU** posture the T1 tamperer and the operator are the same Unix +principal (so whole-home rollback is undetectable locally — the operator +controls any local head), making T1's live bite today *accidental* (backup +staleness, a copied home); its adversarial bite arrives at G-MULTITENANT / +when the home leaves the local filesystem boundary, and its confidentiality +half is P17/RF-15. + +**T2 — active same-privilege writer across prepare→publish.** An adversary +of the *same* Unix uid holds an open descriptor, a hardlink, or a racing +thread across a publication's prepare→verify→rename window. Here **no +sequence of pathname checks can win**: the same-inode-before-rename recheck +narrows the race but a sufficiently determined same-uid attacker can still +swap an inode in the sub-microsecond gap, hold a descriptor that survives the +rename, or pre-plant a hardlink. The honest answer is **not a better check — +it is OS-enforced exclusion**: the fabric home reachable by exactly one +principal, and the agent reaching it only through the broker (the two-surface +convention made topology). T2 publication-safety claims therefore hold under +**COOP** (the agent is not adversarial) and **require W-4 containment** to +hold under **G-ADVERSARIAL**. This tier is P7's "trust boundary is the Unix +account," stated as a storage adversary. + +**T3 — the legitimate concurrent human edit.** A human edits the vault while +the fabric is idle or between attested roots. This is **not an attack** and +must never be handled as corruption or silent loss: it lands in **M8 drift +attribution** (`human_local` quiet for the solo operator, `unattributed` +loud), and — during recovery's own downtime window — in the A24/R14 +capture-and-attribute-before-restore path. A model that fails T3 closed +(bricking on an honest edit) or open (overwriting it unrecorded) is wrong in +both directions; T3's correctness criterion is *attributed, never lost*. + +**The active-publication window (T3, unresolved — external review round 1, +finding 1; the item ADR 0007 R14 explicitly deferred here).** M8 covers edits +*between* attested roots and A24/R14 covers the crash-recovery downtime, but +neither covers an edit made **during a live promotion/revert**: the gate lock +(`kernel.rs`) serializes fabric *processes*, not a human with a text editor, +so a vault edit landing after the prepare-time capture-equals-`before` check +and before the apply's rename is overwritten by the rename with no CAS capture +and no drift event — T3's "never lost" violated inside the one window the model +had not addressed. This is **protocol-class** (its remedy either narrows the +topology — the human's edit surface is excluded from the publication window — +or adds a preservation/refusal step, a new commit point), so it is surfaced as +a decision point, not designed here. It is the direct continuation of the R14 +note that named "the concurrent human-edit exposure … exactly SI-32 tier 3's +item — compose there." + +## Candidate normative rules (A27) + +**A27.1 — The staged-bytes rule (normative; ratifies RF-20's discipline).** +A commit consumes bytes **verified in memory during prepare and never +re-reads mutable storage to source the bytes it installs**. Every +store-mutating commit path holds its verified image (fs: per-entry verified +content; sqlite: the whole verified image) in the prepared plan and installs +only those bytes; re-reading a CAS blob, a live file, or any pathname at +commit time *to obtain install bytes* is prohibited. Re-reading live storage +for **comparison or enumeration** is permitted and safe (the fs apply walks +the live tree to enumerate deletions and reads a live target only to skip an +identical-by-hash write — a mismatch triggers a write of already-verified +bytes, a match means the live bytes already equal the verified content): +the invariant is on the *provenance of installed bytes*, not on avoiding all +reads. This is the T1 defense at the write +boundary — it makes prepare the single verification point and commit a pure +function of already-trusted bytes. (As-built: `prepare_restore` retains +verified bytes; `commit_restore`/`write_atomic_verified` consume them; the +`RESTORE-INTEGRITY` contract proves a blob mutated after prepare cannot reach +the live store. This rule ratifies that as the general requirement, not an +implementation accident.) + +**A27.2 — Verify-on-read-back (normative, tier-labeled).** Every read-back of +storage bytes that will *bear authority* MUST content-address-verify (rehash +to the requested address) before the bytes are consumed; a mismatch is a +loud denial, never a fallback. This holds against **T1**. Three classes are +explicitly outside "content-address-verify" and each carries its own rule: +- **Trust-root reads** (signing keys, KEK, and live credentials — kept + distinct, external review round 1, finding 3): there is no address to verify + against — the bytes *are* the trust root — so under T1 they hold **only by + the storage boundary** (0o700/0o600 permissions, symlink-rejecting open), and + 0o600 does **not** constrain a same-uid T1 tamperer. Their substitution does + *not* reliably "degrade to a signature failure": swapping a **signing key** + *together with* the signed storage it verifies produces a **locally + self-consistent forged home** (nothing external is trusted — SI-27's trust + anchor is unresolved); swapping a **live credential** authenticates + successfully *as a different account* rather than failing. So under same-uid + T1 the trust root is **not defended by this model** — it is an + **SI-27/RF-14-backed residual**, accepted under SU (the tamperer is the + operator) and closed only when SI-27 lands an external trust anchor and RF-14 + moves key/credential custody behind an independent boundary. Labeled + **T1-boundary (residual)**, never T1-cryptographic; whether to accept it now + or scope T1 to a trusted verifier key is a decision point. +- **Signed-object reads** (events, manifests, capabilities, and the recovery + journal / `state_change_recovery` txn): verified by Ed25519 signature, which + subsumes content-addressing (A19). Holds against T1 substitution and, for + order/completeness and the rollback carve-out (c), composes with A23's + `VerifiedPrefix` and anchor. +- **Unsigned-index reads** (the R7 gap): the code reads store paths, spans, + and other operational pointers from unsigned `meta`/index rows that a + substituted `fabric.db` controls, while the *events* in the same DB are + signature-verified. This is the one authority-bearing read-back that + currently trusts a pathname under T1. **Candidate rule:** operational + pointers consumed for authority (which store a capture/restore targets; + which span is the substrate span) MUST derive from signed substrate or be + bound into it, never from an unsigned row a T1 tamperer controls. Reach + note: `substrate_span` is not merely a capture/restore pointer — it feeds + broker authority evaluation (grant ordering, A22 closure), so the gap's + reach is if anything understated. This is A23's seam from the read side: the + anchor proves the head is current, but the store paths and span its events + reference must themselves derive from signed substrate, or a T1 tamperer + redirects them under an otherwise-fresh head. Surfaced as a gap (proposed + RF-41); posture-bounded under SU (the tamperer is the operator) but a real + T1 hole once the home leaves the boundary. + +**A27.3 — Per-kind entry rules (candidate).** Publication safety is stated +per store kind because the fs tree and the SQLite file have different +substitution surfaces: +- **fs-tree stores:** the canonical grammar is exactly A24/R25's ratified + tagged path-state domain — `absent | file(content hash, executable mode) | + implicit-directory` (a directory is implicit as the proper ancestor of a + tracked file; empty and untracked directories are outside the boundary), and + any other kind (symlink, fifo, socket, device) is definitionally + non-canonical (external review round 1, finding 5 — the directory/non-kind + rules the filing asked for are A24's, carried here rather than re-derived). + The rule per operation: **capture** rejects symlinks (`capture_fs`'s explicit + `path_is_symlink` check) and folds implicit-directory structure by content; + **recovery** runs A24/R24's kind-complete scan (every non-canonical live + entry fails closed in place); **publication** writes through an + O_EXCL|O_NOFOLLOW randomized sibling and rechecks the same non-symlink inode + immediately before rename. Excluded directories (`.git`) are never written or + deleted; the one inconsistency the inventory found — R3: `sync_store`'s fs + walk omits the `.git` exclusion (and, unlike `capture_fs`, has no symlink + guard at all) — is fsync-only with no integrity impact, and the fix is to + bring it into line with capture's exclusion (the "no-follow open" framing is + corrected: `capture_fs` rejects symlinks with an explicit check, it does not + use an `O_NOFOLLOW` open, so no-follow on `sync_store` is *proposed + hardening*, not an existing discipline `sync_store` alone violates). +- **SQLite stores:** the whole file is the unit; the image is captured + WAL-checkpointed (folded, no live WAL), published through the same atomic + primitive, and its stale `-wal`/`-shm` sidecars removed after the swap so a + restored image cannot be polluted by a leftover WAL. **As-built gap (external + review round 1, finding 4):** only the fabric's *own* `fabric.db` is + symlink-rejected before use (`w13_validate_existing_fabric_home`'s + `symlink_metadata` no-follow); a **registered** SQLite store (e.g. + `db:memory`) is captured by `capture_sqlite`, which uses `is_file` → + `Connection::open` → `fs::read`, **all symlink-following** — so a symlinked + registered store DB redirects capture to an attacker-chosen database. The + candidate rule is that *every* SQLite store path, registered ones included, + is symlink-rejected before capture/open; the merged code does this only for + `fabric.db`. Proposed **RF-42**, with a no-effect negative after ratification. +- **Hardlinks** (the filing's explicit third entry-kind ask): a hardlinked + regular file is captured by *content* like any regular file (capture reads + by value), so a hardlink to in-scope content adds no capture surface. A + hardlink pre-planted at a *publication* target is a T2 same-uid act, not a + pathname-checkable defense — and the atomic publisher already defeats the + write-through-the-link variant structurally: it creates a randomized O_EXCL + sibling and renames *over* the target, replacing the directory entry rather + than writing through any existing link. So hardlinks need no new rule beyond + A27.1/the atomic publisher (write-through variant, covered) and A27.4 (the + pre-plant-and-race variant, COOP/W-4-bounded). Stated so the filing's ask is + visibly closed, not silently dropped. + +**A27.4 — The containment boundary (candidate, the load-bearing tier +statement).** Every T2 publication-safety claim is labeled **"holds under +COOP; requires W-4 containment at G-ADVERSARIAL."** No amount of pathname +checking is claimed to defeat a same-uid active adversary; the spec must not +imply it does. Concretely: `write_atomic_verified`'s same-inode recheck MUST be +documented as *race-narrowing, not race-closing* (as-built its comment claims +neither; this ADR is where the honest label originates), and the security +argument for it MUST name COOP as the assumption it rests on. This is the +honest sentence RF-20→RF-40 kept rediscovering the absence of. + +## Residue disposition (inventory R1–R11 → tier → action) + +| Site | Tier | Disposition | +|---|---|---| +| R1 `Cas::put` no fsync/O_EXCL | T1 (durability) + T1 (integrity) | Integrity held lazily by `Cas::get` rehash (A27.2). Durability fsync is **G-PRODUCTION** (DEBUG posture; same class as the WAL+NORMAL durability seam). Label, don't fix now. | +| R2 `materialize_*` plain write | T1 | Writes CAS-verified bytes into a **branch**, re-captured/re-hashed before bearing authority — outside A27.1's commit boundary. Holds; label as branch-scratch, not a publication. | +| R3 `sync_store` fs walk omits `.git` exclusion (no symlink guard) | T1 | fsync-only, no integrity impact, but breaks "excluded means untouched" uniformity. **Candidate correction** (A27.3): match capture's `.git` exclusion; the no-follow half is proposed hardening, not a capture discipline it violates. Follow-up RF. | +| R4 `Cas::get` exists→read TOCTOU | T1 | Rehash makes substitution a denial. Holds (A27.2). | +| R5 `gate_lock` no O_EXCL/O_NOFOLLOW | T2 | Advisory flock; a pre-planted `gate.lock` symlink is a same-uid act → T2/COOP. Label; W-4 owns. | +| R6 key/secret read-back trusts pathname | T1-boundary (residual) | The trust root; substitution can forge a self-consistent home or authenticate as another account under same-uid T1. A27.2's trust-root class; **SI-27/RF-14 residual**, accepted under SU. | +| **R7 unsigned `fabric.db` meta rows trusted** | **T1** | **A genuine T1 authority gap.** A27.2's unsigned-index rule; **proposed RF-41**, posture-bounded under SU, real at G-MULTITENANT. | +| R8 `write_private_atomic` rename target no-follow | T1-boundary | Plaintext secret, in-memory source; rename replaces a symlink node. Bounded; note under A27.3. | +| R9 journal/sidecar remove no symlink guard | T1 | Fixed paths; removes the link not a target. Minor; note. | +| **R10 `capture_sqlite` follows symlinks (registered stores)** | **T1** | **A genuine gap** (external review round 1, finding 4): `is_file`/`Connection::open`/`fs::read` all follow a symlink; only `fabric.db` is symlink-checked, not registered SQLite stores. A27.3's SQLite rule; **proposed RF-42**, posture-bounded under SU. | +| R11 CLI/demo/tooling writes | n/a | Non-authority-bearing (sockets, agent working store, demo fixtures, reports). Out of scope; state so. | + +## Ratification decision points (the human choices) + +1. **Tier count and boundaries.** Three tiers as above, or split T1 into + T1-offline vs T1-at-rest-confidentiality (the latter is P17/RF-15's, and + this model currently folds confidentiality into T1's note rather than a + fourth tier). Recommend: three tiers, confidentiality cross-referenced not + re-tiered. Sub-choice: is the root-vs-unprivileged-different-uid boundary + (P7 / the permissions boundary) stated correctly — root out of scope, an + unprivileged other uid defended by permissions? +2. **T1-rollback carve-out and its precise domain.** Ratify that + content-addressing answers T1 *substitution/corruption/truncation* but + **not rollback**, whose answer is A23 — layer 1 detecting only rollback + *inconsistent relative to a surviving expected terminal*, and a coherent + suffix regression (whole-DB rollback with no surviving pin) being layer 2's + at its gates. Under SU it is undetectable locally, an accepted + accidental-only residual until G-MULTITENANT. This is the seam where SI-32 + hands off to A23; ratifying "content-addressing answers T1" ratifies an + unsound tier. Recommend ratify as stated; the alternative is to declare + rollback wholly A23's and out of SI-32's scope. +3. **Trust-root substitution under same-uid T1 (external review finding 3).** + Accept key/KEK/credential substitution as an **SI-27/RF-14 residual** + (recommend: yes, posture-bounded under SU — the tamperer is the operator — + closed when SI-27 lands an external anchor and RF-14 moves custody behind an + independent boundary), or scope T1's guarantees to a *trusted verifier key* + assumed outside the tamperable home. The first is honest about today; the + second is the shape production takes. +4. **Active-publication-window human edit (external review finding 1; + protocol-class).** T3's one uncovered window — a human edit during a live + promotion/revert, overwritten by the apply with no capture or drift. Resolve + by **topology** (the human edit surface is excluded from the publication + window — e.g. the vault is not concurrently human-writable while a gate + holds) or by a **ratified preservation/refusal protocol** (a new commit + point that captures-or-refuses on an in-window edit, the A24/R14 shape + extended to the in-process gate window). Protocol-class either way — file as + an SI before implementation. Recommend: decide the topology question first; + it may dissolve the protocol need. +5. **A27.1 staged-bytes as normative** — ratify RF-20's discipline as the + general rule, or leave it implementation-internal? Recommend normative: + it is the T1 write-boundary invariant and future stores (Tier-2/3) must + inherit it. +6. **A27.2's unsigned-index rule and RF-41.** Is R7 a ratifiable gap to file + now (recommend: yes, posture-bounded, carried when the home-boundary + posture graduates), or folded into P17/RF-15's at-rest work? This decides + whether SI-32 spawns a new RF or reuses one. (RF-42, the registered-SQLite + symlink gap, is the same call — file now or fold.) +7. **A27.4 containment boundary wording** — the "holds under COOP; requires W-4 containment at G-ADVERSARIAL" + label on every T2 claim. Ratify as the standing sentence, or scope it + per-site? Recommend standing sentence, since it is the same honest answer + at every T2 site. +8. **G-PUBLISH containment mapping (external review finding 6; the filing's + explicit ask).** The filing asked *which publication-safety claims require + containment before G-PUBLISH*. The real fork: may the spec **publish the + conditional T2 claims** (labeled "holds under COOP; requires W-4 containment at G-ADVERSARIAL") + with W-4 deferred, or must **W-4 containment land before publication** so no + published claim rests on an unbuilt topology? Recommend: publish the + conditional claims with the label — the label *is* the honest disclosure — + and record the determination as a **G-PUBLISH row in the posture ledger** + (T2 publication-safety claims are conditional-published, W-4-gated), so a + reader of the published spec sees the dependency. +9. **R3 correction** — bring `sync_store` into the `.git` exclusion now + (small), or file as a follow-up? Recommend follow-up RF (it is fsync-only; + not worth growing a threat-model ratification with a mechanism change). +10. **Where this lands in the spec.** §5.3 (owned-state transition, where + publication lives) plus a §9 fork note, or a new §-level "storage adversary + model" subsection? Recommend a §5.3 subsection cross-referenced from §1 + (payload store) and §9 (F2 addressing), since publication is §5.3's and the + tiers label claims spec-wide. + +## Reserved seams (not resolved here) + +W-4 owns the T2 containment topology (the sandbox whose only door is the +broker); this ADR states the *requirement* that T2 claims rest on it, not the +mechanism. P17/RF-14/RF-15 own at-rest confidentiality (T1's confidentiality +half). G-PRODUCTION owns durability (R1's fsync, the WAL+NORMAL seam). The F2 +CID/DAG-CBOR transition (ADR 0002) changes the *addressing* of CAS blobs but +not this model — A27's rules are stated over content addresses generally, so +they survive the transition; the migration note belongs to W-6/F2, not here. +SI-26 owns the signed-transcript/type binding that would let A27.2's +signed-object class extend to the new §6.2 objects. + +## Validation plan (candidate, on ratification) + +Two-sided contracts per tier claim: a `STAGED-BYTES` contract (positive: a +commit installs the prepared bytes; negative: a blob/file/row mutated after +prepare cannot reach the live store — the existing `RESTORE-INTEGRITY` tests +are its core); a `READBACK-VERIFY` contract (positive: a valid address reads; +negative: every authority-bearing read-back rejects a substituted byte string +without effect — CAS, payload, journal, and the R7-fixed meta path once +RF-41 lands); and the per-kind entry negatives (symlink rejected at fs-tree +capture and publication *and at registered-SQLite capture* once RF-42 lands; +non-canonical kinds — fifo/socket/device — fail closed per A24/R24; directory +path-states resolve per A24/R25; sidecar-WAL removed; excluded-dir untouched +including under R3's correction). Every tier label in the ratified spec text carries a +conformance-sweep row naming its enforcing line or its RF/gate deferral. The +T2 claims explicitly carry *no* negative test that asserts race-closure — +their conformance is the documented COOP dependency plus W-4's future +topology, and the sweep says so rather than implying a test proves T2 safety. + +## Rejected alternatives (candidate) + +- **A single flat "trusted local filesystem" assumption** (what exists today, + implicitly): it is why every review re-derives the attacker — an unlabeled + claim cannot be checked, and the reviewer cannot tell a tier-appropriate + gap from a real one. +- **Claiming pathname checks defeat T2**: the same-inode recheck is + race-narrowing; asserting it closes the race is the SI-10/A21 lie surface + (a guarantee enforcement does not provide), and it would let an + actuation/G-ADVERSARIAL grant proceed on a false floor. +- **Folding T3 into the attacker model**: an honest human edit handled as + corruption is the M8 gap A20 closed and A24/R14 hardened; re-tiering it as + an attack would reopen that. +- **Deferring the whole model to W-4**: W-4 is the T2 *answer*, but T1 is + live now (backups, copied homes, the R7 gap) and needs its cryptographic + guarantees labeled independently of containment. + +## Appendix — publication site inventory + +Grounded against the PR #48 merge base. Every filesystem publication and +authority-bearing read-back, by artifact class, with file:line anchors; +`write_atomic_verified` is the gold standard the residue table measures against. +R-numbers match the residue disposition table. + +- **Gold-standard publisher.** `write_atomic_verified` / `_with_hook` + (`snapshot.rs:534`/`543`, the guard sequence in the hook variant): + randomized `O_EXCL|O_NOFOLLOW` sibling, mode set, file fsync, retained-handle + rehash-before-rename, same-inode non-symlink recheck, atomic rename, parent + fsync. `commit_restore` Swap and `apply_fs_in_place` pass 2 route through it. +- **Verifying read-backs (authority-bearing).** `Cas::get` (`snapshot.rs:152`) + and `payload::get` (`payload.rs:122`) rehash; `recover_pending_state_change` + (`kernel.rs:816`) verifies the journal signature + type; key/secret reads + (`keys.rs:186`) trust by pathname — the trust root (R6). +- **CAS blobs (R1, R4).** `Cas::put` (`snapshot.rs:128`): randomized tmp + + rename, no fsync/`O_EXCL`/`O_NOFOLLOW`; dedup branch re-verifies via `get`; + integrity enforced lazily on read. +- **Recovery journal (R9).** `publish_state_change_journal` (`kernel.rs:122`): + `O_EXCL|O_NOFOLLOW`, fsync file + parent, fixed path (presence = pending bit). +- **Keys/secrets (R6, R8).** `Keystore::initialize` (`keys.rs:67`): staged dir + + atomic rename; per-file `create_private_new` (`keys.rs:261`) = `O_EXCL` + + 0o600 + fsync. +- **SQLite (R7, R10).** ledger `fabric.db` WAL+NORMAL (`kernel.rs:432`); + `w13_validate_existing_fabric_home` (`kernel.rs:328`) symlink-checks + `fabric.db` **only**; `capture_sqlite` (`snapshot.rs:268`) follows symlinks + (RF-42); meta `stores`/`substrate_span` read unsigned (`kernel.rs:505`, + RF-41). Payload bytes live in `fabric.db`, none on the filesystem. +- **Branch / lock / sync / CLI (R2, R3, R5, R11).** `materialize_*` + (`snapshot.rs:337`) plain write into branch scratch; `gate_lock` + (`kernel.rs:1015`) advisory `flock`, no `O_EXCL`/`O_NOFOLLOW`; `sync_store` fs + walk (`snapshot.rs:608`) omits the `.git` exclusion (R3); CLI/demo/tooling + writes are non-authority-bearing (R11). + +## Ratification addendum — determinations (2026-07-15) + +Operator session, challenge pass over the round-1-folded candidate. One +divergence from the candidate's stated lean (D32-4); everything else +ratified as drafted, with the reasoning recorded so it is citable. + +- **D32-1 — three tiers, confidentiality cross-referenced.** Ratified as + drafted. At-rest confidentiality stays P17/RF-15's (a fourth tier would + duplicate a ledger row as a threat tier). The boundary statement is + ratified precisely: root/privileged-host out of scope (the trust + boundary is the Unix account, P7); an unprivileged different uid is + *defended* by the enforced 0700/0600 permissions, not out of scope. +- **D32-2 — the T1-rollback carve-out, kept inside the model.** Ratified + as drafted, and deliberately NOT moved out of SI-32's scope: the tier + table is where future readers look up "what defeats T1," and a model + that omits the rollback seam invites a later wholesale ratification of + "content-addressing answers T1." Layer 1 detects rollback inconsistent + relative to a surviving expected terminal; a coherent suffix regression + is layer 2's at its gates; under SU the T1 rollback bite is accidental + (backup staleness, copied homes), adversarial at G-MULTITENANT. + **Correction to the preserved body (external delta round, finding 2 — + this addendum wins):** the body's parenthetical "layer 2's job (the + external anchor / caller-pinned expected head)" mis-classes the caller + pin. A caller-pinned expected head is **layer 1's** — §6.2's local + `TraceCheckpoint` supplies the caller-pinned completeness tier at + every profile — so a regression that leaves a surviving caller pin + ahead of it is layer-1-detectable; only the **external monotonic + anchor** is layer 2. The determination's operative wording + ("inconsistent relative to a surviving expected terminal" = layer 1) + was already correct; the body sentence was the error. +- **D32-3 — trust-root substitution accepted as the SI-27/RF-14 + residual.** Ratified option 1. The alternative — scoping T1 to a + trusted verifier key outside the tamperable home — labels an anchor + that does not exist (SI-27 is open; RF-14 keys are plaintext in the + home); that is aspirational labeling, the disease A27.4 treats. The + residual is honest about today and names its own closure (SI-27 + external anchor + RF-14 custody boundary). Revisit the verifier-key + shape when SI-27 ratifies. +- **D32-4 — the active-publication window: topology arm FORECLOSED; + SI-40 filed (the addendum's one divergence from the candidate's + lean).** The candidate recommended deciding the topology question + first. Determination: the topology remedy is structurally unavailable + for shared-state stores — native, unmediated human access to the vault + is the product thesis (brief §2), W-4's sandbox contains the *agent* + and never the human, and momentary exclusion (chmod/lock during apply) + fails T3 in the other direction: a bricked save is failing closed on + an honest edit. The eventual answer is the preservation/refusal + protocol (the A24/R14 capture-or-refuse shape at a per-entry + pre-rename point), and it is NOT designed now: SI-40 files it with the + leading candidate named, P29 bounds the live window (1HUMAN/1SESS, + sub-second Tier-1 applies, self-inflicted concurrency), and the + triggers are any non-sub-second apply window (Tier-2/3 stores), + G-2HUMAN, or an observed loss in dogfooding. The R14/SI-39 + narrow-and-file pattern, applied at ratification time. +- **D32-5 — A27.1 staged-bytes normative.** Ratified. As-built with a + two-sided contract (`RESTORE-INTEGRITY`); normative status costs + nothing today and binds Tier-2/3 stores before they exist. +- **D32-6 — RF-41 and RF-42 filed now, not folded.** Folding integrity + gaps into P17/RF-15 buries them in a confidentiality tracker (wrong + ledger). Carriers assigned at filing: RF-41 → W-15a/W-15b (the W-15 + split in flight in PR #52; deriving + operational pointers from signed substrate is the same region as the + signed-chain work; `substrate_span` binding is the read-side of the + `VerifiedPrefix`); RF-42 → the RF-40 mechanical bugfix lane (defined + at RF-42's entry). +- **D32-7 — A27.4 standing sentence.** Ratified as the standing sentence + on every T2 claim; per-site scoping re-derives the same sentence N + times, which is the RF-20→RF-40 disease this ADR treats. The + `write_atomic_verified` comment correction (race-narrowing, never + race-closing) is mechanism-class and rides any PR. +- **D32-8 — conditional T2 publication.** Ratified: T2 claims publish as + labeled conditionals; the label is the honest disclosure, and gating + publication on W-4 would put a containment project on the spec's + critical path for no honesty gain (W-7/F2 gate publication regardless). + Recorded as the T2 entry under the posture ledger's G-PUBLISH gate; a + published T2 claim missing its label is a publication defect. +- **D32-9 — R3 filed as RF-43.** Follow-up RF riding RF-42's mechanical + lane; a threat-model ratification is not grown with a mechanism change + (the triage rule: remediation may shrink a PR under review, never grow + it). +- **D32-10 — spec placement.** §5.3 bullet family (matching the + section's protocol-bullet architecture), cross-referenced from §1 + (payload read-back) and §9 F2 (rules survive the CID transition); + changelog v0.10. A new top-level section was rejected: publication + lives in §5.3, and the tiers label claims spec-wide by reference. + +**Validation mapping (per the candidate's plan).** A27.1's pair exists +(`RESTORE-INTEGRITY`); the outstanding negatives are G14(a)–(c) landing +with their RF carriers (RF-41 → W-15a/b; RF-42/RF-43 → the mechanical +lane); G14(d) records the deliberate absence of any T2 race-closure +negative — the conformance for T2 claims is the documented COOP +dependency plus W-4's topology, never a test. Enforcement binds at the +carrier gates per the ledger. + +**Internal pre-review (2026-07-15, before the external delta round, per +the battery).** A fresh-context adversarial pass over the ratification +fold returned 14 findings (4 medium), all applied before push. The +substantive ones: the SI-40 filing's refusal-disposition parenthetical +wrongly routed a preserving refusal through A24's ordinary-failure +rollback, which restores `before` over the divergent entry with no +capture record — recreating the loss SI-40 exists to prevent (corrected: +capture-before-rollback or fail-like-a-crash); the spec's "every +guarantee carries a label" opener over-claimed in the indicative and is +now stated by-reference with an inline-label obligation for new claims; +carrier references to W-15a/W-15b and the RF-40 mechanical lane were +unresolvable on this branch and now cite PR #52; the T1 headline gained +its capture-time per-kind dependency and the D32-1 confidentiality +cross-reference; the A27.4 label was quoted without "containment" in +CLAUDE.md; P29 was missing the dogfooding-loss trigger; and the +adversary-tier notation is now disambiguated from §10's store tiers. + +**External delta round (2026-07-15, the stopping rule's one budgeted +round).** REQUEST CHANGES — five findings, all folded same-day: two +blocking mediums with prescribed remedies (A27.1's ratified T1 label +had been dropped in spec integration, restored; the body's layer-2 +misclass of caller-pinned expected heads, corrected above in D32-2), +one non-blocking medium (the SI-40 substrate-assisted paragraph +over-claimed what a gate-acquisition snapshot preserves — the in-window +edit postdates that snapshot; corrected to outgoing-state-retention at +swap with its reconciliation costs), and two lows (A27.4 label +variants in preserved candidate text normalized to the exact ratified +sentence; two v0.9 version stamps bumped). The round verified: all ten +determinations have integration sites, A27.3 preserves the A24/R24/R25 +grammar by exact reference, SI-40's corrected refusal disposition is +sound, and RF-41/RF-42/RF-43 and G14's source/test claims check out. + +**Confirming delta round (2026-07-15) — no blocking findings; the +cycle closes under the stopping rule.** Remedies 1, 2, 4, and 5 +verified correct; the external-round record and the v0.10 provenance +clause verified accurate. Two non-blocking findings folded under the +battery without a further round: SI-40's per-file clone variant now +requires atomic clone-and-swap semantics (a separate clone→rename pair +re-opens the window in miniature; the dataset-level retain-and-swap +variant is sound as stated), and a branch-local SHA citation in the +roadmap was replaced per the citation rule. Complete cycle cost for +this ratification: one internal adversarial pre-review (14 findings) +plus two delta-scoped external rounds (5 + 2 findings). diff --git a/docs/asf-schema-spec.md b/docs/asf-schema-spec.md index 549fb70..e4e0a26 100644 --- a/docs/asf-schema-spec.md +++ b/docs/asf-schema-spec.md @@ -1,8 +1,8 @@ # Agent State Fabric — Schema Specification -**Draft v0.9 — July 2026 — Companion to the Architecture Brief** +**Draft v0.10 — July 2026 — Companion to the Architecture Brief** -*v0.3 integrated amendments A1–A14 from the wedge paper runs (Hermes agent; workflows: web research → vault distillation, Discord message management, vault maintenance). v0.4 integrated A15–A19 from the Stage 1–3 reference implementation (Coppice): the first amendments forced by running code rather than paper runs. Every A15–A19 decision traces to the implementation sessions via `docs/spec-issues.md` (SI-1…SI-19, all resolved in that version). v0.5 integrates A20 (SI-20, M8 attribution completeness) — the first amendment forced by dogfooding rather than by implementation. v0.6 integrates A21 (SI-21): brokered authority binds by mode declaration plus grant event, never by an embedded capability id — resolving the content-address cycle at the kernel object. v0.7 integrates A22 (SI-24): capabilities gain early closure — a signed `revoke` event as the permanent, prospective, descendant-closing dual of A21's `grant`, with liveness a pure event-derived view evaluated at the operation's durable authorization offset (§5.4). v0.8 integrates A23 (SI-25): the trace substrate gains an authenticated global order — every event binds a signed per-home `global_seq`/`global_prev` (§6.2), making the "verified substrate prefix" that A21/A22 quantify over a mechanically available object; a graduation-gated external monotonic anchor adds freshness against rollback, and the owned-state transition of §5.3 (SI-31) shares its single commit point. v0.9 integrates A24–A26 (SI-31, SI-33, SI-34) — the W-20 retro-ratification of the three protocols W-14 (PR #43) designed inside its remediation cycle: the owned-state transition protocol of §5.3 (one commit point, fabric-signed recovery journal, fail-closed recovery), consumed authority as an event-derived view (§5.5), and approval candidate binding (§6). Ratified with adjustments beyond as-built, extended by review rounds 1–7 plus an internal pre-round-6 review (ADR 0007, determinations plus R1–R25 — round 2 adding the recovery capture record and positional freshness, round 3 hardening them, round 4 extending temporal binding to both consumers and surfacing as-built defects, round 5 completing canonical-entry identity and widening RF-40 to the planner, round 6 closing the first-attempt scan bypass and defining the tagged path-state domain, round 7 — scoped to the round-6 delta — completing R24/R25 in place); W-15 and W-22 carry the mechanisms, RF-35–RF-37/RF-39/RF-40 track the interim (RF-38 files the anomaly-recovery question), SI-39 files the multi-window recovery enhancement. Unenforced clauses bind implementations at their carrier gates per the ledger (the W-20 §0 posture-qualifier convention will make this status first-class). Changelog at end. Risk-review requirements carried since v0.1: **(R2)** read authority is first-class, **(R3)** payloads are hash-referenced and destroyable, **(R6)** trust is domain-scoped, never scalar.* +*v0.3 integrated amendments A1–A14 from the wedge paper runs (Hermes agent; workflows: web research → vault distillation, Discord message management, vault maintenance). v0.4 integrated A15–A19 from the Stage 1–3 reference implementation (Coppice): the first amendments forced by running code rather than paper runs. Every A15–A19 decision traces to the implementation sessions via `docs/spec-issues.md` (SI-1…SI-19, all resolved in that version). v0.5 integrates A20 (SI-20, M8 attribution completeness) — the first amendment forced by dogfooding rather than by implementation. v0.6 integrates A21 (SI-21): brokered authority binds by mode declaration plus grant event, never by an embedded capability id — resolving the content-address cycle at the kernel object. v0.7 integrates A22 (SI-24): capabilities gain early closure — a signed `revoke` event as the permanent, prospective, descendant-closing dual of A21's `grant`, with liveness a pure event-derived view evaluated at the operation's durable authorization offset (§5.4). v0.8 integrates A23 (SI-25): the trace substrate gains an authenticated global order — every event binds a signed per-home `global_seq`/`global_prev` (§6.2), making the "verified substrate prefix" that A21/A22 quantify over a mechanically available object; a graduation-gated external monotonic anchor adds freshness against rollback, and the owned-state transition of §5.3 (SI-31) shares its single commit point. v0.9 integrates A24–A26 (SI-31, SI-33, SI-34) — the W-20 retro-ratification of the three protocols W-14 (PR #43) designed inside its remediation cycle: the owned-state transition protocol of §5.3 (one commit point, fabric-signed recovery journal, fail-closed recovery), consumed authority as an event-derived view (§5.5), and approval candidate binding (§6). Ratified with adjustments beyond as-built, extended by review rounds 1–7 plus an internal pre-round-6 review (ADR 0007, determinations plus R1–R25 — round 2 adding the recovery capture record and positional freshness, round 3 hardening them, round 4 extending temporal binding to both consumers and surfacing as-built defects, round 5 completing canonical-entry identity and widening RF-40 to the planner, round 6 closing the first-attempt scan bypass and defining the tagged path-state domain, round 7 — scoped to the round-6 delta — completing R24/R25 in place); W-15 and W-22 carry the mechanisms, RF-35–RF-37/RF-39/RF-40 track the interim (RF-38 files the anomaly-recovery question), SI-39 files the multi-window recovery enhancement. v0.10 integrates A27 (SI-32): the storage adversary model — three tiers (T1 offline tampering, T2 same-uid active writer, T3 legitimate human edit), a tier label on every publication-safety and read-back guarantee, the staged-bytes and verify-on-read-back disciplines ratified normative, per-kind entry rules, and the standing T2 containment-boundary sentence; determinations D32-1…D32-10 in ADR 0008, one protocol-class deferral (SI-40, the active-publication window). Unenforced clauses bind implementations at their carrier gates per the ledger (the W-20 §0 posture-qualifier convention will make this status first-class). Changelog at end. Risk-review requirements carried since v0.1: **(R2)** read authority is first-class, **(R3)** payloads are hash-referenced and destroyable, **(R6)** trust is domain-scoped, never scalar.* --- @@ -30,6 +30,8 @@ PayloadRef { "hash": "sha256:…", "size": 18742, **Cipher suite (A19).** v1 payload encryption and DEK wrapping are AES-256-GCM; each DEK record carries its `alg`, so the suite is per-payload upgradable without a schema change. +**Adversary tiers (A27).** Payload reads are authority-bearing read-backs under §5.3's storage adversary model: bytes rehash to their `PayloadRef.hash` before consumption (A27.2), and the store's publication safety carries A27's tier labels. + ## 2. Principals ```json @@ -216,7 +218,11 @@ Promotion merges a completed branch to trunk and is the only mutation in the sys - **Owned-state transition protocol (A24; resolves SI-31).** Promotion and revert are owned-state transitions: simultaneously a Tier-1 root mutation and a globally-ordered trace event, executed as **one SQLite transaction with one commit point** (§6.2 S1–S5 ratified the seam and the durable-commit profile; this bullet is the §5.3-owned half). Canonical sequence, under M8's gate serialization: (1) verify every live root still equals the transition's `before` image — divergence fails the transition closed; the gate's M8 attribution owns consuming it; (2) prepare forward *and* rollback images for every root, hash-verified out of the CAS into immutable plans before any mutation; (3) stage, in one uncommitted transaction: the signed transition event, the expected-root attestations, and every companion row (promotion status, the A26 companion approval); (4) publish the fabric-signed **recovery journal**, fsyncing file and parent directory *before* any store is touched (R8: the record precedes what it records); (5) apply and fsync every store; (6) **commit the transaction** — the single authoritative commit point; the linked verified event is the authoritative name of the committed root tuple; (7) remove the journal. The naming claim requires **exact realization at canonical-entry grain** — content, presence, kind, and mode — at every stage: the merge planner must surface every canonical-entry difference as an op or conflict (a mode-only branch change silently dropped at planning defeats realization before apply begins — round 5, R23), and the store apply must write every differing dimension and complete legal topology changes (file↔directory) under its pass ordering (round 4). Composition with A17 (R23, completed at the internal pre-round-6 review): rename/move **pairing** remains exact content-hash — A17 unchanged, so a chmod'd rename never un-pairs into the mass-deletion rendering A13 forbids — and a paired rename/move whose endpoints differ in mode or kind additionally surfaces that delta as a `modify` refinement op on the destination path (A18); conflict identity and conflict cards compare canonical entries, not content hashes alone. The merged implementation fails all three stages — as-built defects, RF-40, carried by W-15. Ordinary failure restores and fsyncs every before-root, rolls the transaction back, and removes the journal; failure *during that rollback* retains the journal loudly for reopen recovery. A retried transition is a fresh event — the journal carries authority only to complete or undo the transition it names, never to re-execute it. - **The recovery journal (A24).** A fabric-signed, typed (`state_change_recovery`), versioned artifact linking exactly one transition event and carrying the full before/after root tuples. It is **not a §6 event kind** and never becomes one: §6 records are permanent substrate history, while the journal's *removal* is protocol-meaningful (presence is the recovery discriminator, S4), and the permanence job belongs to the transition event. It is necessarily a **file outside the database**: it is the write-ahead record for the transaction itself, so a row would vanish in exactly the crash it exists to recover (the inverse of R8's checkpoint-is-a-row argument — one artifact per side of the commit point, each durable where its consumer looks). It gains signed `home`/`epoch` binding (its linked event's TracePosition) and the per-store prior-attestation positions (R11) at the layer-1 implementation (W-15) — H3's one-home/one-epoch rule extended to recovery artifacts. The epoch guard splits by arm (R3): roll-forward requires the linked event to lie in the current epoch's activation prefix (ADR 0006 R9); roll-back — where the linked event is by definition absent, absence being the roll-direction discriminator — requires the journal's own signed home/epoch to name the current home and epoch. The guard never reuses the discriminator. - **Recovery (A24).** Reopen with a journal present runs recovery under the gate lock before any other operation; recovery failure keeps the home closed, and recovery is re-runnable — a crash during recovery re-runs it, completing or refusing per the explanation check below. Validation precedes any restore: symlinked, non-file, mistyped, wrong-version, unsupported-kind, malformed-tuple, or event/manifest-misbound journals are rejected. The roll decision reads the verified prefix at the **local verified terminal** (R6, S4): linked event present → roll forward to `after`; absent → roll back to `before`. Two clauses guard the decision. **Freshness (D31-4, as revised in rounds 1–2):** recovery derives the substrate's current-roots view **V** from the verified prefix — per store, the latest signed root attestation in composite order, intensionally *every signed event field attesting that store's live trunk root*, enumerated today as: manifest `snapshot` events, unbranched `tool_call.state_root_after` (non-`branch:` keys only — a branch capture never joins trunk V; R8), promotion `merged`, revert `roots_restored`, drift `observed_root`, and the closing record below — never from the unsigned expected-roots cache (§5.5's doctrine applied to recovery's inputs). V is evaluated once, against pre-recovery signed state, after the closing-record idempotency check and before any recovery emission; comparison projects V onto the journal's store set (a manifest deliberately scoped to a subset of the home's stores journals only that subset); a journal naming a store with no signed attestation fails closed. Freshness is **positional, not value-only** (R11 — roots recur, positions do not: the ABA replay `A → C → A` defeats value equality): the journal records, per store, the event id of the latest root attestation at prepare time (well-defined under the gate lock after drift attribution); roll-back requires each recorded position to still be the latest attestation for its store, roll-forward requires the linked event itself to be the latest, and value agreement (`after == V` / `before == V`) is retained as belt-and-braces. Any other relation fails closed, journal retained, loud. Scope of the guarantee (R22): layer 1 rejects a stale journal **relative to a non-rolled-back local verified prefix** — backup-restored, copied, replanted, and same-epoch ABA-replayed journals all fail the positional predicate there. A journal and database rolled back **together** present an internally consistent history layer 1 cannot see through; detecting that is A23 layer 2's anchor-ahead signal, and under the unanchored `local-integrity` profile such a joint rollback can drive a restore — with the capture record and window drift still preserving the overwritten live state as CAS-resident, ledger-visible evidence (wrong, but never silent). **Attribution (D31-6, as revised in rounds 1–2):** before any restore mutation, recovery captures every affected live root to the CAS and publishes the **recovery capture record** (R9) — a second fabric-signed, typed, versioned recovery artifact naming the journal id and the per-store captured roots, fsynced (file and parent) before any store is touched. This is the write-ahead principle at its third boundary (journal↔stores, database↔anchor, capture↔restore), crystallizing a two-sided rule: **intentions are write-ahead files; attestations are write-behind events** — without the record, a crash between restore and emission leaves the captured bytes in the CAS with no durable name, and the retry, seeing live == V, has nothing to say: the divergence window vanishes unrecorded (round 2's refutation of round 1's convergence claim); while a pre-restore closing *event* would attest a restore that has not happened. The record is **first-write-wins** — a retry never re-captures over an existing record; the original capture is the evidence and live may already be half-restored — and is validated by the journal's fail-closed family (signature, type, version, journal-id linkage, and store set **exactly equal to the journal's** — R15: pair-or-neither needs every journaled store's captured root to prove non-divergence, and first-write-wins forbids repairing a partial record) before being consulted. Because first-write-wins blinds the record to divergence arising during a crashed recovery's own downtime, a retry verifies — per store, before any mutation — that **every path-state is explained by the capture record's version or the restore target's version**, quantified over the **union** of paths present in live state, the capture, and the target, with **absence as a value** (R14/R18 — quantifying over live elements alone is vacuous for deletions: a path deleted during the window is not a live element, and the restore would silently recreate it). Canonical entry identity is (relative path, **kind**, presence, content hash, executable mode) — content alone is not identity, and because the canonical store grammar contains only regular files and directories, a live entry of any other kind (symlink, fifo, socket, device) can match neither the capture nor the target and is definitionally unexplained (R21). The kind-complete scan runs on **every active restore attempt** (R24 as completed in round 7): an attempt that has not yet published a capture record scans **before publishing it** and before any mutation (round 6: the round-5 retry-only scoping left the first attempt free to capture-around a socket and restore over it unrecorded; capture rejects symlinks but silently *skips* other non-file kinds, so the grammar exclusion must be enforced by the scan itself, never inherited from a capture-shaped walk); a retry with an existing capture record scans **before any restore mutation** — its scan is the explanation check's own kind-complete walk; and a retry that finds committed closing records naming the journal is **not an active restore attempt**: it routes directly to artifact cleanup per the settled idempotency rule, mutating only the two recovery artifacts — a non-canonical entry appearing after the restore completed is ordinary post-recovery live state, owned by M8 drift attribution at the next gate consumption, never by recovery. On active attempts the walk MUST surface every directory entry regardless of kind, failing closed on unreadable or unstatable entries, and any non-canonical entry within the canonical store boundary fails recovery closed in place. Path-state is a **tagged domain** (R25 as completed in round 7): the path universe is the **non-empty canonical relative paths strictly below the store root** — the root itself is the boundary, not a path-state: always present, created at materialization, never pruned (so an empty capture against an empty target recovers trivially instead of failing closed on the root) — and each path carries `absent` | `file(content hash, executable mode)` | `implicit-directory`, where a path is implicit-directory in the capture or target exactly when it is a proper ancestor of a tracked file path in that files-only tree (directories carry no content or mode dimensions; a live directory is explained by implicit-directory status on either side, a live file where both sides are implicit-directory — or the converse — is a kind mismatch and unexplained); excluded directories and untracked empty directories are scoped to the canonical store boundary, matching capture (their apply-time obstruction handling is RF-40's widened topology rule). Per file for fs stores (whose tmp+rename restore leaves each entry at exactly one of the two versions); whole-image for sqlite (whose swap is atomic). All-explained is an innocent interrupted restore and completes idempotently; any unexplained path-state is a new divergence window and **fails closed in place** — no mutation, the new state stays live, the home stays closed, operator resolution. The automated capture-and-attribute guarantee therefore covers the divergence present when recovery first begins; later-window divergence is detected and preserved by refusal, never silently normalized — automated multi-window preservation is SI-39. **No event is emitted until the restore completes** (emitting first would make the captured root the newest attestation and poison V — round 1). After the stores are restored and fsynced, **one atomic transaction** appends, per divergent store, the window drift (V → captured root, `attribution: "unattributed"` — the crash-to-reopen window is unattended) ordered before that store's **closing record** (drift-kind, `attribution: "fabric_recovery"`, carrying `recovery: `, re-attesting the restored root, §6) — **pair-or-neither per store** (R13): a store whose capture equals V emits nothing. Then the capture record is removed, **then** the journal — that order, so "journal present, capture record absent, closing records committed" is a legible cleanup cell and an orphaned capture record is unreachable. A committed closing record naming the journal id is the idempotency marker: a retry finding one routes directly to artifact cleanup, before the freshness predicate (whose "latest attestation" the closing record has by then become). **Recovery never moves V** (normative): both arms' restore target equals V by the freshness predicate and every pair nets V to itself — the round-1 poisoning is unrepresentable, not merely avoided. Content that diverged while the fabric was down is preserved and ledger-visible, never silently overwritten (M8 applied to the recovery consumer). When live state equals the restore target and no capture differs from V, recovery completes as pure cleanup. -- **Scope fences (A24).** Tier-1-local only: the analog for external effects (a remote side effect landing before its record) is the parked durable external-effect protocol, never this one. The filesystem adversary tiers under which the publication discipline holds — for stores and for both transient recovery artifacts — are SI-32's question. The hard-exit-at-each-syscall crash matrix remains G3 evidence work. +- **Scope fences (A24).** Tier-1-local only: the analog for external effects (a remote side effect landing before its record) is the parked durable external-effect protocol, never this one. The filesystem adversary tiers under which the publication discipline holds — for stores and for both transient recovery artifacts — are A27's, below. The hard-exit-at-each-syscall crash matrix remains G3 evidence work. +- **Storage adversary model (A27; resolves SI-32).** Every publication-safety and authority-bearing read-back guarantee is tier-labeled *by reference* — through this bullet family and ADR 0008's matrices, inventory, and determinations D32-1…D32-10 — naming the *weakest* adversary tier under which it holds; new normative claims MUST carry their label inline. (Adversary tiers, distinct from the Tier-1/2/3 *store* tiers of the brief §5.2 and §10's walkthrough.) **T1 — offline storage tampering between processes** (backup/restore, copied homes, same-uid writes while idle): defeated by cryptography — content-address or signature verification on every authority-bearing read-back, plus A27.3's per-kind rules at capture (a first read has no address to verify against yet) — with three named carve-outs that are NOT content-addressing's to win: *trust-root reads* (keys, KEK, credentials — nothing to verify against; under same-uid T1 an SI-27/RF-14-backed residual, accepted under SU, labeled T1-boundary and never T1-cryptographic), *unsigned-index reads* (operational pointers — store paths, `substrate_span` — MUST derive from signed substrate or be bound into it, never from an unsigned row; RF-41 until enforced), and *rollback/replay to a prior genuinely-signed whole-home state* — rollback is **A23's**, not content-addressing's: layer 1 detects only rollback inconsistent relative to a surviving expected terminal; a coherent suffix regression is layer 2's at its gates (D32-2 — the seam stays inside this model so T1 is never read as complete). T1's at-rest-confidentiality half is P17/RF-15's — cross-referenced, not re-tiered (D32-1). **T2 — active same-privilege writer** across a publication's prepare→verify→rename window: no sequence of pathname checks wins; the honest answer is OS-enforced exclusion (W-4's topology). **T3 — the legitimate concurrent human edit**: not an attack; its correctness criterion is *attributed, never lost* (M8 between attested roots; A24/R14 during recovery downtime; the remaining in-publication window is SI-40, posture-bounded by P29). Out-of-scope boundary: root/privileged-host is out of scope (the trust boundary is the Unix account, P7); an ordinary different-uid process is *defended* by the enforced 0700/0600 home permissions, not out of scope. +- **Publication and read-back discipline (A27.1/A27.2, normative).** *Staged bytes (A27.1):* a commit consumes bytes verified in memory during prepare and never re-reads mutable storage to source the bytes it installs; re-reading live storage for comparison or enumeration is permitted — the invariant is on the provenance of installed bytes. Prepare is the single verification point; commit is a pure function of already-trusted bytes. Holds against **T1** — the write-boundary complement of A27.2's read-back rule (D32-5). *Verify-on-read-back (A27.2):* every read-back of storage bytes that will bear authority MUST content-address-verify (or signature-verify, which subsumes it — A19) before the bytes are consumed; a mismatch is a loud denial, never a fallback. Holds against T1, with the three carve-out classes above carrying their own rules. +- **Per-kind entry rules (A27.3, normative).** *fs-tree stores:* the canonical grammar is exactly A24/R25's tagged path-state domain — `absent | file(content hash, executable mode) | implicit-directory`; any other kind (symlink, fifo, socket, device) is definitionally non-canonical. Capture rejects symlinks; recovery runs the R24 kind-complete scan; publication writes through an `O_EXCL|O_NOFOLLOW` randomized sibling with a same-inode non-symlink recheck immediately before rename — documented as *race-narrowing, never race-closing* (see A27.4); excluded directories are never written, deleted, or traversed ("excluded means untouched," uniform through every walk — RF-43 until `sync_store`'s walk complies). *SQLite stores:* the whole file is the unit; images are captured WAL-checkpointed, published through the same atomic primitive, stale `-wal`/`-shm` sidecars removed after the swap; **every** SQLite store path — registered stores included, not only `fabric.db` — is symlink-rejected before capture/open (RF-42 until enforced). *Hardlinks:* captured by content like any regular file; the write-through-a-link variant is defeated structurally by the atomic publisher (rename replaces the directory entry); the pre-plant-and-race variant is T2, COOP/W-4-bounded. +- **Containment boundary (A27.4, normative).** Every T2 publication-safety claim carries the standing sentence: **"holds under COOP; requires W-4 containment at G-ADVERSARIAL."** No pathname check is ever claimed to defeat a same-uid active adversary, and the security argument for any race-narrowing step MUST name COOP as the assumption it rests on. T2 claims are publishable as labeled conditionals (D32-8; the posture ledger's G-PUBLISH gate entry makes the dependency reader-visible); a published T2 claim missing its label is a publication defect. ### 5.4 Closure and revocation (A22) @@ -380,7 +386,7 @@ Not a score: raw, trace-backed counters per (principal, domain, skill-version). ## 9. Forks - **F1 — resolved (v1):** broker-minted capabilities. Central, revocable, meterable; format stays compatible with offline attenuation (the `parent` chain + §5.2); revisit when deep delegation trees arrive and metering has an answer. "Revocable" has normative semantics since A22 (§5.4): signed `revoke` events, permanent per id, prospective, descendant-closing through the ancestry view. -- **F2 — resolved in direction (A16; ADR 0002 in the reference implementation):** the control plane stays JCS (with the §0 integer rule); state roots become CIDv1 over DAG-CBOR tree nodes with raw leaf blobs and chunked large objects (sqlite chunked on page boundaries). `sha256:` is the sanctioned interim root encoding until the execution checkpoint (post-dogfooding storage tripwires); readers MUST accept both during the transition. Scope fence: CIDs ≠ IPFS — an addressing/serialization format only; no DHT, no gateways, no sync protocol. +- **F2 — resolved in direction (A16; ADR 0002 in the reference implementation):** the control plane stays JCS (with the §0 integer rule); state roots become CIDv1 over DAG-CBOR tree nodes with raw leaf blobs and chunked large objects (sqlite chunked on page boundaries). `sha256:` is the sanctioned interim root encoding until the execution checkpoint (post-dogfooding storage tripwires); readers MUST accept both during the transition. Scope fence: CIDs ≠ IPFS — an addressing/serialization format only; no DHT, no gateways, no sync protocol. A27's publication/read-back rules (§5.3) are stated over content addresses generally and survive this transition unchanged; the migration note is W-6/F2's, not the adversary model's. - **F3 — partially resolved:** sensitivity derived (domain defaults × taint propagation), ceilings from channel strength. Only finer-than-domain granularity remains open. - **F4 — resolved (default):** `summary` carries only fields the capability's caveats reference — minimization is automatic because the consent-relevant extract is definitionally the caveat-relevant extract. All fields redactable. Per-tool overrides possible at registration. @@ -394,6 +400,12 @@ Not a score: raw, trace-backed counters per (principal, domain, skill-version). 4. Run: each tool call traced with checks, meters, summary, `state_root_after`. Unknown-recipient draft → escalation → one-tap approval (channel-stamped). Third similar approval this month → clerk drafts a rule (k=3, least-general, counterfactuals attached, domain-matched, domain-scoped pin) for ratification at `approval.min_auth`. 5. Promotion gate: trace re-verified against capability; three-way merge to trunk; promotion event. Sixty days on, email payloads hit TTL → shred events. The run stays forever explainable, no longer readable. +## Changelog — v0.10 (amendment A27) + +*Resolves SI-32 (filed 2026-07-13 from the PR #43 review-cycle analysis; W-20 item 3). Candidate ADR 0008 built under the codified review battery (PR #51): publication-site inventory with file:line anchors, audit battery + internal adversarial pre-review before filing (which caught the rollback/A23 blocking find), external review round 1 folded (8 findings, among them the active-window T3 edit, the rollback carve-out's precise layer-1 domain, and the trust-root/different-uid boundary split; 5 of the class the battery now guards against). Ratified 2026-07-15, determinations D32-1…D32-10 in ADR 0008's addendum; the pre-round internal adversarial review returned 14 findings (4 medium), all applied before push. Per the review-battery stopping rule (adopted 2026-07-15, PR #52): one delta-scoped external round on this ratification text; continuation only on blocking findings.* + +A27 — storage adversary model: §5.3 gains the three-tier model (T1 offline tampering / T2 same-uid active writer / T3 legitimate human edit) with a tier label on every publication-safety and authority-bearing read-back guarantee. T1 is cryptography's (content-address/signature verification on read-back) with three carve-outs that are not: the trust root (SI-27/RF-14 residual under same-uid T1, accepted under SU — D32-3 rejected scoping T1 to a trusted verifier key as labeling an anchor that does not exist), unsigned indexes (operational pointers derive from signed substrate — RF-41 files the gap; `substrate_span` feeds broker authority, so its reach exceeds capture targeting), and rollback — **A23's, never content-addressing's** (D32-2: layer 1 detects only rollback inconsistent relative to a surviving expected terminal; a coherent suffix regression is layer 2's at its gates; the seam is kept inside the tier model so T1 cannot be read as complete). T2 is topology's: no pathname check is claimed to close the same-uid race; A27.4's standing sentence — "holds under COOP; requires W-4 containment at G-ADVERSARIAL" — attaches to every T2 claim, the same-inode recheck is documented race-narrowing, and T2 claims publish as labeled conditionals with the posture ledger's G-PUBLISH gate entry (D32-8). T3 is attribution's: attributed-never-lost, M8 between roots and A24/R14 during recovery downtime; the in-publication window is the ratification's one protocol-class deferral — **SI-40**, with D32-4 foreclosing the topology arm (native human access to shared state is the product thesis; momentary exclusion fails T3 closed on an honest save) and naming the preservation/refusal protocol (the A24/R14 capture-or-refuse shape at a per-entry pre-rename commit point) as leading candidate, bounded by P29 (1HUMAN/1SESS, sub-second Tier-1 windows) with triggers at slow-apply stores, G-2HUMAN, or a first observed loss in dogfooding. A27.1 ratifies the staged-bytes rule as normative (commit installs only prepare-verified bytes; comparison/enumeration reads permitted — the invariant is installed-byte provenance); A27.2 verify-on-read-back; A27.3 per-kind entry rules (fs-tree = A24/R25's tagged domain with non-canonical kinds rejected; SQLite whole-image, WAL-folded, sidecars removed, **every** store path symlink-rejected — RF-42 files the registered-store gap; hardlinks closed by content-capture plus the atomic publisher). RF-43 files the `sync_store` exclusion-uniformity defect; G14 files the outstanding negatives; enforcement binds at the carriers (RF-41 → W-15's signed-chain lane, the W-15a/W-15b split in flight in PR #52; RF-42/RF-43 → the RF-40 mechanical bugfix lane, defined at RF-42's entry). Rejected: a flat trusted-local-filesystem assumption (unlabeled claims are why every review re-derived the attacker); claiming pathname checks defeat T2 (the SI-10/A21 lie surface); folding T3 into the attacker model (re-opens the M8 gap A20 closed); deferring the whole model to W-4 (T1 is live now — backups, copied homes, RF-41/RF-42). + ## Changelog — v0.9 (amendments A24–A26) *Resolves SI-31, SI-33, SI-34 (filed 2026-07-13 from the PR #43/W-14 review cycle; ratified 2026-07-14 in the W-20 retro-ratification session, PR #48 — challenge pass over the merged implementation as candidate, fresh-eyes source verification, determinations D31-1…D31-6, D33-1…D33-5, D34-1…D34-4 as adjusted by seven rounds of review adjustments R1–R25 (round 7 scoped) plus an internal pre-round-6 review, all recorded in `docs/adr/0007-owned-state-consumed-authority-approval-binding.md`; provenance in `docs/spec-issues.md`). Claims about the candidate code carry three distinct labels — ratified as built, adjusted beyond as-built (RF-35–RF-37/RF-39; W-15/W-22 carry), and as-built defect (RF-40) — and unenforced clauses bind implementations at their carrier gates per the ledger. The first retro-ratifications: protocols designed inside a remediation cycle (the review-finding triage rule's founding case), ratified after the fact — with adjustments the merged code must still meet, which is what distinguishes a challenge pass from a rubber stamp. Round 1 of the independent-context review returned REQUEST CHANGES (nine findings, four high); it refuted one determination rationale outright (D34-3's narrowing theorem), caught a composition defect between two others (D31-4 × D31-6), a self-contradictory predicate (D31-2's epoch guard), and a false authority-surface evidence claim (gate replay) — ratified as R1–R7, filing RF-36/RF-37/RF-38 and W-22. Round 2 (seven findings, three high) refuted the round-1 repairs in turn: V's totality claim omitted unbranched `tool_call.state_root_after` (a genuine revert-crash recovery bricks — R8); the convergence claim did not survive a second crash (captured evidence had no durable name — R9's recovery capture record, the write-ahead principle's third boundary); value-only freshness passes ABA replays (R11's positional binding); "same per-store results" recreated the blanket re-park R2 rejects (R12's agent-originated quantifier); the exemption candidate was version-ambiguous under A9 batching and displayed from unsigned rows (R10, RF-39); a singular closing record cannot attest a multi-store tuple (R13's pair-or-neither). Ratified as R8–R13; recovery is now V-preserving by construction. Round 3 (five findings, two high, confined to the round-2 additions and evidence bookkeeping) caught the repeated-crash window R9's first-write-wins opened (an edit during a crashed recovery's own downtime could be normalized unrecorded — R14's element-wise explanation check fails closed in place, the guarantee honestly narrowed, SI-39 filing the multi-window enhancement), the capture record's too-weak subset validation (R15: exact set), and the undefined comparison basis for multi-path ops (R16: per-op touched-path result equality against the previewed merged tree); ratified as R14–R16, with the reviewer confirming no original SI decision point was silently dropped. Round 4 (five findings, three high) changed character: alongside R17 (the temporal-binding gap proved to live at **both** consumers — a later approval retro-funds an earlier effect at decision time too; RF-36 widened, the round-1 "gate weaker than decision" record corrected), R18 (R14's quantifier was vacuous for deletions — path-state over the union of live/capture/target, absence as a value, canonical-entry identity), and R20 (R16's claimed merged-tree referent did not exist — previews gain CAS-retained per-store `merged` roots, digest-covered), it surfaced the cycle's first **as-built defects**: the merged fs apply skips mode-only differences and deadlocks on file↔directory topology swaps (RF-40 — a committed event could name a root live state does not realize; a legitimate recovery bricks). Ratified as R17–R20. Round 5 (three blocking findings) completed canonical-entry identity with **kind** — a symlink matching on bytes and mode was indistinguishable under the R18 tuple; non-canonical kinds are now definitionally unexplained (R21) — **layer-qualified the stale-journal guarantee** (R22: joint journal+database rollback is layer 2's anchor-ahead case; the drafted "can never drive a restore" overclaimed layer 1), and widened RF-40 to the merge planner (R23: entries reduced to content hash drop mode-only branch changes as silent no-op promotions — a deliberate, unledgered Stage-3 shortcut whose own rationale fails when no side is chosen). It also cleared three seeded attack surfaces as sound (R17 cross-span positions; R20 referent retention with parked promotions as future GC roots; RF-40's topology remedy under excluded dirs). Ratified as R21–R23. The internal pre-round-6 review then corrected 13 findings in the round-5 text (two high: the R23×A17 rename/mode composition; the kind-complete-walk vacuity), and round 6 — the narrowest of the cycle, completeness table fully green — ratified **R24** (the kind-complete scan runs on every recovery attempt, before the capture record and any mutation: the retry-scoped version left the first attempt free to restore over a socket unrecorded) and **R25** (the tagged path-state domain — absent | file(hash, mode) | implicit-directory — defining directory path-states over the files-only tree so the union quantifier neither bricks nested stores nor misses file↔directory mixed states), with G13(m)–(q) filing the completion negatives and the SI-33/SI-34 labels corrected to the three-category discipline. Round 7, scoped to the round-6 delta, completed R24/R25 in place: the scan quantifies over **active restore attempts** (first attempt: before capture-record publication; retry: before any restore mutation; closing-record cleanup exempt — a post-completion non-canonical entry is M8's at next consumption, never recovery's) and the path universe excludes the store root (the boundary, not a path-state — an empty-capture/empty-target recovery completes instead of bricking on the root); the G13(m–q) carrier roll reached W-15/W-22. Three mediums, no highs, no skeleton findings; findings 1–2 of round 6 verified closed by these completions, 3–4 by the round-6 commit.* @@ -440,4 +452,4 @@ A1 domain-scoped behavior pinning · A2 re-manifest on bundle change (M5) · A3 --- -*Status: v0.9, mid-dogfooding. v0.3's grammar survived three dissimilar paper workflows with amendments but no redesign; v0.4's amendments came from running code; A20 came from dogfooding, A21 from its readiness review, A22 from the revocation design review that separated key destruction from authority closure ahead of first egress, and A23 from the W-20 kernel-security-protocol pass — the first amendment ratifying the operational stratum beneath the schema (authenticated order for the substrate the invariants quantify over) rather than refining the schema itself. A24–A26 continue that pass with the first retro-ratifications: protocols an implementation designed inside a remediation cycle, challenged after the fact and adjusted across seven adversarial review rounds (round 7 scoped to the round-6 delta) plus an internal pre-round-6 review (five mechanism trackers filed — RF-35 recovery, RF-36 consumption-binding parity at both consumers, RF-37 re-merge equality, RF-39 exemption binding, RF-40 fs-pipeline entry identity — carried by W-15/W-22, plus RF-38 filing the anomaly-recovery question for the operator-surface pass; SI-39 filed for the multi-window recovery enhancement); rounds 4–5 notably surfaced as-built defects in merged dogfooding code, the strongest vindication of retro-ratifying under adversarial review — the ratify-first discipline holding even when code arrived first. Every wave still clusters on precision, not missing concepts; the compositional-grammar thesis is holding.* +*Status: v0.10, mid-dogfooding. v0.3's grammar survived three dissimilar paper workflows with amendments but no redesign; v0.4's amendments came from running code; A20 came from dogfooding, A21 from its readiness review, A22 from the revocation design review that separated key destruction from authority closure ahead of first egress, and A23 from the W-20 kernel-security-protocol pass — the first amendment ratifying the operational stratum beneath the schema (authenticated order for the substrate the invariants quantify over) rather than refining the schema itself. A24–A26 continue that pass with the first retro-ratifications: protocols an implementation designed inside a remediation cycle, challenged after the fact and adjusted across seven adversarial review rounds (round 7 scoped to the round-6 delta) plus an internal pre-round-6 review (five mechanism trackers filed — RF-35 recovery, RF-36 consumption-binding parity at both consumers, RF-37 re-merge equality, RF-39 exemption binding, RF-40 fs-pipeline entry identity — carried by W-15/W-22, plus RF-38 filing the anomaly-recovery question for the operator-surface pass; SI-39 filed for the multi-window recovery enhancement); rounds 4–5 notably surfaced as-built defects in merged dogfooding code, the strongest vindication of retro-ratifying under adversarial review — the ratify-first discipline holding even when code arrived first. A27 extends the same operational stratum sideways — the storage adversary beneath the publication discipline — and its ratification was the first full run of the codified battery with the stopping rule (one internal pre-review, one delta-scoped external round). Every wave still clusters on precision, not missing concepts; the compositional-grammar thesis is holding.* diff --git a/docs/posture-assumptions.md b/docs/posture-assumptions.md index 87bd6a5..31ae6e0 100644 --- a/docs/posture-assumptions.md +++ b/docs/posture-assumptions.md @@ -182,6 +182,18 @@ default cannot save this class retroactively. (`human_local`); `tool_known` / `unattributed` deferred. → multi-actor roots/visibility (roadmap parked); SI-20/A20 (timing resolved); `dogfooding.md` "invite a second person" gate. +- **P29** *(filed 2026-07-15, SI-40/D32-4)* A human edit landing inside a + live promotion/revert apply window (after the prepare-time + capture-equals-`before` check, before an entry's rename) is overwritten + with no CAS capture and no drift event — T3's "attributed, never lost" + violated in the one window M8 and A24/R14 do not cover. Safe now: + 1HUMAN/1SESS make the colliding writer the same person who initiated + the transition, and the Tier-1 apply window is sub-second. Un-safed by: + a second human (this gate), or — earlier, in expectation — any store + whose apply window is not sub-second (Tier-2/3 applies), or a first + observed loss in dogfooding (evidence this rationale failed). → SI-40 + (preservation/refusal protocol, leading candidate; the topology arm is + foreclosed by D32-4 — the human is never excluded from shared state). ### G-CONCURRENT — before a second concurrent session in one home *(relaxes 1SESS)* - **P20** `current_manifest` / `current_span` are mutable **home-global** @@ -282,12 +294,19 @@ in scope, or when storage leaves that filesystem boundary. extract §5.4 conformance vectors from the A22 contract tests. - SI-23 constraints and the brief §8 landscape claim should also be settled before publication (both already tracked). +- **T2 conditional publication (A27/D32-8, 2026-07-15 — NEW):** every + published T2 publication-safety claim carries the A27.4 label — "holds + under COOP; requires W-4 containment at G-ADVERSARIAL." The label is the + honest disclosure; W-4 need not land before publication, but a published + T2 claim missing its label is a publication defect, and the conformance + sweep for any published spec text checks the labels rather than implying + a test proves T2 safety. --- ## The shortcut ledger (backing index) -All 28 currently tracked, grouped by filing status. `SU/COOP/LOCAL/NOACT/ +All 29 currently tracked, grouped by filing status. `SU/COOP/LOCAL/NOACT/ 1SESS/1HUMAN/1TEN/DEBUG` = the invariant(s) that make each safe now. ### Tier 1 — items this sweep filed or newly gated @@ -304,6 +323,7 @@ All 28 currently tracked, grouped by filing status. `SU/COOP/LOCAL/NOACT/ | P26 | Corpus-ingest homes: placeholder identities/behavior signed into a real substrate; evidence-quarantined by convention only (second P8 site) | `asf-cli corpus/ingest.rs` (`placeholder_key`, behavior literal) | COOP SU | W-3 mechanical exclusion (G-RATCHET); `agent-trace-corpora-2026-07-11.md` boundaries | | P27 | Injected credentials enter a downstream adapter whose response reaches the agent unchanged | `broker.rs:560-576`, `proxy.rs:501-571` | COOP LOCAL, first-party/no credential | RF-23; G-EGRESS/G-3P-TOOL; W-18 | | P28 | Staged A23 consumption: clerk/TrustRecord counting on the `VerifiedPrefix`; decision/gate/recovery still on the W-11 verified-row view (cross-span order unauthenticated on those paths) | `broker.rs`, `kernel.rs` (post-W-15a) | SU 1SESS COOP (broker locally the sole approval producer) | W-15b consumption swap; RF-13/P15 remainder | +| P29 | Human edit inside a live apply window overwritten uncaptured (post-prepare-check, pre-rename) | gate window (`kernel.rs`); fs apply (`snapshot.rs`) | 1HUMAN 1SESS (sub-second Tier-1 windows; self-inflicted concurrency) | SI-40 (D32-4); G-2HUMAN / first slow-apply store / first observed loss | ### Tier 2 — items already tracked (this ledger just indexes and gates them) @@ -361,3 +381,7 @@ All 28 currently tracked, grouped by filing status. `SU/COOP/LOCAL/NOACT/ filed P28 for the staged `VerifiedPrefix` consumption between W-15a and W-15b — the ledger's same-change rule, applied at decision time rather than late. +- **Follow-up (SI-32 ratification, 2026-07-15, A27):** added P29 (the + active-publication-window edit, SI-40/D32-4) and the G-PUBLISH T2 + conditional-publication row (D32-8) — the ledger's same-change rule, + applied at ratification time. diff --git a/docs/review-findings.md b/docs/review-findings.md index 23325da..12d43c8 100644 --- a/docs/review-findings.md +++ b/docs/review-findings.md @@ -1128,6 +1128,80 @@ entry-identity requirement by cross-reference. RF-40 may constitute the parked RF-27 recovery-hardening trigger; un-parking is an operator gate decision, not a review outcome. +## RF-41 — unsigned `fabric.db` meta rows are consumed for authority while sibling events are signed — open (medium, posture-bounded) + +**Severity: medium. Direction: FAIL-OPEN at future boundaries (copied, +substituted, or shared homes); none under SU.** Filed by the SI-32 +ratification (ADR 0008 inventory R7, A27.2's unsigned-index rule; +surfaced drafting the candidate, confirmed by external round 1). Store +paths (the `stores` rows) and `substrate_span` are read from unsigned +meta/index rows ([kernel.rs:505](crates/asf-kernel/src/kernel.rs:505)) +that a substituted `fabric.db` controls, while the *events* in the same +database are signature-verified. Reach: `substrate_span` is not merely a +capture/restore pointer — it feeds broker authority evaluation (grant +ordering, A22 closure liveness), so a T1 tamperer can redirect which +store a capture/restore targets or which span is the substrate span +under an otherwise-fresh, fully-verifying head. This is A23's seam from +the read side: the anchor proves the head is current, but the +operational pointers its events reference must themselves derive from +signed substrate. + +**Why not currently exploitable:** SU — the same-uid tamperer holds the +fabric key and forges signed events outright (RF-13's boundary +argument); real at G-MULTITENANT and whenever the home leaves the local +filesystem boundary. + +**Fix direction:** operational pointers consumed for authority derive +from signed substrate or are bound into it (A27.2, normative) — never +from an unsigned row. Natural carrier: **W-15a/W-15b** (the W-15 split +in flight in PR #52's heading-check filings) — the signed +global chain work touches the same region, and binding `substrate_span` +and store registration into signed events is the read-side complement of +the `VerifiedPrefix`. Negative (G14(a)): a substituted meta row must not +redirect any authority-bearing read — no effect, loud denial. + +## RF-42 — `capture_sqlite` follows symlinks for registered SQLite stores; only `fabric.db` is symlink-rejected — open (medium, posture-bounded) + +**Severity: medium. Direction: FAIL-OPEN (wrong bytes bear authority).** +Filed by the SI-32 ratification (ADR 0008 inventory R10, A27.3's SQLite +rule; external round 1, finding 4). `capture_sqlite` +([snapshot.rs:268](crates/asf-kernel/src/snapshot.rs:268)) reaches the +store via `is_file` → `Connection::open` → `fs::read`, all +symlink-following; only the fabric's own `fabric.db` is symlink-checked +(`w13_validate_existing_fabric_home`, +[kernel.rs:328](crates/asf-kernel/src/kernel.rs:328)). A symlinked +registered store (e.g. `db:memory`) redirects capture to an +attacker-chosen database: the captured root — and every manifest, +promotion, and drift comparison attesting it — derives from bytes +outside the store boundary. + +**Why not currently exploitable:** SU/COOP — planting the symlink is a +same-uid act, and registration is first-party only (P6). + +**Fix direction:** symlink-reject every SQLite store path, registered +stores included, before capture/open — the same `symlink_metadata` +no-follow discipline `fabric.db` already receives (A27.3, normative). +Carrier: the **RF-40 mechanical bugfix lane** — PR #52's W-15 re-cut +records the operator option to pull RF-40's mechanism-class fixes +forward of W-15 as an independent PR; RF-42 and RF-43 ride whichever +runs first, that pull-forward or W-15b. Negative (G14(b)): a symlinked +registered store fails capture with no effect. + +## RF-43 — `sync_store` fs walk omits the `.git` exclusion and carries no symlink guard — open (low, hygiene) + +**Severity: low. Direction: none (fsync-only; no integrity impact) — +a uniformity defect.** Filed by the SI-32 ratification (ADR 0008 +inventory R3, A27.3). `sync_store` +([snapshot.rs:608](crates/asf-kernel/src/snapshot.rs:608)) walks the +store for durability fsyncs without `capture_fs`'s `.git` exclusion, +breaking the "excluded means untouched" uniformity A27.3 ratifies; it +also has no symlink check — *proposed hardening*, not an existing +discipline it violates (capture rejects symlinks via an explicit +`path_is_symlink` check, not an `O_NOFOLLOW` open). Fix: bring the walk +into line with capture's exclusion; take the no-follow hardening in the +same touch. Carrier: the same mechanical lane as RF-42. Negative +(G14(c)): the excluded directory is untouched through `sync_store`. + ## Verified sound during review (recorded so they aren't re-litigated) - Per-payload DEKs each perform exactly one encryption → no GCM nonce reuse diff --git a/docs/roadmap.md b/docs/roadmap.md index 116b541..4657dfb 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -137,7 +137,59 @@ items: the internal independent-context pre-review before each paid external round caught 13 findings (two high) at a fraction of a round's cost — carry it forward for items (3)–(6) and consider it in the W-20 close-out as standing practice for authority-surface PRs. -Next: item (3), SI-32. +**Item (3), SI-32 — candidate drafted 2026-07-14 (ADR 0008, +PROPOSED), queued for operator ratification.** Three-tier filesystem +attacker model (T1 offline tampering → content-address verification; +T2 same-uid active writer → W-4 containment, not pathname checks; T3 +human edit → M8/A24 attribution), candidate rules A27.1–A27.4, grounded +in a publication-site inventory appendix with file:line anchors. +Surfaces two genuine T1 gaps (RF-41: unsigned `fabric.db` meta rows — +incl. `substrate_span`, which feeds broker authority — trusted while +sibling events are signed; RF-42: `capture_sqlite` follows symlinks for +registered SQLite stores, only `fabric.db` is symlink-checked), an +unresolved protocol-class T3 case (a human edit during a live +promotion/revert overwritten with no capture/drift — the item ADR 0007 +R14 deferred here), and one uniformity correction (R3: `sync_store` +omits the `.git` exclusion). Ten ratification choices enumerated. Built +via the codified review battery (PR #51): audit battery + internal +adversarial pre-review before filing (which caught the rollback/A23 +blocking find), then external review round 1 (8 findings — 5 the battery +now guards against, all folded). **Item (3) ratified 2026-07-15** as +A27 (spec v0.10 §5.3; determinations D32-1…D32-10 in ADR 0008's +addendum): three tiers with the rollback carve-out kept inside the +model (D32-2) and the trust-root residual accepted (D32-3); +staged-bytes and verify-on-read-back normative; the A27.4 standing +containment sentence; conditional T2 publication with the G-PUBLISH +gate entry (D32-8). One divergence from the candidate's lean: D32-4 +forecloses the topology arm for the active-publication window (native +human access to shared state is the product thesis) and files **SI-40** +with the preservation/refusal protocol as leading candidate, bounded by +P29. Filings: RF-41 (carrier W-15a/b — the split PR #52 files), +RF-42/RF-43 (the RF-40 mechanical bugfix lane, defined at RF-42), SI-40, +P29, the G-PUBLISH gate entry, G14 negatives. Internal adversarial +pre-review before the external round: 14 findings (4 medium), all +applied — recorded in ADR 0008's addendum. Per the +review-battery stopping rule (adopted 2026-07-15, PR #52): one +delta-scoped external round on the ratification text, continuation only +on blocking findings. The delta round returned REQUEST CHANGES — five +findings, two blocking mediums with reviewer-prescribed remedies +(A27.1's dropped T1 label; the candidate body's layer-2 misclass of +caller-pinned heads, corrected in D32-2), one non-blocking medium (the +SI-40 substrate-assisted paragraph's snapshot-timing over-claim), two +lows — all folded same-day and recorded in ADR 0008's addendum; the +round verified all ten determinations integrated, the A24/R24/R25 +grammar preserved, and the RF/G14 source claims. Operator gate decision +2026-07-15: a confirming delta round, scoped to PR #50's five-finding +fold delta, within the stopping rule's blocking-round continuation. +**The confirming round returned no blocking findings — the cycle is +closed (2026-07-15).** Its two non-blocking findings were folded under +the battery: the per-file clone variant in SI-40's candidate text now +requires atomic clone-and-swap semantics (a separate clone→rename pair +re-opens the window in miniature), and this entry's own branch-local +SHA citation was replaced per the citation rule. Cycle cost, complete: +one internal adversarial pre-review (14 findings) + two external delta +rounds (5 + 2 findings) — against PR #48's seven. W-20 closes when this +PR merges, per the close-out decision below. **Close-out decision (operator, 2026-07-15 heading check): the batch closes when item (3) ratifies.** Items (4)–(6) leave the batch and defer to their named triggers — SI-26/SI-28/SI-29 to W-16 and the W-6 diff --git a/docs/spec-issues.md b/docs/spec-issues.md index 588557d..71da8c6 100644 --- a/docs/spec-issues.md +++ b/docs/spec-issues.md @@ -33,7 +33,12 @@ > (TracePosition genesis representation) by the fourth (2026-07-14). > **SI-39** (recovery-window divergence: automated multi-window > preservation) was filed by round 3 of PR #48's review — the -> narrow-and-file remedy ratified as R14. New issues start at **SI-40**. +> narrow-and-file remedy ratified as R14. **SI-32 is resolved in v0.10 +> as A27** (W-20 item 3, ratified 2026-07-15: the three-tier storage +> adversary model, determinations D32-1…D32-10 in ADR 0008; RF-41–RF-43 +> file the surfaced gaps; the active-publication-window T3 edit is +> re-filed as **SI-40**, the ratification's one protocol-class +> deferral). New issues start at **SI-41**. Tracked per the handoff: where the spec is ambiguous or contradicts itself, we record the question, the interpretation the kernel implements, and why — @@ -46,6 +51,84 @@ tests encode it; flipping the reading is cheap. --- +## SI-40 — a human edit during a live promotion/revert apply window is overwritten with no capture or drift (§5.3, A24, M8) — open + +The one T3 window ADR 0008's model found uncovered (external review +round 1, finding 1 — the item ADR 0007 R14 explicitly deferred to +SI-32): M8 covers edits *between* attested roots and A24/R14 covers the +crash-recovery downtime, but the gate lock (`kernel.rs`) serializes +fabric *processes*, not a human with a text editor. A vault edit landing +after the prepare-time capture-equals-`before` check and before the +apply's rename is overwritten by the rename with no CAS capture and no +drift event — T3's "attributed, never lost" criterion violated inside a +live publication. + +Protocol-class (a remedy adds a preservation/refusal step — a new commit +point — or changes the write topology), so designed here, not in a PR. +**D32-4 (ADR 0008 addendum) forecloses one arm:** the topology remedy — +excluding the human edit surface during the publication window — is +rejected as structurally unavailable for shared-state stores: native, +unmediated human access to the vault is the product thesis (brief §2, +open-world shared state), and momentary exclusion (chmod/lock games) +fails T3 in the *other* direction — a bricked save is failing closed on +an honest edit. The **leading candidate** is therefore the +preservation/refusal protocol: the A24/R14 capture-or-refuse shape +extended to the in-process gate window — per-entry, immediately before +each rename, detect that the live target diverged from the prepare-time +image and either capture-and-attribute the divergent bytes (a new +write-ahead point inside the apply) or refuse that entry and fail the +transition closed with the divergence preserved. Design must answer: +where the mid-apply refusal leaves the half-applied tree — NOT the A24 +ordinary-failure rollback as-is: that arm restores `before` over the +divergent entry with no capture record (the record exists only on the +crash-recovery path), destroying exactly what the refusal exists to +preserve; a preserving refusal captures the divergent bytes before any +rollback, or fails like a crash (journal retained, home closed) so +reopen recovery's capture path owns them — what the capture artifact is +(the R9 capture record generalizes), and the cost budget (a per-entry recheck on every +apply pays a stat per file to defend a sub-second window). + +A third remedy direction, noted by the operator at ratification and +corrected by the external delta round (finding 3): **substrate-assisted +preservation.** Stated precisely, because the intuitive version +over-claims: a CoW snapshot taken at gate-lock acquisition does NOT +close this window — the SI-40 edit is by definition made *after* that +snapshot (it lands post-prepare-check), so it is absent from the +snapshot and still overwritten by the rename; the snapshot preserves +exactly the state the prepare check already verified was not at risk. +What the block layer can genuinely buy is retention of the **outgoing +live state at swap time**: a clone-and-swap publication that retains +the outgoing dataset — the sound variant, since the dataset swap is one +atomic point — or a per-file clone of each target immediately before +its rename **only under atomic clone-and-swap semantics**: a separate +clone followed by a separate rename re-opens the window in miniature +(an edit landing between them is absent from the clone and still +unlinked), so absent atomicity the per-file variant is race-narrowing, +not window-closing (the confirming review's finding). Either way +divergent bytes survive the swap instead of being unlinked. Even then the fs artifact is only the preservation +substrate: T3's criterion is attributed-never-lost, so the retained +outgoing state must still be diffed against the prepare image, ingested +into the CAS, and drift-attributed — retention, reconciliation, and +cleanup costs the per-entry capture-or-refuse candidate does not pay. +Plus the costs from the original note: a platform dependency the fabric +has so far refused (the CAS is deliberately CoW-snapshots-in-userspace, +portable anywhere) and per-platform divergence exactly where A27.3 just +unified per-kind semantics. Shape: a deployment-floor option (the +RF-15/P17 "OS/full-disk floor" class), not the portable default — +evaluate against the per-entry capture-or-refuse candidate when a +trigger fires. + +Bounded today by **P29**: under 1HUMAN/1SESS the colliding writer is +the same person who initiated the transition, the Tier-1 apply window is +sub-second, and the loss is one file version usually still in an editor +buffer. Triggers, in expected order: any store whose apply window is not +sub-second (Tier-2/3 applies — minutes, not milliseconds — are when this +protocol earns ratification); G-2HUMAN (a second human makes the window +adversary-reachable in spirit); or a first observed loss in dogfooding. +*Provenance: PR #50 external review round 1, finding 1; ADR 0007 R14's +deferral; ratified as the narrow-and-file remedy in D32-4 +(2026-07-15).* + ## SI-39 — recovery-window divergence: automated preservation across repeated recovery crashes (§5.3, A24, M8) — open A24's recovery protocol (D31-6/R9/R14, ratified in PR #48) preserves and @@ -385,7 +468,36 @@ tables are demoted to compatibility caches never read for authorization; signer-anomalous capability, caveat, manifest, auth-strength, zero-use, duplicate-binding, and cross-capability edges fail closed. -## SI-32 — store publication has no defined filesystem attacker or required OS primitives (§5.3, §9 F2) — open +## SI-32 — store publication has no defined filesystem attacker or required OS primitives (§5.3, §9 F2) — RESOLVED (author, 2026-07-15) + +**Resolution: ratified as amendment A27 (spec v0.10, §5.3) — the +three-tier storage adversary model, determinations D32-1…D32-10 in ADR +0008's ratification addendum.** Ratified as candidate-drafted: three +tiers with T1's answers split by mechanism (content-address/signature +verification for substitution/corruption/truncation; **rollback +carved out to A23** — layer 1 detects only rollback inconsistent +relative to a surviving expected terminal, coherent suffix regression +is layer 2's at its gates — D32-2 keeps the seam inside the tier model +so T1 is never read as complete); trust-root substitution under +same-uid T1 accepted as an **SI-27/RF-14 residual** (D32-3; the +trusted-verifier-key alternative rejected as labeling an anchor that +does not exist); A27.1 staged-bytes and A27.2 verify-on-read-back +normative with the three read-back classes; A27.3 per-kind entry rules +(fs-tree = A24/R25's tagged domain; every SQLite store path +symlink-rejected; hardlinks closed by content-capture + the atomic +publisher); A27.4's standing containment sentence on every T2 claim +(D32-7); conditional T2 publication with the G-PUBLISH ledger row +(D32-8; the posture ledger's G-PUBLISH gate entry). **One divergence +from the candidate's lean (D32-4):** the +active-publication-window human edit is filed as SI-40 with the +topology arm foreclosed (native human access is the product thesis) +and the preservation/refusal protocol as leading candidate, bounded by +P29. Gaps filed: RF-41 (unsigned meta rows → the W-15a/b carrier — the +W-15 split in flight in PR #52), RF-42 +(registered-SQLite symlink capture) and RF-43 (`sync_store` exclusion +uniformity) → the RF-40 mechanical lane; outstanding negatives G14. +Enforcement binds at the carriers per the ledger; the tier labels are +normative now. The spec assumes content-addressed preparation and coherent restore but never defines the filesystem adversary those operations run against. @@ -411,6 +523,42 @@ verification); the symlink/hardlink/directory-entry rules per store kind (fs tree vs. SQLite file); and which publication-safety claims require containment before G-PUBLISH. +**Candidate awaiting ratification (ADR 0008, 2026-07-14; W-20 item 3):** +the three-tier model — T1 offline tampering (answered by content-address +verification on every authority-bearing read-back), T2 active same-uid +writer (no pathname check wins; the honest answer is W-4 containment, so +every T2 claim is labeled "holds under COOP; requires W-4 containment +at G-ADVERSARIAL"), T3 legitimate human edit (not an attack; M8 attribution +and A24/R14 recovery-window capture). Candidate normative rules +(candidate amendment A27): A27.1 the staged-bytes rule (ratifies RF-20's +discipline); A27.2 verify-on-read-back, tier-labeled, with three classes +(cryptographic content-address; trust-root by-boundary; and the +**unsigned-index gap** — the code reads store paths from unsigned +`fabric.db` meta rows a T1 tamperer controls while the events beside them +are signed, proposed **RF-41**, posture-bounded under SU); A27.3 per-kind +entry rules (fs-tree canonical grammar = A24/R25's path-state domain, +non-canonical kinds via A24/R24's kind-complete scan; SQLite whole-image + +sidecar removal, plus the RF-42 registered-store symlink gap); A27.4 the +containment-boundary sentence. +The ADR enumerates **ten** human choices (tier count + root-vs-uid +boundary; the T1-rollback/A23 seam and its precise layer-1 domain; +trust-root substitution as an SI-27/RF-14 residual; the active- +publication-window human edit, protocol-class; staged-bytes normativity; +RF-41/RF-42 vs P17 folding; containment wording; G-PUBLISH conditional +publication; the R3 correction; spec placement) and grounds every claim +in a publication-site inventory appendix with file:line anchors. Two +gaps beyond RF-41 surfaced by folding external review round 1: +**RF-42** (`capture_sqlite` follows symlinks for registered SQLite +stores; only `fabric.db` is symlink-checked), and the active-publication- +window T3 edit (a human write during a live promotion/revert is +overwritten with no capture/drift — the item ADR 0007 R14 deferred here, +protocol-class). SI-32 remains **open**; no tier label or rule is +normative until those choices are ratified. Candidate filed as PR #48-era +main; passed the audit battery and an internal adversarial pre-review +before filing, then folded external review round 1 (8 findings — 5 that +the codified review battery, PR #51, now guards against) before this +revision. + ## SI-31 — owned-state transition: "atomically" has no commit point, journal semantics, or crash matrix (§5.3) — RESOLVED (author, 2026-07-14) **Resolution: ratified as amendment A24 (spec v0.9, §5.3) — the owned-state diff --git a/docs/substrate-theory-review-prompts.md b/docs/substrate-theory-review-prompts.md new file mode 100644 index 0000000..23800dc --- /dev/null +++ b/docs/substrate-theory-review-prompts.md @@ -0,0 +1,270 @@ +# Substrate-theory review prompts — the adversarial panel + +Companion to `docs/substrate-theory.md`. Five independent review stances +plus a synthesis pass, built on the house review discipline: enumeration +produces verdict tables, adversarial work produces findings, the two +never bundle; diverse lenses catch what redundant ones cannot; a single +decisive refutation outweighs any number of agreements. + +**How to run.** Each stance goes to a FRESH context (a new session, or a +different model — model diversity per stance beats one model five +times). Reviewers never see each other's output before synthesis. Every +prompt needs `docs/substrate-theory.md` pasted or readable; each lists +its optional grounding. Collect the five outputs, then run the +synthesis prompt over all of them. Verdicts compose because every +reviewer uses the same contract. + +**The shared contract (paste as the header of every stance prompt):** + +``` +You are reviewing "Substrate theory — ASF's theoretical basis and the +operating-system endgame", a deliberately speculative THEORY NOTE with +numbered claims C1–C12 and falsifiers F1–F6. The authors want it +damaged: deference is a defect. But fabricated objections are worse — +your success metric is the strength of your best attack, not your +finding count. + +Rules: +1. Argue from your discipline's actual literature and named systems, + papers, and incidents — never from vibes. +2. Output PART A first — a verdict table, one row per assigned claim: + | claim | verdict (AGREE / REFINE / REFUTE) | strongest + counterargument you considered (mandatory even for AGREE) | your + argument, with citations | +3. Output PART B second — free-form numbered findings (things wrong + with the note that no single claim row captures), each tagged + high / medium / low. +4. Never mix parts A and B. +5. End with exactly two sentences: the ONE claim you would delete + outright, and the one claim you would bet on. +``` + +--- + +## Stance 1 — the capability-systems historian + +Assigned: **C1, C2, F6.** Optional grounding: the ASF brief §3/§5 +(design principles, caveat grammar), spec §5/§7. + +``` +[shared contract] + +You are a historian of capability systems and object-capability theory: +Hardy's confused deputy, KeyKOS/EROS, Miller's Robust Composition and +the E language, Capsicum, macaroons and biscuit tokens. You have +watched every generation of "capabilities finally arrive" claims fail, +and you know the internal critiques of the tradition as well as the +external ones. + +Your assigned claims: C1, C2, and falsifier F6, plus any claim you +believe misuses the lineage. + +Attack vectors to open with (starting points, not limits): +- Is "prompt injection is the confused deputy" exact or an analogy + stretched past its warrant? Hardy's deputy CONFLATED designation with + authority; an injected agent FOLLOWS adversarial instructions inside + authority it legitimately holds. Does ocap's remedy (no ambient + authority; designation carries rights) actually address + instruction-following, or only blast-radius? +- Are caveat-attenuated broker-minted capabilities actually ocap — or + scoped bearer tokens plus audit? Macaroons are not object references; + which of Miller's composition properties survive the difference, and + do the surviving ones carry the weight C1 puts on the lineage? +- F6, pressed hard: if the mechanically-checkable caveats only fence + the cheap cases and every interesting delegation decision lands in + the model-judge/escalation layer, is the ocap framing mechanism or + marketing? What fraction of Hardy-class failures do caveats alone + stop? +- C2's claimed novelty: does "consequence bounding" have a lineage the + note ignores (transactional memory, sagas and compensations, + reversible/undo research, Ken)? Is behavior-version pinning genuinely + absent from the ocap literature, or is it a membrane/revocation + pattern under a new name? +``` + +## Stance 2 — the Linux kernel and security-subsystem engineer + +Assigned: **C3, C4, C5, C11, F4.** Optional grounding: spec §5.3 (the +A27 storage adversary model), posture ledger rows P5/P7. + +``` +[shared contract] + +You are a Linux kernel engineer with deep LSM, namespace, and container +runtime experience — you know SELinux, AppArmor, Landlock, seccomp, +keyrings, user namespaces, gVisor/Kata/Firecracker, and you have strong +opinions about what is and is not "a new principal." + +Your assigned claims: C3, C4, C5, C11, and falsifier F4. + +Attack vectors to open with: +- Is "the uid is POSIX's finest durable principal" actually true? + SELinux domains ARE per-process principals; keyrings, user + namespaces, and Landlock rulesets all attach finer-than-uid identity. + Is the defensible claim only "no unprivileged, application-definable, + delegation-scoped principal" — and if so, which of the note's + downstream conclusions survive the narrowing? +- Is manifest-as-principal kernel work at all, or an LSM plus a policy + compiler? Specify what breaks if you build it as a stacked LSM today. + If nothing breaks, C5's "one real kernel change" is wrong in an + interesting direction. +- C11's delta: what does an "agent-runtime OS" add that + Firecracker/Kata plus a policy engine does not already provide? Name + the delta precisely or call the claim a rebrand. +- F4, pressed hard: when the fleet spans hosts, manifest principals + need a cross-host trust root — does kernel enforcement help at all, + or does the anchor/identity problem simply recur one layer down? +- The secure-attention-key analogy for approval surfaces: real or + romantic on modern Linux (SAK's practical death, Wayland, polkit)? +``` + +## Stance 3 — the storage and filesystems engineer + +Assigned: **C6, C7, C8, C9, F3.** Optional grounding: spec §5.3 +(owned-state transition + A27), the SI-40 entry in +`docs/spec-issues.md`. + +``` +[shared contract] + +You are a storage engineer who has shipped filesystems and replication +systems — you know CoW internals (btrfs/ZFS/APFS), rename semantics, +inotify/FSEvents, NFS/SMB caching behavior, and the failure modes of +sync daemons against atomic publication. + +Your assigned claims: C6, C7, C8, C9, and falsifier F3. + +Attack vectors to open with: +- Is the three-family taxonomy (exclusion / detection / indirection) + exhaustive? Where do journaled intent logs and lease-based coherence + (NFS delegations, SMB oplocks) fit — a fourth family, or subfamilies + that blur the partition C6 relies on? +- Watcher semantics across an exchange-rename of the store root: do + inotify/FSEvents consumers and sync daemons (Syncthing, Dropbox) see + a delete+recreate storm? If every publication triggers a sync-daemon + re-scan or conflict cascade, family 3's costs re-enter through the + side door — quantify. +- Hardlink staging aliasing: unchanged entries share inodes between the + retained tree and the new live tree, so a POST-swap edit through the + new live path also mutates the retained tree. The note's + reconciliation reads only changed entries — is the retained tree + actually valid evidence, and does the aliasing blur the drift + attribution window in either direction? +- The O(changed) reconciliation bound under rename-heavy diffs and + file↔directory topology changes: does it hold, or does correct + reconciliation require O(tree) walks in exactly the messy cases? +- NFS symlink-flip: is rename-over-symlink actually atomic and + cache-coherent for NFS clients (attribute caching, lookup caching)? + How wide is the stale-resolution window in practice? +- F3, quantified: at what store size, churn rate, and sync-daemon + presence does family-3 retention+reconciliation lose to family-2 + per-entry detection? Sketch the crossover. +``` + +## Stance 4 — the infrastructure strategist and platform economist + +Assigned: **C10, C12, F1, F2, F5.** Optional grounding: the ASF brief +§2 (why now), §8 (landscape), §9 (strategy). + +``` +[shared contract] + +You are a platform strategist and economist of infrastructure adoption +— standards wars, format governance, developer-tool gravity, and the +history of who captured value when a layer commoditized (containers, +browsers, databases, mobile). + +Your assigned claims: C10, C12, and falsifiers F1, F2, F5. + +Attack vectors to open with: +- "No POSIX loyalty at the trust boundary": the labs are entrenching + bash-first agent harnesses at massive scale right now. Does harness + gravity constitute exactly the boundary loyalty C10 denies? Be + precise about WHO must adopt the new boundary and what their + incentive is. +- Read Docker/OCI honestly: the format won and the format's author + captured almost nothing. Is C12's precedent an argument FOR the + formats strategy or a warning that format authors get commoditized? + What, specifically, distinguishes the trust-ledger accumulation asset + from Docker Hub — which also looked like the accumulating asset? +- F5, made concrete: enumerate the actual candidates to ship a + vertically integrated agent substrate (hyperscalers, OS vendors, + labs), their format incentives, and the realistic window for a + neutral format to accumulate network effects first. +- F2, made testable: design the disconfirming observation — what + dogfooding evidence within 90 days would show that recoverable + histories do NOT increase delegation? If F2 cannot be made + observable, say so; an untestable falsifier is decoration. +- C11's buyer: who purchases an agent-runtime OS before + manifest-as-principal exists, and what is their switching cost from + Firecracker-plus-policy? +``` + +## Stance 5 — the minimalist editor and completeness critic + +Assigned: **all of C1–C12, §5, F1–F6** — but for shape, not depth. + +``` +[shared contract — PART A covers all twelve claims, one row each, +verdicts here mean KEEP / MERGE / DELETE with one-line justification] + +You are a ruthless editor of technical arguments. You do not evaluate +whether claims are true — the other reviewers do that. You evaluate +whether the document is the smallest, sharpest version of itself and +whether it practices the epistemics it preaches. + +Your questions: +- Which claims are load-bearing and which are decorative? Produce the + five-claim version of this note: which survive, which merge, which + die, and what is lost. +- Which falsifiers are real (their triggering would visibly change + behavior) and which are performative? Rank F1–F6 by bite. +- Is §5's fence ("what this theory does not license") credible, or + does the note smuggle a roadmap despite it? Quote any sentence that + functions as a work item. +- Where does the note contradict the documents it claims grounding in? +- What is MISSING — the claim this theory needs and does not state? + (The known candidate: the note theorizes state, authority, and + substrate but is nearly silent on the behavior lineage and the + judge. Decide whether that silence is a gap or a correct scope + fence, and say which.) +``` + +## Synthesis pass — after all five return + +``` +You are synthesizing five independent adversarial reviews of +"Substrate theory" (C1–C12, F1–F6). Inputs: the note plus five +outputs, each a verdict table (PART A) and findings (PART B). The +reviewers never saw each other. + +Method — in order, no averaging at any step: +1. Build the cross-matrix: claim × reviewer verdict. Classify every + REFUTE/REFINE as: convergent (independently raised by 2+ stances), + stance-dependent (one stance, explicable by its lens), or singleton + (one stance, not lens-explained — treat as live, not dismissible). +2. For each convergent refutation, steelman the NOTE against it — + write the best defense the note could mount — and only then rule: + the refutation stands, partially stands, or fails against the + steelman. A decisive singleton can outrank three agreements; say so + when it does. +3. Produce the amendment list: per claim, KEEP / REFINE (with the + replacement sentence) / STRIKE (with the refutation preserved + inline, per the house ADR norm). Update the falsifier list with any + new falsifiers the reviews surfaced and rank all by bite. +4. Close with two short sections: "What the theory survived" — the + claims that held under genuinely adversarial pressure and why that + is informative — and "What it cannot survive" — the single open + question whose resolution most determines whether this note matters. +``` + +--- + +**Operational notes.** (1) Independence is the point: five stances in +one shared conversation converge into one opinion wearing five hats. +(2) If a stance returns only agreement, that is a signal about the +prompt or the model, not the note — re-run it on a different model +before believing it. (3) The synthesis output, not the raw reviews, is +what folds back into `docs/substrate-theory.md` — amendments land there +with refutations preserved, and anything actionable still enters only +through SI/W/P filings per the note's own §5. diff --git a/docs/substrate-theory.md b/docs/substrate-theory.md new file mode 100644 index 0000000..7207360 --- /dev/null +++ b/docs/substrate-theory.md @@ -0,0 +1,271 @@ +# Substrate theory — ASF's theoretical basis and the operating-system endgame + +**Status: THEORY NOTE — exploratory, non-normative.** Nothing here is +spec, roadmap, or a work item; nothing here authorizes implementation. +Anything actionable that emerges from this document enters the system +the ordinary way — an SI, W, or P filing judged on its own merits — +never by citation to this note. Provenance: the 2026-07-15 side-session +thought experiment split off from the SI-32/SI-40 ratification cycle +(operator + agent), written down so the reasoning can be attacked +rather than remembered. Claims are numbered **C1–C12** so critique can +cite them; the intended review protocol is the house battery shape — +fresh-context readers returning a row-per-claim verdict table +(agree / refute / refine, with the argument), never prose-only +impressions. A refuted claim gets corrected or struck here with the +refutation preserved, in the ADR tradition. + +Grounding documents: `docs/agent-state-fabric-brief.md` (the product +thesis this note must not contradict), `docs/asf-schema-spec.md` (A27 +§5.3 — the storage adversary model; §5.4–§5.5 — authority as event- +derived views; §7 — rules and trust records), `docs/spec-issues.md` +(SI-32, SI-40), `docs/posture-assumptions.md` (P7, P29, the gate +vocabulary). + +--- + +## 1. What problem this architecture is actually an instance of + +**C1 — Prompt injection is the confused deputy problem, restated for +LLMs.** The founding document of capability theory is Hardy's "The +Confused Deputy" (1988): a program acting with its *caller's* ambient +authority is steered by its *input* into actions the caller never +intended. Replace Hardy's compiler with an agent reading a poisoned +webpage, and the billing file with everything the user's session token +can touch: the attack is unchanged. Forty years of object-capability +work (KeyKOS, EROS, the E language, Miller's *Robust Composition*; +credential form: Google's macaroons, 2014, the direct ancestor of the +§5 caveat grammar) built the answer — no ambient authority; rights are +explicit, attenuable, and travel with the request — and never found a +mainstream workload that would pay the compatibility cost. C1's +consequence: ASF is not a reaction to this year's agent-safety +discourse; it is the object-capability tradition applied to delegation, +arriving with the workload that finally demands it. + +**C2 — ASF's addition to the ocap tradition is consequence, not +authority.** Object capabilities bound what *can happen*. They say +nothing about what what-happened *costs* — ocap has no undo. ASF's +synthesis is to pair the authority bound (capabilities + caveats) with +a consequence bound (content-addressed snapshots + coherent revert + +reversibility classes), and then couple them: evidence that outcomes +were recoverable compiles — through human ratification — into wider +standing authority (§7, the ratchets). The third leg, behavior-version +pinning (trust is evidence about a *specific* behavior; mutation drops +grants to escalation), addresses something the ocap tradition never had +to face, because its subjects were programs-as-artifacts, not evolving +learners. The brief claims the pinning as novel; this note claims the +*triple* — authority bound, consequence bound, evidence-coupled — as +the theoretical identity of the fabric. + +## 2. The substrate anchoring + +**C3 — POSIX's finest durable principal is the uid, and this single +fact generates most of the fabric's hard security problems.** Read +A27's tier model through this lens. T2 ("no sequence of pathname checks +can win; the honest answer is OS-enforced exclusion") exists because +once two processes share a uid, the kernel offers no boundary between +them: every pathname race, hardlink pre-plant, descriptor survival, and +"the approval socket is reachable by granted hands" (P7, P5) is +downstream of the kernel being unable to say *this process acts under +manifest X, that one under manifest Y*. The same fact shapes the +dogfooding hygiene rules (two surfaces by convention), the T1 trust-root +residual (keys readable by anything wearing the uid), and W-4's whole +reason to exist. + +**C4 — The container ecosystem is subtractive security; the caveat +grammar is additive; the adapter between them is where the failure +modes live.** Namespaces, chroot, seccomp, cgroups, Landlock: each +starts from a process born with full ambient authority and carves +pieces away. Subtractive security fails open by construction — the +recurring container CVE is "we forgot to carve away X." Capability +discipline is the additive inverse — born with nothing, handed specific +rights — and fails closed by construction. ASF's authority model is +additive (unknown dimensions fail closed; attenuation is subset-only), +but it runs on a subtractive substrate, so every enforcement claim +bottoms out in an emulation layer (deny rules today, W-4 containment +at graduation) whose job is to fake an additive boundary out of +subtractive parts. A27.4's standing sentence — "holds under COOP; +requires W-4 containment at G-ADVERSARIAL" — is the honest label on +that adapter. + +**C5 — Manifest-as-principal is the kernel-shaped hole.** The one +genuinely missing operating-system concept, reduced from the "AI-native +OS" intuition: processes born bound to a delegation manifest, with +authority attenuating at spawn, the uid demoted to an accounting +detail, and the reference monitor evaluating caveats instead of mode +bits. Under manifest-as-principal, A27's T2 tier dissolves *by +construction* (there is no "same principal" between agent and broker to +race within), C2's approval surface is enforced the way memory +protection is, and "the agent never holds the real key" stops being an +architectural achievement and becomes how the machine works. Nearly +everything else the intuition wants already ships as parts: CoW state +(btrfs/ZFS/overlayfs; composefs + fs-verity is a content-addressed, +integrity-verified store as a mount type), additive scoping primitives +(Landlock, 5.13+), measured behavior (IMA/EVM, dm-verity), ambient +tracing (eBPF, auditd), and even C2's ancestor — the secure attention +key, the unfakeable-dialog invariant shipped since Windows NT (1993). +The distro is ~80% assembly; the principal is the ~20% that is real +kernel work. + +## 3. Publication theory (what the SI-40 exploration generalized) + +**C6 — For publication into a substrate with unmediated concurrent +writers, the solution space is exactly three families.** (1) +*Exclusion*: lock writers out during publish — foreclosed for +shared-state stores because unmediated human access is the product +thesis (brief §2; D32-4). (2) *Detection at the write boundary*: +per-entry compare-and-capture — race-narrowing forever, protocol-heavy; +it fights the substrate. (3) *Indirection*: never overwrite in place; +publish as one atomic namespace transition; retain the superseded +state. Every mature storage system chose family 3 (MVCC, git's +immutable objects + atomic ref update, LSM trees, CoW filesystems, +symlink-flip deploys). + +**C7 — The load-bearing mechanism in family 3 is retention, not +snapshotting — and the snapshot-at-a-point variant is refuted.** A +snapshot taken at any fixed point (gate-lock acquisition, apply start) +cannot preserve an edit that postdates it, and the SI-40 edit postdates +the prepare check by definition; the snapshot captures exactly the +state that was never in danger. (This variant was independently refuted +by this exploration's first pass and by external review of the SI-40 +filing — the convergence is recorded because the wrong variant is the +intuitive one.) What works: the atomic swap makes every concurrent +edit's fate *well-defined* — before the swap it lands in the retained +outgoing state (kept, diffed, CAS-ingested, attributed); after, it is +ordinary drift on the new live state. Nothing falls between, because +there is no between. "Attributed, never lost" achieved by construction +rather than by detection. The residual is the descriptor tax: an open +fd can still write into retained/unlinked state post-swap — a loss mode +the in-place design already carries today, socially mitigated by +editors' own file-changed detection, removable only by families 1 or +full mediation. + +**C8 — Publication guarantees should be stated as properties with +per-substrate profiles, never as primitives.** The property: *one +atomic namespace transition; superseded state retained until diffed, +captured, and attributed*. The bindings form a ladder discovered at +store registration and recorded ledger-visibly (the durable-commit +profile pattern): exchange-rename (Linux `renameat2(RENAME_EXCHANGE)`, +2014 — ext4/btrfs/xfs/tmpfs; Darwin `renamex_np(RENAME_SWAP)` is the +later port), symlink-flip (pure POSIX, NFS-safe), journaled two-rename +(universal floor; non-atomic but fail-visible and recoverable through +the existing recovery grammar). Staging recipe: unchanged entries +hardlink from live (inode/mtime preservation; edits ride through); +changed entries reflink from CAS (`FICLONE`/`clonefile` — independent +inodes, so no writable path into the CAS; plain copy as floor). +Retention under this recipe is nearly free (unique bytes ≈ old versions +of changed entries, already CAS-resident from prepare) and +reconciliation is O(changed), not O(tree). + +**C9 — Substrates grade up, and the local POSIX directory is the +floor, not the model.** The same store contract degrades or dissolves +by substrate: raw POSIX dir (families 2/3 emulated in userspace) → CoW +filesystem (family 3 native; hosted infrastructure can simply provision +it — zero-friction constrains the *user's* machine, not the product's +cloud) → branch-native platform (Tier-2 stores publish through the +platform's own atomic branch operation; the SI-40 window mostly does +not exist there) → fabric-served view (every write mediated and +attributed; T3 dissolves). The last rung is deliberately not taken for +user machines: it is the closed-world assumption wearing a mount point, +and brief §2 bets the market on its negation. Corollary, double-edged: +hosted CoW infrastructure also makes *whole-home rollback* a +one-command accident — the exact coherent-suffix-regression case A27 +assigns to the external anchor — so the same substrate that solves +publication sharpens the case that layer-2 anchoring is a hard +G-PRODUCTION requirement. + +## 4. The endgame ladder and why the timing is not romantic + +**C10 — Agent fleets are the first workload class in decades with no +POSIX loyalty at the trust boundary.** Capability operating systems +(KeyKOS → EROS → Capsicum → seL4 → Fuchsia) lost to compatibility +economics, not to refutation: nobody rewrites the world's software for +a better security model. Agents change the economics selectively: the +*trust boundary* around an agent has no legacy-software constituency, +even though the agent's *toolbox inside* the boundary remains +POSIX-hungry (today's agents live in bash). So the claim is refined, +not naive: POSIX survives indefinitely as the toolbox inside the +sandbox; it is replaceable as the boundary *around* it. That is exactly +the shape of a microVM whose only door is the broker. + +**C11 — The buildable near-term form is a single-purpose agent-runtime +OS, and it is W-4's limit case.** Not a desktop distro for humans; the +Talos/Bottlerocket pattern ("the Kubernetes OS," "the container OS") +applied to delegation: the image an agent's microVM boots — fabric +daemon adjacent to PID 1, every workload manifest-scoped, branches as +subvolumes, Landlock/cgroup profiles compiled from caveats, eBPF trace +feeding the signed substrate, approval surfaces on the host side of the +VM boundary. No new kernel; one patch series (the principal) at most, +assembly otherwise. This is not a departure from the roadmap; it is +W-4 containment matured until the sandbox profile *is* the boot image. +Design discipline that follows today: W-4's emulation interface should +be designed as if it were the future kernel interface, because on this +path it becomes one. + +**C12 — The strategic precedent is Docker/OCI: name the unit, ship the +format; formats outlive assemblers.** Docker invented almost no kernel +mechanism (namespaces 2002–2013, cgroups 2007); it named the container, +shipped an image format, and the format — donated to neutral +governance — outlived Docker's market position. The brief's §9 strategy +(open formats, donate the spec, monetize the runtime) already chose +this side. The endgame corollary: design the manifest, caveat, and +trace schemas so that a kernel *could* enforce them — substrate-free +semantics, posture-bound implementations — and the fabric's formats +become the candidate wire format of whatever agent-OS eventually +exists, whoever builds it. The house discipline already trends this +way (properties with profiles; T2 answered by topology, not syscalls); +this claim just names why it matters beyond tidiness. + +## 5. What this theory does NOT license + +Stated to keep the note honest and the critique aimed: + +- It does not reprioritize anything. The current queue (dogfooding, the + ratchet, W-15a) is where the product lives; this note is a horizon, + not a backlog. A theory note that quietly becomes a roadmap is the + spiral this project just corrected. +- It does not weaken the open-world bet. C5/C11 describe substrates the + product may *provision*; they never justify requiring one from a + user's laptop. +- It does not claim the fabric needs an OS to be valuable. The wedge + thesis (userspace proxy, drop-in, vendor-free) is unchanged; the + ladder is one-directional option value. + +## 6. Falsifiers and open weaknesses (attack here first) + +- **F1 (vs C10):** if agent *authority* patterns turn out to require + deep POSIX semantics at the boundary (not just inside it) — e.g., + tool ecosystems that structurally resist brokered mediation — the + boundary-replaceability claim fails and the adapter (C4) is permanent. +- **F2 (vs C2):** if consequence-bounding does not actually compile + into delegation confidence — i.e., dogfooding shows users do not + widen authority even with clean recoverable histories — the coupling + thesis is decoration and the product is "just backup." +- **F3 (vs C6/C8):** if reconciliation costs blow up on real stores + (huge vaults, high churn, sync-daemon interference), family 3's + "nearly free" claim degrades and family 2 re-enters. +- **F4 (vs C5):** manifest-as-principal may reintroduce the identity + problem one level down: cross-host principals need a trust root, and + the anchor problem (spec §6.2 layer 2) recurs inside the kernel + boundary rather than being solved by it. +- **F5 (vs C12):** the formats-win analogy fails if a hyperscaler ships + a vertically integrated agent-OS with proprietary formats *before* + neutral formats accumulate network effects — the brief's timing risk, + restated at the substrate layer. +- **F6 (vs C1/C2):** the ocap framing may flatter the design: caveat + grammars are coarser than ocap's object granularity, and the judge/ + escalation layer is an admission that mechanical authority alone + cannot express intent. If the interesting delegation decisions all + land in the judgment layer, the capability lineage is marketing, not + mechanism. + +## 7. Reading lineage + +Hardy, "The Confused Deputy" (1988) · Miller, *Robust Composition* +(2006) and the E language · KeyKOS / EROS · Watson et al., Capsicum +(USENIX Security 2010) · seL4 (verified capability microkernel) · +Fuchsia/Zircon (handles, no ambient authority) · Birgisson et al., +"Macaroons" (NDSS 2014) · Landlock (Linux 5.13) · IMA/EVM, dm-verity, +fs-verity, composefs · NixOS / ostree (immutable, generation-based +system state) · Talos, Bottlerocket (single-purpose OS pattern) · +`renameat2(2)` / `renamex_np(2)` · Firecracker (microVM isolation) · +OCI (the format-outlives-assembler precedent). diff --git a/docs/testing-theory.md b/docs/testing-theory.md index 68fb22a..886acff 100644 --- a/docs/testing-theory.md +++ b/docs/testing-theory.md @@ -527,6 +527,32 @@ contract lanes land with W-15, as do RF-40's (i)/(j)/(l)/(n)/(o) and RF-35's (k)/(m); (p)/(q) land with RF-37's W-22 lane, and (d)/(h) with RF-36's. +**G14. A27 tier-claim negatives outstanding after the SI-32 ratification +(2026-07-15).** (a) **Unsigned-index redirect** (A27.2/RF-41): a +substituted `fabric.db` meta row (store path, `substrate_span`) must not +redirect any authority-bearing read — no effect, loud denial; lands with +RF-41's carrier (W-15a/b — the W-15 split in flight in PR #52), +completing the READBACK-VERIFY contract the ADR's validation plan names. +Its would-be members exist today under other registrations +(`cas_get_rehashes_content_before_returning_it`, +`row_substitution_cannot_change_what_a_hash_resolves_to`, +`w14_reopen_rejects_mistyped_or_unsafe_recovery_journal_before_mutation`); +the contract id is minted when the lane lands. +(b) **Registered-SQLite symlink capture** (A27.3/RF-42): a symlinked +registered store fails capture with no effect — the existing +`fabric_home_and_database_symlinks_cannot_redirect_reopen_or_initialization` +covers `fabric.db` only; lands with RF-42's mechanical lane. (c) +**Excluded-dir uniformity through `sync_store`** (A27.3/RF-43): `.git` +untouched by the durability walk; same lane. (d) **Deliberate absence, +recorded so it is never "fixed":** T2 claims carry NO race-closure +negative — their conformance is the documented COOP dependency plus +W-4's future topology (A27.4); a test asserting the same-inode recheck +closes the race would assert a guarantee enforcement does not provide +(the SI-10/A21 lie surface). A27.1's core pair already exists as the +`RESTORE-INTEGRITY` contract (blob mutated after prepare cannot reach +the live store); the SI-40 preservation protocol's negatives are +designed with SI-40, not before. + ## Automation lanes | Lane | Purpose |