Skip to content

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

Open

Description

@brettheap

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 whose lanes-index.conf says mode=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/STORE in 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"); a mode=primary writer whose transaction finds the switch not primary refuses 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; renders lanes/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 under lanes/ it did not make STOPS it (status: diverged at <sha>). Direct file writes (commit included) refuse on mode=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/history listings may read the mirror and say read: 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-projection answers 0/8/1 from the database on mode=primary; unreachable → 1 (UNKNOWN), never legacy.
  • lanes-index import --at <commit> and rollback (S5): import loads the tables and the journal's first sequence from the workspace repository at a commit, renders into a scratch tree, REFUSES unless git diff --quiet between the commit's lanes/ and the render; the cutover sets the switch, pushes ONE commit adding lanes/STORE as a fast-forward of the imported commit (aborting if main moved under lanes/), and flips mode=primary where act 3 configured. Rollback: switch → files, render to current + byte-identical, ONE commit removing lanes/STORE, clear mode=primary; refused when the database is unreachable unless the mirror was last shown current.
  • Ordering (S6): the claim pre-check and the CLAIMED insert run SERIALIZABLE (or under an object lock); the loser exits 2 writing nothing; CLAIM-LOST is 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.sh and 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 postgres process 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; two HOLDs 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's lanes-index sync; it starts on demand (socket activation or first call) and needs no supervisor of its own to be useful.
  • The UserPromptSubmit guard becomes a thin client (~10 lines): send (session uuid, window id, session name, workstation) over the socket, get ok / 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 SessionStart block 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 guard as 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

  1. brettheap commented on Oct 5, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoTools-3, session 83619980-06bb-4469-ae73-2a8c5e61981a@Eagle, 2026-10-05T13:09:08Z, for #165

    Logged in lanes/log/openRepoTools-3.md at 9aacd3820fbd391534ac03fbd3a97d7d27bbfbaa.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoTools#165:
       none
    2. `gh pr list --repo opensoft/openRepoTools --state all --search "#165"`, kept where the row or its body names `#165`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoTools` branches naming `165` as a whole token:
       none
    
  2. brettheap commented on Oct 6, 2026

    @brettheap
    ContributorAuthor

    Deferred under the close-out direction of 2026-10-06; nothing was written. Two findings from the stopped writer for whoever resumes act 2:

    Lane: openRepoTools-3

  3. brettheap commented on Oct 9, 2026

    @brettheap
    ContributorAuthor

    (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):

    • Two Rule 6 landings by lane openXwallet-2 (Re-pin the spec leg at its 4.8 merge 1506bbdb, gitlink and contracts/spec-pin.yaml in one commit (ruled 2026-10-09) openWallet#4 and Bump the pinned openRepoShape submodule to 0fa73b7, past the park/resume carve #5) each issue five lanes-edit.sh writes in ~40 s (append-line LANDING, set-row-state, append-line LANDED, set-row-state, log LANDED). In both landings all five writes reported rebase 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 of lanes/LANES.md: every lane appends there, so any two appends inside one fetch→rebase→push window conflict textually.
    • A failed write stays committed locally, so the next write stacks on it and conflicts too; the checkout ended ahead 6, behind 56 the first time and ahead 5, behind 8 the 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.
    • Recovery each time was manual: backup branch, 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=primary writers 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), and lanes-edit.sh re-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.md NOTED lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions