From 01904b8f693332552fcbe6bb303cdea7bc6763a2 Mon Sep 17 00:00:00 2001 From: fred Date: Thu, 27 Aug 2026 10:39:32 +0000 Subject: [PATCH] docs: custody pointer and consent schema contract (S2 contract 7) (#1440) --- docs/requirements/custody-schema.md | 1742 +++++++++++++++++++++++++++ 1 file changed, 1742 insertions(+) create mode 100644 docs/requirements/custody-schema.md diff --git a/docs/requirements/custody-schema.md b/docs/requirements/custody-schema.md new file mode 100644 index 00000000..6308a17f --- /dev/null +++ b/docs/requirements/custody-schema.md @@ -0,0 +1,1742 @@ +# Data Custody Contract — Pointer and Consent Schema (D14) + +Status: DRAFT — awaiting ratification (webui-audit S2, contract 7 of 9). +Authority: PRD D14/D6 (Part I §7) — sensitive profile categories live in +the **user's own brain ONLY**; PostgreSQL holds structural data, consent +records, and pointers — never the content; "user data does not leak" is +enforced by architecture, not policy. The Standalone split is a MAY with +a recommended-default (kept for conversion forward-compatibility, D3). +PRD D4 (Part I §6) — profile answers feed `USER.md` and/or the user's +data store subject to the custody rule; connectors carry granular +agentic-access consent. PRD D6 — estate brains hold operational records; +only product-relevant material migrates into repository docs. + +Revision 2 (sol r1 findings F1–F6): the registry is now a full profile +classifier that can represent non-sensitive rows, with an exact row +shape, versioning, refusal semantics for unknown keys, and transition +rules (F1); the consent model gains a concrete grantee reference model, +an active-row uniqueness constraint, append-only re-grant semantics, +and explicit mutation authority (F2); the pointer schema names its +exact column set, binds the one-pointer rule to a database constraint, +defines the `brain_ref` grammar and owner-bound resolution, replaces +the unkeyed hash with a keyed construction, and bounds orphan repair +(F3); the witnesses import the hierarchy contract's §6.2 column +allowlist and §6.3-style closed route inventories, add negative +controls and a column-type allowlist, and cover every binding rule +(F4); the Standalone physical split, both valid layouts, the election +record, and v1 phase timing are defined consistently with the wizard +and mode-conversion contracts (F5); drafting additions are disclosed in +§8 and the ruling request is one sentence with one decision (F6). + +Revision 3 (sol r2 findings F1–F9): reclassification gains a +custody-config write fence (shared/exclusive row lock) so no +concurrent writer can commit a stale-version route, a declared +supported-version refusal, and witnesses for both transition +directions (F1). The grantee reference becomes three per-type FK +columns with a generated discriminant, real referent tables for +`connector` and `feature` (contract-owned, empty at v1), the +enrolled-agent binding named to the rank-4 family's table, and +revoked-row immutability enforced by a database trigger (F2). The +content hash is domain-separated per (user, category, locator) with +canonical bytes, an embedded key id, and a rotation rule; the audit FK +referent and the non-sensitive profile table (`profile_answers`) are +named; tree-object resolution prohibits symlink escape (F3). The §7 +route witnesses import hierarchy §6.3's full prong set, scan +`plugins/`, extend the no-content probe to every platform table, and +add the missing negatives (F4). `custody_config` is singleton by +constraint (F5). §8 discloses the previously omitted policies (F6). +Mutation authority is split into two disjoint actor predicates — +subject mutations and the precondition-checked system auto-revocation +(F7). Hash mismatch gets a representable pointer state with a defined +terminal outcome (F8). Managed deletion is an ordered git-then-database +protocol with fault-injection witnesses at both interruption points, +replacing the impossible cross-store transaction (F9). + +Revision 4 (sol r3 findings F1/F2/F4/F6/F7/F9 residual, F10/F11 new): +the write fence pins `READ COMMITTED` isolation and lock-first +statement order for the reclassification migration, and the +supported-version declaration becomes a named source symbol checked +under the same lock as each decision (F1). The user foreign keys +adopt the identity store's actual key type — `users.id` is `text` +(BetterAuth identifiers), so every `user_id` column is `text` (F10). +The agent referent binds to the live platform `agents` table by +symbol, since `identity-lifecycle.md` defines no enrollment surface; +an amendment re-binds if one arrives (F2). The system auto-revocation +predicate is withdrawn entirely: grantee cessation is handled +structurally by `ON DELETE CASCADE` from the grantee referent (as +subject cessation already is from `users`), default-deny ends access, +and the audit event table keeps the durable history — which removes +the trigger/actor contradiction rather than patching it (F2, F7). +Sensitive-write routes gain a static write-set closure witness so a +derived representation cannot land in any generic platform table +(F4). Managed deletion step 2 becomes a compare-and-delete on the +hash removed in step 1, witnessed against a concurrent rewrite (F9). +Ordinary sensitive writes get an ordered content-first protocol with +deterministic locators, ack-after-pointer-commit, idempotent retry, +and fault-injection witnesses (F11). §8 discloses every rule above +plus the previously undisclosed §4.3 committed-tree/symlink rule +(F6). + +Revision 5 (sol r4 findings F9/F11 residual, F12/F13 new): the +pointer row gains a monotonic `generation` counter bumped by every +upsert, and managed-deletion step 2 conditions on the generation +recorded in step 1 instead of the content hash, closing the +identical-content ABA in which a byte-identical rewrite reproduced +the old hash and a stale deletion unpointed fresh content (F9). The +ordered-write protocol gains a contract-defined locator scheme — +injective over (category, slot), with the slot set fixed to the +single value `answer` at version 1 — and a per-locator advisory lock +spanning both steps, so same-locator writers are serialized and a +completed write always leaves the pointer hash describing the +current blob; §7.16 adds the concurrent-writer, +crash-before-acknowledgment, stale-retry, and locator-injectivity +witnesses (F11). Cascaded consent deletion now emits a semantic +cessation event in the same transaction as the referent deletion, +written by a trigger on the consent table, so the audit trail +records when and why each active grant ended, not only that it began +(F12). The stale §3.6/§5.2 cross-references and the "first +predicate" remnant are corrected (F13). + +Revision 6 (sol r5 residuals F9/F12 + F14): the deletion and repair +token becomes the PAIR (row `id`, `generation`) — the uuid primary +key is minted fresh on every insert and never reused, so it is the +row-incarnation identity the bare counter lacked; a stale +compare-and-delete can no longer match a row reincarnated at +generation 1 after an orphan-repair delete, and §7.10 adds the +delete/reinsert ABA control (F9). The §3.7 locator lock now covers +EVERY blob/pointer mutator for a locator, not only ordinary writers: +managed deletion (§4.7) and reconciliation repair (§4.5) acquire the +same advisory lock across their observe-and-mutate spans, so the +reverse interleaving — a deleter destroying a blob a locked writer +has committed but not yet pointed — is unschedulable while the +tokens keep the crash-window (lock released by session death) +harmless; §7.10 adds the reverse interleaving and its lock-removed +control (F14). The cessation-event snapshot gains the deleted +consent row's `id` and its `user_id`, so events from two subjects' +grants to one ceasing grantee are attributable to their exact rows +and subjects, witnessed with a two-subject case (F12). + +Revision 7 (sol r6 F15): the reconciliation try-acquire no longer +discharges a locator for the cycle — a failed try-acquire QUEUES the +locator within the same cycle, and before the cycle completes the +sweep revisits every queued locator with a blocking acquire, running +the same locked observation and token-conditioned repair. A +triggered reconciliation cycle is complete only when every in-scope +pointer has been processed under its locator lock, so the §4.5 +one-cycle orphan bound holds unconditionally on lock contention; +the retry terminates because every §3.7 hold is transaction- or +session-scoped and session end releases the lock automatically. +§7.10 adds the lock-held contention control (sweep pass skips +without blocking; cycle completion waits for the queued repair; +the orphan is deleted before the cycle reports completion), its +repeated-contention variant, and a third static control failing an +implementation that defers a skipped locator to a later cycle +(F15). + +Revision 8 (sol r7 residual F15): the one-cycle bound gains its +missing termination proof. §3.7 adds two liveness rules for the +locator lock: a declared finite HOLD DEADLINE, enforced with +session-level timeout mechanisms on every lock-holding path — a +live holder that stalls past the deadline is terminated, session +end releases the lock, and the stalled-holder schedule collapses +into the already-harmless crash window — and an arrival-order +GRANT rule, so a blocked waiter's wait is bounded by the current +holder and the waiters already ahead, never by later arrivals. +§4.5's blocking retry therefore terminates within a wall-clock +bound fixed when it starts waiting, and the implementing PR +derives and declares a maximum cycle duration whose exceedance is +surfaced as an error, never a silent hang. §7.10 adds the +hold-deadline control (a non-cooperating stalled holder is +terminated at the deadline and the orphan is still deleted before +cycle completion), the grant-order control (a stream of later +contenders cannot starve the queued repair, and the declared +cycle-duration limit is exercised as a checked bound), and a +fourth static control failing any lock-holding path without the +declared deadline (F15). + +Revision 9 (sol r8 residual F15 + new F16): the hold deadline +becomes independently enforceable and the external store gains a +fence. §3.7's deadline is now enforced by a deadline supervisor +whose liveness does not depend on the stalled holder — the +database's timeout mechanisms are defense in depth, not the +enforcement path, since step 1's brain commit is not a database +statement — and every brain mutation performed under a locator +lock is FENCED: applied conditionally on the locator's brain state +still equaling the value recorded at lock acquisition, so a +terminated holder's in-flight external operation, resuming after +the lock has passed on, is refused atomically and can never remove +or replace a later holder's committed content (F16). §4.5's +deferred repair now RESERVES its queue place at the moment its +try-acquire fails, fixing the predecessor set at skip time and +closing the window between skip and queue entry; the cycle maximum +becomes the computed formula `M = S + Σᵢ(D·(Qᵢ+1)+R)` over +snapshots taken at or before each reservation, fixed when the last +reservation is placed and never enlarged; and exceedance is an +explicit transition — the cycle reports failure, never completion, +withdraws its pending reservations, and persists a durable +carry-over set the next cycle processes first — so the orphan +bound binds every completing cycle and every non-completing path +is a surfaced error, and the grant-order rule must be bound to a +documented mechanism guarantee or an application FIFO queue, not +assumed (F15). §7.10 adds two external-step deadline controls (a +stalled managed deletion and a stalled ordinary-writer brain +commit, each terminated by the supervisor mid-external-operation, +resumed after a fresh holder's acknowledged write, and asserted +refused by the fence), the reservation control, the exceedance +control, and static controls binding the grant-order guarantee and +the fenced deletion path; §7.16 adds the static fenced-commit +control (F15, F16). + +Revision 10 (sol r9 residual F16 + new F17): the brain fence becomes +ABA-resistant. §3.7's fence value is now a per-locator fence EPOCH +with two invariants: every successful brain mutation mints a fresh, +never-reused value — byte-identical recommits and transitions to +absence included, absence being a unique tombstone state rather than +a recurrable bare-missing-path value — and every holder performs a +FENCE-ADVANCE immediately after lock acquisition, minting a fresh +epoch before its first brain observation or mutation, so every +earlier holder's recorded fence is invalidated at handoff, before the +new holder relies on exclusivity. A stale external tail is therefore +refused unconditionally: neither a byte-identical recommit, nor a +return to absence, nor resumption before the new holder's first +mutation can re-present a consumed epoch (F16). The live holder's +fence-pass now follows from handoff invalidation rather than from +lock possession, and a defect-only refusal has a specified +abort-and-surface transition. §4.5 repair performs the advance before +the absence observation it relies on. The unqualified distinct-locator +no-contention claim is narrowed to locator locks: a shared atomic +update point may cost swap-loop retries across locators but never +invalidates a distinct locator's condition — a liveness cost, never a +correctness coupling (F17). §7.10 adds two ABA fence controls +(byte-identical stale deletion; absence→write→delete→stale-writer +resurrection), a handoff-invalidation control with a +repair-observation variant, and a static fence-epoch-invariant +control; §7.16's static fenced-commit control is restated over the +epoch (F16, F17). + +Revision 11 (sol r10 residual F16 + new F18): the fence-advance +itself is fenced against an older acquisition. §3.7 adds a +per-locator ACQUISITION TOKEN minted by the lock authority at every +lock grant — fresh, strictly increasing, existing before the +holder's first brain operation and carried unchanged through every +brain operation and swap-loop retry of that acquisition — and the +brain store records the greatest accepted token atomically with the +locator's epoch and content state, refusing any brain mutation, +the fence-advance included, that carries an older token. The +advance's acceptance condition is exactly this comparison (an epoch +condition would be circular, an unconditional mint would let a +terminated holder's delayed advance supersede a later holder's); a +refused advance mints nothing and returns no usable fence, with a +mandatory abort-release-surface transition that is an EXPECTED +outcome after deadline supersession, not a defect. Revision 10's +"refused unconditionally" claim is precisified to an exact +boundary: after the newer holder's advance linearizes, every +older-acquisition operation — its delayed advance included — is +refused; a stale content mutation linearizing BEFORE that advance +serializes entirely before anything the new holder relies on (the +advance preserves the content state it finds and mints after it), +indistinguishable from completion before the previous holder's +lock release — the defined legal outcome. The pre-step-1 +interruption predicates in §3.7, §4.7, and the §7.16 fault +injection are restated exactly — content presence, content bytes, +and database rows unchanged, the advance's epoch mint and token +recording expressly permitted — resolving their contradiction with +the mandatory advance (F18). §7.10 adds a stale-advance refusal +control (fresh-writer and repair-observation variants) and a +pre-advance linearization control, and extends the fence-invariant +static control with the token invariants; §7.16's static +fenced-commit control carries the token (F16, F18). + +Revision 12 (sol r11 minors F18 residual, F19, F20): three +exactness corrections to the revision-11 mechanism, no behavioral +change. §3.7's token comparison is made exact at both ends: +strictly less-than refuses (an already-recorded token re-presented +by a swap-loop retry or a later mutation of the same acquisition is +accepted), and an absent recorded token compares lower than every +minted token, so a virgin locator's first advance is accepted and +atomically creates the token record — with a §7.10 virgin-locator +control and a delayed-first-grant race variant (F19). §7.10's +remaining managed-deletion fault predicate ("both stores +unchanged") is restated as the exact §4.7 predicate — content +presence, content bytes, and database rows unchanged, the +advance's epoch mint and token recording expressly permitted +(F18 closed). The pre-advance-linearization outcome no longer +assigns the (`id`, `generation`) pair to every stale operation: +the §3.7 boundary text and the §7.10 control now distinguish the +database halves — deletion's and repair's step 2 stays +pair-conditioned, while a terminated ordinary writer performs no +pointer upsert and can continue only through a fresh acquisition +under a new token (F20). + +Revision 13 (sol r12 confirm, F20 residual): revision 12's +ordinary-writer branch wrongly assigned the terminated writer's +committed-but-unpointed content to §4.5 repair — §4.5 enumerates +pointers and repairs the opposite state (a pointer whose content +is absent); unpointed content is outside its input. The §3.7 +boundary text and the §7.10 control now state what §3.7 and §7.16 +already specified: the state is inert (no pointer references it, +no read path serves it, no repair transition exists for it — a +§4.5 cycle run against it changes nothing), the terminated run is +never acknowledged, and convergence comes only through the +client's retry as a fresh acquisition under a new token, +recommitting and completing the pointer upsert. No behavioral +change; the wrong repair attribution is withdrawn. + +This contract binds the profile-category registry (§2), the custody +placement rule (§3), the pointer schema (§4), the consent schema and its +evaluation (§5), mode application (§6), witnesses (§7), and disclosed +drafting additions (§8). It defines schemas and placement; wizard step +flow stays with contract 3 (`onboarding-wizard.md`), mode and conversion +with contract 6 (`mode-conversion.md`), identity with +`identity-lifecycle.md`, tool mapping with `tool-gateway-mapping.md`. + +## 1. Definitions + +1. **User brain**: the git-tracked per-user data store. Its two valid + Standalone layouts are defined in §6.2; in Enterprise it is the + user's own brain repository. +2. **Sensitive content**: any profile answer or derived text in a §2 + category classified `sensitive`. +3. **Pointer**: a database record referencing sensitive content that + lives in a user brain, carrying no content (§4). +4. **Grantee**: a non-subject principal that may be granted access to a + user's sensitive content: a platform agent (a row of the `agents` + table, §5.2), a connector, or a platform feature (§5.2). +5. **Consent record**: a database record granting one grantee access to + one category of one user's data (§5). +6. **Registry version**: the monotonically increasing integer + identifying the active state of the §2 registry. +7. **Physical split** (Standalone): sensitive user content living in a + per-user brain repository separate from the estate mosaic-brain, as + opposed to the unsplit layout where it lives in a dedicated + user-files subtree of the single mosaic-brain (§6.2). +8. **Audit event table**: the single platform table into which the + hierarchy contract (contract 1, `hierarchy-schema.md` §5.2) commits + its semantic audit events. No sibling contract names the physical + table, so this contract binds it by identity, not by name: every + `audit_event_id` column in §3.5, §4.1, §5.1, and §6.3 is a foreign + key to that table, the implementing PR that creates the audit store + binds these FKs to it, and the §7.1 witness asserts that each such + FK references the same physical table the contract-1 audit write + path inserts into. + +## 2. Profile-category registry + +1. **Model.** The registry is the single classifier for every profile + category, closed and versioned, in the platform database. Table + `profile_category_registry`, columns exactly: + + | Column | Type | Constraints | + | ---------------- | ----------- | ---------------------------------------------------- | + | `category_key` | text | primary key | + | `classification` | text | NOT NULL, CHECK in (`sensitive`, `non-sensitive`) | + | `since_version` | integer | NOT NULL (registry version that introduced this row) | + | `created_at` | timestamptz | NOT NULL | + + The current registry version is a single integer held in + `custody_config` (§6.3). Rows are added or reclassified ONLY by + amendment to this contract shipped as a migration that bumps the + registry version; no runtime write path may insert, update, or + delete registry rows. + +2. **Initial registry (version 1).** Drawn from the D4 profile step and + D14's "disabilities, family, communication style, and similar": + + | Category key | Classification | Covers | + | ------------------------- | -------------------------- | ------------------------------------------------------------------- | + | `disabilities` | sensitive | disabilities including ADHD/autism/PDA/vision | + | `family-social` | sensitive | family, pets, friends | + | `communication-style` | sensitive | desired agent communication style, voice-matching interview product | + | `personal-interests` | sensitive | hobbies, likes/dislikes | + | `connector-content` | sensitive | email and drive content reached through user connectors | + | `professional-background` | non-sensitive (per ruling) | professional background summary used for agent configuration | + | `education` | non-sensitive (per ruling) | education summary used for agent configuration | + + The `Covers` column is contract documentation, not a database + column. Account identity fields (email, name, credentials) are + identity-contract data, not profile custody data, and have no + registry row. + +3. **Unknown category — refusal.** A profile write naming a + `category_key` with no registry row is REFUSED with an explicit + error. Nothing is stored anywhere, no registry row is auto-added + (§2.1), and no pointer is created (so the §4.1 foreign key is never + asked to reference a missing row). Fail-closed means refusal, not + silent routing. +4. **Reclassification transitions.** A reclassification ships as a + contract amendment plus migration that bumps the registry version. + Non-sensitive → sensitive: the same migration moves every existing + relational value for that category into its owning user's brain, + creates the pointers, and deletes the relational values, all before + the new version activates. Sensitive → non-sensitive: existing + brain content and pointers remain valid and are never automatically + materialized into the database; only writes evaluated after the new + version activates route relationally. +5. **Unreadable registry — refusal.** If the registry or its version + cannot be read at decision time, every routing and consent decision + that depends on it is refused. There is no cached-default or + assume-sensitive fallback that performs a write. +6. **Write fence.** Every profile-write transaction reads the current + registry version by taking a **shared lock** on the `custody_config` + row (`SELECT ... FOR SHARE`) inside the same database transaction + that performs the write, and routes by the version so read. A + reclassification migration takes an **exclusive lock** on that row + (`SELECT ... FOR UPDATE`) before its scan and holds it through the + value moves and the version bump. Isolation and statement order + are part of the fence, not implementation freedom: both the + profile-write transaction and the migration transaction run at + `READ COMMITTED`, and the migration's `SELECT ... FOR UPDATE` is + the FIRST statement of its transaction — no read precedes it, so + the migration establishes no snapshot before it holds the lock. + Under `READ COMMITTED` each subsequent statement takes a fresh + snapshot, so the migration's scan — which runs only after the lock + is granted — sees every row committed by every `FOR SHARE` holder + that finished before the lock was granted. Consequently a + concurrent writer either commits before the lock is granted — its + value is then visible to and swept by the scan — or blocks until + the migration commits and then routes by the new version. No + interleaving exists in which a value routed under the old version + escapes the scan, including the writer-commits-while-migration- + waits interleaving (witness §7.6g). +7. **Supported version.** Each release declares the exact registry + version its code implements as an exported compile-time constant + in the custody module of the shared schema package; the + implementing PR names the symbol and §7.6(h) pins it by symbol + identity, so the declaration is source, not configuration. Every + profile routing decision and every consent decision reads + `custody_config.registry_version` under a `SELECT ... FOR SHARE` + on the `custody_config` row inside the same database transaction + as the decision (write decisions share the §2.6 fence transaction; + read-only consent evaluations take the shared lock in their own + transaction), so the supported-version comparison and the decision + it gates use one atomically read version. If the declared version + does not equal the version so read, the decision is refused — a + readable but unsupported registry is a refusal, never best-effort + routing under either version (witness §7.6h). + +## 3. Custody placement rule + +1. Sensitive content is written to the owning user's brain ONLY. + PostgreSQL tables MUST NOT store sensitive content in any column — + not as text, not as excerpts or previews, not as encodings, and not + as embeddings or other derived representations that reconstruct + content. +2. The database MAY hold, about sensitive content: the pointer records + of §4, the consent records of §5, the registry of §2, and the + custody configuration of §6.3. Nothing else. +3. Every write path for profile answers routes by the registry: + `sensitive` → brain write + pointer upsert; `non-sensitive` → the + `profile_answers` table (§3.5); unknown → refusal (§2.3). The + routing decision is made server-side from the registry at its + current version, inside the §2.6 write fence; a client-supplied + classification or routing override is ignored. +4. D6 boundary: operational records stay in estate brains and are + linked, not migrated. This contract governs user-profile custody + only and creates no new obligation on estate brains. +5. **Non-sensitive profile table.** Non-sensitive profile answers land + in exactly one declared table, `profile_answers`, columns exactly: + `id` (uuid, primary key), `user_id` (text, NOT NULL, FK → users(id) + ON DELETE CASCADE), `category_key` (text, NOT NULL, FK → + profile_category_registry(category_key)), `value` (text, NOT NULL), + `created_at` and `updated_at` (timestamptz, NOT NULL), and + `audit_event_id` (uuid, NOT NULL, FK → the §1.8 audit event table); + plus UNIQUE (`user_id`, `category_key`). A value may sit in this + table ONLY while its category's registry classification is + `non-sensitive` (§2.4 migrates the rows on reclassification). No + other platform table stores profile answer content. +6. **User-key type binding.** The identity store declares `users.id` + as `text` (BetterAuth identifiers, `packages/db` schema); every + `user_id` column in this contract (§3.5, §4.1, §5.1) is therefore + `text`, matching the referenced key's declared type exactly. The + identifiers are opaque strings, not guaranteed UUIDs; no custody + column re-types, parses, or reformats them. If a sibling contract + ever migrates the user key type, an amendment to this contract + follows it (§7.1 asserts the FK types match the live referenced + columns). +7. **Ordered sensitive-write protocol.** An ordinary sensitive write + spans two stores and is ordered, content first. + + **Locator scheme.** The locator is a pure function + `L(category_key, slot) → brain_ref` declared by the implementing + PR, with three contract-bound properties: it is deterministic (a + retry of the same write resolves to the same path), it is + INJECTIVE — distinct (category_key, slot) pairs map to distinct + `brain_ref` values, witnessed with a collision control (§7.16) — + and it is stable across releases (a scheme change is a contract + amendment with a migration). An **answer slot** is the stable + identifier of one answer's position within a category; at + version 1 every registry category is single-valued (mirroring + §3.5's UNIQUE (`user_id`, `category_key`)), so the slot set is + the single constant `answer`. An amendment introducing a + multi-valued category must extend the slot set while preserving + injectivity. + + **Serialization.** The entire write — both steps — runs under a + per-locator mutual exclusion: before step 1 the writer acquires + an exclusive advisory lock keyed on (`user_id`, `category_key`, + slot) (e.g. a session-scoped `pg_advisory_lock` on a stable hash + of the triple), held until step 2 commits or the writer's session + ends (session end releases it automatically, so a crashed holder + never wedges the locator). Two liveness rules bound this + exclusion. **Hold deadline:** every session that acquires a + locator lock — ordinary writer, managed deleter (§4.7), + reconciliation repair (§4.5) — runs under a declared finite hold + deadline: the implementing PR declares the value and enforces it + through a **deadline supervisor** whose liveness is INDEPENDENT + of the holder — a separate live component, not a timer inside + the possibly-stalled holder process, that observes lock + acquisition times and terminates the database session of any + holder past its deadline. The database's session-level timeout + mechanisms (statement timeout, idle-in-transaction timeout) are + configured as defense in depth but are not the enforcement + path: a statement timeout aborts a statement without ending the + session, an idle-in-transaction timeout fires only on an idle + open transaction, and step 1's brain commit is not a database + statement at all — so a session stalled inside an external + operation is terminated by the supervisor, which no + database-side mechanism replaces. A holder that stalls past the + deadline thus has its session TERMINATED — session end then + releases the lock automatically per the rule above. The + terminated holder's partial DATABASE protocol is exactly the + crash window the tokens already make harmless (§4.5, §4.7); its + possibly still in-flight EXTERNAL operation is made harmless by + the brain fence below. No holder + — live, stalled, or crashed — holds a locator lock longer than + the declared deadline. **Grant order:** conflicting waiters on + one locator lock are granted in arrival order — a requester that + starts waiting after another never acquires before it. The + implementing PR MUST bind this property to a mechanism, not + assume it: either it cites the pinned advisory-lock mechanism's + documented queue-order grant guarantee, or, absent such a + documented guarantee, it routes every conflicting locator-lock + acquisition through an application-level FIFO queue whose order + invariant is itself part of the implementation; the binding is + checked by a §7.10 static control and the property is exercised + dynamically (§7.10) — so a blocked waiter's wait is bounded by the + current holder's remaining deadline plus the deadline-bounded + holds of the waiters already ahead of it, never by later + arrivals. Writers to one locator are therefore + totally ordered; writes to distinct locators do not contend FOR + LOCATOR LOCKS. The brain store's atomic update point MAY be + shared across locators (a single head compare-and-swap in a + git-backed store serializes physically unrelated writes): losing + that swap costs a retry of the swap loop, and the retry + re-validates only the writer's own per-locator condition (Brain + fence below), which an unrelated locator's mutation never + changes — so cross-locator contention at a shared update point + is a liveness cost, never a correctness coupling. + Because no two same-locator writes interleave, a completed write + always leaves the pointer's `content_hash` describing the blob + currently committed at the locator — the divergent interleaving + (writer A's pointer over writer B's content) is unschedulable, + not merely unlikely. + + **Brain fence.** Deadline termination releases the lock while an + external brain operation started by the terminated holder may + still be in flight; lock release is therefore not, by itself, a + fence for the external store. Two per-locator values fence it: + an **acquisition token** ordering holders and a **fence epoch** + ordering mutations. The acquisition token is minted by the lock + authority at EVERY grant of the locator lock: a fresh, strictly + increasing per-locator value handed to the holder with the + grant — it therefore exists before the holder's first brain + operation, independently of any brain state — and carried + UNCHANGED through every brain operation of that acquisition, + including every retry of a swap loop; a holder never re-mints + or refreshes its token. The fence epoch is a per-locator value + stored in the brain store, atomically readable + and comparable together with the locator's content state, whose + defining property is that every change to it MINTS a fresh value + never before used at that locator. A per-locator monotonic + counter satisfies this, as does a unique commit identity in a + git-backed store; the locator's content BYTES do not — two + byte-identical states at different times are distinct epochs — + and ABSENCE is itself an epoch-bearing state, recorded as a + unique tombstone, never a bare missing path whose "value" can + recur. Three rules bind them. **Minting:** every successful + brain mutation under a locator lock — a content commit, a + byte-identical recommit, a content deletion (transition to + absence) — advances the locator's epoch to a fresh never-reused + value, atomically with the mutation. **Token recording:** the + brain store records, per locator and atomically with the + locator's epoch and content state, the greatest acquisition + token it has accepted; EVERY brain mutation under a locator + lock — the fence-advance below included — presents its holder's + acquisition token, is REFUSED atomically when that token is + less than the recorded value, and records it on acceptance. + The comparison is exact at both ends: it is strictly + less-than — re-presenting the already-recorded token, as a + swap-loop retry or a later mutation of the same acquisition + does, is accepted — and a locator with NO recorded token + compares as lower than every minted token, so a virgin + locator's first presented token is never refused by this + comparison and its acceptance atomically CREATES the token + record; an implementation whose absent-token comparison can + refuse or fail to record the first holder is nonconforming + (witness §7.10). + **Fence-advance at + acquisition:** immediately after acquiring the locator lock, + before its first brain observation or mutation at the locator, + the holder performs a fence-advance — an atomic epoch mint that + leaves the locator's content presence and bytes unchanged — and + records the resulting epoch as its **fence value**; the advance + invalidates every fence recorded by any earlier holder before + the new holder relies on anything it reads or on its + exclusivity. The advance's OWN acceptance condition is exactly + the token comparison — not the epoch (no fence exists for the + acquisition until the advance returns, so an epoch condition + would be circular) and not nothing (an unconditional mint would + let a terminated holder's delayed advance supersede a later + holder's): the token is minted at lock grant, so it exists + before the advance, and grants for one locator are totally + ordered, so the comparison encodes acquisition order. A refused + advance returns NO usable fence: the holder MUST NOT perform + any brain observation or mutation, MUST abort its protocol run + before any later step, release the lock if it still holds it, + and surface the error — and this refusal is an EXPECTED outcome + for an acquisition whose lock was deadline-released and + re-granted before its delayed advance applied, not a defect. + Every other brain mutation performed under a locator + lock — an ordinary write's step-1 content commit (below), a + managed deletion's step-1 content deletion (§4.7) — is applied + conditionally: the brain store applies it only if the locator's + epoch at application time still equals the holder's recorded + fence value AND the token-recording comparison accepts, with + the checks and the application atomic with + respect to other brain mutations (compare-and-swap semantics; an + atomic ref update in a git-backed store satisfies this, with the + per-locator conditions re-validated inside the swap loop + when the swap is taken on a coarser head). A stale operation — + one whose session was terminated and whose lock has passed to a + newer holder — is refused at an exact boundary. Once the newer + holder's fence-advance has linearized, EVERY operation of the + older acquisition is refused: its epoch condition fails (the + advance consumed its epoch, and no sequence of mutations can + re-present a consumed one — whether the intervening history + changed the bytes, recommitted byte-identical content, returned + the locator to absence, or consists of the advance alone), its + token comparison fails, and its own delayed fence-advance is + refused by the token comparison, minting nothing — a superseded + acquisition cannot manufacture a fresh fence. Before the newer + advance linearizes, a stale content mutation whose conditions + still hold MAY linearize first: the advance then finds the + post-mutation state, expressly preserves the content presence + and bytes it finds, mints its epoch after that mutation, and + the new holder's first relied-on observation follows its own + advance — so the stale effect serializes entirely before + anything the new holder relies on, indistinguishable from the + same operation completing before the previous holder's lock + release, an outcome the protocol already defines as legal. What + the stale operation's DATABASE half may then do depends on its + protocol: a managed deletion's or repair's step 2 remains + conditioned on the recorded (`id`, `generation`) pair (§4.5, + §4.7) and cannot unpoint any later row incarnation; an ordinary + write's terminated run performs NO pointer upsert — its + database session is dead, the committed-but-unpointed content + is exactly the inert step-1-to-step-2 interruption state this + section already defines (no pointer references it, no read + path serves it, and no automatic repair transition exists for + it — §4.5 repairs the opposite state, a pointer whose content + is absent), and the write converges only through the unacknowledged + client's retry, which is a NEW lock acquisition under + a fresh token — recommitting the locator and completing the + pointer upsert — never a continuation of the dead run. These are the only two outcomes: a stale + operation either serializes wholly before the new holder's + advance or is refused; it never applies after the advance, and + it cannot remove or replace content committed by any later + holder, whether it resumes microseconds + or hours after the termination. A live holder's own fenced + mutation cannot fail its conditions — not because it holds the + lock, but because handoff invalidation plus minting mean the + only epochs minted since its fence-advance are its own, and + because lock grants are serialized by the lock itself, no + greater acquisition token can be recorded while it holds the + lock; a + holder whose fenced mutation is nonetheless refused (reachable + only under a defect breaching these invariants) MUST abort its + protocol run, release the lock, and surface the error — it + never retries the mutation under its stale fence. Every + brain-mutation path carries the fence and the acquisition + token, and every brain-RELYING + observation path (reconciliation repair's absence observation, + §4.5) performs its fence-advance before the observation it + relies on; a path applying an unfenced or token-free brain + mutation fails the + §7.10 and §7.16 static controls. + + **Steps.** Step 1 commits the content to the owning user's brain + at `L(category_key, slot)` as a fenced mutation (Brain fence + above); step 2, only after step 1 has + committed, upserts the pointer row (insert, or update of the + existing row for that (user, category, brain_ref), incrementing + `generation` per §4.1) in its own database transaction inside the + §2.6 fence. The write is acknowledged to the caller only after + step 2 commits. Interruption before step 1 leaves content + presence, content bytes, and every database row unchanged — the + post-acquisition fence-advance may already have minted an epoch + and recorded an acquisition token, the only brain change the + protocol permits before step 1. Interruption between the steps leaves committed brain + content with no pointer: unpointed content is inert — it lives in + the correct custody store, no read path serves it (reads resolve + through pointers only), and nothing dangles in the database. + Interruption after step 2 commits but before the acknowledgment + reaches the caller leaves the converged state; the retry below + observes it and returns success without corrupting it. The + client's retry — the write was never acknowledged — re-acquires + the locator lock, recommits the same locator, and completes the + pointer upsert, converging to the pointed state; the retry is + idempotent whichever interruption point preceded it. A stale + retry (an earlier write retried after a later same-locator write + completed) also executes under the lock and leaves a consistent + pointed state — same-locator outcomes are last-completed-write + wins, and no completion order can leave the hash describing a + non-current blob. The reverse order (pointer before content) is + forbidden: an ordinary write never creates a pointer whose + content has not committed. Witness §7.16 fault-injects the + interruption points, drives retry convergence, the same-locator + concurrent interleaving, the stale retry, and the injectivity + control. + +## 4. Pointer schema + +1. **Exact columns.** Table `profile_pointers`, columns exactly: + + | Column | Type | Constraints | + | ---------------- | ----------- | ------------------------------------------------------ | + | `id` | uuid | primary key | + | `user_id` | text | NOT NULL, FK → users(id) ON DELETE CASCADE (§3.6) | + | `category_key` | text | NOT NULL, FK → profile_category_registry(category_key) | + | `brain_ref` | text | NOT NULL, CHECK against the §4.3 grammar | + | `content_hash` | text | NOT NULL (§4.4 construction) | + | `generation` | bigint | NOT NULL (1 on insert; every §3.7 upsert increments) | + | `created_at` | timestamptz | NOT NULL | + | `updated_at` | timestamptz | NOT NULL | + | `mismatch_at` | timestamptz | NULL unless the pointer is in the §4.6 mismatch state | + | `audit_event_id` | uuid | NOT NULL, FK → the §1.8 audit event table | + + plus the database constraint UNIQUE (`user_id`, `category_key`, + `brain_ref`) — the one-pointer rule is a constraint, not a + convention. + +2. **Opacity.** `brain_ref` and every other pointer column MUST NOT + embed content or content-derived text (no titles, snippets, or + free-text descriptions). A locator is structural, not descriptive. +3. **`brain_ref` grammar and owner binding.** `brain_ref` is a + normalized repository-relative POSIX path: one or more segments + matching `[A-Za-z0-9][A-Za-z0-9._-]*`, joined by `/`, with no + leading `/`, no empty segment, and no `.` or `..` segment; the + stored form matches + `^[A-Za-z0-9][A-Za-z0-9._-]*(/[A-Za-z0-9][A-Za-z0-9._-]*)*$`. + Resolution ALWAYS roots at the brain owned by the row's `user_id` + (the resolver takes the owner from the row, never from the + locator); the locator carries no repository, host, or user + component, so a cross-user or traversal reference is + unrepresentable, not merely forbidden. Resolution operates on the + brain repository's committed git tree (tree and blob objects), + never through filesystem path lookup: if any segment of the path + resolves to a symbolic link — or to anything other than a tree + (intermediate segments) or a blob (final segment) — resolution is + refused. A symlink therefore cannot redirect a locator outside the + owner's brain (witness §7.8). +4. **`content_hash` construction.** `content_hash` is + `hmac-sha256::` — HMAC-SHA-256, keyed with a platform + integrity key held in the secrets backend and never stored in the + database or any repository, over the length-prefixed concatenation + of: a fixed domain-separation string naming this contract and the + construction version, the row's `user_id`, the row's + `category_key`, the `brain_ref`, and the canonical content bytes. + The canonical content bytes are the exact bytes of the committed + blob the locator resolves to (§4.3) — no normalization. Because + the input is domain-separated per (user, category, locator), equal + answers from different users or categories store different hashes: + the column is neither an offline dictionary oracle (external key) + nor an equality/correlation oracle across rows (domain + separation). `` names the key used; rotation introduces a + new key id for new writes, re-verification accepts any + still-registered key id, and a key is retired only when no stored + hash references it. On read, a hash mismatch refuses the read and + puts the pointer into the §4.6 mismatch state. +5. **Bounded orphan repair.** Pointers are deleted when their content + is deleted; dangling pointers are repaired toward deletion, never + toward re-creating content in the database. Reconciliation for a + user's pointers runs on two triggers: every profile write for that + user, and a periodic sweep whose interval the implementing PR + declares (at most daily). A pointer whose content is absent is + deleted by the next triggered reconciliation cycle that reports + COMPLETION — no orphan survives a completed cycle; a cycle that + cannot complete within its computed maximum fails as an explicit + surfaced error with durable carry-over (below), never silently — + and repair performs no database content write. + Per-pointer repair runs under the §3.7 locator lock: before + observing content absence for a pointer, reconciliation acquires + the pointer's locator lock, performs the §3.7 fence-advance + under repair's own acquisition token — + invalidating any stale in-flight operation's fence BEFORE the + observation repair relies on, so no terminated holder's resuming + mutation — its delayed fence-advance included, refused by the + §3.7 token comparison — can change the locator between repair's + observation and + its DELETE — holds the lock across the observation and the + DELETE, and releases it after. The sweep pass MAY use a + try-acquire so it never blocks mid-pass behind a writer, but a + failed try-acquire defers the locator, never discharges it — and + the deferral RESERVES its place: at the moment the try-acquire + fails, the sweep submits the locator's blocking lock request + (the **reservation point**), so the request enters the lock + queue immediately and its predecessor set — the current holder + plus the waiters already queued at that instant — is fixed at + skip time; contenders arriving afterward are later arrivals + that §3.7 grant order places behind it, and there is no window + between the try-acquire failure and queue entry in which a new + contender can join the predecessor set. The sweep proceeds with + the pass while the reservation waits, and before the cycle + completes it returns to every reserved locator, completes the + blocking acquire, and runs the same locked observation and + token-conditioned repair. A + triggered reconciliation cycle is COMPLETE only when every + in-scope pointer has been processed under its locator lock; + deferral moves work to the end of the cycle, never into a later + one except through the exceedance transition below — which + fails the cycle loudly, never completes it — so the + completed-cycle bound above holds unconditionally on lock + contention. The blocking retry terminates within a WALL-CLOCK + BOUND, not merely eventually: every holder is subject to the + §3.7 hold deadline (a stalled live session is terminated by the + independent supervisor at the deadline and session end releases + the lock — voluntary release is not assumed), and grants follow + §3.7 arrival order (no later requester overtakes the queued + reservation), so the reservation is granted within the current + holder's remaining deadline plus the deadline-bounded holds of + the waiters already queued ahead — a finite quantity fixed at + its reservation point, independent of how many contenders + arrive later. Cycle completion is therefore itself bounded by a + COMPUTED maximum fixed within the cycle: the implementing PR + declares the hold deadline `D`, a sweep-pass bound `S` over the + cycle's scope (the scope is snapshotted when the cycle starts), + and a per-locator repair bound `R` (at most `D`), and computes + the cycle maximum `M = S + Σᵢ (D·(Qᵢ+1) + R)` over the reserved + locators `i`, where `Qᵢ` is the number of waiters queued ahead + of reservation `i` at its reservation point. Every quantity in + the formula is a snapshot taken at or before the corresponding + reservation, so `M` is fully determined when the last + reservation is placed and is NEVER enlarged afterward — queue + growth after a reservation cannot extend it, because later + arrivals are not predecessors. **Exceedance transition.** A + cycle that reaches its computed `M` with reserved locators + unprocessed stops: it withdraws its pending lock reservations, + reports FAILURE — never completion — surfaces the error, and + PERSISTS its unprocessed reserved locators as a durable + CARRY-OVER set. The next triggered reconciliation cycle takes + the carry-over set into its scope FIRST — fresh reservations, + its own computed maximum — before its own sweep. An orphan is + deleted before its cycle reports completion, within that + cycle's computed `M`; the only path past a cycle boundary is + the exceedance transition, an explicit surfaced failure with + durable carry-over — never a silent hang and never a silent + drop. + The repair DELETE is additionally token-conditioned like §4.7 + step 2: it records the row's `id` and `generation` when it + observes the content absent and conditions the DELETE on BOTH — + the uuid `id` is minted per insert and never reused, so it + identifies the row incarnation; `generation` orders upserts + within the incarnation. A concurrent §3.7 write that recommits + the locator between the observation and the delete (bumping + `generation`), and a delete-then-reinsert that reincarnates the + locator's row (fresh `id`, `generation` back at 1), each make + the repair match zero rows — reconciliation never unpoints + content it did not observe absent, under either token + dimension. +6. **Mismatch state.** A failed §4.4 verification stamps the pointer's + `mismatch_at` and the read is refused; every subsequent read of a + pointer with `mismatch_at` set is refused without re-serving + content. Reconciliation (§4.5 triggers) re-verifies each flagged + pointer: verification success clears `mismatch_at`; content absent + → the pointer is deleted (§4.5); persistent mismatch → the pointer + is retained flagged with reads refused — the terminal outcome. The + subject's next successful profile write for that category replaces + the content, recomputes the hash, and clears the flag. Mismatch + handling never deletes brain content and never copies content into + the database. +7. **Managed deletion protocol.** A managed deletion of sensitive + content spans two stores and is an ordered protocol, not a single + transaction. **Lock coverage**: the deleter FIRST acquires the + §3.7 advisory lock on the target locator (`user_id`, + `category_key`, slot) — the same lock ordinary writers hold — + before reading the pointer row or touching either store, and + holds it until step 2 commits or the deleter's session ends. + Every blob/pointer mutator for one locator — ordinary writes + (§3.7), managed deletion, and reconciliation repair (§4.5) — + therefore serializes on one lock: the reverse interleaving, in + which a deleter reads the old pointer and destroys a blob that a + lock-holding writer has committed in its step 1 but not yet + pointed, is unschedulable, because the deleter cannot enter its + protocol while the writer holds the lock. **Steps**: step 1 + commits the content deletion to the user's brain repository as + a FENCED mutation (§3.7 Brain fence — conditioned on the fence + epoch recorded at the deleter's post-acquisition fence-advance, + carrying the deleter's acquisition token, + and itself minting the locator's unique absence tombstone + epoch), recording the target row's + `id` AND `generation` as read after + lock acquisition and before step 1; step 2, only after step 1 + has committed, deletes the pointer row in its own database + transaction as a **compare-and-delete** — the DELETE is + conditioned on the recorded `id` AND `generation`. The token is + this PAIR, not the content hash and not the generation alone, + deliberately: every §3.7 upsert increments `generation` even + when it recommits byte-identical content (whose §4.4 hash is + unchanged), so a same-row rewrite moves the row past the + recorded generation; and the uuid `id` is minted fresh on every + insert and never reused, so a row REINCARNATION — the orphan row + deleted by §4.5 repair, then an ordinary write recreating the + locator with a fresh row whose `generation` is back at 1 — + cannot reproduce the recorded pair even though the counter value + repeats. In every such case step 2 matches zero rows, deletes + nothing, and the fresh content stays pointed: deletion never + removes a pointer row it did not observe. The lock excludes the + live interleavings; the pair token makes the crash windows + harmless — a deleter whose session dies after step 1 releases + the lock (§3.7 rule), and whatever runs afterward, its never-run + step 2 has no effect and a later deletion request is a NEW + protocol run reading current state. A deleter TERMINATED at the + §3.7 hold deadline while its step-1 brain deletion is still in + flight is likewise harmless on the brain side: the in-flight + deletion is fenced, so once a later holder performs its §3.7 + fence-advance — before that holder's first mutation, and + regardless of whether its eventual commit is byte-identical to + the state the deleter recorded — the stale deletion's + conditional application fails and removes nothing: content + committed and acknowledged after the termination cannot be + destroyed by the terminated deleter's resuming operation + (witness §7.10). Interruption before step 1 + commits leaves content presence, content bytes, and every + database row unchanged — the deleter's post-acquisition + fence-advance may already have minted an epoch and recorded an + acquisition token, the only brain change the protocol permits + before step 1. Interruption between the + steps leaves a dangling pointer, which §4.5 repairs toward + deletion within one reconciliation cycle. At no point does any + compensation write content into the database (witness §7.10, + including both concurrent-rewrite interleavings — changed + content and identical content — the delete/reinsert + reincarnation, and the reverse writer/deleter interleaving with + its lock-removed control). + +## 5. Consent schema and evaluation + +1. **Exact columns.** Table `profile_consents`, columns exactly: + + | Column | Type | Constraints | + | ---------------- | ----------- | ----------------------------------------------------------------------------- | + | `id` | uuid | primary key | + | `user_id` | text | NOT NULL, FK → users(id) ON DELETE CASCADE (the data subject, §3.6) | + | `grantee_type` | text | NOT NULL, CHECK in (`agent`, `connector`, `feature`) | + | `agent_id` | uuid | NULL, FK → agents(id) ON DELETE CASCADE (§5.2) | + | `connector_id` | text | NULL, FK → custody_connector_registry(connector_key) ON DELETE CASCADE (§5.2) | + | `feature_key` | text | NULL, FK → custody_feature_registry(feature_key) ON DELETE CASCADE (§5.2) | + | `grantee_ref` | text | generated stored: COALESCE(agent_id::text, connector_id, feature_key) | + | `category_key` | text | NOT NULL, FK → profile_category_registry(category_key) | + | `state` | text | NOT NULL, CHECK in (`granted`, `revoked`) | + | `granted_at` | timestamptz | NOT NULL | + | `revoked_at` | timestamptz | CHECK ((state = 'granted') = (revoked_at IS NULL)) | + | `actor` | text | NOT NULL (the authenticated principal that recorded the change) | + | `audit_event_id` | uuid | NOT NULL, FK → the §1.8 audit event table | + + plus: the CHECK that exactly one referent column is non-NULL and + matches the discriminant — `(grantee_type = 'agent') = (agent_id +IS NOT NULL)` AND `(grantee_type = 'connector') = (connector_id IS +NOT NULL)` AND `(grantee_type = 'feature') = (feature_key IS NOT +NULL)` — and the partial unique index UNIQUE (`user_id`, + `grantee_type`, `grantee_ref`, `category_key`) WHERE `state = +'granted'` — at most one active grant per (user, concrete grantee, + category), as a database constraint. + +2. **Grantee reference model.** A grantee is identified by + (`grantee_type`, the matching referent column); its canonical + display form is `:`. The discriminant + set is closed at the three CHECK values, and every referent is a + real foreign key: + - `agent` → `agent_id` references the platform **`agents` table** + (`agents.id`, uuid) as declared in the shared schema package + (`packages/db`). This is a binding to the live schema, named + honestly: the ratified `identity-lifecycle.md` defines no agent + enrollment surface or enrolled-agent table, so there is no + sibling-defined referent to bind to — the `agents` table is + today's only platform representation of agents, and the §7.11 + witness pins the FK's referenced table to that schema symbol. + If a sibling amendment later introduces an agent-enrollment + surface with its own table, an amendment to this contract + re-binds the FK; until then, rows in `agents` are the closed + set of possible agent grantees. + - `connector` → `connector_id` references this contract's own + table `custody_connector_registry`, columns exactly: + `connector_key` (text, primary key), `since_version` (integer, + NOT NULL), `created_at` (timestamptz, NOT NULL). Rows are added + ONLY by amendment to this contract (§2.1 pattern); the table is + EMPTY at version 1. + - `feature` → `feature_key` references this contract's own table + `custody_feature_registry`, with the same three-column shape + (`feature_key` primary key) and the same amendment-only rule; + EMPTY at version 1 (no feature grantee exists until an amendment + names one). + + A consent row naming a nonexistent referent violates its FK and is + refused by the database. A grant to one agent confers nothing on + another agent of the same type; the constraint key includes + `grantee_ref`, so two same-type grantees are distinct rows. + +3. **Default deny.** Absence of an active `granted` row for (user, + grantee, category) means no access. There are no implicit grants, + no platform-admin bypass, and no mode in which default-deny is + suspended. +4. **Mutation authority — subject only.** Every consent mutation + (create, grant, revoke) must satisfy the single predicate: the + authenticated actor IS the row's data subject — the server asserts + `actor` equals the principal of the row's `user_id` before the + mutation commits. A platform admin has no consent-mutation + capability over another user's rows — an admin grant or revocation + on another user's data is refused at write time, closing the + write-side route around §5.3. There is no system-actor mutation + predicate: revision 3's system auto-revocation is withdrawn + (disclosed, §8), because grantee cessation is handled structurally + instead — + **Cessation by cascade**: when a grantee ceases to exist — the + `agents` row is deleted, or a `custody_connector_registry` / + `custody_feature_registry` row is removed by an amendment + migration — the referent FK's `ON DELETE CASCADE` removes that + grantee's consent rows in the same transaction as the referent + deletion. Access ends with the rows (§5.3 default deny: no row, no + access); no UPDATE is performed, so no actor question arises. + **Cessation event.** The cascade is not silent: an AFTER DELETE + trigger on `profile_consents` inserts, for every deleted row whose + deletion the §5.5 trigger permitted (i.e. a cessation cascade), + one semantic **consent-cessation event** into the §1.8 audit + event table, in the same database transaction as the referent + deletion — if the deleting transaction rolls back, no cessation + event survives, and if it commits, the events commit with it. + Each event carries: the cause (`subject-cessation` or + `grantee-cessation`, plus the parent table and deleted key), the + initiating actor — a platform surface that deletes a referent + sets a transaction-local actor variable the trigger reads; absent + one, the event records actor `system:cascade` (a migration or + direct referential action) — an immutable snapshot of the ended + grant identifying its exact row and subject: the deleted consent + row's `id`, its `user_id` (the data subject), grantee type and + ref, `category_key`, `state`, `granted_at`, and `revoked_at` — + and the event time. The row `id` and `user_id` are REQUIRED + precisely because the cause alone identifies only the ceasing + referent: when two users hold grants to one ceasing grantee, the + cascade removes both rows in one transaction, and only the + per-row snapshot distinguishes which subject's grant each event + records. The snapshot is consent + metadata, never profile content, so §3.1 is not implicated. The + audit table therefore records both ends of every grant's life: + the mutation events written at grant/revoke time survive the + cascade (audit events are never cascade-deleted), and the + cessation event records when, why, and by whom the active grant + ended. The same rule already governs subject cessation via the + `user_id` FK. §7.12 witnesses the subject predicate, its refusal + complement, the cascade, and the cessation event's atomicity, + fields, and survival; every §7.3-enumerated consent-mutation + route asserts the subject predicate. + +5. **Revocation and re-grant.** Revocation flips exactly one active + row to `revoked` and stamps `revoked_at`; it is effective for every + access evaluated after the revoking write commits. Revoked rows are + retained as history and never mutated again — enforced by a + database trigger on `profile_consents` that permits UPDATE only + when it is the granted → revoked transition (the state flip plus + the `revoked_at` stamp, every other column unchanged), and permits + DELETE only when the row's referenced subject or grantee no longer + exists — the trigger checks that the `users` row named by + `OLD.user_id`, or the referent row named by the grantee column, is + absent, which is true exactly during a §5.4 cessation cascade + (referential cascades delete the parent row before the dependent + rows, so the check distinguishes a cascade from a standalone + DELETE). Every other UPDATE and every DELETE with both referents + alive is rejected, so a revoked row cannot be flipped back to + `granted` and history cannot be pruned by anyone, including + through direct SQL (witness §7.13). A re-grant after + revocation inserts a NEW row (append-only history) — repeated + grant/revoke cycles are represented as successive rows, and the + §5.1 partial unique index guarantees the old revoked rows cannot + keep access live. +6. **Evaluation placement.** Access to sensitive content is mediated by + the platform (Gateway/tooling) evaluating consent before any brain + read on behalf of a grantee; the evaluation fails closed + (`rbac-grant-model.md` §3.5 pattern), including when the consent + state cannot be read (§2.5 pattern). The user reading their own + data is not a grantee and needs no consent row. +7. Consent records govern agentic/feature access to user data. They are + distinct from hierarchy grants (contract 2) and confer no platform + authorization. + +## 6. Mode application + +1. The §2–§5 schemas are mode-independent: Standalone and Enterprise + use the same tables and the same routing rule. +2. **Standalone layouts.** The D14 physical split remains a MAY. Its + two valid layouts are: **split** — sensitive user content in a + per-user brain repository separate from the estate mosaic-brain + (the recommended default); **unsplit** — sensitive user content in + the dedicated user-files subtree `users//` of the single + mosaic-brain. §3 binds the logical user-brain region identically in + both layouts; the layout election changes where the region lives, + never whether routing applies. +3. **Election record.** The election lives in this contract's own + one-row table `custody_config`, columns exactly: `id` (uuid, + primary key), `singleton` (boolean, NOT NULL, DEFAULT true, CHECK + (`singleton`), UNIQUE — the one-row rule as a database constraint: + a second row cannot satisfy the CHECK and the UNIQUE index + simultaneously), `standalone_layout` (text, NOT NULL, CHECK in + (`split`, `unsplit`), default `split`), `registry_version` + (integer, NOT NULL, §2.1), `elected_at` (timestamptz, NOT NULL), + `actor` (text, NOT NULL), `audit_event_id` (uuid, NOT NULL, FK → + the §1.8 audit event table). It is written at bootstrap and + amended only by an explicit operator action; contract 6's exact, + immutable mode record is not touched or extended by this contract. + The row is also the §2.6 fence anchor. +4. **Phase timing.** v1 ships the §2–§6 schemas and the D14 database + boundary, and the wizard collects no sensitive category in v1 + (contract 3 §3), so v1 contains no sensitive write surface. §3 + binds every sensitive write path from the moment one exists — the + first profile surface that accepts a sensitive category (P2/P3) + activates routing, consistent with contract 6 §5's v1 slice (D14 + database boundary only, custody mechanics outside v1). +5. **Conversion precondition.** An operator electing `unsplit` accepts + conversion-time partitioning: `mode-conversion.md` §4.2 requires + the per-user partition to exist before the Enterprise flip, so + conversion from an unsplit install performs the partitioning first. +6. In Enterprise, the split is mandatory (D3 table); no-leakage between + users is enforced by §3 placement plus §5 default-deny — there is + no cross-user read path to sensitive content through the database, + because the database has no content to serve. + +## 7. Verification requirements + +Binding on the implementing PRs. Every witness below MUST name, in its +implementation, the exact tables, columns, commands, and source roots +it scans; "the custody tables" means `profile_category_registry`, +`profile_pointers`, `profile_consents`, `custody_config`, +`custody_connector_registry`, and `custody_feature_registry`; "the +declared profile table" means `profile_answers` (§3.5). + +1. **Column-allowlist witness** (hierarchy contract §6.2 style): the + custody tables' live column sets are exactly + §2.1/§4.1/§5.1/§5.2/§6.3 and `profile_answers` is exactly §3.5; + no platform table outside `profile_answers` carries profile + answer content; every `audit_event_id` FK (§3.5, §4.1, §5.1, + §6.3) references the same physical table that the contract-1 + (hierarchy §5.2) audit write path inserts into (§1.8 binding); + and every FK column's declared type equals the referenced + column's live declared type — in particular each `user_id` is + `text` matching `users.id` (§3.6 binding) and `agent_id` is + `uuid` matching `agents.id` (§5.2 binding). +2. **Column-type allowlist witness:** the custody tables and + `profile_answers` use only the column types named in + §2.1/§3.5/§4.1/§5.1/§5.2/§6.3 (uuid, text, integer, bigint, + timestamptz, boolean) — no bytea, json/jsonb, array, vector, or tsvector + column exists in them, closing the encoded/derived-representation + routes by type rather than by probe alone. +3. **Closed write-route witness** (hierarchy contract §6.3, full + prong set): a static, re-export-aware inventory over `apps/`, + `packages/`, and `plugins/` (production code, tests excluded) + enumerates every module that writes the custody tables, + `profile_answers`, or profile answers generally, detecting access + through each of hierarchy §6.3's prongs — schema-symbol imports, + SQL string literals naming the tables, raw-execution primitives, + and runtime code construction — with the database client reachable + only through a closed importer allowlist. Every enumerated write + route implements §3.3 registry routing inside the §2.6 fence, and + every enumerated consent-mutation route asserts the §5.4 subject + predicate; a route outside the enumeration, or a client import + outside the allowlist, fails the assertion. **Write-set closure + for sensitive paths:** for every enumerated route that performs a + sensitive write, the same static inventory (same prong set) + enumerates every database table that route's code path can write; + the set MUST equal exactly {`profile_pointers`, the §1.8 audit + event table} — the brain write goes through the declared + brain-write surface, never the database. Any additional + platform-table write statically reachable from a sensitive-write + path — an embedding insert, a cache row, any derived + representation in any generic table — fails the witness. Control: + a mutated route that adds one insert into an arbitrary existing + platform table is detected. This closes the §3.1 escape in which + a conforming-looking route also writes derived sensitive data + elsewhere: §7.5 probes for planted content; this closure bounds + what a sensitive-write route can write at all. +4. **Closed brain-read witness** (same style): the inventory enumerates + every production route that reads user-brain content on behalf of a + grantee, and every enumerated route calls the §5.6 consent + evaluation; a brain-read route outside the enumeration fails. +5. **Routing witness:** a sensitive-category answer submitted through + the profile surface results in a brain write plus a pointer row and + zero content bytes in the database; a non-sensitive answer lands in + its declared table. The no-content probe is a negative control: the + witness first plants the fixture text in a scratch column of a + throwaway table to prove the probe detects it, then asserts its + absence — as plaintext, base64, hex, and JSON-string encodings — + across every column of every table in the platform database: §3.1 + bans sensitive content in every column of every table, so the + probe scope is the entire schema, not only the custody tables. +6. **Registry witnesses:** (a) the version-1 registry state is exactly + the seven §2.2 rows with their classifications; (b) a + `professional-background` answer routes relationally (or per the + ruling's alternative); (c) an unknown `category_key` is refused + with nothing stored (§2.3); (d) with the registry unreadable, the + write is refused (§2.5); (e) a runtime insert/update/delete against + `profile_category_registry` outside a migration is refused (§2.1); + (f) a reclassification migration (non-sensitive → sensitive) on + seeded data moves the values to brains, creates pointers, and + leaves zero relational values (§2.4); (g) fence race: a profile + write transaction opened before a reclassification migration takes + its exclusive lock either commits before the lock is granted (its + value is swept) or blocks and, on commit, routes by the new + version — the witness drives both interleavings, INCLUDING the + interleaving in which the writer holds `FOR SHARE` when the + migration requests `FOR UPDATE`, the migration waits, the writer + commits its relational row, and the migration's scan (running + after lock grant, at `READ COMMITTED`, with the lock as its first + statement per §2.6) is asserted to sweep that row — and asserts no + relational value for the reclassified category exists after any + interleaving completes (§2.6); the witness also asserts the + migration implementation takes its exclusive lock as the first + statement of its transaction (a migration variant that reads + before locking fails review of this witness's static check); + (h) with `custody_config.registry_version` set to a value the + release's declared compile-time constant (§2.7, pinned by symbol) + does not equal, profile writes and consent decisions are refused, + and the witness asserts the version comparison reads the row under + the §2.7 shared lock in the same transaction as the refused + decision (§2.7); (i) sensitive → non-sensitive: after the + version bump, existing brain content and pointers remain intact + and readable, nothing is materialized into the database, and the + next write for that category lands in `profile_answers` (§2.4). +7. **Server-side classification witness:** a client-supplied + classification or routing override on a profile write is ignored; + the registry decision is applied (§3.3). +8. **Pointer-constraint witnesses:** inserting a second pointer for the + same (user, category, brain_ref) violates the §4.1 unique + constraint; a `brain_ref` failing the §4.3 grammar (leading `/`, + `..` segment, empty segment) is rejected by the CHECK; resolution + of a valid `brain_ref` under user A's row never reads user B's + brain (owner binding, §4.3); with a symlink committed at a + locator's path or as an intermediate segment, resolution is + refused and no content outside the owner's brain is read (§4.3 + tree-object rule). +9. **Hash witnesses:** `content_hash` verifies via the keyed §4.4 + construction; the database value alone, without the external key, + does not equal any unkeyed digest of the fixture content (oracle + control); the same fixture content stored for two users, and for + two categories of one user, yields different stored hashes + (domain-separation control — no cross-row equality oracle); after + a key rotation, a pointer written under the old key id still + verifies and a new write stores the new key id; a mismatch refuses + the read and stamps `mismatch_at`, subsequent reads stay refused, + reconciliation of a persistently mismatched pointer retains it + flagged without deleting brain content or writing content to the + database, and the subject's re-write clears the flag (§4.4, §4.6). +10. **Orphan and deletion-protocol witnesses:** starting from a + PRE-EXISTING orphan (content already absent, pointer present), + the next triggered reconciliation deletes the pointer and writes + no content anywhere in the database (§4.5). The §4.7 protocol is + fault-injected at both interruption points: killed before the + brain commit, content presence, content bytes, and every + database row are unchanged — the deleter's post-acquisition + fence-advance may already have minted an epoch and recorded an + acquisition token, the only brain change the protocol permits + before step 1; killed between the brain + commit and the pointer delete, the dangling pointer is deleted by + the next reconciliation — and at no point is content written to + the database or restored to the brain. **Concurrent-rewrite + interleavings (two):** deletion step 1 commits and the deleting + session is fault-injected dead before step 2 — the §4.7 lock + releases with the session, opening the crash window; a + concurrent §3.7 write then recreates the same `brain_ref` and + completes its pointer upsert; the stale step-2 compare-and-delete + is then replayed with the recorded token. The witness drives + this once with CHANGED + content (new `content_hash`, incremented `generation`) and once + with BYTE-IDENTICAL content (unchanged `content_hash`, + incremented `generation` — the ABA case a hash token would + miss), and in both asserts the DELETE matches zero rows, the + fresh pointer survives, its content resolves and verifies, and + no unpointed content and no dangling pointer exist afterward. + **Delete/reinsert reincarnation (third interleaving):** in the + same crash window, §4.5 reconciliation first removes the + now-orphan pointer row; an ordinary §3.7 write then recreates + the locator as a FRESH row — new `id`, `generation` back at 1 + and therefore EQUAL to the recorded generation value; the + replayed stale step 2 is asserted to match ZERO rows (the + recorded `id` names the dead incarnation), and the fresh pointer + survives with its content resolving and verifying — the case a + bare generation token fails. **Reverse writer/deleter + interleaving:** a §3.7 writer holds the locator lock and has + committed its step-1 brain content but not its pointer upsert + when a managed deletion for the same locator starts. The witness + asserts the deletion BLOCKS at §4.7 lock acquisition until the + writer's pointer upsert and release, and that afterward no + acknowledged write's content is absent and no pointer names + absent content; a lock-removed control schedules the deleter's + observation and content removal inside the writer's + step-1-to-step-2 gap and asserts the witness DETECTS the + resulting acknowledged-write loss, establishing that the lock is + what excludes it. **Lock-held contention control (one-cycle + bound):** starting from a PRE-EXISTING orphan, a §3.7 writer + session holds the orphan's locator lock while the triggered + reconciliation's sweep pass runs. The witness asserts the pass + completes without blocking and without repairing the held + locator (the try-acquire skip), that the cycle does NOT report + completion while the lock is held, and that after the holder + releases, the queued blocking retry deletes the orphan under + the recorded (`id`, `generation`) token BEFORE the cycle + reports completion — the orphan is gone by cycle completion, + proving the §4.5 bound under contention. A repeated-contention + variant hands the lock to a second holder the moment the first + releases and asserts the queued locator is retried until + acquired — the orphan is still deleted by cycle completion once + the last holder releases, never dropped after one failed + attempt. A **hold-deadline control** starts from a pre-existing + orphan, has a live holder acquire the orphan's locator lock and + stall past the declared §3.7 hold deadline WITHOUT ending its + session (idle transaction kept open), and asserts the holder's + session is terminated at the deadline, the lock releases + without any voluntary action by the holder, and the queued + blocking retry then deletes the orphan under the recorded + token before the cycle reports completion — proving termination + does not depend on a cooperative holder. A **grant-order + control** blocks the reserved repair behind a holder, keeps + a stream of later contenders requesting the same locator lock + while the reservation waits, and asserts the reservation is + granted before every later contender and the cycle completes + within its computed maximum `M` — proving later arrivals cannot + starve the queued repair, and exercising the computed + cycle maximum as a checked bound rather than prose. A + **reservation control** has the sweep's try-acquire fail + against a holder and asserts the blocking lock request is + enqueued AT the try-acquire failure (the §4.5 reservation + point), not at end of pass — then starts contenders + immediately after the failed try-acquire and asserts every one + is granted after the reservation, and that the cycle's `M` was + fixed when the last reservation was placed and is not enlarged + while the late contenders queue. Two **external-step deadline + controls** exercise termination at the real external boundary, + not an idle transaction. First, the stalled managed deletion: + a §4.7 deleter acquires the locator lock and starts its step-1 + brain deletion, which is suspended mid-operation — the session + neither idle in a transaction nor executing a database + statement; the control asserts the independent §3.7 supervisor + terminates the session at the deadline with no cooperation + from the holder and the lock releases; a fresh §3.7 writer + then acquires the lock, commits new content at the locator + with its pointer upsert, and is acknowledged; the suspended + stale deletion is then RESUMED and asserted REFUSED by the + brain fence — the fresh content is still present, its pointer + resolves and verifies, and no mismatch is stamped. Second, the + stalled ordinary writer: a §3.7 writer stalls inside its + step-1 brain commit, is terminated at the deadline, a fresh + writer completes a full acknowledged write at the same + locator, and the stale commit is resumed and asserted refused + — the locator serves the fresh writer's bytes and the fresh + pointer's hash verifies. Two **ABA fence controls** exercise + value recurrence, the cases a state-equality fence misses. + Byte-identical stale deletion: a §4.7 deleter records its + fence, is suspended inside its step-1 brain deletion and + terminated by the supervisor; a fresh §3.7 writer then + recommits BYTE-IDENTICAL content (unchanged §4.4 hash) with + its pointer upsert and is acknowledged; the stale deletion is + resumed and asserted REFUSED — the identical bytes carry a + fresh epoch — with the content still present, resolving, and + verifying, and no mismatch stamped. Absence resurrection: an + ordinary writer records the locator's ABSENCE as its fence, is + suspended inside step 1 and terminated; a fresh writer + completes an acknowledged write at the locator; a managed + deletion then removes that content and its pointer, returning + the locator to absence; the stale writer is resumed and + asserted REFUSED — the post-deletion absence is a fresh + tombstone epoch, not a recurrence of the recorded one — with + the locator still absent and NO unpointed content existing + anywhere afterward. A **handoff-invalidation control**: a + holder is terminated mid-external-operation; the next holder + acquires the lock and performs its §3.7 fence-advance but has + NOT yet applied any brain mutation when the stale operation is + resumed — the stale operation is asserted refused (the advance + alone invalidated it, before any fresh mutation), and the new + holder's own subsequent fenced mutation is asserted to + succeed. A repair-observation variant runs the same schedule + with reconciliation as the new holder: repair advances, + observes content absence, and the stale writer resuming before + repair's DELETE is asserted refused — the DELETE then + completes under its recorded (`id`, `generation`) token and + NO unpointed content is resurrected at the locator. A + **stale-advance refusal control** exercises the §3.7 token + guard on the advance itself: writer W1 acquires the locator + lock and its mandatory fence-advance is suspended BEFORE its + atomic application; the independent supervisor terminates W1's + session at the deadline and the lock releases; the next holder + acquires the lock (receiving a greater acquisition token) and + completes its own fence-advance; W1's suspended advance is + then RESUMED and asserted REFUSED by the token comparison — + it mints no epoch and returns no usable fence — and W1's + protocol run is asserted to abort with the error surfaced, + performing no further brain observation or mutation, while + the next holder's fence remains valid and its subsequent + fenced mutation is asserted to succeed. A fresh-writer + variant runs the next holder as an ordinary §3.7 writer + through a full acknowledged write; a repair-observation + variant runs it as §4.5 reconciliation — repair advances, + observes content absence, W1's late advance resumed before + the DELETE is asserted refused and fenceless, and the DELETE + completes under its recorded (`id`, `generation`) token with + no unpointed content resurrected. A **virgin-locator token + control** exercises the §3.7 absent-token base case: the FIRST + lock grant ever issued for a locator (no recorded acquisition + token exists in the brain store) performs its mandatory + fence-advance and is asserted ACCEPTED — the advance mints the + locator's epoch and its acceptance atomically creates the + token record — and the holder's subsequent fenced mutation + succeeds; a delayed-first-grant race variant suspends that + first holder's advance before application, terminates the + session at the deadline, lets a second holder acquire the + lock (receiving a greater token) and complete its own advance, + then resumes the first holder's advance and asserts it REFUSED + by the token comparison — the base case never admits a + superseded first grant. A **pre-advance + linearization control** exercises the §3.7 legal-outcome + boundary: a terminated holder's stale content mutation is + scheduled to linearize immediately BEFORE the next holder's + fence-advance swap; the control asserts the store serializes + the stale mutation first, the advance preserves the + post-mutation content presence and bytes and mints its epoch + after it, the new holder's first relied-on observation + reflects the committed stale effect, and the end state is + consistent — equivalent to the stale operation having + completed before the previous holder's lock release. The + control runs BOTH database-half branches: with the stale + mutation a managed deletion's step-1 brain half, its stale + step-2 DELETE remains conditioned on the recorded + (`id`, `generation`) pair and is asserted to match ZERO rows + against a later row incarnation; with the stale mutation an + ordinary §3.7 writer's step-1 content commit, the terminated + run is asserted to perform NO pointer upsert and to receive + NO acknowledgment; the committed content is asserted + unpointed and inert — referenced by no pointer, served by no + read path, and subject to no repair transition (a §4.5 cycle + run against the state is asserted to change nothing) — and + convergence is asserted only through the client's retry, + which acquires the lock as a fresh acquisition + under a new token, recommits the locator, and completes the + pointer upsert to the pointed state (§3.7, witness §7.16). An **exceedance control** contrives a + cycle that cannot finish within its computed `M` (an injected + sequence of deadline-length holders on a reserved locator) and + asserts the cycle reports FAILURE — never completion — + surfaces the error, withdraws its pending reservations, and + persists the carry-over set; the next triggered cycle is + asserted to take the carry-over locators into scope first and + delete the orphan before reporting completion. A + static control asserts the deletion implementation conditions on + BOTH the recorded row `id` and `generation` — not on + `content_hash` and not on `generation` alone — a second + static control asserts the managed-deletion and + reconciliation-repair implementations each acquire the §3.7 + locator lock before their observation reads (§4.5, §4.7), and a + third static control asserts the reconciliation implementation + reserves a failed try-acquire's queue place at skip time for a + within-cycle blocking acquire — an implementation that defers + a skipped locator to a later cycle other than through the §4.5 + exceedance transition (which fails the cycle and persists the + carry-over set), or that reports cycle completion with a + reserved locator unprocessed, FAILS. A fourth static control asserts every + lock-holding path (ordinary write §3.7, managed deletion §4.7, + reconciliation repair §4.5) is configured with the declared + §3.7 hold deadline — an implementation with any unbounded + lock-holding path FAILS. A fifth static control asserts the + §3.7 grant-order binding: the implementation either cites the + pinned advisory-lock mechanism's documented queue-order grant + guarantee or routes conflicting locator-lock acquisitions + through an application-level FIFO queue — an implementation + relying on an undocumented grant order FAILS. A sixth static + control asserts the managed-deletion brain step applies its + mutation conditionally on the recorded §3.7 fence epoch — an + unfenced brain-deletion path FAILS. A seventh static control + asserts the §3.7 fence-epoch invariants: every fenced-mutation + path mints a fresh never-reused per-locator epoch atomically + with its mutation — byte-identical recommits and transitions + to absence (the unique tombstone) included — every + lock-acquiring path that mutates or relies on brain state + (ordinary write §3.7, managed deletion §4.7, reconciliation + repair §4.5) performs the fence-advance immediately after + acquisition and records the post-advance epoch as its fence + value, and no path represents the fence by content bytes, + content hash, or bare path absence — an implementation whose + fence value can recur, or that observes before advancing, + FAILS. The same control asserts the §3.7 acquisition-token + invariants: the lock authority mints a fresh, strictly + increasing per-locator token at every lock grant; every + brain-mutation path — the fence-advance included — presents + its holder's token unchanged across every swap-loop retry; + the brain store's token comparison and recording are atomic + with the mutation they guard; the advance is refused when its + token is less than the recorded value; and a refused advance + yields no usable fence — an implementation whose advance + applies unconditionally, that re-mints or refreshes a token + within an acquisition, or whose holder proceeds to any brain + observation or mutation after a refused advance, FAILS. +11. **Default-deny and granularity witnesses:** an agent grantee with + no active row is refused; with a `granted` row for category A + only, category B is refused; with agent X granted, agent Y of the + same type is refused for the same (user, category); `connector` + and `feature` are exercised via their FK refusal paths, since + both registries are empty at version 1 (§5.2); a consent insert + naming a nonexistent agent id violates the `agent_id` FK, and the + witness pins that FK's referenced table by symbol identity to the + `agents` table exported by the shared schema package (§5.2 + binding); a platform admin requesting + another user's sensitive content through any production read + surface is refused — admins are not a grantee type and + default-deny applies (the access-time complement of §7.12's + write-time refusal); a hierarchy owner or manager grant over the + subject confers no consent access (§5.7); with the consent state + unreadable, evaluation refuses (§5.6). +12. **Mutation-authority witnesses:** a platform admin attempting to + create a grant on another user's data is refused at write time; a + hierarchy owner or manager likewise; the data subject succeeds; + `actor` equals the subject's principal on every mutation row — + there is no non-subject mutation path (§5.4). **Cessation + cascade:** deleting an agent's `agents` row removes that agent's + consent rows in the same transaction via the FK cascade, the next + consent evaluation for that grantee refuses (no row), the linked + audit events survive, and no other grantee's rows are touched; a + connector-registry amendment migration removing a key cascades + identically (§5.4). **Cessation events:** the same deletions + each produce one consent-cessation event per removed row in the + §1.8 audit event table, carrying the §5.4 fields (cause with + parent table and key, actor — the surface-set actor when one + initiated the deletion, `system:cascade` otherwise — the grant + snapshot including the deleted consent row's `id` and `user_id`, + and time); a **two-subject discrimination witness** has two + distinct users each hold a `granted` row to ONE agent grantee + for the SAME category, deletes that grantee's `agents` row, and + asserts exactly two cessation events exist and that each event's + snapshot carries a distinct consent-row `id` and the correct + `user_id` — every event names exactly which subject's grant of + which row ended; an atomicity control rolls the deleting + transaction back after the parent DELETE and asserts no + cessation event persists (no orphan event without a deletion, + no deletion without its events); the events survive subsequent + operation; and a subject-cessation (`users` row) deletion + produces its events identically (§5.4). +13. **Revocation/re-grant witnesses:** after revocation commits, the + next evaluation refuses and the revoked row persists unmutated; a + full grant → revoke → re-grant cycle yields two rows (one + revoked, one active) and access follows only the active row; a + second concurrent grant attempt for the same key violates the + §5.1 partial unique index; a direct SQL UPDATE flipping a revoked + row back to `granted`, an UPDATE altering any other column of a + revoked row, and a standalone DELETE of a row whose subject and + grantee both still exist are each rejected by the §5.5 trigger; + the complement controls pass: deleting the subject's `users` row + cascades the subject's consent rows, and deleting the grantee's + referent row cascades that grantee's rows — both permitted by the + trigger's absent-referent prong (§5.5). +14. **Self-access witness:** the data subject reads their own content + without consent rows (§5.6). +15. **Mode witnesses:** the custody-table schemas are byte-identical + under Standalone and Enterprise migrations (§6.1); a fresh + Standalone bootstrap records `standalone_layout = 'split'` by + default, and an explicit opt-out records `unsplit` with actor and + audit linkage (§6.3); conversion from an `unsplit` install refuses + the mode flip until partitioning has produced the per-user region + (§6.5, with contract 6 §4.2); in an Enterprise fixture with two + users, user A's grantee with a grant on user A cannot reach any of + user B's content (§6.6); inserting a second `custody_config` row + violates the §6.3 singleton constraint. +16. **Ordered-write protocol witnesses:** the §3.7 protocol is + fault-injected at its interruption points: killed before the + brain commit, content presence, content bytes, and every + database row are unchanged — the post-acquisition + fence-advance's epoch mint and token recording are the only + permitted brain change — and the caller receives + no acknowledgment; killed between the brain commit and the + pointer upsert, the brain holds unpointed content, no pointer row + exists, no read surface serves the content, and the caller + received no acknowledgment — then the retried write recommits the + same deterministic locator, completes the pointer upsert, and the + witness asserts the converged state (one pointer, verifying hash, + content served to the subject); killed after the pointer commit + but before the acknowledgment reaches the caller, the retry + executes under the locator lock, observes the converged state, + returns success, and the witness asserts exactly one pointer with + a verifying hash and an unchanged answer (idempotent completion, + no duplicate content commit that changes the served bytes). + **Same-locator interleaving:** two concurrent writers to one + (user, category, slot) run to completion; the witness asserts + both are serialized by the §3.7 advisory lock (their step-1/step-2 + spans do not interleave), the final pointer's hash verifies + against the blob committed at the locator, and no `mismatch_at` + is stamped by a subsequent read; a control with the lock removed + schedules the divergent order (A's pointer upsert after B's + content commit) and asserts the witness DETECTS the divergence — + proving the lock is what excludes it. **Stale retry:** writer A + is interrupted before acknowledgment; writer B then completes a + full write to the same locator; A's retry executes under the + lock and the witness asserts the end state is consistent (the + pointer's hash verifies against the current blob — last + completed write wins, no permanent mismatch). **Locator + injectivity:** the declared scheme maps every distinct + (category_key, slot) pair in the registry to a distinct + `brain_ref` (exhaustive at version 1: seven categories x the + `answer` slot), and a deliberately colliding scheme control + fails the witness. A static control asserts the implementation + orders content before pointer: no enumerated sensitive-write + route creates a pointer row before its brain commit has been + confirmed (§3.7). A second static control asserts every + enumerated sensitive-write route applies its step-1 brain + commit as a fenced mutation conditioned on the recorded §3.7 + fence epoch — recorded at the route's post-acquisition + fence-advance, the commit itself minting a fresh epoch and + carrying the route's §3.7 acquisition token — an + unfenced or token-free brain-commit path FAILS. + +## 8. Drafting additions (PRD §12.1 disclosure) + +The following are proposed drafting additions, visible here for +ratification; none is claimed as a PRD mandate, and each is severable: + +1. The `feature` grantee type, with a contract-owned registry table + that is empty at version 1 (§5.2). +2. Append-only consent history: re-grants insert new rows; revoked + rows are retained unmutated, enforced by trigger (§5.5). +3. Cessation by cascade: when a grantee or subject referent row is + deleted, consent rows are removed by `ON DELETE CASCADE` in the + same transaction; access ends through default deny, and durable + history is carried by the §1.8 audit event table. The trigger's + DELETE prong permits a child-row DELETE only when the referenced + subject or grantee row is absent (§5.4, §5.5). Each cascaded + deletion additionally emits a consent-cessation audit event — + written by an AFTER DELETE trigger in the same transaction as the + referent deletion, carrying cause, actor (surface-set or + `system:cascade`), the grant snapshot — which, per revision 6, + includes the deleted consent row's `id` and its `user_id`, so + events from different subjects' grants to one ceasing grantee + remain distinguishable — and time — so the audit + trail records when and why each active grant ended (§5.4, + §7.12). This replaces the revision-2 system-actor auto-revocation + predicate, which is withdrawn. +4. The `custody_config` election record for the Standalone layout, + singleton by constraint (§6.3). +5. The domain-separated keyed `content_hash` construction, its key + rotation rule, and the `mismatch_at` pointer state (§4.4, §4.6). +6. The bounded dangling-pointer reconciliation policy and the ordered + managed-deletion protocol (§4.5, §4.7). +7. The column-type allowlist verification requirement (§7.2). +8. The amendment-only, versioned registry classifier itself, its + unknown-key refusal, and both reclassification transition policies + (§2.1, §2.3, §2.4). +9. The custody-config write fence and the declared supported-version + refusal, including the revision-4 pinning: both fence transactions + run at `READ COMMITTED` with the migration's `SELECT ... FOR +UPDATE` as the first statement of its transaction, and the + supported version is an exported compile-time constant in the + custody module of the shared schema package, read under the same + lock as the decision it gates (§2.6, §2.7). +10. Subject-only consent mutation authority (§5.4, the single + subject predicate). +11. The declared non-sensitive profile table `profile_answers` and + its closed schema (§3.5). +12. The typed grantee referent columns, the + `custody_connector_registry` and `custody_feature_registry` + tables, the audit-table binding by identity (§5.1, §5.2, §1.8), + and the agent-grantee binding by symbol identity to the `agents` + table exported by the shared schema package (§5.2, §7.11). +13. Shipping all §2–§6 schemas in v1 ahead of any sensitive write + surface (§6.4). +14. The committed-tree-only resolution rule: content reads resolve + only through the committed tree of the brain repository, and + symlink or non-tree/non-blob resolution is refused (§4.3, §7.8). +15. The text user-key binding: every `user_id` column is `text` + matching the live `users.id` declaration, values are opaque + strings not guaranteed to be UUIDs, and any sibling user-key + migration triggers amendment of this contract (§3.6, §7.1). +16. The ordered sensitive-write protocol for ordinary writes: + content-first at a deterministic locator, pointer upsert second + in its own transaction, acknowledgement only after the pointer + commit, idempotent retry convergence, and the + pointer-before-content prohibition — with the revision-5 + locator scheme (injective over (category, slot), the slot set + fixed to `answer` at version 1, stable across releases) and the + per-locator advisory lock spanning both steps — held, per + revision 6, by EVERY blob/pointer mutator for the locator: + ordinary writers, managed deletion, and reconciliation repair + (try-acquire with the revision-7 within-cycle blocking retry, + so a failed try-acquire defers repair to the end of the cycle, + never past it; per revision 8 every hold runs under a declared + finite hold deadline with arrival-order grants, so the deferred + repair — and with it cycle completion — is wall-clock bounded, + not merely eventual; per revision 9 the deadline is enforced + by a supervisor independent of the holder, the deferred repair + reserves its queue place at the moment its try-acquire fails, + the cycle maximum is a computed formula over + reservation-point snapshots that is fixed when the last + reservation is placed, exceedance is an explicit failed-cycle + transition with a durable carry-over set the next cycle + processes first — so the one-cycle orphan bound binds every + cycle that reports completion — and every brain mutation under + a locator lock is fenced by a conditional application; per + revision 10 the fence value is an ABA-resistant per-locator + fence EPOCH — minted fresh by every mutation, byte-identical + recommits and transitions to absence (unique tombstone) + included, and advanced by every new holder immediately after + acquisition, before it relies on brain state — so a stale + tail is refused even when bytes or absence recur and even + before the new holder's first mutation; per revision 11 the + fence-advance itself is guarded by an acquisition token — + minted fresh and strictly increasing by the lock authority at + every lock grant, carried unchanged across an acquisition's + brain operations and swap-loop retries, compared and recorded + by the brain store atomically with every brain mutation, the + advance included — so a superseded acquisition's delayed + advance is refused and mints nothing, a refused advance + yields no usable fence with a mandatory + abort-release-surface transition, and a stale operation + either serializes wholly before the new holder's advance — + the defined legal pre-release-completion-equivalent outcome — + or is refused; per revision 12 the token comparison is exact + at both ends — strictly less-than refuses, an absent recorded + token compares lower than every minted token so a virgin + locator's first advance is accepted and creates the record — + and the pre-advance-linearization outcome distinguishes the + stale database halves: pair-conditioned step 2 for deletion + and repair, no pointer upsert ever for a terminated ordinary + writer), so live interleavings + between any two mutators are excluded by one serialization + protocol, and stale external tails are excluded by the fence + (§3.7, §4.5, §4.7, §7.16). +17. The compare-and-delete managed-deletion step: deletion step 2 is + conditioned on the PAIR of the target row's uuid `id` (the + immutable row-incarnation identity) and its monotonic + `generation` counter, both recorded in step 1 under the locator + lock (revision 6; the revision-4 `content_hash` token was + withdrawn as ABA-prone under identical-content rewrites, and the + revision-5 bare `generation` token is withdrawn as ABA-prone + under delete/reinsert reincarnation, where the counter restarts + at 1), so a stale deletion surviving a crash window is never + able to unpoint a fresh row — changed, byte-identical, or + reincarnated (§4.1, §4.7, §7.10). +18. The sensitive-path write-set closure: a static inventory of every + table a sensitive-write route can write, required to equal + exactly the pointer table plus the §1.8 audit event table + (§7.3). + +The revision-1 "reporting" rationale for relational +background/education storage is withdrawn; the traced rationale is +agent configuration (PRD D4). + +## Ruling request + +Ruling requested (one decision): classify `professional-background` +and `education` as **non-sensitive** in the version-1 registry (stored +relationally, used for agent configuration) — or, as the alternative, +classify both **sensitive** (user-brain custody with pointers), +accepting that agent-configuration reads then go through pointer +indirection and consent evaluation?