Files
alkblobs/docs/reviews/002-first-consumer-review.md
T
glm-5.3-flash 2f3b0d4c27 review(wave-13): the first-consumer gate — review-002 dispositioned, gates green, the publish hold lifts
- C-1 verified end to end: the one-way door's bytes read against
  ADR-018 line by line and exact (registry 0x03/0x04, preimage-identity
  derivation, fleet threading per §Decision 7); interop green against
  the real git CLI
- C-2 verified: ADR-019's mint-boundary cap + the typed MINT_REFUSED
  answer; the attack shape on paper; "not silence" exceeded
- C-3/C-4 verified as chartered (busy_timeout=5000 pinned; KeyBytes)
- review-002 stamped closed with disposition pointers; the prototype
  branch poc-o-git-objects deleted after parity confirmation
- Minor inline fixes: the CHANGELOG [0.1.0] entry absorbs wave 13
  (verified counts 218 default / 473 all-features, ADR-018/019 refs),
  the README names the git-object family honestly
- Stamps: the plan's wave-13 row + frontmatter completed,
  AGENTS.md now publish-ready

Verification: cargo test default 155+54+7+2; cargo test
--all-features vs the live pglo-poc harness 338+125+7+3; both clippy
variants -D warnings clean; cargo fmt --check clean; cargo doc
--no-deps clean; fuzz corpus replay 7/7; cargo publish --dry-run
--allow-dirty green (94 files).
2026-10-18 00:27:05 +00:00

17 KiB

status, last_updated, dispositions, reviewed_artifacts, tool, reviewer, scope_note
status last_updated dispositions reviewed_artifacts tool reviewer scope_note
closed Closed 2026-10-18 — the wave-13 review gate stamps the round dispositioned: C-1 by [ADR-018] + the landed family (5a34759, 9134d44), C-2 by [ADR-019] + the landed cap/posture (61966b6), C-3 by the pinned busy_timeout (a5bebc6), C-4 by the KeyBytes/overloads (9c33d79), the alkcall bump (a575588); the prototype branch deleted after parity confirmation. Original scope note: report-only evidence; each finding was recommendation-shape, the addressing sessions re-verified the anchors and re-derived the decisions.
C-1 C-2 C-3 C-4 prototype-branch
ADR-018 (docs/architecture/decisions/018-git-object-derivation-family.md) + landed in c1-git-object-family-impl — all five decisions owned, fleet threading included; verified at the wave-13 gate ADR-019 (docs/architecture/decisions/019-token-registry-admission-posture.md) + landed in c2-token-custody-posture — the (a)+(c) split: the mint-boundary cap (RegistryFull / MINT_REFUSED) and the embedder-facing custody posture; (b) rejected on the record landed in c3-sqlite-busy-timeout — busy_timeout=5000 pinned at open (src/sqlite.rs), the contention posture contracted landed in c4-borrowed-key-overloads — Key::key_bytes() + KeyBytes; the review's local.rs:899 anchor corrected in the task notes (a #[cfg(test)] helper, not a serving path) poc-o-git-objects (f0b045c) deleted 2026-10-18 — scratch reference; parity confirmed by c1-git-object-family-impl's re-derivation (registry/derivation byte-for-byte, CLI-validated interop green); not merged, per the review's own framing
alkgit POC findings (the consumer evidence of record): /workspace/@alkdev/alkgit/docs/research/pocl-findings.md (2026-10-16), poco-findings.md (2026-10-17), pocr-findings.md (2026-10-17), substrate-recomposition.md (the upstream-ask ledger, §Upstream-push candidates)
src/ anchors re-verified this session against the working tree (currently checked out on the prototype branch poc-o-git-objects, f0b045c; main = 253d90e — branch-dependent anchors are marked)
prototype branch poc-o-git-objects (the DQ-1a working reference — NOT proposed for merge as-is; see C-1)
desktop review of external POC evidence + targeted source verification (grep/sed) of this session's anchors; no full source re-read (review 001's pass is the standing whole-surface record) alkgit consumer seat — the first consumer of the store's LFS (POC-L), git-object (POC-O), and registry-reactivity (POC-R) surfaces; findings/asks only, resolutions belong to their own sessions pre-publish consumer review. Covers only what the POC rounds surfaced; review 001 (the storage-surface general review) remains the crate's own full-surface record. Everything here is a finding-to-verify, not a decided change.

