docs(architecture): ADR-012 — pre-decomposition consistency rulings; tree verified single-valued
Full-tree review before task decomposition found five composition
defects (mechanisms specced correctly in isolation, composition
unruled) and a set of caller-facing gaps. ADR-012 rules each:
- Sweeps are never errors: aborts are GcAbortCause report data in
Ok(SweepReport) (ProtectFailed / SweeperLock / NoLivenessSources);
GcAborted retired from the error enum; direct-delete refusal is
the GcRefuse error covering the full protection set
- Pin token gains its wire shape: blobs/put response {token, digest};
blobs/have's token-renewal form (one digest + token); token
validity domain = the minting serving node, process-lifetime
mapping
- Fleet mode is an explicit constructor declaration (fleet: true),
never inferred from engine choice
- All fleet GC state hosts on the fleet's kv engine (postgres) — one
arbitration domain; large=pg-lo fleet nodes required onto the same
pg instance (composite predicate's new clause); large=local fleet
puts pin-row-first, publish-second
- Engine-state seam reduced: sqlite pin/sweep-lock bodies dropped
(dead machinery); non-SQL engines stage delete-window candidates
in-process; one-window-host rule per instance
- Facade clarifications: fall-through for all key-addressed ops,
kv-only put-time rejection, mem+local dual-tier valid, error-model
member/return-shape ruling (trait/facade family split), has ->
bool, PinState variants, fleet liveness-table registration form,
window executor = the next sweep
Alignment edits across all specs and ADR-005/008/009/010/011
(bracketed corrections per the established pattern); OQ-11 (pg-only-kv
feature graph) added to the parked index for auditability.
Verification: two independent review passes; all findings resolved;
verdict READY for task decomposition.
This commit is contained in:
1 parent
abf8c5ae9c
commit
7b9d904b8a
13 files changed
+771
-184
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-10-03 (pin-token renewal contract pinned per ADR-010 §4)
|
||||
last_updated: 2026-10-03 (ADR-012 §2 — put response {token, digest}; have token-renewal form; fetch verification wording; non-fleet token scope)
|
||||
---
|
||||
|
||||
# Ops surface (alkcall-backed network operations)
|
||||
@@ -55,11 +55,19 @@ JSON control ops (`Visibility` per ADR-001 §Decision):
|
||||
|
||||
- **`blobs/stat`** `{namespace, digest}` → `{len}` — probe before
|
||||
offering; ACL-gated like fetch (read action on the namespace)
|
||||
- **`blobs/have`** `{namespace, digests[]}` → `{present[]}` — the
|
||||
have half of have/need set diffing (hashes only — content never
|
||||
- **`blobs/have`** `{namespace, digests[], token?}` → `{present[]}` —
|
||||
the have half of have/need set diffing (hashes only — content never
|
||||
traverses this op); read-gated: an existence probe over arbitrary
|
||||
digests is a discovery surface, so it is gated exactly like fetch,
|
||||
not public
|
||||
not public. `present[]` corresponds to `digests[]` by index. The
|
||||
optional `token` is the **renewal form** (ADR-012 §2): when present,
|
||||
exactly one digest accompanies it — the handler verifies the token
|
||||
maps to that digest's pin row, renews it (crate-internal
|
||||
`pin_renew` via the ops module's store-core access, ADR-010 §4),
|
||||
and answers with `present[]` as usual; an expired/foreign row
|
||||
answers `present: false` (the putter's backstop is re-put). Without
|
||||
`token`, `have` remains a pure existence probe (ADR-008's
|
||||
discipline).
|
||||
- **`blobs/delete`** `{namespace, digests[]}` — operator machinery;
|
||||
`Visibility::Internal`, evaluated under the internal authority
|
||||
context per alkcall ADR-017 (internal calls switch authority context,
|
||||
@@ -72,8 +80,12 @@ Binary channel ops (registered via the `channel_open` marker):
|
||||
|
||||
- **`blobs/fetch`** (`Sub` — the server→client streaming op shape)
|
||||
— `{namespace, digest, ranges?}` in;
|
||||
verified bytes out: the consumer hash-checks each received chunk/whole against
|
||||
the carried digest (ADR-006). Broadcast fanout above the store:
|
||||
verified bytes out: the consumer hash-checks the received whole
|
||||
against the carried digest (ADR-006's whole-blob verification —
|
||||
there are no per-chunk or per-range checks derivable under the
|
||||
canonical digest; ranged fetch responses carry the same ADR-006
|
||||
slice-digest convention `read_range` returns). Broadcast fanout
|
||||
above the store:
|
||||
one reader, store arm + subscriber arms (POC #3 finding A3); late
|
||||
joiners degrade to a normal post-commit `get` — identical bytes under
|
||||
CAS. Slow-subscriber policy (drop-and-late-join) is ops-layer
|
||||
@@ -81,7 +93,10 @@ Binary channel ops (registered via the `channel_open` marker):
|
||||
- **`blobs/put`** (`Sink` — the client→server streaming op shape)
|
||||
— `{namespace, digest?, len?}` offer +
|
||||
byte stream in; server verifies against the canonical derivation
|
||||
before commit. A known-length offer is the encouraged path (one-
|
||||
before commit. **Response: `{token, digest}`** (ADR-012 §2) — the
|
||||
pin token (§Pin hand-over below) and the stored entry's canonical
|
||||
digest (hex), which an unknown-length putter needs for every later
|
||||
op. A known-length offer is the encouraged path (one-
|
||||
pass); unknown-length rides the store's pre-threshold buffering path
|
||||
(ADR-003). **Need half of have/need**: a fetch miss *is* the need
|
||||
announcement — the consumer computes its need set by diffing
|
||||
@@ -96,7 +111,8 @@ the server. The hand-over contract:
|
||||
|
||||
1. the put lands pinned (ADR-005) — the entry cannot be swept while
|
||||
the handler holds the pin;
|
||||
2. the `blobs/put` response returns a **pin token** (opaque handle —
|
||||
2. the `blobs/put` response returns a **pin token** in the specced
|
||||
response shape `{token, digest}` (ADR-012 §2 — opaque handle
|
||||
minted by the ops layer over the pin row the put committed; the
|
||||
token↔(key, owner) mapping is ops-layer state, not durable state —
|
||||
the durable state is the pin row itself, ADR-010 §4); the putter
|
||||
@@ -115,31 +131,40 @@ the server. The hand-over contract:
|
||||
conversion on the server side (token → registered liveness source)
|
||||
is the mechanism the embedder plugs its registry into.
|
||||
|
||||
**Token renewal (ADR-010 §4 — the fleet-TTL race, resolved): the
|
||||
token *is* the pin — there is no second pin state.** The server-side
|
||||
handler's in-process `Pin` guard drops when the `blobs/put` response
|
||||
completes (if it never dropped, the TTL machinery would be dead on
|
||||
the ops path — the pin row's expiry, not the guard's presence, is the
|
||||
fleet liveness signal). Under fleet
|
||||
**Token renewal (ADR-010 §4, wired per ADR-012 §2 — the fleet-TTL
|
||||
race, resolved): the token *is* the pin — there is no second pin
|
||||
state.** The server-side handler's in-process `Pin` guard drops when
|
||||
the `blobs/put` response completes (if it never dropped, the TTL
|
||||
machinery would be dead on the ops path — the pin row's expiry, not
|
||||
the guard's presence, is the fleet liveness signal). Under fleet
|
||||
engines the put's pin is an expiry-carrying row (ADR-008: TTL 60 s
|
||||
default, renewed every TTL/3). The remote putter holding a token is
|
||||
the row's owner and renews by the ops it already speaks: a
|
||||
`blobs/have` probe **presenting the token alongside the digest** (the
|
||||
handler verifies the token maps to the digest's pin row and calls the
|
||||
crate-internal `pin_renew` via the ops module's store-core access —
|
||||
the ops module is in-crate and crosses the seam by construction, the
|
||||
same way the put handler holds the `Pin` guard; `has` itself remains
|
||||
the row's owner and renews via the renewal form of `blobs/have`
|
||||
(ADR-012 §2: one digest, token alongside — the handler verifies the
|
||||
token maps to the digest's pin row and calls the crate-internal
|
||||
`pin_renew` via the ops module's store-core access — the ops module
|
||||
is in-crate and crosses the seam by construction, the same way the
|
||||
put handler holds the `Pin` guard; `has` itself remains
|
||||
a pure existence probe per ADR-008 — renewal is a token-authenticated
|
||||
act on the pin row, not a `has` semantic), or a re-put of the same
|
||||
digest (CAS dedup renews the row). **A putter may pause past the
|
||||
TTL/3 mark (20 s at the default TTL) only if it renews before expiry;
|
||||
a putter pausing past TTL lets the row expire** — the sweeper reaps
|
||||
it (ADR-008) and delete-then-recover (ADR-005) is the backstop: the
|
||||
a putter pausing past TTL lets the row expire** — the renewal probe
|
||||
answers `present: false`, the sweeper reaps the row (ADR-008), and
|
||||
delete-then-recover (ADR-005) is the backstop: the
|
||||
putter re-offers the digest; CAS makes the re-put idempotent. The
|
||||
race resolves to a re-put, never to silent loss. For non-fleet
|
||||
(`local`-engine) serving nodes the token is the in-process guard —
|
||||
nothing expires; the process-crash case is ADR-005's in-flight-put
|
||||
loss with the same backstop.
|
||||
race resolves to a re-put, never to silent loss. **Token validity
|
||||
domain: the minting serving node** (ADR-012 §2) — the token↔(key,
|
||||
owner) mapping is ops-layer in-memory state in the node that minted
|
||||
it (process-lifetime, outliving the handler); renewal probes must
|
||||
reach that node, fleet peers refuse tokens they did not mint (`present:
|
||||
false`, not an error), and a minting node's death before renewal is
|
||||
ADR-005's in-flight-put loss case with the same re-put backstop — a
|
||||
documented embedder duty (route renewals to the serving node).
|
||||
For non-fleet serving nodes (any engine — sqlite, single-instance
|
||||
postgres, `local`, pg-lo; ADR-012 §2) the token is the in-process
|
||||
guard's handle — nothing expires; the process-crash case is ADR-005's
|
||||
in-flight-put loss with the same backstop.
|
||||
|
||||
Placement of the op *registration* (which embedders wire where they
|
||||
want them exposed) matches the alkgit ops pattern (its
|
||||
@@ -186,6 +211,7 @@ fleet-specific op exists.
|
||||
| [006](decisions/006-verification-posture-and-transfer-encoding.md) | Verification | verified-fetch via carried digests; no chunk trees |
|
||||
| [008](decisions/008-trait-size-probe-fleet-gc-and-large-engines.md) | Fleet re-routing topology | non-shared-media fleets route large-blob access through a storage node via these ops; no new ops |
|
||||
| [010](decisions/010-backend-io-seams-and-fleet-state-home.md) | Pin-token renewal | the token is the pin; renewal rides have/re-put; TTL expiry falls to delete-then-recover |
|
||||
| [012](decisions/012-pre-decomposition-consistency-rulings.md) | Wire-shape rulings | put response `{token, digest}`; have's token-renewal form; token validity domain = minting node; non-fleet token scope generalized |
|
||||
| [011](decisions/011-vocabulary-tiers-engines-and-fleet-state.md) | Vocabulary + modes | instance/node/fleet split; kv-only mode named (the re-routing posture's client shape) |
|
||||
|
||||
## Open Questions
|
||||
@@ -204,4 +230,5 @@ deployment).
|
||||
machinery this module rides)
|
||||
- `docs/research/poc-largeblob-findings.md` finding A3 (fanout seam)
|
||||
- ADR-001; [store-api.md](store-api.md); [gc-and-namespaces.md](gc-and-namespaces.md)
|
||||
(namespace-as-resource)
|
||||
(namespace-as-resource); ADR-012 §2 (the wire shapes this doc's
|
||||
op payloads carry)
|
||||
Reference in new issue
Block a user