- The OQ-21 branch-mode POC ran (commit 9f520a9 on poc/w10-oq21-lock, dropped): the real lfs.locks API composes over the substrate stack against a real git-lfs 3.7.0 client with lfs.locksverify true. - The mapping decided with a corrected claim: plain co-tenant rows carry the exclusion, not alkstore's named-TTL machinery — the machinery's silent TTL lapse is the two-writer data-loss window, its custody is process-local, and its trait surface carries no enumeration for the API's list/verify verbs (all measured, alkstore 0.1.0); positions stated on TTL lapse (N/A — no TTL) and renew (N/A — no client verb; measured that published renew DOES refresh the exclusion, a new full TTL window from now). - The verify-then-transfer interplay confirmed: a locked-by-another push refused before the batch (no transfer, no residue); the unlock- then-push riding the pre-keyed custody unchanged. - The sqlite contact point measured: lock-traffic hammer vs receive- pack on the co-tenant file — push median ~1.3-1.6x under concurrent lock traffic, zero storage failures (closes the DQ-7 gap). - Records: lfs.md §Lock server note; OQ-21 entry (Investigation resolved, build deferral stands unchanged); OQ-03's substrate publish blocker dropped; README's OQ summary updated; the POC task + tracker completed (taskgraph valid). Verification: cargo test (default + --all-features, live pg green), clippy --all-targets/-D warnings (both feature sets), fmt --check, cargo doc --no-deps, the fuzz corpus gate (13 passed).
30 KiB
status, last_updated
| status | last_updated |
|---|---|
| reviewed | 2026-10-23 (the wave-5 release gate — OQ-19 resolved; the core-side OQ-03 inventory settled) |
Open Questions
All unresolved architecture questions, centrally tracked. Status values:
open (needs resolution now), partially resolved (decision made but a
narrower question remains — named in the entry), resolved (decision
made, ADR recorded), deferred(scope) (waiting on external information
or on a not-yet-existing internal artifact — a booked work item —
the blocked-on condition states which), deferred(unclear) (pieces
exist, shape needs investigation). See docs/sdd_process.md for the
deferral protocol (blocker tasks in tasks/architecture/).
Door type classifies reversal cost: one-way decisions are
expensive/impossible to reverse once published (wire formats, public API
shapes); two-way decisions can be revisited while nothing is published.
Door type does not change urgency — all decisions here need resolution
when their impacts say so; it records how careful the resolution must be.
Deferred / Blocked summary
| OQ | Status | Blocked on / investigation |
|---|---|---|
| OQ-05 | deferred(scope) | ecosystem need for sha256 |
| OQ-03 | partially resolved | first-publish timing (release decision; the core-side freeze inventory settled at the wave-5 gate, 2026-10-23 — enumerated in the gate record; the substrate-side inventory settles at the substrate crate's publish) |
| OQ-16 | deferred(scope) | first cross-assembly record sharing (distributed-git phase; tracker architecture/oq-16-grant-identity-namespace) |
| OQ-17 | resolved (ADR-024) | — |
| OQ-19 | resolved (2026-10-23) | — (all three riders served/dispositioned at the wave-5 gate, or recorded honestly) |
| OQ-20 | deferred(scope) | per-oid pruning of stale LFS reference rows — first concrete row-volume need (repo-lifetime scoping is correct v1) |
| OQ-21 | deferred(scope) | build: first client needing the LFS lock server (lfs.locksverify false is the verified workaround) — the composition-validation half resolved 2026-10-24 (branch-mode POC, posture evidenced) |
OQ-02 and OQ-04 resolved this cycle (ADR-014, ADR-013; §3/§6 amended by
ADR-020); OQ-08/07/09/01 resolved in earlier cycles; OQ-19 resolved
2026-10-23 at the wave-5 review gate (all three riders served; none
retracted). OQ-06 (resolved by
ADR-012: registry-file default) is superseded by ADR-019 — the
registry backing is now the substrate composition and registry-file
is scrapped. The substrate re-baseline cycle (ADR-019..023, 2026-10-19)
resolved the recomposition DQ ledger: DQ-1/2/3/4/6/8 in ADR-019,
the receive-pack wire facts in ADR-020, DQ-5 in ADR-022, the
replication posture in ADR-023; DQ-7's co-tenancy/connection facts
resolved as assembly documentation (ADR-019's negative
consequences, substrate.md §assembly shapes — measured data, tuning
knobs, and the connection budget, not an ADR-shaped decision). The
2026-10-20 crate-shape round (ADR-025, relitigating OQ-09's
granularity sub-decision) made alkgit a two-crate workspace — protocol
core + substrate composition; OQ-03's freeze inventory re-partitions
per crate. OQ-16 is deferred but blocks nothing in
v1 (grants key on cluster-local ids within one assembly).
Theme: composition / crate shapes
OQ-09: Slim-crate model — doors as family infrastructure, git as a payload service
- Origin: user session (2026-09-21); ADR-006, ADR-001, ssh.md
- Status: resolved — ADR-010 (pure protocol crate, the alktty/
alktunnels template): single
alkgitcrate, producer/consumer halves, backend traits with feature-gated gix, no doors, no binary; ALPNalk/git; http mounting → alkhttpgitfeature; git-over-ssh → alkssh (russh scaffolding dropped); the monorepo/binary framing was an init-agent artifact (vision amended). (Amended 2026-10-20: the crate-granularity sub-decision within that resolution was superseded by ADR-025 — alkgit is now a two-crate workspace; the protocol-core framing and everything else in the resolution stands.) - Resolution: [decisions/010-pure-protocol-crate.md] (the
gixengine feature later dissolved — ADR-021 §1; the impls are the substrate crate — ADR-025; granularity superseded by [decisions/025-two-crate-workspace.md]). The full sub-decision set recorded there (ssh deletion, http home, crate granularity, backend-trait surface, binary fate); ADR-025 owns crate granularity hereafter. - Cross-references: ADR-001/006 (superseded), ADR-025, OQ-01, OQ-03, OQ-08, doors.md, backend.md, overview.md
OQ-01: HTTP adapter home and composability (alkhttp git feature vs alkgit-owned factory)
- Origin: user session question (OQ-01, resolved 2026-09-21)
- Status: resolved (subsumed by OQ-09/ADR-010) — the smart-http
stateless substrate stays in
alkgit(IO-abstract); the http mounting (routes, content types,with_extra_routeswiring) becomes an alkhttpgitfeature published after alkgit's first publish. The ADR-006 router-factory shape is superseded; the alkhttp-side feature is the outcome. - Cross-references: ADR-006 (superseded), ADR-010, doors.md
OQ-03: Downstream embedding surface (what "embeds alkgit" means concretely)
- Origin: [overview.md], [transport.md], vision §"ALPN as a service"
- Status: partially resolved — the shape is settled (ADR-010
as amended by ADR-019..021; its granularity sub-decision and
ADR-021's graph superseded by ADR-025): embedding = the
protocol core
alkgit+ the five backend traits (own storage viaalkgitalone, or the shipped composition viaalkgit-substratewith its feature graph:sqlite/postgres/lfs) + optional door features. ADR-025 (2026-10-20) re-partitioned the freeze inventory across the two crates: the core's surface (the five traits, seam types,GitSession, producer half, thegit/repo/*op set, the preamble shapes,Limits, errors) freezes at the core's first publish; the composition's member list (substrate.md §public API — fixes the members, not the identifiers) freezes at the substrate crate's first publish. The identifiers on both sides remain deferred to the publish-time freeze. The substrate-side inventory was seeded at the wave-6 gate (tasks/implementation/substrate/review-wave-6.mdcarries the pinned names so far); the wave-8 composition entry + wave-9/10 surfaces extend it before the freeze. What ADR-025 changed was the inventory's crate assignment, not its members. The prior single-crate pinning history: ADR-012 added thegit/repo/*op set (names + schemas) to the inventory; ADR-013/014 added the push/negotiation trait surface (GitPackIngestbinding,GitPackGen::common_haves); ADR-016 added the native-path preamble wire shapes (the{repo, service}open-op params schema and the git-daemon request-line grammar — the last native-path wire surface); ADR-017 added theGitSessionpublic API; the registry types/schemas draft (backend.md §"Registry types and schemas", review 001 N-3) pinnedRepoRecord,RegistryError, and thegit/repo/*op schemas; ADR-018 pinned the object-storage trait signatures, the shared backend-seam types (RefLine/RefUpdate/RefOutcome/PreparedPush/PushOptions), andStorageError— the backend-seam inventory is complete. The substrate round's additions: the revisedRepoRecordschema (nostorage_root— ADR-019 §8) and the substrate composition's public members (nowalkgit-substrate's freeze set). - Door type: one-way (API freeze is registry-visible to dependents)
- Priority: medium
- Impacts: blocks the first publish only, not implementation. The re-partition also un-blocks the core's freeze from the substrate-side validations (OQ-21's composition POC gated the substrate crate's publish — it resolved 2026-10-24, evidence in the register; not the core's — the core's freeze waits only on OQ-19's wire validations against the real clients, which exercise the core).
- Blocked on: per crate — the core's wire-validation blocker is
cleared (OQ-19 solved 2026-10-23: all three riders served or
dispositioned; the core-side freeze inventory settled at the wave-5
gate —
tasks/implementation/core/review-wave-5.mdcarries the enumeration); the core's publish now waits only on release timing (core/w5-publish). The substrate crate's publish-side architecture blocker is cleared the same way (OQ-21's composition-validation POC resolved 2026-10-24 — the register entry carries the evidence); the substrate's publish now waits only on release timing (the wave-10 gate,substrate/review-wave-10→substrate/w10-publish). Both additionally wait on first-publish timing (a release decision, not an architecture question). The API surface inventory lives in backend.md §public API, transport.md §public API, and substrate.md §public API. - Cross-references: ADR-010, ADR-025, ADR-002, ADR-012, ADR-013, ADR-014, ADR-016, ADR-018, ADR-019, ADR-021 (superseded), backend.md, transport.md, substrate.md
Theme: transport / protocol
OQ-02: V2 multi-round negotiation (ack/NAK logic, wait-for-done retirement)
- Origin: [transport.md], poc2-findings §"does NOT settle"
- Status: resolved — ADR-014 (V2 negotiation ack loop). The
duplex-capture walkthrough against git 2.43.0 (ground-truth negotiation
mock, cross-checked against
fetch-pack.c) resolved the grammar: no-donerounds getacknowledgments(ACK <oid>per recognized have,NAKwhen none, flush — neverready, so FLUSH is always the terminator); thedoneround generates closure(wants) − closure(haves) with no cross-round server state (clients re-send wants + commons every round);wait-for-donestays and the advertisement text is unchanged; the ack check is a new backend-trait method (common_haves) keeping the honest boundary at the seam. Captures:docs/research/negotiation-captures.md. - Resolution: [decisions/014-v2-negotiation-ack-loop.md]
- Cross-references: ADR-003, ADR-004, ADR-005, ADR-009, ADR-014, transport.md §fetch, backend.md §"The trait family" (GitPackGen)
OQ-04: receive-pack (push) — validation gap
- Origin: [transport.md], poc3-findings §"does NOT settle"
- Status: resolved — ADR-013 (receive-pack state machine). The
walkthrough against real
git pushcaptures (git 2.43.0: file://, git://, smart-http, plus raw stdio requests into realgit receive-pack) resolved every listed unknown: the push path is V0-framed by upstream design (no version negotiation, ADR-003 governs fetch only); the advertisement is a V0-shaped ref advertisement (caps on the first ref line,capabilities^{}sentinel only for empty repos) with served setreport-status(-v2) delete-refs side-band-64k atomic ofs-delta object-format=sha1(+push-optionsconfig-gated); shallow request lines are rejected up front for v1 (symmetric with fetch); thin packs accepted with bases from the server odb (default client behavior, no capability); ingestion =Bundle::write_to_directory_eagerly+gix-fsck+ onegix-reftransaction per push (.keep-guarded pack landing) — (the ingestion composition and the pack-arrival shape are amended by ADR-020 on the substrate baseline: unpack-to-loose under the pin bridge, the bounded packless-push peek); CAS timing is unpack-first-then-per-ref (observed upstream order); status report is band-1 pkt-line-framed with inner flush when sideband was selected. Captures:docs/research/push-captures.md. - Resolution: [decisions/013-receive-pack-state-machine.md] (amended by [decisions/020-receive-pack-duplex-amendments.md])
- Cross-references: ADR-003, ADR-004, ADR-005, ADR-009, ADR-013, ADR-020, transport.md §receive-pack, backend.md §"The trait family" (GitRefs, GitPackIngest), doors.md, substrate.md
OQ-05: sha256 support policy
- Origin: [transport.md], git-protocol.md §"Open items"
- Status: deferred(scope)
- Door type: two-way
- Priority: low
- Impacts: none for v1 (sha1 pinned); feature-flag passthrough compiles but is untested end-to-end.
- Blocked on: ecosystem need (a real client/repo requiring sha256) or
upstream gix sha256 maturity; POC-2 left the pipeline hash-generic but
untested. Tracker task:
tasks/architecture/oq-05-sha256.md. - Cross-references: ADR-003, ADR-004
Theme: identity / auth
OQ-08: Registry identity space + vault placement (narrowed)
- Origin: [overview.md], [doors.md], [backend.md]; originally "identity sources per front door"
- Status: resolved — ADR-011 (per-repo authorization): the
identity model is alkcall's resolved
Identitykeyed on its stable logical id (alkcall ADR-025 property, referenced); alkgit stores no identity records; grants live in repo records; the public+read / always-authenticated-write policy is alkgit-core'sauthorizefunction (the clause ADR-007 pinned but the static ACL engine could not express); vault placement resolved as nothing to place in v1 (no credential-shaped material in metadata). (Grant-key id format declared deliberately open by ADR-015 §7 — the cross-identity-namespace question this leaves is tracked as OQ-16.) - Resolution: [decisions/011-per-repo-authorization.md]
- Cross-references: ADR-007, ADR-008, ADR-011, ADR-012, OQ-06, OQ-07, backend.md §GitRegistry, doors.md
OQ-16: Grant-key identity namespace (globally-comparable ids for multi-hub grants)
- Origin: ADR-015 §7; user session (2026-09-26)
- Status: deferred(scope)
- Door type: two-way (schema field format; grants are already opaque strings, so this constrains only the contents, not the alkgit API)
- Priority: low (blocks nothing in v1 — grants key on alkcall's cluster-local id within one assembly, which is the only deployment v1 serves)
- Question: what identity-id format do grants key on when repo
records are shared across assemblies (the distributed/replicator
deployment: contract-watching hubs mirroring grant state)? Today the
id is alkcall's
Identity.id— stable within one cluster, but meaningless across hubs. Candidate shapes:{namespace}/{id}compound ids (the{user|org}/{repo}convention familiar from self-hosted git — e.g.alkdev/alkgitlocally,alkimiadev/alkgitwhen mirrored — extended to identity:gh:alkimiadev,alk:alkdev), did-key / contract-derived identifiers (the planned smart-contract org/user/repo model), or a two-part (authority, local-id) tuple. - Constraints any resolution must satisfy (recorded from ADR-015 §7): grants remain flat sets keyed on opaque strings; alkgit never parses, validates, or interprets the id — collision-freedom is the identity-provider's guarantee; a downstream namespace can be introduced at the assembly seam without an alkgit change.
- Impacts: only the downstream identity-provider seam and the
future platform app / replicator grant-sync format. The v1
authorizepolicy function, the grant schema shape, and the op gate are all id-format-agnostic by construction. - Blocked on: the first deployment that shares repo records across
assemblies (the distributed-git phase; also touches alkcall's
identity model if a family-wide id format is preferred over a
payload-level namespace convention). Tracker task:
tasks/architecture/oq-16-grant-identity-namespace.md. - Cross-references: ADR-011 §1, ADR-015 §7, OQ-08
Theme: storage / metadata
OQ-06: Registry/metadata backing store (gix feature)
- Origin: [backend.md] (was storage.md), ADR-008
- Status: resolved then superseded — ADR-012 (registry backing +
write surface): the alknet repo/adapter pattern applied —
GitRegistryread trait +GitRegistryStorewrite supertrait; default backing was theregistry-filefeature, persistence adapters future/additive. Superseded 2026-10-19 by ADR-019 (the substrate re-baseline): the registry backing is now the co-tenant relational composition over the substrate engines behind the family adapter pattern (engine twins for sqlite/postgres + the in-memory default adapter), andregistry-fileis scrapped outright (no shim, no also-ran fallback — the family's scrap discipline). The read/write trait split itself survives verbatim (the ADR-018 signatures held with zero drift). - Resolution: [decisions/012-registry-backing-and-ops.md] §1–2, §4 (superseded) → [decisions/019-substrate-baseline.md] §3
- Cross-references: ADR-008, ADR-010 (granularity superseded by ADR-025), ADR-011, ADR-019, backend.md §crate and feature model, substrate.md
OQ-17: Relational registry schema migrations (versioning story for the co-tenant tables)
- Origin: POC-R's "not settled" list; ADR-019 → substrate.md (the shipped composition's tables)
- Status: resolved — ADR-024 (2026-10-19): migrations are construction-run on the adapter's own connection, ordered monomorphic per-twin steps one transaction each, concurrent-open-safe via the alkstore race-verification pattern; one alkgit-owned stamp table versions the co-tenant schema as a unit (records + refs + seq + LFS rows); additive-first with the coexistence contract as its declared consequence and no refuse-gate while steps are additive (a non-additive step is major-version with its own ADR owning the refusal semantics); the LFS refcount column ships day-one in the initial schema (no migration owed); the in-memory adapter is stamp-free. No operator surface is owed (ADR-010).
- Resolution: [decisions/024-registry-schema-migrations.md]
- Cross-references: ADR-019 §3, ADR-022 §4, ADR-024, substrate.md, lfs.md, backend.md
OQ-19: Implementation-phase wire validations (unborn-HEAD rider; common_haves over the substrate odb)
- Origin: [transport.md] §ls-refs (the N-5 rider); poco-findings §"what this does NOT settle" + pocx-findings (the NAK-only ack caveat)
- Status: resolved (2026-10-23, the wave-5 review gate): all
three riders validated or dispositioned, none retracted —
(a) the unborn-HEAD symref line served against a real client on both
substrates (
git ls-remote+ an unborn-HEAD clone complete over the duplex git:// and stateless http doors,tests/real_client.rs); the capture corrected the wire emission shape to upstream's literalunborntoken (a zero-oid HEAD makes a real client want the null oid and error the session — ADR-018's amendment note; the inbound parser accepted both shapes all along). (b) the positive-ACK capture landed machinery-side (core/w5-oq19-positive-acks) — validated on both substrates; the substrate-odb facet re-runs in track B wave 7 against the real Find before the substrate crate's freeze. (c) the real-client token inventory (core/w5-oq19-real-client-tokens):include-tagaccepted + honored by omission (un-gated in git's V2 send path, nofetch=slot to retract through),session-id=honestly retracted (never advertised → never sent), the rest declined fail-closed as unimplemented. - Door type: two-way (capability tokens retractable until published) — now frozen with the core's publish (OQ-03 core-side)
- Priority: low (resolved; was neither blocking anything but the freeze)
- Question: two advertised/machinery promises lacked capture-level
validation over the final substrate shape: (a) serving an unborn
HEAD's symref line for
ls-refs=unbornon both substrates with a real client (the capability token was client-accepted in POC-1; the line was never exercised; the unborn fixture is trivial now that refs are rows); (b)common_havesactually recognizing haves over the substrate Find in a multi-round ack loop (POC-X's fetch ran multi-round with NAK-only acks — the refinement is a negotiator optimization, not a correctness need: the trim rides haves-closure). (A third rider joined from the wave-2 gate: the negotiation machine's fail-closed declines of tokens real clients send.) - Impacts: the advertisement text (drop the token if (a) fails) and the negotiation behavior's validation record. Not structural.
- Resolution: the wave-5 wire-validation sessions (the tracker
task
tasks/architecture/oq-19-wire-validations.mdcarries the outcomes per rider; OQ-03's core-side freeze inventory settled at the gate —tasks/implementation/core/review-wave-5.md). - Cross-references: ADR-003, ADR-014, transport.md, backend.md, OQ-03
OQ-07: Admin API operation set (v1 scope)
- Origin: [overview.md], alk-stack.md §"The gitea lesson"
- Status: resolved — ADR-012 (§3, §5): the crate ships a minimal
CRUD op set (
git/repo/{create,delete,update,get}) as thin call ops overGitRegistryStore,Visibility::External, always-on, gated by the global admin scope and per-repo grants. The old "Internal ops over an admin listener" framing is superseded (right mechanism, wrong axis — visible-surface = authorized-surface is served by the ACL). The gate table as written used scope+ownership and is replaced by ADR-015 (themanagegrant tier — admin scope OR manage grant, handler- evaluated; create seeds the creator's{read, write, manage}grants). Ops stay record-scoped; anything beyond repo records (users, orgs) is a downstream platform crate (the recorded split trigger, ADR-012 §5) — the app compiles roles/teams into flat grants (ADR-015 §6). - Resolution: [decisions/012-registry-backing-and-ops.md] §3, §5 (gate amended by [decisions/015-manage-grant-and-op-gate.md])
- Cross-references: ADR-007, ADR-011, ADR-012, ADR-015, alkcall ADR-017/011, backend.md §"The two op kinds"
Theme: LFS
OQ-20: Per-oid pruning of stale LFS reference rows
- Origin: [lfs.md] (the pointer-lifecycle table's scoping note)
- Status: deferred(scope) — the repo-lifetime scoping is correct v1
behavior; per-oid pruning is a future shape awaiting a real need
(the register's earlier
openhere was a classification slip: the blocked-on condition was stated all along, so this was never a resolve-now question) - Door type: two-way (row policy; nothing published)
- Priority: low
- Question: the v1 row store scopes rows to the repo's lifetime (written at reference assertion; removed with the repo or by pointer removal). Rows for oids no longer referenced by any live ref in their repo (history rewritten, branch deleted) linger until repo deletion. Per-oid pruning — a row-reclamation op that drops rows whose pointers are no longer reachable (via the ref-graph walk alkgit already does for object liveness) — is the future pointer-lifecycle shape that reclaims them sooner. The sweep-window semantics make the cost of not having it bounded but nonzero (rows are tiny; the media may be re-protected while any row remains).
- Impacts: only the pointer-lifecycle ops' future surface and the LFS liveness source's precision.
- Blocked on: a concrete need (row volume) or the first LFS-heavy
deployment; the repo-lifetime scoping is correct v1 behavior.
Tracker task:
tasks/architecture/oq-20-lfs-row-pruning.md. - Cross-references: ADR-022 §4, lfs.md, OQ-17
OQ-21: The LFS lock server (lfs.locks)
- Origin: [lfs.md] (the constraint note), POC-L (unimplemented)
- Status: deferred(scope), one layer remaining — the surface
build waits on a client need. The composition validation owed
before the v0.1 publish resolved 2026-10-24 (branch-mode POC,
poc/w10-oq21-lock— posture evidenced, main's tree untouched; the Investigation carries the record). The build deferral stands unchanged:lfs.locksverify falseremains the client-verified posture and no lock capability is advertised. - Door type: two-way (additive surface)
- Priority: low (the build only — nothing remains gated on this entry; the POC's outcome feeds the wave-10 release gate's evidence)
- Impacts: none on v1 behavior —
lfs.locksverify falseis the client-verified posture and no lock capability is advertised. The specs' "the routes and alkstore lock machinery exist and compose" assertion was the unevidenced half (POC-L recorded the lock server as unimplemented); the validation supplied the compose evidence or forces a retraction — it evidenced a corrected posture: the machinery does not honestly carry the API (see Investigation), so the mapping is plain rows. - Investigation (resolved 2026-10-24 —
tasks/implementation/ substrate/w10-oq21-lock-poc, branchpoc/w10-oq21-lock, the five rows insubstrate/tests/lock_poc.rsdriving a real git-lfs 3.7.0 client withlfs.locksverify trueand the LFS test door's lock routes; findings merged here, branch dropped):- The mapping decision: plain co-tenant rows (candidate (b)),
not the lock machinery (candidate (a)). The machinery leg
measured alkstore 0.1.0's
Store::try_lock/Lockagainst the API's semantics: exclusion works and release is clean, but (1) exclusion lapses silently at TTL expiry while the holder's guard is alive — for no-expiry LFS locks that silent lapse is exactly the two-writers-write-one-path event the API's semantics forbid (a holder would need a server-side renew loop against a contract whose exclusion can vanish underneath it); (2) the custody is process-local (Box<dyn Lock>) — LFS locks outlive the connection and the server process; (3) the trait surface carries no enumeration — GET /locks and POST /locks/verify are unanswerable fromtry_lock+ handle alone (the decisive gap: the API is a query surface). (4) The renew contract row measured:renew(ttl)returnstrueand does refresh the exclusion (the holder survived past the original ttl — a new full TTL window from now, per the published 0.1.0 contract); the earlier task-text phrasing "renewdoes not refresh TTL" is not what published 0.1.0 does. A stale guard's release is a no-op (false, not an error). - The plain-row shape: repo-scoped rows
(repo_id, id, path, owner, locked_at)withPRIMARY KEY (repo_id, path)— create = INSERT-if-absent in one BEGIN IMMEDIATE transaction (the cross-connection race serializes on the write lock; the loser answers the standing row — the API's 409); list/verify = plain SELECTs (cursor/limit pagination, the path/id filters; verify is the owner-split); unlock = owner-checked DELETE (+ the client'sforce). Positions on the two contract rows the POC owed: TTL lapse — not applicable, the mapping carries no TTL: locks live until explicit unlock or the repo-deletion leg (which must ride the registry-removal transaction like the LFS liveness rows); renew — not applicable, no LFS client verb exists (the push-time verify is coordination, not renewal; the machinery's renew is moot for this surface). - The four verbs over the stack, real client:
git lfs lock/locks/unlockwalked create/list/unlock over the door's lock routes riding Basic (the batch door's tolerance posture; anonymous rides nothing — locking is a writer's op); the repeat create is the client-visible 409; the non-owner unlock is the 403; both writers' lists show the co-tenant locks with owner names; the verify's ours/theirs owner-split drives the real push's pre-transfer decision. - The verify-then-transfer interplay: confirmed. A locked-by-
another push is refused by the client at the verify step — no
batch, no PUT, no stored content, no reference row (zero
transfer residue); the lock owner's own push rides
oursthrough the full pre-keyed custody chain unchanged (the minted per-action token PUT, the canonical key, the asserted row — the wave-9/POC-L custody pieces, intact under alkblobs 0.2's pre-keyed wire-put world); after the unlock the same push rides the full verified transfer. The lock rows are pure coordination on the same co-tenant file — they touch nothing of the custody chain. Force-unlock policy (any writer vs a manage grant) is recorded as a build-time call; the POC's door served the permissive reading. - The sqlite contact point, measured (the co-tenant file: the
alkstore engine + the alkblobs kv tier + the registry/refs tables
- the lock rows): three lock-traffic hammers (~670–830 full verb cycles over ~5 s each run) concurrent with eight real receive- pack pushes — push median 158 ms → ~200–250 ms (~1.3–1.6x), p95 160–230 ms → ~290–850 ms (one run's tail outlier), hammer verb cycles median 1–2 ms / p95 3–4 ms, and zero storage failures across runs (the busy windows absorb; WAL readers do not block). The contention is real but bounded — lock traffic co-tenants fine with receive-pack on the single sqlite file (closes the DQ-7 gap: the measured story did not include lock traffic).
- Outcome: posture evidenced — the build deferral stands unchanged; a future build carries one explicit ask upstream (the lock rows' DDL belongs in the migrations step set, ADR-024, and the repo-deletion leg in the removal transaction — both build-time placements, nothing OQ-gated).
- The mapping decision: plain co-tenant rows (candidate (b)),
not the lock machinery (candidate (a)). The machinery leg
measured alkstore 0.1.0's
- Blocked on: the first deployment/client that needs the lock API (contention control for shared big-media files). The validation's blocker is cleared (2026-10-24).
- Cross-references: lfs.md (§Constraints and posture — the Lock server note), doors.md, OQ-03 (the publish gate — its substrate half unblocked by this resolution), OQ-19 (the sibling owed-validation, same honesty rule)