Review 002 — The First Consumer's Evidence (alkgit's recomposition POCs, pre-publish)

Purpose

Record the findings/asks alkgit's three recomposition POCs surfaced against this crate, before publication, while every still-private change is cheap. C-1 and C-2 are the publish-hold reasons; C-3 rides the same round; C-4 is a non-blocking ride-along candidate.

The three POCs were full end-to-end consumption of real surfaces:

  • POC-L — a real git-lfs 3.7.0 client (push/pull/fresh-consumer clone) against the ops surface (blobs/stat|have for the batch, blobs/fetch|put for transfer) over mem / dual_tier_sqlite_local / dual_tier_postgres_local; ranged 45 MiB fetch with per-window slice digests; 70 MiB whole fetch; sweep with an external liveness table.
  • POC-O — this crate as a git object store: git clone/git push 2.43.0 end-to-end from substrate-only storage, thin-pack ingest with bases resolving through a custom gix_pack::Find over the store, packs stored as large-tier blobs served through read_range windows, GC reclaim of cached packs and orphans.
  • POC-R — the reactive/GC machinery consumed by a co-tenant registry composition: pin + bridge + sweep through alkstore's scheduler/queue/leader-lock chain, cross-process.

The consumption itself validated the published design surfaces (verification-on-get survives real ingest; the ranged arm's establish-time bounds held; the pin/bridge/sweep composition worked under a real receive flow; ADR-017's global-pool caveat is the one posture the consumer had to design around). What follows is what the consumption wants changed.

C-1 [publish-decision] — The git-object derivation family: extend the hash registry to non-blob git forms (DQ-1a)

Finding. alkblobs' canonical hash is the blob-only derivation H("blob <len>\0" + content) — trees/commits/tags derive with "<type> <len>\0" prefixes, so git's non-blob objects cannot be stored under their own oids. git's oid function is uniform across kinds, so the extension is one derivation family, not a kind matrix: key = [tag] [digest] with two new registry bytes (0x03 git-object-sha1, 0x04 git-object-sha256), and the stored preimage is the loose-object form itself (<type> <len>\0 + content — the preamble lives inside the value). Because a digest lookup cannot carry a kind, read-side verification re-derives with key.algorithm() over the stored preimage byte-identically — verification-on-get, dedup-by-construction, and external-oid addressing (key = tag + oid, no map) survive unbroken.

Evidence. Working reference: the throwaway prototype branch poc-o-git-objects (f0b045c) — Algorithm members, derivation arms + streaming hasher arm, put_git_object(algorithm, len, stream) (the known-length put path; a git object's length is always known up front), key decode of the new tags, and tests/git_interop.rs validated byte-exact against real git hash-object -t (tree/commit/ tag round-trips, dedup, and the negative: the same oid is unreachable through the blob tags — no cross-family collision). End-to-end: a custom Find over the extended store drove real clone and push (PACK-O). main carries none of this (0 hits for the family in main's src/hashing.rs); the extension is branch-only today.

The parts the prototype does NOT carry (the addressing session's work):

  1. Fleet put paths — the fleet branches' hasher sites still hardcode the canonical blob derivation (representative anchor, verified this session: src/local.rs:899 — Key::derive(content, content.len() as u64)). The derivation must thread through the fleet/non-fleet hasher sites uniformly. Mechanical per the POC, but it is the merge-time work.
  2. The ops surface's answer to the kind question. Digest::from_git_oid_hex (and any 40-hex → which family? constructor) cannot be answered by digest length alone once non-blob forms share the digest space. The ADR owns this decision; unprototyped.
  3. The stat semantic — in the extended registry, stat returns the stored preimage length (the loose form, preamble included), not the content length. A deliberate, documented difference from the blob family; pin it in the ADR.
  4. ADR-002's domain-separation invariant — this family is preimage-identity hashing (no store-added separation; git's own header is the separation, inside the preimage). It fits ADR-002 only as an annotated exception; the ADR text should carry the annotation explicitly, not leave the invariant silently bent. The invariants that survive verbatim: digest length fixed per algorithm, rejects-never-guesses key decode, total order (0x03/0x04 sort after the blob members), verification-on-get.
  5. Namespaces — git oids are global by nature; the global-pool caveat (ADR-017) applies unchanged. The consumer accepted that posture for git objects on the same terms as its LFS acceptance; the ADR should state the posture for the git-object family explicitly.

