Expand description
The registry schema migrations (ADR-024) and the co-tenant connection handle.
§The machinery (the shared pattern)
One alkgit-owned stamp table versions the co-tenant schema as a
unit — the record rows, ref rows, the registry_seq singleton,
the mutation-marker rows, and the LFS liveness rows ship and evolve
together, so one integer stamp covers them all (ADR-024 §2). Steps
are ordered, hand-written, and monomorphic per engine twin (the
twins already carry engine-specific SQL; migrations ride them — no
framework, no builder). The runner’s contract, identical for both
twins:
- One transaction per step — the step’s DDL and its stamp advance commit together; a failed transaction rolls back whole and nothing is left half-applied by the failed step.
- Construction-run, loud failure — migrations run when the
adapter constructs its connection, and any unverified failure
rides
OpenError::Migration: a half-migrated store refuses service rather than degrading silently (the fallible-constructor posture ADR-024 §1). Every construction re-runs the runner, so a half-migrated store keeps refusing until the state is real or the store is reset. - Concurrent-open-safe — the stamp is read inside each step’s
own
BEGIN IMMEDIATEtransaction (the write lock serializes racing openers; a loser sees the winner’s committed stamp and skips). A step whose SQL nevertheless fails verifies state against committed data — the alkstore ADR-012 §5 re-key pattern: the step’s effect present ⇒ benign race swallowed (and the stamp healed, converging the store); absent ⇒ propagate. Load-bearing on sqlite, where multi-process co-tenancy of one file is native. - Absent stamp ⇔ fresh store — the stamp table bootstrap creates the stamp row at value 0; a fresh store runs the whole step set and stamps it.
- Additive-first, no refuse-gate in v1 — an unknown-forward stamp is safe to open against, and the runner leaves it untouched (the coexistence contract: explicit column lists, unknown columns/tables unread — ADR-024 §4). A non-additive step is major-version territory and owns its refusal semantics then.
§The engine-neutral logical DDL (the shared schema, both twins)
The initial co-tenant schema, stamped step 1 (ADR-024 §2/§5), in
engine-neutral terms — both engines’ SQL lands in this module over
exactly this logical schema: the sqlite twin’s step set is the
sqlite module below; the postgres twin writes step 1’s pg SQL
(BIGINT sequence columns, CHECK’d visibility text) in its own
module:
alkgit_schema_stamp (id = 1, stamp)— the alkgit-owned stamp row (the machinery’s meta table, beside its own tables).git_repos (repo_id PK, visibility in {'public','private'}, grants, seq)— the record rows: the backend.md §RepoRecordserde shape serialized (grantsis the JSON object identity-id → action array;seqis the row’s mutation sequence number, assigned inside the row-write transaction).git_refs (repo_id, name) → oid, symref_target— the ref rows (POC-O-validated shape; ADR-019 §2). HEAD is a symref row:oidNULL with a set target — exactly the unbornRefLine { oid: None }shape.registry_seq (id = 1, value)— the watermark singleton; every registry mutation bumps it inside the row-write transaction (transactionally exact, ADR-019 §5).repo_mutation_markers (repo_id PK, seq)— the per-push mutation-marker rows (composition-internal, ADR-019 §7: pushes do not touchRepoRecord; the receive flow’s watermark leg; the record schema is untouched).lfs_objects (repo_id, oid) → size, refcount— the LFS liveness rows with the day-one refcount column (ADR-024 §5 — nothing published predates it, so no migration is owed and none should be written; the column’s consumption is wave 9). The table ships in the step set unconditionally — the tables evolve as one composition; thelfsfeature gates the surface consuming it, so an inert table is harmless and the stamp stays unit-shaped.
The stamp table itself is bootstrapped idempotently before the
first read (CREATE TABLE IF NOT EXISTS + INSERT OR IGNORE at
0), so “absent stamp ⇔ fresh store” holds under concurrent opens.
§The co-tenant connection handles (one per engine twin)
The handles’ placement is symmetric across engines — one file-local module per engine twin in this, the ADR-024 machinery home:
CoTenantSqlite(thesqlitefeature) — the thin wrapper over the rusqlite connection that the registry adapter and the refs layer both consume: it opens the co-tenant connection (WAL, NORMAL synchronous, pinned 5000 ms busy timeout — substrate.md §assembly tuning datums) beside the engine-owned schemas on the same file (alkblobs ADR-013 §3’s composition posture), runs the migrations at construction, and hands out the connection for the twins’ blocking SQL (which the composition’s concurrency model bridges off the async executor).- [
CoTenantPg] (thepostgresfeature) — the shape’s other half: the thin wrapper over the tokio-postgres client the postgres twins consume, natively async (no blocking bridge — the shared client lives under atokio::sync::Mutexwhose guard is held for one statement batch or transaction, the twin’s short-held-lock model). Its engine-locally-realized runner (below) rides the advisory migration lock instead ofBEGIN IMMEDIATE.
The postgres twin’s pg machinery also carries the crate’s advisory
lock-key plumbing over the co-tenant schema: advisory_lock_key
(the deterministic key over a scope’s identity string) plus the
named scope-key constructors beside it — the stamp-table/migration
runner’s key, the git_refs CAS scope’s key (git_refs_lock_key,
the refs CAS twins’ and every registry transaction touching the
refs table’s serialization point), and the lfs_objects rows
scope’s key (lfs_objects_lock_key) — every transaction touching
a co-tenant table holds that table’s advisory lock (the wave-9
lock-discipline invariant), and the composition spells the scope
identity through these constructors only, never hand-inlined. The
runner’s step set is the pg step builder (build_steps), step
order monotone with sqlite’s (STEP_VERSIONS).
Structs§
- CoTenant
Sqlite - The co-tenant connection handle — the thin wrapper over the rusqlite connection that the sqlite registry adapter and the refs layer both consume (alkgit owns its tables and its connection, co-tenant beside the engine-owned schemas on the same file — alkblobs ADR-013 §3’s composition posture).