Repository navigation
Amendment 22 act 2: the SQL register under lanes-edit.sh — mode=primary writers in one transaction each, the estate switch and lanes/STORE, the write journal and the renderer (one mirror commit per write), exit 69, fail-closed readers, lanes-index import/rollback, SERIALIZABLE claim order, and the agreement tests #165
Description
Governing issue for Amendment 22's adoption act 2 — the tooling — under the amendment ratified by Brett Heap on 2026-10-05 ("ratify 50"; brettheap/new-workstation#50): the SQL register — primary where a workstation is configured for it, the Git files its rendered mirror; no fallback writes. It builds on Amendment 14's act 2 (#160) and shares the schema with #161/#162; it lands AFTER #160.
What lands here (the amendment's act 2)
- The storage layer under
lanes-edit.sh: on a workstation whoselanes-index.confsaysmode=primary(Postgres only; SQLite refused), every register write —claim,release,log(every verb),set-row-state,append-line,add-row,rename-lane,retire-rows,archive-rows,migrate-state-cells,append-session-id,replace-in-row,request-handoff,commit, and the managed projection writer — is ONE transaction. Unreachable or refused → exit 69 (EX_UNAVAILABLE; confirm free in the exit table), naming the store and the error, nothing written anywhere, never the files instead. The prompt guard still answers 2. - The estate switch (S1): a database row plus the marker
lanes/STOREin the workspace repository (primary, the database's name without credentials, the import commit, the UTC). A files-mode writer whose pulled tree carries the marker refuses (69, "this estate's register is SQL; configure mode=primary"); amode=primarywriter whose transaction finds the switch notprimaryrefuses the same way. Every row carries the mode that wrote it last. - The write journal and the renderer (S2): each transaction appends sequence, the write's commit message as today, and its line edits; the writer forks the renderer after commit; the renderer commits every sequence the mirror lacks, in order, one commit per write, message +
Register-Seq: <n>trailer; renderslanes/LANES.md,lanes/log/<lane>.md,lanes/aliases.tsv,lanes/archive/and touches nothing else; write-behind, never waited on; loses a push race → discards its unpushed render commits and re-renders onto the new tip, never a rebase; a commit underlanes/it did not make STOPS it (status: diverged at <sha>). Direct file writes (commitincluded) refuse onmode=primary. - Readers (S3): every act and guard reads the database inside its transaction and fails closed (69; guard 2); the SessionStart block, the launcher's bare window reads, and the
lanes/historylistings may read the mirror and sayread: mirror at <sha12>, <k> sequences behind or unknown (sql unreachable: <why>). A reader says which store answered when it is not the default. - The managed seam (S4):
managed-projectionanswers 0/8/1 from the database onmode=primary; unreachable → 1 (UNKNOWN), never legacy. lanes-index import --at <commit>androllback(S5): import loads the tables and the journal's first sequence from the workspace repository at a commit, renders into a scratch tree, REFUSES unlessgit diff --quietbetween the commit'slanes/and the render; the cutover sets the switch, pushes ONE commit addinglanes/STOREas a fast-forward of the imported commit (aborting ifmainmoved underlanes/), and flipsmode=primarywhere act 3 configured. Rollback: switch →files, render to current + byte-identical, ONE commit removinglanes/STORE, clearmode=primary; refused when the database is unreachable unless the mirror was last shown current.- Ordering (S6): the claim pre-check and the
CLAIMEDinsert run SERIALIZABLE (or under an object lock); the loser exits 2 writing nothing;CLAIM-LOSTis no longer produced; HOLD/LANDING ordered by commit sequence; UTC from the database clock. - Roles (b): a REGISTER-WRITER role scoped to the register tables, the journal and the switch, held by
lanes-edit.shand its renderer; Amendment 14 act 2: lanes-index (sync / reconcile / status), the detached post-write nudge, --index on lanes and history, the versioned schema with the owner column, and the offline-no-read / poisoned-index / wiped-reconcile tests #160's indexer role cannot write the register's tables and vice versa; the READER role for hooks and listings. README-lanes.md: "The SQL register" section with the GRANTs.
Agreement tests (the amendment names them)
- the mirror byte-identical to a full render from SQL after every write (fixture Postgres: a disposable local
postgresprocess in the suite, or a fake libpq where none — never a real shared database); - refusal (69, no file touched) when the database is unreachable; a files-mode writer refusing under the marker;
- ordering under two concurrent writers: one
CLAIMED, the other exit 2; twoHOLDs in commit order; - two renderers racing: one commit per sequence, in order;
- a stray commit under
lanes/stops the renderer; - the import's byte-identical proof and its refusal on a difference;
- rollback: files byte-identical, marker gone, SQL writes refused;
- the measured cost of one write (recorded in the PR body).
Not here
Act 3 (Eagle AND Raven configured together: mode=primary, store=postgres, the two roles — Brett's) and act 4 (the cutover, in the window he names).
Refs brettheap/new-workstation#50, brettheap/new-workstation#49, #160, #161, #162, #145.
Lane: openRepoTools-3
🤖 Generated with Claude Code
Folded in on Brett Heap's word of 2026-10-05 ("fold the guard into 165")
His question: "is the guard running in the model cli? if so, can we move this to LS?" It is: openRepoTools --install writes lanes-edit.sh guard as a UserPromptSubmit hook and the SessionStart block into ~/.claude/settings.json; every prompt forks the 15,000-line helper, reads the register and tmux, and under Amendment 22 would open a database connection per prompt.
The register service, and the guard as its client
lanes-register(the storage layer of this issue, served over a local socket): one process per workstation holds the database connection and the credential (the REGISTER-WRITER and READER roles — one holder instead of every session), the parsed register, and answers the reads every act and guard makes (who,binding,live-holder,next-free,managed-projection, the guard's triple check) in milliseconds. It is the natural home for S2's renderer and Amendment 14'slanes-index sync; it starts on demand (socket activation or first call) and needs no supervisor of its own to be useful.- The
UserPromptSubmitguard becomes a thin client (~10 lines): send(session uuid, window id, session name, workstation)over the socket, getok/refuse <reason>/offer <the three A12 Addendum 1 choices>, exit 0 or 2. The hook stays — the harness's exit code is the only thing that refuses a prompt — but it no longer forks bash or reads files. - The
SessionStartblock becomes a client the same way (read-only; still exit 0 always; still offline-capable through the mirror). - Service unavailable: the hook runs the direct read itself (
lanes-edit.sh guardas today) — a READ fallback to the same truth, which Amendment 22 permits; writes never fall back (S1). An unreadable source still refuses (A12 D3). - Other agents: the socket API is the surface the 001 feature's lanes service (T055: lane → MCP bridge → lanes service → supervisor) adopts or wraps, so Codex and the managed path ask the same question of the same process. The API is documented in README-lanes.md; the Codex lane is told through Brett.
Tests added
- the guard's answer through the service is byte-identical to the direct read for ok / refuse / offer;
- service down → the hook falls back to the direct read and says
guard: direct (service unavailable)on stderr, same exit code; - one credential holder: the hook's environment carries no database URL or password;
- latency: the service-backed guard under 50 ms on the suite's fixture register (recorded in the PR body).
Lane: openRepoTools-3
Activity
brettheap commentedon Oct 5, 2026
CLAIMED — lane openRepoTools-3, session 83619980-06bb-4469-ae73-2a8c5e61981a@Eagle, 2026-10-05T13:09:08Z, for #165
Logged in
lanes/log/openRepoTools-3.mdat9aacd3820fbd391534ac03fbd3a97d7d27bbfbaa.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 6, 2026
Deferred under the close-out direction of 2026-10-06; nothing was written. Two findings from the stopped writer for whoever resumes act 2:
tests/test_lanes_index.py(PR Amendment 14 act 2: lanes-index derives a non-gating index of the lane register, nudged after each landed write and read only behind --index; history reads across lanes #164) does not skip on a missing database URL and CI provides no Postgres; the pattern is SQLite under the test's temp directory plus a fakepsql("no test creates a real database"). Act 2's Postgres-onlymode=primarytherefore needs a test strategy decided first (a CI service container, or a fake that proves the SQL shape).--sweep --yesafter #170, lane-start recording a lane's existing trees #181 carries them..claude/worktrees/a22-primary(branchfeat/amendment-22-sql-primary, no commits, upstream unset) is left in place.Lane: openRepoTools-3
brettheap commentedon Oct 9, 2026
(lane openXwallet-2, 2026-10-09T01:25:26Z) Field evidence for the SQL register, noted on Brett Heap's word in session ("this … is another reason to use the sql. note that in OpenRepoTools").
What happened (2026-10-09, 00:59Z–01:24Z, register checkout
brett-wip-runtime-openxwallet-team007-*, shared by lanes openXwallet-1/2/3 on one host):lanes-edit.shwrites in ~40 s (append-line LANDING, set-row-state, append-line LANDED, set-row-state, log LANDED). In both landings all five writes reportedrebase conflict on origin/main — not pushed by this attempt, while lanes 1 and 3 were writing their own lines in the same minute. The conflicting hunk is the tail oflanes/LANES.md: every lane appends there, so any two appends inside one fetch→rebase→push window conflict textually.ahead 6, behind 56the first time andahead 5, behind 8the second. One of the stacked commits was lane 1's (a RULED log line), so a reset by lane 2 to recover its own lines dropped lane 1's, which then had to be replayed by lane 1 from a backup branch. Lane 1 later hit the same stuck-unpushed state on its own.reset --hard origin/main, replay the identical writes through the tool at their original UTCs, verify on origin. Lane 2 now runs its writes through a wrapper that levels the checkout with origin before each write and retries on conflict; it still has to refuse when another lane's unpushed commit is in the stack.Why this argues for #165 as designed: a tail-appended text file under git has no row-level write; the unit of conflict is the file, so concurrency across lanes is only as good as the fetch→push window.
mode=primarywriters in one transaction each, with the renderer producing the mirror commit, make the append a row insert and the conflict disappear; a SERIALIZABLE claim order gives Rule 1 the race it was always meant to have. Until then two interim mitigations help: one register checkout per lane (stacks stop mixing, so one lane's recovery cannot drop another's commit), andlanes-edit.shre-levelling to origin and retrying an identical write on conflict instead of leaving it stacked.Source lane record: opensoft/openXwallet#25 (comments of 2026-10-09 ~01:0xZ–01:2xZ) and lane log
lanes/log/openXwallet-2.mdNOTED lines.