Decision framing. One-way door — key bytes persist in kv rows, local files, and pg-lo companions. Pre-publish is the cheap window: after publishing, this is a 0.2 registry extension with stored-shape migration weight. The store's own ADR conversation owns the merge; the prototype branch is a scratch reference to re-implement from, not to merge.

Verification status: anchors checked this session (branch state, main's absence of the family, the local.rs derive site); the byte-exact claims rest on the branch's CLI-validated tests and the POC-O e2e — re-verify by running the branch's tests during addressing.

C-2 [security posture, publish-decision] — The S-5 admission cap: the concrete attack shape now exists

Finding. Review 001's S-5 named the unbounded capability resources family and fixed the wire-schema part (the token field's maxLength/pattern, 437ab7f); the admission cap on the token registry was judged there as "a mint-contract policy decision, not security-round material" and recorded as known future hardening (module doc at src/ops/tokens.rs:182-191 — process-lifetime retention posture + the deferred admission cap). The consumer's POC supplies the missing input that judgment said it was waiting for: a named, client-reachable attack shape.

The shape (POC-L, tests/sweep.rs + the loopback binding): mint_custodial(namespace, key, guard) binds a put's guard into the token; the confirm act is left to the caller. On the fleet shape a TTL bounds the held pin. On a non-fleet store an unconfirmed token holds its pin indefinitely — custody that never expires, registered per mint, with no cap on mints. The LFS deployment shape makes the loop realistic: a hostile client POSTs uploads whose content never matches the claimed oid (or abandons the confirm step after each mint) — each minted token is a held pin and a registry entry, so the loop is unbounded pin-holding plus unbounded registry growth on any embedder whose door mints tokens per request without confirming.

Recommended shape recorded (to be re-derived, not adopted as-is): an admission cap at the mint boundary (registry entry count and/or held-pin count, engine of the cap a registry-level knob), and/or the fleet-form TTL posture made available (and loud in the docs) for non-fleet embedded deployments whose door cannot prove confirm. The review 001 judgment ("a refused mint answers a committed put") still stands as the counter-consideration: the cap must answer the already-verified put honestly, which is exactly why it is a mint-contract decision and needs its own session.

Decision framing. Publish-decision: either land the cap pre- publish, or the ADR/docs record the non-fleet no-confirm custody posture as an explicit embedder responsibility with the attack shape on paper. What should not happen is publish with silence — the current state is the worst of both (deferred-and-undocumented).

Verification status: the attack shape is POC-L's recorded, deliberately-open custody leak (it was NOT patched in the POC — put on record instead); the anchors (tokens.rs mint/custody/registry sites) were re-read this session. Re-verify during addressing by reproducing the loop against a mem store.

C-3 [contract, ride-along] — sqlite busy_timeout is coincidence, not contract

Finding. The sqlite engine sets its durability posture explicitly — PRAGMA journal_mode = WAL + PRAGMA synchronous = NORMAL (src/sqlite.rs:156-163, verified) — but sets no busy_timeout. Contention behavior rides rusqlite's implicit 5-second default, which happens to equal alkstore's explicit 5000 ms (alkstore-sqlite sets its own). The first consumer's deployment shape is a co-tenant assembly on one sqlite file (alkstore machinery + alkblobs kv + consumer-own tables), where this store's writers serialize behind every other co-tenant writer — the durability/contention posture is load-bearing there and should be a visible contract, not a library-default coincident.

Recommended shape (to be re-derived): an explicit pragma (a pinned constant, or a knob on the engine options alongside the durability posture) + the module doc line. Small; rides the publication round.

