Files
alkgit/docs/architecture/lfs.md
T
glm-5.3-flash 70694708ee docs(architecture): the wave-10 release gate — green; substrate/lfs flip stable
The substrate release gate (substrate/review-wave-10) verdict, landed:
- OQ-21 outcome confirmed (posture evidenced; build deferral stands
  verbatim; OQ-03's substrate publish-side blocker stays dropped)
- the substrate-side OQ-03 freeze inventory settled and reconciled
  against the crate root's actual re-export list (recorded for
  w10-publish to freeze)
- the release-readiness surface verified (docs, matrix incl. the three
  off-matrix clippies, CHANGELOG/README, dry-run re-run; F-w10-B4/B5
  + N-3 dispositions confirmed)
- all flagged recorded interpretations confirmed (O-g future work,
  the through-store probe stamp, mem_registry removal, testutil home,
  LIVE_GATE stay)
- the test evidence read green; review 006 R-1..R-7 verified absorbed

Inline findings fixed in-session:
- D-1: the architecture README's status-index cells were stale since
  the wave-5 flip; made truthful with the substrate.md/lfs.md
  `stable` flips
- D-2/D-3: two task-record slips (the test-evidence Summary's
  O-b/O-c cross-reference; the placement task's unticked ACs)
- D-4: DEFAULT_SCHEMA + LOCKED_OUT_RETRY_SECS promoted to the
  crate-root re-export list (both module-path-public, never root
  re-exported — the freeze authority byte-exact; additive pre-freeze)

Verification: cargo test (376/186), --all-features 379, the live-pg
harness run 212 passed 0 skips, the fuzz corpus gate 13 targets,
fmt + five clippy passes (-D warnings), cargo doc 0 warnings,
publish dry-run green, taskgraph validate (90 tasks).
2026-10-24 21:54:12 +00:00

12 KiB
Raw Permalink Blame History

status, last_updated
status last_updated
stable 2026-10-24

LFS: the large-media surface

What this is

Git LFS support for alkgit assemblies: the pointer-file flow, the media storage placement, the door mapping, and the GC liveness wiring. LFS is v1 scope (ADR-022) because the substrate makes it nearly free: alkblobs' two-tier shape is exactly "small objects in kv, big media in large tier" (local files on sqlite, pg-lo on postgres), and the media protocol is a small batch/transfer adapter. Everything here rides the substrate stack in substrate.md; the decision rationale is ADR-022.

Evidence: POC-L (pocl-findings.md) — real git-lfs 3.7.0 push/pull/fresh-consumer round trips, both door bindings, mem/sqlite/ postgres engines, GC sweep behavior. Requirements reference: rudolfs (/workspace/rudolfs) — batch/transfer protocol behavior and Range serving (facts only, license-clean; its no-auth posture is the opposite of ours).

The model in one paragraph

A pushed git lfs push commits pointer-file blobs — ordinary git blobs under the git-object family, handled by the normal git path with no LFS awareness. The media lives in alkblobs, keyed under Algorithm::GitObjectSha256 with the raw content as the stored preimage: key = [0x04] + sha256(content), byte-exact the LFS oid (alkblobs ADR-018's preimage-identity derivation) — so the key is formable in advance from the pointer's metadata, oid addressing needs no translation table, and every read verifies under the same rule's discipline: the kv tier's materialized reads re-derive at the store, while the large tier (where the media lives) carries the per-window slice digests mid-flight — the end-to-end oid conclusion is the batch adapter's whole-span oid check (the wave-9 gate's F-S3 correction to "verification-on-get re-derives every read"; ADR-022's Status carries the amendment). Which repos reference which media is not a store fact (the CAS pool is global — their ADR-017) — that reference map is alkgit's co-tenant liveness row table, wired into the sweep chain by default.

The three surfaces

1. The batch/checking adapter (the JSON protocol)

The batch API (POST <url>/objects/batch) is an alkhttp-route surface — the door maps it onto alkblobs' ops through its own OperationContext (identity + ownership provider wired — alkcall ADR-048's loud default is the guard against the static-fallback leak).

  • Existence check: blobs/stat with the formable key (tag + oid). No have use — that is the replication diff verb; stat answers the batch's objects[].{oid,size,authenticated} shape.
  • Authorization: the door's per-repo grant governs batch access (ADR-011 read/write actions map to batch upload/download); anonymous-public repos ride the door-synthesized anon identity composed onto the ops ACL (POC-L's validated composition). Push is always authenticated, as everywhere.
  • hash_algo answers sha256 only (409 otherwise — the batch is pinned to the media algorithm alkgit keys on); verify is not served; the batch request's transfers negotiation is the door's to read — refuse a request whose list excludes basic before the batch reaches the adapter (the adapter answers basic and carries no negotiation input); hrefs are door-minted absolute URLs carrying per-action Authorization tokens — the LFS hand-off, client-verified.
  • Batch auth accepts Basic userinfo on this surface (alkhttp ADR-073's door tolerance) — real LFS clients authenticate the batch with Basic userinfo; transfer actions carry the minted Bearer tokens.

2. Transfer (the byte protocol)

  • Upload (PUT <href>): mapped onto the wire ops blobs/put with the mandatory pre-keyed claim — key formed from the pointer's oid, len from the batch's size (size always present; a size-less offer is refused at the door). Restored to the wire by ADR-022 §2 — the pre-keyed form is satisfiable for LFS, which is exactly what identity-family keying buys. A mislabeled body is refused at the stream's derivation check: no stored content, no held pin.
  • Download (GET <href>, Range-bearing): mapped onto the wire ops blobs/fetch, ranged arm — per-window slice digests verified mid-flight (the ranged arm's answer; the door owns window splitting; measured: 45 MiB as two 32/13 MiB opens, byte-exact). The whole arm is unbounded at establish, so the door budgets it (per-assembly object-size ceiling); the ranged total is bounded by fetch_max_total_bytes (an OpsConfig knob, default 64 MiB — raise per assembly to trade opens for windows).
  • Bindings: for the embedded door, DirectOps — the door binds the ops machinery in-process (it holds the OpsContext and invokes the op registry directly, no loopback), the recommended form (POC-L's coined term, pocl-findings.md §"where do transfer/token hand-offs bind best"; measured ~25 ms per-request vs ~80 ms through the per-request loopback bridge); the loopback alk/channels bridge stays the shape for a door that must exercise the real ops byte-path (fleet/remote consumers) — with the pre-keyed upload restored it now works in both directions (the read-only limitation was blob-family keying's artifact, superseded by ADR-022 §2); an in-process embedder that binds the store directly uses the facade seam put_as(GitObjectSha256, len, stream) / read_range with no ops plane at all.
  • Token layers compose as measured: door-minted href tokens (per-action capabilities — lfs:<hex> Bearer) over alkblobs PinTokens (store custody, mint/confirm). The hostile upload-loop threat shape is met by the sibling's mint-boundary admission cap (RegistryFull → MINT_REFUSED — alkblobs ADR-019); fleet forms give pin-row TTLs by engine configuration.

3. The pointer lifecycle / liveness table (alkgit-owned)

One co-tenant row per reference: (repo_id, oid, size).

  • Written when a reference is asserted — at batch check/upload time (the moment the server learns the pair) — into the same relational store as refs/registry (substrate.md's composition; its own transaction). No stream event: the reactive halves' publish leg is record-write-scoped (ADR-019 — refs and liveness rows ride their own durable surfaces; consumers of repo-record changes need neither).
  • Consumed by alkgit's shipped LFS LivenessSource, wired into the sweep chain by default (ADR-019 §6; lfs feature default-on — fail-safe: an unregistered source is the dangerous default for a mixed deployment).
  • Cross-reference sharing: the same content in two repos is one key with two rows; the source must re-protect the key while any row references it (POC-L's refcount requirement — a crude second scan in the POC, replaced by the shipped design; the refcount column ships in the initial v1 schema, ADR-024 §5 — no migration owed).
  • Orphans (rows gone from refs, content unreferenced) are sweep-eligible: the store's sweep-window stages then deletes them (POC-L: orphaned entry deleted, referenced survive).
  • Repo deletion: the rows go with the repo (same removal as refs/registry); media becomes unreferenced and sweeps — nothing LFS-specific exists here.
  • PinConverter stays alkblobs' sync-only/failure-free/Key-only contract — rich reference metadata lives in these rows, never in the converter (POC-L's structural fact).

Constraints and posture

  • Engines: LFS ⇒ a dual-tier constructor (dual_tier_sqlite_local on sqlite; dual_tier_postgres_pglo/+local on postgres; dual_tier_mem_local for completeness). kv-only shapes refuse over-threshold puts — the deployment fails loudly at the first big upload, which is the right failure (ADR-022 §6).
  • Isolation: the CAS global-pool caveat applies unchanged — media is cross-reachable by digest knowledge; per-repo isolation means per-trust-domain store instances (the accepted posture, ADR-022 §7).
  • No native LFS op set of alkgit's own: git-lfs speaks HTTP only; the door is the alkhttp lfs feature (family infrastructure, after alkgit's publish — doors.md's sequencing rule). The native plane LFS rides is alkblobs' own ops.
  • Route shapes (empirically pinned by POC-L, and the static tables the door mounts — no route fallback — so these are the feature's routes): batch POST /{repo}/objects/batch (the /info/lfs prefix is a gitea URL-embedding convention, not a client rule — with lfs.url = http://host/<repo> the client POSTs at {url}/objects/ batch); transfer PUT/GET /{repo}/lfs/objects/{oid} — hrefs are door-minted absolute URLs pointing at these static transfer routes; two-segment repo ids enumerate patterns (same caveat as the git feature, backend.md §repo-id grammar).
  • Encryption: non-goal (storage of pointer references and media is custody-neutral; any at-rest encryption story rides alkvault and the substrate's own posture, not this surface).
  • Lock server (lfs.locks API): not served v1 — lfs.locksverify false is the client-verified workaround; a future additive surface. Its composition is now evidenced (OQ-21's validation POC, 2026-10-24 — branch mode, poc/w10-oq21-lock, main's tree untouched; the record lives in open-questions.md OQ-21's Investigation): the honest mapping is plain co-tenant rows — repo-scoped (repo_id, id, path, owner, locked_at) with the INSERT-if-absent primary key as the exclusion, one BEGIN IMMEDIATE transaction per verb — not alkstore's named-TTL lock machinery (whose silent exclusion lapse at TTL expiry is the two-writer data-loss window, whose custody is process-local, and whose trait surface carries no enumeration for the API's list/verify verbs). Real-client evidence: the four verbs (create/list/verify/unlock) over the batch door's auth posture (Basic tolerance; anonymous rides nothing — locking is a writer's op); a locked-by-another push refused before the batch (no transfer, no residue); the unlock- then-push riding the pre-keyed custody unchanged; sqlite contention measured (push median ~1.3–1.6x under concurrent lock traffic, zero storage failures — the busy windows absorb).

Design Decisions

ADR Decision Summary
022 LFS keying + door + liveness the decisions this doc details
019 GC wiring the sweep chain the liveness rows feed
021 Feature graph the default-on lfs feature (placement re-homed: alkgit-substrate — ADR-025, which supersedes this ADR's graph)

Open Questions

  • OQ-20: per-oid pruning of stale reference rows (beyond the repo-lifetime scoping this spec pins — the pointer-lifecycle ops' future shape).
  • OQ-21: the LFS lock server (lfs.locks API) — surface build deferred on a client requirement (lfs.locksverify false is the verified workaround). The composition-validation half resolved 2026-10-24: the POC evidenced the plain-row mapping and the verify-then-transfer composition (see the Lock server constraint note); the register entry carries the split.
  • OQ-17 (resolved — ADR-024): the liveness refcount ships in the initial schema; the co-tenant tables' migration story is construction-run/version-stamped/additive-first.

References

  • ADR-022 (the decision record — read this first), substrate.md (the composition), backend.md (seam + repo grammar), doors.md (door sequencing)
  • alkblobs ADR-018 §1/§3 (preimage-identity derivation; the stat semantic), ADR-017 there (global-pool caveat), ADR-019 there (mint admission cap), ADR-020 there (pre-keyed wire put)
  • docs/research/pocl-findings.md (the gate evidence, route/auth facts, custody postures, mid-flight verification measurements)
  • /workspace/rudolfs — the requirements reference (protocol facts only; license-clean — see docs/research/reference-policy.md)