docs: custody-schema contract revision 10 (sol r9 F16 residual + F17: ABA-resistant per-locator fence epoch — fresh mint on every mutation incl. byte-identical and absence tombstone, fence-advance at lock acquisition invalidating stale tails before first mutation, repair advances before observing; distinct-locator no-contention narrowed to locator locks)
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
fred
2026-08-27 03:04:52 -05:00
parent c6a2237dde
commit b10cfb1e69
+166 -43
View File
@@ -187,6 +187,33 @@ 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).
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
@@ -421,7 +448,15 @@ with contract 6 (`mode-conversion.md`), identity with
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.
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
@@ -431,31 +466,60 @@ with contract 6 (`mode-conversion.md`), identity with
**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. Every 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 FENCED: immediately after acquiring the lock the
holder records the locator's current brain state (the content
version at `L(category_key, slot)`, or its recorded absence) as
its **fence value**, and the mutation is applied conditionally —
the brain store applies it only if the locator's state at
application time still equals the recorded fence value, with the
check 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 condition
re-validated inside the swap loop when the swap is taken on a
coarser head). A later holder that commits at the locator
changes the locator's state, so a stale operation — one whose
session was terminated and whose lock has passed to a newer
holder — no longer matches its recorded fence value and is
REFUSED atomically: it cannot remove or replace content
fence for the external store. The fence is a per-locator **fence
epoch**: a 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. Two rules bind the epoch. **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. **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. Every 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, with the check 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 epoch condition 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 unconditionally: its recorded epoch
predates the next holder's fence-advance, so the condition fails
whether the intervening history changed the bytes, recommitted
byte-identical content, returned the locator to absence, or
consists of the advance alone — refusal does not wait for the
new holder's first mutation, and no sequence of mutations can
re-present a consumed epoch. 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 mutation
passes its fence trivially: it holds the locator lock, so no
other mutator has changed the locator since it recorded the
value. Every brain-mutation path carries the fence; a path
applying an unfenced brain mutation fails the §7.10 and §7.16
static controls.
or hours after the termination. A live holder's own fenced
mutation cannot fail its condition — not because it holds the
lock, but because handoff invalidation plus minting mean the
only epochs minted since its fence-advance are its own; 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 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 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
@@ -559,8 +623,12 @@ with contract 6 (`mode-conversion.md`), identity with
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, holds it across the observation and
the DELETE, and releases it after. The sweep pass MAY use a
the pointer's locator lock, performs the §3.7 fence-advance —
invalidating any stale in-flight operation's fence BEFORE the
observation repair relies on, so no terminated holder's resuming
mutation 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
@@ -655,8 +723,10 @@ with contract 6 (`mode-conversion.md`), identity with
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 — the fence value recorded
immediately after lock acquisition), recording the target row's
a FENCED mutation (§3.7 Brain fence — conditioned on the fence
epoch recorded at the deleter's post-acquisition fence-advance,
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
@@ -681,11 +751,14 @@ with contract 6 (`mode-conversion.md`), identity with
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 commits at the
locator, 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
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 both stores unchanged. Interruption between the
steps leaves a dangling pointer, which §4.5 repairs toward
deletion within one reconciliation cycle. At no point does any
@@ -1107,7 +1180,37 @@ declared profile table" means `profile_answers` (§3.5).
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. An **exceedance control** contrives a
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. 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 —
@@ -1138,8 +1241,20 @@ declared profile table" means `profile_answers` (§3.5).
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 value — an
unfenced brain-deletion path FAILS.
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.
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
@@ -1248,7 +1363,9 @@ declared profile table" means `profile_answers` (§3.5).
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 value — an unfenced brain-commit path FAILS.
fence epoch — recorded at the route's post-acquisition
fence-advance, the commit itself minting a fresh epoch — an
unfenced brain-commit path FAILS.
## 8. Drafting additions (PRD §12.1 disclosure)
@@ -1334,11 +1451,17 @@ UPDATE` as the first statement of its transaction, and the
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 the next
holder's commit invalidates), 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).
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), 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