Verification status: anchor verified this session (the pragma block; absence of any busy_timeout in the engine). The co-tenancy claims are POC-O's/DQ-7's measured record (no contention observable at POC scale) — independent of this ask's merit.

C-4 [nit, non-blocking] — &Key borrowed-key overloads on serving paths

Finding. Key::as_bytes() allocates (src/keys.rs:55 — returns Vec<u8>), and hot-path callers reach for it per call (src/local.rs:899 is a serving-path derive+as_bytes site). The consumer's serving loops (custom-Find pack generation, ranged serving) pay it once per object lookup — measured harmless at POC scale, which is exactly why this is a nit. Borrowed-key overloads (&self -> &[u8] slices where ownership is not needed) would drop the per-call allocation on serving paths.

Recommended shape: ride along with the next touching change; not worth its own session.

Verification status: anchor verified this session. The "non-load-bearing" judgment is the substrate-recomposition ledger's, not independently re-measured.

What this review asks for, in one line each

# Ask When Blocking publish?
C-1 git-object derivation family (0x03/0x04; loose form as stored preimage) + fleet threading + ops kind decision + ADR-002 annotation own ADR + implementation session the held-open reason
C-2 token-registry admission cap (or the non-fleet custody posture on paper with the attack shape recorded) own session, pre-publish yes in substance (posture must be documented before publish even if the cap defers)
C-3 explicit sqlite busy_timeout (knob or pinned constant) ride-along no
C-4 &Key borrowed overloads ride-along no

Disposition space (for the addressing sessions)

Per the operator's framing: this review records findings and recommendations only. Each C-item gets its own addressing session that re-verifies the anchors, re-derives the decision, and either implements or dispositions with a pointer to where the record lives (ADR, module doc, or the upstream-ask ledger in alkgit's substrate-recomposition.md). The publication gate for the crate is C-1's and C-2's outcomes.

Disposition (2026-10-18 — the wave-13 review gate's stamp)

Closed with every ask addressed. The one-line-per-ask table's outcomes:

  • C-1 — [ADR-018] accepted (all five decisions owned with rejected alternatives named; fleet threading fixed as Decision 7, not a follow-on) and the family landed (c1-git-object-family-impl: registry/derivation/hasher arms per src/hashing.rs, key decode/order per src/keys.rs, put_git_object via the derivation-parameterized put_known_as, fleet threading with exact-count sweep-outcome tests, ops-surface kind delta as docs made honest, interop green against the real git CLI). The one-way door's bytes read against the ADR line by line at this gate: registry bytes 0x03/0x04, the preimage-identity derivation, the inert len parameter, and the blob-family-only from_git_oid_hex contract are exactly as ADR-018 pins them.
  • C-2 — [ADR-019] accepted and landed (c2-token-custody-posture): the (a)+(c) split — the mint-boundary admission cap over live entries, refusing-not-evicting, typed as the sixth error-family member RegistryFull and answered on the wire as the declared MINT_REFUSED (committed digest in the details, no token, no custody); the non-fleet custody posture documented embedder-facing with the attack shape; the non-confirming loop reproduced against a mem store at both altitudes; (b) the non-fleet TTL rejected on the record.
  • C-3 — landed as chartered (c3-sqlite-busy-timeout): busy_timeout = 5000 pinned at open, the contention posture joining the durability posture as stated contract, verified by the pragma test.
  • C-4 — landed as chartered (c4-borrowed-key-overloads): Key::key_bytes() + the KeyBytes Copy wrapper; the serving-path borrow temporaries converted, zero behavior change. Anchor correction recorded: the review's src/local.rs:899 is a #[cfg(test)] helper, not a serving path.
  • alkcall 0.10 — the manifest bump landed (a575588); the survey's no-impact conclusion held (no code adaptation).
  • The prototype branch — poc-o-git-objects (f0b045c) deleted after parity confirmation: a scratch reference re-implemented from, never merged; the reference material now lives in the ADR and the landed, CLI-validated surface.

The publish hold lifts; publication runs manually, coordinator-side.