You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
Amendment 14's adoption act 2 — the tooling — on Brett Heap's word of 2026-10-05, verbatim "build 160", under the amendment he ratified that day, verbatim "ratify 48" (brettheap/new-workstation#48). The register's truth stays where it is; SQLite — or QA Postgres where a workstation is configured for it — becomes a DERIVED index of it, written behind the sources, read only by tooling that decides nothing, and never a gate.
lanes-index (new, Python 3 stdlib only, placed by openRepoTools --install as the sixteenth file): sync, reconcile [--dry-run], status, and export (the read flags' own reader).
The nudge in lanes-edit.sh: after a push has LANDED and the mutex is released, one DETACHED lanes-index sync — stdin closed, stdout/stderr on /dev/null, descriptors 3–9 closed, started from a subshell that exits at once, never waited, its status never read; skipped where lanes-index is not on PATH or LANES_INDEX=off.
--index on lanes and on lanes-edit.sh history, typed per invocation, never from env/config: stderr says read: index (<store>) at register@<sha12>; <k> commits behind or read: sources (index <why>); stdout and exit are the source read's.
history --all / history --repo <owner/repo> — every lane's NOTED/RULED on one timeline (UTC, then file order), from the sources; --index optional.
docs/README-lanes.md: "The derived index (Amendment 14)".
Why
Amendment 14 "Why" 3: on Eagle history over two lanes was refused (exit 64) and every cross-lane question was a full parse of 1.27 MB of register and 26 logs. The amendment answers it with an index nothing acts on, so an outage, a stale copy or a wrong row can refuse nothing.
The clauses, as implemented
clause
where
(a) truth unchanged
the index only ever reads origin/<branch> of the workspace checkout (git ls-tree/cat-file --batch at one commit); nothing in any source is written from it
(b) non-gating by construction
no act has a path to the index: LANES_IDX_DIR is assigned empty at start-up whatever the environment says and set only by index_open, which only the lanes and history arms call behind a typed --index. Eight published-read primitives (register_text, archive_text, lane_alias_text, known_lanes, log_path_ci, remote_log_events, lane_log_events, lane_log_exists) answer out of the index's image only then; the local reads (this checkout's register and logs, session records, tmux) are untouched — which is why the answer is byte-identical
(c) the store
SQLite ${XDG_STATE_HOME:-~/.local/state}/openRepoTools/lanes-index.sqlite (0600), else Postgres through psql only where url= is set. Config ${XDG_CONFIG_HOME:-~/.config}/openRepoTools/lanes-index.conf or $LANES_INDEX_CONFIG; refused (exit 2) when group/world-readable, inside a git work tree, under ~/.agents/, a URL carrying a password (userinfo, ?password=, keyword form), a passfile not 0600, or a SQLite path inside a work tree. Writer url=/passfile= for sync/reconcile; reader reader_url=/reader_passfile= for status/export, required beside url= — without it the reads are refused (read: sources (index refused …)) and never fall back to the writer's credential; PGPASSWORD stripped, PGPASSFILE set. The SQLite store is put back to 0600 on every write-open. The GRANTs are in the manual
(d) write-behind, indexer alone, never back
sync reads what landed; upserts keyed by (source, record id), versioned by a content digest (an upsert bearing the stored digest changes nothing); provenance compare-and-swapped IN the transaction (BEGIN IMMEDIATE on SQLite; a DO block with GET DIAGNOSTICS first in the script on Postgres); a non-descendant commit writes nothing; records the source no longer holds leave in the same sync; managed-projection reads the ref, so the ref is asked again once every owner is in and a ref that moved writes nothing (the sync reads the new tip whole); one sync per workstation (flock; a rerun mark the contender leaves and re-checks, and the holder looks for again after it releases); the Postgres schema version is read before any DDL, so a store of another schema is never touched; reconcile compares stored rows by CONTENT, not by the digest they carry
(e) what is indexed
lanes (owner via managed-projection 0/8/1 — the indexer carries no marker parser, and a test greps for one), log_lines (unreadable lines kept with file, line, reason), register_lines (LANDING/LANDED/HOLD/HOLD RELEASED), lane_aliases, transcript_pointers, source_files, holds as a VIEW that computes holders_of's answer (each lane's own last line on an object, the lane case-insensitive, in file order; not while another lane's own last line there is a TAKEOVER; objects keyed as written), provenance, schema_version. An unknown owner is asked again by the next sync even at the same commit
(f) no offline rule for the index
nothing asks whether it is reachable; an unreachable store is one read: sources line
Created empty, written by nothing here:swap_states and job_summaries (the ledger half, ledger:<Workstation>, T058's — act 4) and worktrees, reserved for #161 (each lane's #97 inventory under inventory:<Workstation>, with #161's documented columns).
Every row carries digest, provenance (the register commit it was last written at), indexed_utc, schema_version; schema 1. A store of another schema is never written and never read (unknown-schema → sources).
Tests
the amendment's test
where
what it proves
offline no-read
test_offline_no_read_every_act_is_byte_identical_against_an_unreachable_index; suite § "Amendment 14"
17 acts/reads (claim, log, set-row-state, release, who ×3, lane-objects, managed-projection ×3, binding, live-holder, register-row, canon-lane, history, lanes) and, in the shell suite, lane-start --dry-run and the name guard — exit/stdout/stderr identical with an indexer installed against an unreachable Postgres; each write's nudge DID reach the fake psql, each read reached none
poisoned index
test_a_poisoned_index_changes_no_acts_answer; suite § "Amendment 14"
forged holds, owners, states, narrative and register lines change no act's answer — and history --index shows the poison, so the sameness is not vacuous
a 30 s stub on PATH: the write returned in 0.626 s against 0.599 s with the nudge off (one local run); the stub started, with EOF stdin, sync, and was alive after the writer returned
valid → managed ledger-1, malformed → unknown, plain → legacy; every lane asked through a logging LANES_EDIT wrapper; no marker vocabulary in lanes-index
missing, unreachable, wiped, unknown-schema, refused, off — read: sources (index <kind>…) and the source read's stdout and exit, for history (lane, --all, --repo, an alias) and lanes (helper and wrapper)
Also: LOG_AWK parity (the indexer's parse equals lanes-edit.sh's own awk, events and warnings, over a log written to be awkward), byte-identical --index reads when current and 1 commits behind when not, the flag never taken from the environment, clause (c)'s nine refusals, the Postgres writer/reader split with one transaction whose first act is the CAS, an unreachable store, status lag, the sync lock, export never creating a store. From the review rounds: a symlinked configuration or store directory judged where it points, transcript pointers of two logs one case apart, a ref that moves while owners are asked, the rerun handshake, an unknown owner re-asked at the same commit, the reader-or-nothing rule, the holds view against who object by object (a takeover, a hand-back and re-claim, two spellings of one lane), the store made 0600 on every write, a schema-2 Postgres store never sent DDL, --out being export's alone, and lanes --fetch --index under a fallen-back fetch. Each fails against the head before its fix. tests/conftest.py and the shell suite set LANES_INDEX=off so no other case nudges an installed indexer.
Measurements (Eagle, live register read-only into a scratch SQLite)
First sync of the live register (opensoft/brett-wip at c560130d7f73: 61 lanes, 3,166 log lines, 1,921 register lines, 26 files, 5,647 records): 3.96 s wall, 61 managed-projection calls run eight at a time. Store: 7.1 MB SQLite. Re-measured at the final head (da4c9e06dd7b, 5,696 records): 3.95 s.
Incremental syncs as other lanes wrote: 9 records 1.06 s, 3 records 0.85 s. Replay (index at the tip): 0.26 s before review; 0.46 to 0.64 s at the final head. The live register carries one unknown owner (app/openxfactory, a row managed-projection refuses as no lane name), so a same-commit sync now takes the long way and asks that one again. reconcile (every row compared by content, every owner re-asked): 4.8 s, and 4.0 s at the final head.
holds against who on the live register: the view answers in under 0.01 s (a (source, object) index). Compared object by object with lanes-edit.sh who for all 130 held objects and 40 free ones, there was one difference, opensoft/codexFactory#492. That object is named under its pre-rename repository spelling, which who folds through lanes/repos.tsv and the view keys as written. The manual says so.
lanes --all vs lanes --all --index (local by default): 2.40 / 2.27 / 2.60 s vs 2.37 / 2.71 / 2.54 s, the same: the per-lane bash loop and the live session reads dominate, and those the flag leaves alone by design.
history --all vs --index: 2.93 / 2.71 / 2.84 s (the source read fetches first) vs 0.45 / 0.58 / 0.51 s. Without the fetch (LANES_NO_FETCH=1): 0.48 s vs 0.55 s. history <lane>: 0.25 s vs 0.51 s.
Byte-identity on live data: history (one lane, --all, --repo, --since) identical; lanes --all and lanes-edit.sh lanes --all --closed identical but for AGE across a minute boundary.
Load average 6.5 to 11 throughout (other lanes' suites).
Review
Copilot round 1: 4 findings, all taken (09279bb). They were a symlinked config, pointer ids, owners against a moving ref, and the reader falling back to the writer.
Codex: 2 findings, both taken (09279bb). They were the rerun handshake, and unknown owners being skipped by the same-tip path.
Copilot round 2: 2 findings plus 2 "previously missed", all taken (a28a80f). They were the store's mode on every write, the Postgres version before DDL, the holds view's semantics, and --out.
Copilot round 3: 1 "previously missed" finding, taken past the cap because it broke this PR's own byte-identity promise (50594dc). It was lanes --fetch --index printing the read: line on stdout.
CI flake fixed in this PR's own shell section (4bfa584): a detached git gc --auto in the sandbox origin raced the A14 snapshot restore (tests-no-submodule at 50594dc). Automatic gc is off there now, and the restore waits for any running gc and retries.
macOS gate (run 37333795218):test_the_holds_view_answers_as_who_does was red on darwin because of the fixture. Two logs one case apart are one file on a case-insensitive disk. Fixed in 8fa6ca2: the fixtures are now built through git, and a new case pins file order against UTC order and line 9 against line 10.
Copilot on 8fa6ca2: 1 finding, taken in 20e23a4. An inherited PGPASSFILE reached psql when no passfile= was set.
A second @codex review hit the Codex account's usage limit. Every thread is resolved.
Not here
Act 3 (Eagle/Raven configuration and the two Postgres roles — Brett's), act 4 (T058's ledger half), #161 (the worktrees rows and lanes --index --worktrees), #162 (the sweep act).
Amendment 14 (ratified by Brett Heap 2026-10-05, "ratify 48") keeps the
register's truth where it is and adds a DERIVED index of it, written
behind the sources by the indexer alone and read only by tooling that
decides nothing. This is that indexer: `sync`, `reconcile [--dry-run]`,
`status`, and `export`, the read flags' own reader.
It reads `origin/<branch>` of the workspace checkout at one commit,
never the working tree, and upserts records keyed by (source, record id)
and versioned by a content digest, so a replay changes nothing. The
source's provenance is compared and swapped in the same transaction as
the rows, and a commit that does not descend from the indexed one writes
nothing. `reconcile` compares stored rows by content, so a row changed
by hand is found; on a wiped index it is the rebuild.
The store is SQLite under XDG_STATE_HOME, or QA Postgres through `psql`
only where `url=` is configured, with a writer and a SELECT-only reader
credential. The configuration is refused when group- or world-readable,
inside a git work tree or under ~/.agents/, or when a URL carries a
password. The owner column is `managed-projection`'s answer (0/8/1);
the indexer carries no parser of the marker. The ledger half's tables
and #161's `worktrees` table are created empty.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Amendment 14(d): after a push has landed and the mutex is released,
`lanes-edit.sh` starts one detached `lanes-index sync` from `cleanup`,
with stdin closed, output on /dev/null, never waited and its status never
read. It is skipped where `lanes-index` is not on PATH or LANES_INDEX=off,
so the suite's thousands of writes pay one `command -v` and print nothing.
Amendment 14(b): `--index` on `lanes` and `history`, typed per invocation
and never from the environment, swaps the eight published-read primitives
for the index's image and leaves every local read alone, so the answer is
the source read's byte for byte where the index is current. Stderr says
which was read; any failure to answer falls back to the sources with the
source read's exit. LANES_IDX_DIR is assigned empty at start-up, so no
act can reach the index.
`history --all` and `history --repo <owner/repo>` answer the cross-lane
question that was refused with 64: every lane's NOTED and RULED lines on
one timeline, from the sources, with or without the flag.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`openRepoTools --install` places `lanes-index` beside the lane helpers,
at the end of INSTALLABLES so every index a test takes still names the
file it did. The file count moves 15 -> 16 and the artifact count 29 ->
30 wherever they are stated, with the count history extended rather than
restated; the stale receipt sentence now says 30 artifacts, 28 rows.
The hygiene suite also compiles `lanes-index`, the one shipped command
that is Python: a syntax error there fails every workstation's first sync.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"The derived index (Amendment 14)": what it is for and what it never
does, the three words and the nudge, `--index` and its two stderr lines,
the configuration and its refusals, the two Postgres roles with their
GRANTs, the schema (the ledger half's tables and #161's `worktrees`
created empty), and the eight tests' promise. The `history` usage gains
`--all` and `--repo`.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/test_lanes_index.py builds a whole estate in tmp_path and holds the
indexer to offline no-read, poisoned index, wiped-index reconcile,
replay, monotonic provenance, a hung indexer, the managed seam and
fallback, plus LOG_AWK parity, byte-identical --index reads, the
configuration refusals and the Postgres writer/reader split against a
fake psql. No real database is created.
The shell suite gains a section running lane-start --dry-run, the name
guard, writes and reads three ways from one snapshot: no index, an
installed indexer against an unreachable store, and a poisoned SQLite.
Both the suite and conftest set LANES_INDEX=off so no other case nudges
an installed indexer.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Introduces Amendment 14's non-gating derived lane index, with transactional SQLite/Postgres indexing, detached post-push synchronization, explicitly typed indexed reads and cross-lane history, installer/documentation updates, and extensive isolation and correctness tests.
Sequence diagram for detached lane index synchronization after a landed write
sequenceDiagram
participant Writer as lanes-edit.sh
participant Git as Git remote
participant Index as lanes-index
participant Store as SQLite or Postgres
Writer->>Git: git push
Git-->>Writer: push landed
Writer->>Writer: release mutex
Writer-->>Index: sync (detached, stdin closed)
Index->>Git: read origin/<branch>
Index->>Store: transactional upsert and provenance CAS
Writer-->>Writer: return without waiting or reading status
Loading
Sequence diagram for typed indexed lane history with source fallback
sequenceDiagram
participant User
participant History as lanes-edit.sh history
participant Index as lanes-index
participant Store as SQLite or Postgres
participant Sources as Published lane sources
User->>History: history --all --index
History->>Index: export
Index->>Store: read indexed image
alt index available
Store-->>Index: events, provenance, lag
Index-->>History: exported events
History-->>User: timeline and read: index stderr
else index unavailable or stale image cannot answer
Index-->>History: failure reason
History->>Sources: read source logs
Sources-->>History: source events
History-->>User: source timeline and read: sources stderr
end
Loading
File-Level Changes
Change
Details
Files
Add a standalone Python stdlib indexer that materializes the landed lane sources into a versioned, derived SQLite or configured Postgres store.
Implement sync, reconcile, status, and export commands with content digests, row cleanup, schema validation, private-store/config checks, locking, and monotonic provenance CAS.
Index lane rows, parsed log/register data, aliases, pointers, source metadata, and computed holds while delegating managed-owner detection to the existing managed-projection command.
Support separate Postgres writer/reader credentials and safe fallback behavior for missing, stale, invalid, or unreachable stores.
lanes-index
Integrate write-behind indexing and explicitly opt-in indexed reads into lane tooling without changing default act behavior.
Start one detached, non-blocking sync after a landed push, with PATH and LANES_INDEX=off guards.
Add typed --index handling to lanes and history, including source fallback diagnostics and lag reporting while preserving source-derived output and exit codes.
Add history --all and --repo cross-lane timeline queries with UTC/file-order sorting and optional indexed reads.
Route only published register, archive, alias, log, and event reads through an exported index image; keep act reads, local session data, and writers on sources.
lanes lanes-edit.sh
Ship the indexer through the installer and document Amendment 14's derived-index contract and operational rules.
Increase the installed command set from fifteen to sixteen and include lanes-index in help, receipts, counts, and installation atomicity checks.
Document index commands, nudge semantics, opt-in reads, configuration restrictions, schema contents, Postgres roles, fallback guarantees, and reserved future tables.
Update repository guidance to reflect the new installed artifact.
Add broad automated coverage for index correctness, isolation, integration, installation, and tooling hygiene.
Test parser parity, replay idempotence, reconciliation, wiped stores, provenance monotonicity/CAS, locking, hung indexers, managed-projection ownership, schema contents, config security, Postgres role separation, and export behavior.
Verify indexed reads are byte-equivalent to source reads, history selectors work across lanes, all failure modes fall back, environment variables cannot implicitly enable indexing, and acts never consult poisoned or unreachable indexes.
Disable accidental nudges in existing test fixtures and validate installation lists plus syntax compilation of the extensionless Python command.
Provide the lanes-index tool with sync, reconcile, and status operations that maintain a versioned, derived, non-gating SQLite/Postgres index of lane-register sources, including provenance, owner classification, schema tables/views, secure configuration, writer/reader separation, replay safety, and monotonic updates.
Trigger a detached, non-blocking lanes-index sync after landed writes in lanes-edit.sh, while ensuring the index is never consulted by normal acts and cannot affect source writes or decisions.
Add typed --index reads for lanes and lanes-edit.sh history, including cross-lane history, explicit index/source diagnostics and source fallback, documentation, installation support, and the required offline, poisoned-index, reconcile, replay, provenance, hung-indexer, managed-owner, and fallback tests.
Trigger a new review: Comment @sourcery-ai review on the pull request.
Continue discussions: Reply directly to Sourcery's review comments.
Generate a GitHub issue from a review comment: Ask Sourcery to create an
issue from a review comment by replying to it. You can also reply to a
review comment with @sourcery-ai issue to create an issue from it.
Generate a pull request title: Write @sourcery-ai anywhere in the pull
request title to generate a title at any time. You can also comment @sourcery-ai title on the pull request to (re-)generate the title at any time.
Generate a pull request summary: Write @sourcery-ai summary anywhere in
the pull request body to generate a PR summary at any time exactly where you
want it. You can also comment @sourcery-ai summary on the pull request to
(re-)generate the summary at any time.
Generate reviewer's guide: Comment @sourcery-ai guide on the pull
request to (re-)generate the reviewer's guide at any time.
Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
pull request to resolve all Sourcery comments. Useful if you've already
addressed all the comments and don't want to see them anymore.
Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
request to dismiss all existing Sourcery reviews. Especially useful if you
want to start fresh with a new review - don't forget to comment @sourcery-ai review to trigger a new review!
Six findings on #164, all genuine, all taken:
- Resolve the configuration and SQLite paths before asking git which
work tree holds them: a 0600 symlink outside every repository that
points into one bypassed clause (c)'s refusal (Copilot).
- Key a transcript pointer by its file when two logs one case apart
carry a PAUSED at the same line, as log_lines already is (Copilot).
- Ask origin/<branch> again once every owner is in: managed-projection
reads the ref, not the snapshot sha, so a ref that moved writes
nothing and sync reads the new tip whole (Copilot).
- Refuse a Postgres read with no reader_url= instead of falling back
to the writer's credential; the read flags then answer from the
sources (Copilot).
- Close the rerun handshake on both sides: a contender marks and looks
once more, and the holder looks again after it releases, so no mark
is left with nobody due to read it (Codex).
- Skip the same-tip fast path while any owner is unknown, so a failed
managed-projection read clears on the next sync (Codex).
Each fix has a case in tests/test_lanes_index.py.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.
View ignores takeover semantics and mishandles case variants
lanes-index:990
This view does not implement the source reader's hold semantics. It chooses the last row per physical file and never applies superseded_by; therefore CLAIMED by lane A followed by TAKEOVER by lane B remains as two open holds, and case-variant logs can yield multiple winners for one lane/object. Reduce events per (source, lane, object) in file order and apply the same takeover rule as holders_of.
--out is accepted by unsupported subcommands
lanes-index:1892
--out is accepted for every subcommand but consumed only by export; for example, lanes-index sync --out /tmp/x silently performs a write instead of rejecting the invalid option. Restrict this option to export, as --dry-run and status --source are restricted.
Copilot's second round raised two findings on changed lines and two
on lines it had missed in round one. All four are genuine:
- Compute the holds view the way holders_of does, which is the answer
`who` gives. A lane's last line on an object is found
case-insensitively and in file order. A lane does not hold the
object while another lane's own last line there is a TAKEOVER.
Before this, the view picked the last line per physical file and
never applied supersession.
- Chmod the SQLite store to 0600 on every write-open, not only when it
is created, so a mode that drifted is put right before more rows
go in.
- Read the Postgres schema version before running any DDL. The DDL
includes CREATE OR REPLACE VIEW, which used to rewrite a store of
another schema before it was refused. Now only an absent schema is
bootstrapped.
- Accept --out for export only, so `sync --out` is a usage error
instead of a silent write.
Each change has a case in tests/test_lanes_index.py, and each case
fails against the previous head.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot round 2 (review 5415998976): all four findings were taken in a28a80f. The two inline threads are answered and resolved. The two listed under "previously missed" have no thread, so they are answered here:
The holds view ignored takeover semantics and case variants (lanes-index, the view in ddl). The view now computes holders_of's answer, which is the answer who gives. It takes each lane's own last line on an object, with the lane compared case-insensitively and "last" meaning the file-ordered position: byte order of the file, then the line, never UTC. That line holds while its verb is open, unless another lane's own last line on the object is a TAKEOVER. New case test_the_holds_view_answers_as_who_does builds a takeover, a hand-back followed by a re-claim, and a lane split across two logs one case apart. It then compares the view object by object against lanes-edit.sh who. The manual's schema row now says the same. A (source, object) index on log_lines keeps the correlated subqueries cheap.
--out was accepted by subcommands that do not take it.--out is now export's alone, as --dry-run is reconcile's. sync, reconcile and status exit 64 when given it, and nothing is written. Covered by test_out_belongs_to_export_alone.
Each new case fails against the previous head (09279bb) and passes at a28a80f: tests/run.sh -k lanes_index gives 45 passed. This was Copilot's second and last round on this PR. No finding from it is left over, so no follow-up issue was filed.
Fallback duplicates index diagnostics and pollutes stdout
lanes:376
When the fetch falls back, this forwards the read: line to stderr but leaves it in fetch_err; the fallback footer later prints all of fetch_err to stdout (lines 475/619). Thus lanes --fetch --index adds the index diagnostic to stdout and prints it twice, breaking the promised source-read stdout parity. Remove the forwarded diagnostic from the buffered fetch notes before rendering the footer.
Under `lanes --fetch --index`, when the fetch falls back the wrapper
forwards the helper's `read:` line to stderr. It also prints all of
the helper's notes again on stdout, beneath the "read LOCALLY"
footer. So the index line appeared twice, once on stdout, and
`--index` changed the listing it promises to leave byte-identical.
Copilot raised this in round 3 as a line it had missed before. It is
taken because it breaks the PR's own parity promise.
This commit also says in the manual that the holds view keys an
object as written. A line that names a repository by a spelling
lanes/repos.tsv has since renamed is its own key there, while `who`
folds its argument into the canonical spelling. A live comparison of
170 objects against `who` found exactly that one difference.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot round 3 (on a28a80f) had one finding, listed under "previously missed", with no thread. It is taken in 50594dc:
A fallen-back fetch duplicated the index diagnostic on stdout (lanes:376). After the read: line is forwarded to stderr, it is now removed from fetch_err. So the notes printed under the "read LOCALLY" footer are the helper's own, and lanes --fetch --index prints the same listing as lanes --fetch. New case: test_a_fetch_that_falls_back_keeps_the_read_line_off_stdout. It uses the shell suite's helper-in-fallback stub, fails against a28a80f's lanes, and passes at 50594dc.
This was past the two-round cap. I took it only because it broke this PR's own byte-identity promise for --index. Copilot findings after this round are filed rather than taken.
The same commit adds one precision to the manual's holds row. I compared the view with lanes-edit.sh who, read-only on the live register, for all 130 held objects plus 40 free ones. There was exactly one difference: opensoft/codexFactory#492, whose earlier lines name the repository by its pre-rename spelling. The view keys an object as written. who folds its argument through lanes/repos.tsv (R20) into the canonical spelling, so it reads only the lines written under that spelling.
CI's tests-no-submodule job at 50594dc failed three checks in the
shell suite's Amendment 14 section, in the unreachable-store run of
its first write. The log shows why:
rm: cannot remove '.../origin.git': Directory not empty
By that point the suite has pushed thousands of commits, so the
control run's push can start a detached `git gc --auto` in origin.
That gc kept writing while a14_restore removed the repository. The
removal left the directory behind, `cp -a` copied the snapshot inside
it, and the next push found no repository at origin. The same
section passed at the three heads before this one, in both Linux
jobs.
Switch automatic gc off in origin and in the clone before the first
snapshot (the snapshot carries the setting forward). Wait out any gc
already running before each snapshot and restore. Retry a removal
that a straggler is still writing into. If a tree will not go away,
report it instead of copying into it.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preserve LOG_AWK final unreadable-line summary in indexed history
lanes-edit.sh:2977
history <lane> --index drops LOG_AWK's final unreadable-line summary. The exported warn file contains filename-prefixed warnings followed by an unprefixed per-file count, but this filter keeps only the prefixed rows; the source path prints both. Recompute the count while filtering so indexed single-lane history preserves the source warning stream (the current parity test compares only stdout).
Copilot's review of 4bfa584 had one finding, under "previously missed", with no thread: history <lane> --index does not print LOG_AWK's closing "N unreadable line(s) above" sentence on stderr. That review came after the two-round cap, and the finding affects stderr only; stdout and the exit code, which this PR holds to parity, are unaffected. So it is filed as item 3 of #166, with the fix and the test it needs, and not taken here. #166 also holds round 4's export snapshot finding and the holds repository-spelling note.
4bfa584 itself is the fix for a CI flake in this PR's own shell section, not a review change. tests-no-submodule at 50594dc logged rm: cannot remove '…/origin.git': Directory not empty. A detached git gc --auto started by the control run's push was still writing into origin while the snapshot was being restored. Automatic gc is now off in the section's two repositories before the first snapshot. Each snapshot and restore waits for any running gc first, and a tree that will not go away is reported instead of being copied into.
The macOS gate on #164 failed test_the_holds_view_answers_as_who_does
at `'opensoft/repoA#22' not in view`, and failed it on darwin only.
The view was not the cause. The fixture wrote lanes/log/REPOA-2.md
beside lanes/log/repoA-2.md. On macOS's default case-insensitive
disk those are one file, so the "other spelling" CLAIMED was appended
after the RELEASED in a single log, and the lane really does hold
#22. The test's own comparison against `who` passed on darwin for
exactly that reason. A Linux run that writes the two logs into one
file reproduces it: the view and `who` both answer HELD.
The view never used a bare-column aggregate. It picks each lane's
last line on an object with a correlated NOT EXISTS over (file,
ordinal), with ordinal stored as an INTEGER and the file compared in
byte order.
So the fixtures change, not the view. commit_paths writes blobs with
hash-object and update-index --cacheinfo under core.ignorecase=false,
then commits and pushes. Two spellings are therefore two paths on
every platform, and the test asserts that the pushed tree holds both.
The transcript-pointer case had the same blind spot: it passed on
macOS without the collision it exists to test. It is moved onto the
same helper and now asserts that the two PAUSED lines sit at one line
number.
A new case pins the ordering. One lane writes two objects with lines
out of UTC order, at lines 4/5 and 10/11. The test checks that the
view and `who` follow the file, and that line 10 comes after line 9.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PostgresStore._env dropped an inherited PGPASSWORD but kept an
inherited PGPASSFILE, and it set PGPASSFILE only when passfile= was
configured. With no passfile= (a passwordless role), libpq therefore
read whatever file the calling environment named, or ~/.pgpass by
default. That broke clause (c)'s rule that the credential comes from
the configured 0600 file and nowhere else. Copilot raised it on
8fa6ca2.
Both inherited variables are now dropped. PGPASSFILE is always set:
to passfile= when one is configured, and otherwise to an empty 0600
file the indexer keeps in its own state directory. libpq finds no
password there, so peer, trust and certificate authentication still
work. The new case fails against the previous head: the inherited
file reached psql.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The macOS gate failure (test_the_holds_view_answers_as_who_does, run 37333795218) came from the fixture, not the view. It is fixed in 8fa6ca2.
The test wrote lanes/log/REPOA-2.md beside lanes/log/repoA-2.md. On macOS's default case-insensitive disk those are one file. So the "other spelling" CLAIMED on #22 was appended after the RELEASED in a single log, and the lane really does hold #22. That is why the same test's comparison against who passed on darwin: the view and who agreed on the data they were given. I reproduced it on Linux by writing both spellings into one file, and the view and who both answered HELD.
The view never relied on a bare-column aggregate or an unordered pick. It selects each lane's last line on an object with a correlated NOT EXISTS over (file, ordinal):
ordinal is stored as an INTEGER;
file is compared in byte order (BINARY on SQLite, COLLATE "C" on Postgres).
The fixtures are built through git (hash-object plus update-index --cacheinfo under core.ignorecase=false), so two spellings are two paths on every platform. The tests assert that the pushed tree holds both.
The transcript-pointer case had the same blind spot, and passed on macOS without the collision it exists to test. It is moved onto the same helper.
A new case, test_the_holds_view_follows_file_order_not_the_clock, pins the ordering. It writes two objects with lines out of UTC order at lines 4/5 and 10/11. A view ordered by UTC, or by the line number as text, fails it; I checked both mutants.
Linux was green at 8fa6ca2 (tests 27m2s, tests-no-submodule 18m1s). 20e23a4 then took Copilot's one finding on 8fa6ca2, an inherited PGPASSFILE reaching psql, as a real defect. Its thread is resolved.
Bring #168 current with main so the lander can squash it on a green
head. The merge was clean: #171 and #168 share only
docs/README-lanes.md, where git placed #171's worktrees-index section
and #168's lane-worktrees section without overlap, and both stay.
#171 adds no placed file, so the install lists and their counts keep
lanes-index (#164) and lane-worktrees (#168) as they were.
Lane: openRepoTools-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
readylanding gate: runs tests-macos once before the squash
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Amendment 14's adoption act 2 — the tooling — on Brett Heap's word of 2026-10-05, verbatim "build 160", under the amendment he ratified that day, verbatim "ratify 48" (brettheap/new-workstation#48). The register's truth stays where it is; SQLite — or QA Postgres where a workstation is configured for it — becomes a DERIVED index of it, written behind the sources, read only by tooling that decides nothing, and never a gate.
lanes-index(new, Python 3 stdlib only, placed byopenRepoTools --installas the sixteenth file):sync,reconcile [--dry-run],status, andexport(the read flags' own reader).lanes-edit.sh: after a push has LANDED and the mutex is released, one DETACHEDlanes-index sync— stdin closed, stdout/stderr on/dev/null, descriptors 3–9 closed, started from a subshell that exits at once, never waited, its status never read; skipped wherelanes-indexis not onPATHorLANES_INDEX=off.--indexonlanesand onlanes-edit.sh history, typed per invocation, never from env/config: stderr saysread: index (<store>) at register@<sha12>; <k> commits behindorread: sources (index <why>); stdout and exit are the source read's.history --all/history --repo <owner/repo>— every lane'sNOTED/RULEDon one timeline (UTC, then file order), from the sources;--indexoptional.docs/README-lanes.md: "The derived index (Amendment 14)".Why
Amendment 14 "Why" 3: on Eagle
historyover two lanes was refused (exit 64) and every cross-lane question was a full parse of 1.27 MB of register and 26 logs. The amendment answers it with an index nothing acts on, so an outage, a stale copy or a wrong row can refuse nothing.The clauses, as implemented
origin/<branch>of the workspace checkout (git ls-tree/cat-file --batchat one commit); nothing in any source is written from itLANES_IDX_DIRis assigned empty at start-up whatever the environment says and set only byindex_open, which only thelanesandhistoryarms call behind a typed--index. Eight published-read primitives (register_text,archive_text,lane_alias_text,known_lanes,log_path_ci,remote_log_events,lane_log_events,lane_log_exists) answer out of the index's image only then; the local reads (this checkout's register and logs, session records, tmux) are untouched — which is why the answer is byte-identical${XDG_STATE_HOME:-~/.local/state}/openRepoTools/lanes-index.sqlite(0600), else Postgres throughpsqlonly whereurl=is set. Config${XDG_CONFIG_HOME:-~/.config}/openRepoTools/lanes-index.confor$LANES_INDEX_CONFIG; refused (exit 2) when group/world-readable, inside a git work tree, under~/.agents/, a URL carrying a password (userinfo,?password=, keyword form), a passfile not 0600, or a SQLite path inside a work tree. Writerurl=/passfile=forsync/reconcile; readerreader_url=/reader_passfile=forstatus/export, required besideurl=— without it the reads are refused (read: sources (index refused …)) and never fall back to the writer's credential;PGPASSWORDstripped,PGPASSFILEset. The SQLite store is put back to 0600 on every write-open. The GRANTs are in the manualsyncreads what landed; upserts keyed by (source, record id), versioned by a content digest (an upsert bearing the stored digest changes nothing); provenance compare-and-swapped IN the transaction (BEGIN IMMEDIATEon SQLite; aDOblock withGET DIAGNOSTICSfirst in the script on Postgres); a non-descendant commit writes nothing; records the source no longer holds leave in the same sync;managed-projectionreads the ref, so the ref is asked again once every owner is in and a ref that moved writes nothing (the sync reads the new tip whole); one sync per workstation (flock; a rerun mark the contender leaves and re-checks, and the holder looks for again after it releases); the Postgres schema version is read before any DDL, so a store of another schema is never touched;reconcilecompares stored rows by CONTENT, not by the digest they carrylanes(owner viamanaged-projection0/8/1 — the indexer carries no marker parser, and a test greps for one),log_lines(unreadable lines kept with file, line, reason),register_lines(LANDING/LANDED/HOLD/HOLD RELEASED),lane_aliases,transcript_pointers,source_files,holdsas a VIEW that computesholders_of's answer (each lane's own last line on an object, the lane case-insensitive, in file order; not while another lane's own last line there is aTAKEOVER; objects keyed as written),provenance,schema_version. Anunknownowner is asked again by the next sync even at the same commitread: sourceslineCreated empty, written by nothing here:
swap_statesandjob_summaries(the ledger half,ledger:<Workstation>, T058's — act 4) andworktrees, reserved for #161 (each lane's #97 inventory underinventory:<Workstation>, with #161's documented columns).Config and schema
Every row carries
digest,provenance(the register commit it was last written at),indexed_utc,schema_version; schema 1. A store of another schema is never written and never read (unknown-schema→ sources).Tests
test_offline_no_read_every_act_is_byte_identical_against_an_unreachable_index; suite § "Amendment 14"lane-start --dry-runand the name guard — exit/stdout/stderr identical with an indexer installed against an unreachable Postgres; each write's nudge DID reach the fakepsql, each read reached nonetest_a_poisoned_index_changes_no_acts_answer; suite § "Amendment 14"history --indexshows the poison, so the sameness is not vacuoustest_wiped_index_reconcile_equals_a_synced_index_row_for_row--dry-runcreates nothingtest_replay_a_second_sync_changes_nothingindexed_utcincluded); one more write upserts exactly 2 recordstest_a_sync_from_a_commit_that_does_not_descend_writes_nothing,test_the_compare_and_swap_refuses_a_provenance_that_movedtest_a_hung_indexer_delays_no_writer_beyond_the_forksync, and was alive after the writer returnedtest_the_owner_column_is_managed_projections_answermanaged ledger-1, malformed →unknown, plain →legacy; every lane asked through a loggingLANES_EDITwrapper; no marker vocabulary inlanes-indextest_each_failure_falls_back_to_the_sources_and_says_so×6read: sources (index <kind>…)and the source read's stdout and exit, forhistory(lane,--all,--repo, an alias) andlanes(helper and wrapper)Also:
LOG_AWKparity (the indexer's parse equalslanes-edit.sh's own awk, events and warnings, over a log written to be awkward), byte-identical--indexreads when current and1 commits behindwhen not, the flag never taken from the environment, clause (c)'s nine refusals, the Postgres writer/reader split with one transaction whose first act is the CAS, an unreachable store, status lag, the sync lock,exportnever creating a store. From the review rounds: a symlinked configuration or store directory judged where it points, transcript pointers of two logs one case apart, a ref that moves while owners are asked, the rerun handshake, anunknownowner re-asked at the same commit, the reader-or-nothing rule, theholdsview againstwhoobject by object (a takeover, a hand-back and re-claim, two spellings of one lane), the store made 0600 on every write, a schema-2 Postgres store never sent DDL,--outbeingexport's alone, andlanes --fetch --indexunder a fallen-back fetch. Each fails against the head before its fix.tests/conftest.pyand the shell suite setLANES_INDEX=offso no other case nudges an installed indexer.Measurements (Eagle, live register read-only into a scratch SQLite)
opensoft/brett-wipatc560130d7f73: 61 lanes, 3,166 log lines, 1,921 register lines, 26 files, 5,647 records): 3.96 s wall, 61managed-projectioncalls run eight at a time. Store: 7.1 MB SQLite. Re-measured at the final head (da4c9e06dd7b, 5,696 records): 3.95 s.unknownowner (app/openxfactory, a rowmanaged-projectionrefuses as no lane name), so a same-commit sync now takes the long way and asks that one again.reconcile(every row compared by content, every owner re-asked): 4.8 s, and 4.0 s at the final head.holdsagainstwhoon the live register: the view answers in under 0.01 s (a(source, object)index). Compared object by object withlanes-edit.sh whofor all 130 held objects and 40 free ones, there was one difference,opensoft/codexFactory#492. That object is named under its pre-rename repository spelling, whichwhofolds throughlanes/repos.tsvand the view keys as written. The manual says so.lanes --allvslanes --all --index(local by default): 2.40 / 2.27 / 2.60 s vs 2.37 / 2.71 / 2.54 s, the same: the per-lane bash loop and the live session reads dominate, and those the flag leaves alone by design.history --allvs--index: 2.93 / 2.71 / 2.84 s (the source read fetches first) vs 0.45 / 0.58 / 0.51 s. Without the fetch (LANES_NO_FETCH=1): 0.48 s vs 0.55 s.history <lane>: 0.25 s vs 0.51 s.history(one lane,--all,--repo,--since) identical;lanes --allandlanes-edit.sh lanes --all --closedidentical but for AGE across a minute boundary.Review
unknownowners being skipped by the same-tip path.holdsview's semantics, and--out.lanes --fetch --indexprinting theread:line on stdout.holdsrepository-spelling note above. The finding is thatexportreads several statements and does not share one snapshot across them.history <lane> --indexdrops LOG_AWK's closing unreadable-line count; this affects stderr only, not stdout or the exit code.git gc --autoin the sandbox origin raced the A14 snapshot restore (tests-no-submoduleat 50594dc). Automatic gc is off there now, and the restore waits for any running gc and retries.test_the_holds_view_answers_as_who_doeswas red on darwin because of the fixture. Two logs one case apart are one file on a case-insensitive disk. Fixed in 8fa6ca2: the fixtures are now built through git, and a new case pins file order against UTC order and line 9 against line 10.PGPASSFILEreached psql when nopassfile=was set.@codex reviewhit the Codex account's usage limit. Every thread is resolved.Not here
Act 3 (Eagle/Raven configuration and the two Postgres roles — Brett's), act 4 (T058's ledger half), #161 (the worktrees rows and
lanes --index --worktrees), #162 (the sweep act).Closes #160
Refs brettheap/new-workstation#48
Lane: openRepoTools-3
🤖 Generated with Claude Code