Repository navigation
lanes-index follow-ups of #166 and #173: export reads one snapshot, history --index says its closing stderr count, a foreign source is refused before the lock, a damaged table reads as unusable, an unread lane stays NOT READ in the index, and the worktrees footer names the workstation - #219
Conversation
…index.py: one export snapshot, the history --index stderr count, a fake psql that decodes \echo, a source refused before the lock, a damaged table read as unusable, the footer, an unread lane in the index #166 item 1: an export with a sync landed after its first table, in WAL mode and in the rollback journal, is the old commit's image or the new one and never a mix; a Postgres export is one REPEATABLE READ READ ONLY psql call. Item 3: the byte-identical read test compares stderr too, the `read:` line aside, so `history repoA-1 --index` must say the count that closes its log's nine unreadable lines. Item 4: FAKE_PSQL decodes each `\echo` as psql's lexer does instead of printing the marker for any argument, and a test holds it to psql's escapes. #173: a foreign or nameless inventory source and another register are refused with exit 2 and no rerun mark while another sync holds the lock; a `worktrees` table dropped under valid provenance is `rows: unread` in `status` and `why`/exit 3 in `export --worktrees`, never a traceback; the `lanes --index --worktrees` footer names the workstation to run `lane-reconcile` on; a first sync that cannot read a lane leaves the index read byte-identical to the source's NOT READ, and a later one says the rows it held are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… history <lane> --index says the count closing a log's unreadable lines (#166 items 1 and 3) Item 1: `cmd_export` read the provenance and five tables as separate statements, each its own snapshot on SQLite and its own psql session on Postgres, so a sync committing between two reads made an image of two commits under a `meta` naming the first. `read_image` reads them in one: a SQLite read transaction (the SHARED lock in the rollback journal, a snapshot in WAL) and one psql call inside BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY ... COMMIT, through `_query_many(snapshot=True)`. Item 3: in `lane_log_events`' index branch the filter kept only the `unreadable: <log>:` lines of the export's `warn`; it now keeps the export's own count line that follows them, which is LOG_AWK's END. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… table as unusable, and keeps an unread lane's NOT READ in the index; the worktrees footer names the workstation (#173) The finding (option 1, which the issue's own tests ask for): a lane held because its inventory could not be read gets one `worktrees` marker row, no path and branch `unreadable inventory (<why>)`, which `export --worktrees` writes back as the source's `UNREAD` line. A first sync no longer commits an empty answer for it, and rows held over from an earlier sync are said to be kept. The marker leaves with the first sync that reads the lane; `status` counts it apart from the worktrees. Copilot's fourth review of #171: `cmd_sync` validates the source before it asks for the lock, so behind a held lock a foreign or nameless inventory, or another register, is refused (exit 2) instead of leaving a mark the holder syncs as this workstation's; `SqliteStore.rows` raises StoreUnusable for a sqlite3.Error; and the `lanes --worktrees` footer says to run `lane-reconcile` on the workstation the rows name. Not here: the partial-listing item in `lanes-edit.sh` `worktree_rows`, which is lane openRepoTools-3's region. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 23 hours and 29 minutes by commenting |
Reviewer's GuideThe PR completes selected follow-ups for the derived lanes index: exports now read a coherent snapshot and preserve source stderr, unread inventories are persisted and surfaced without being mistaken for empty results, invalid sources and damaged tables fail safely, and workstation-specific reconciliation guidance and PostgreSQL test emulation are corrected. The proposed changes intentionally leave the partial-listing deletion issue and canonical-repository holds question unresolved. Sequence diagram for a coherent lanes-index export snapshotsequenceDiagram
participant User
participant Export as lanes-index export
participant Store as SQLiteStore or PostgresStore
participant DB as Index database
User->>Export: cmd_export
Export->>Store: read_image
Store->>DB: Begin one read snapshot
Store->>DB: Read provenance and tables
DB-->>Store: Consistent image
Store-->>Export: provenance and rows
Export-->>User: Export result from one commit image
Sequence diagram for persisting an unread inventory markersequenceDiagram
participant Sync as lanes-index sync
participant Inventory as Inventory source
participant Index as Derived index
participant Read as lanes-index --index
Sync->>Inventory: Read lane inventory
alt Inventory cannot be read
Inventory-->>Sync: Read failure
Sync->>Index: unread_record and unread_line
Index-->>Read: inventory NOT READ marker
Read-->>Read: Preserve earlier worktree rows
else Inventory is readable
Inventory-->>Sync: Worktree rows
Sync->>Index: Store current rows
Index-->>Read: Worktree listing
end
Sequence diagram for rejecting an invalid source before lockingsequenceDiagram
participant User
participant Sync as lanes-index sync
participant Source as Inventory source
participant Lock as Index lock
participant Index as Derived index
User->>Sync: cmd_sync --source
Sync->>Source: Validate source
alt Source invalid
Source-->>Sync: Invalid provenance
Sync-->>User: Refuse source
else Source valid
Source-->>Sync: Valid
Sync->>Lock: Acquire lock
Lock-->>Sync: Lock granted
Sync->>Index: Sync inventory
end
Sequence diagram for preserving history index stderr paritysequenceDiagram
participant User
participant History as lanes-edit.sh history
participant Index as Derived index
participant Output as stdout and stderr
User->>History: history lane --index
History->>Index: Read indexed log events
Index-->>History: Events and log warnings
History->>History: lane_log_events
History->>Output: Write events to stdout
History->>Output: Write warnings and closing unreadable count to stderr
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…ad: line is: both are the --index flag's own lines `history --index` sets LANES_NO_FETCH=1 where the index answered (lanes-edit.sh's `history` arm: the read is the index's, so no fetch goes in front of it), and the note that prints is the flag's by design, as the `lanes` wrapper's own filter of the same note says. The comparison now drops both lines on both sides and holds the rest - LOG_AWK's warnings and the count that closes them - to one byte stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, key it apart from every tree, keep two trees of one path, and ask for the workstation once a sync (#219 review) Finding 1: a current-schema sidecar with `tree:` and no `path:` is a TREE line with an EMPTY path, and the marker was read from the empty path alone - exported as `UNREAD`, counted by `status` as a lane not read. A marker is now a row with no path AND a branch beginning `unreadable inventory (`, which no git ref can be (`is_unread_marker`, in `run_inventory`, `cmd_status` and `_export_worktrees`), and its key is `<lane>:` and a US (`unread_key`), so holding a lane no longer writes the marker over a stored empty-path tree keyed `<lane>:`. Two trees of one lane with one path - two empty paths, say - shared one key since #171 and the second replaced the first in `collect_inventory`; a later one is now keyed with its place among them, after a US. Finding 4: `this_workstation` is asked once a process, so the refusal before the lock and the sync behind it make one `lanes-edit.sh workstation` call, not two. Findings 2 and 3, the manual: the stderr of `--index` carries the flag's own lines - the `read:` line and `history`'s no-fetch note - and an indexer from before #173 sharing a Postgres store reads a marker as a TREE with an empty path, the schema staying 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tory's keys are spelled, when a same-path key moves, and what two indexers on one store do (#219 delta check) Nit 1: `is_unread_marker` is `record_id == unread_key(lane)`. A hand-made sidecar with no `path:` and a branch that reads `unreadable inventory (…)` stays a tree, exported as its TREE line and counted as a worktree; the predicate's three callers already read `record_id` and `lane` (`status` now reads `lane` for it). Nit 2: a same-path tree's `<US><n>` holds while those trees keep their sorted order; a change that reorders them moves both keys and that sync upserts both - said in `collect_inventory` and the manual, not "stable". Nit 3: the key forms - `<lane>:<path>`, `<lane>:<path><US><n>`, `<lane>:<US>unread` - in the module docstring, the `worktrees` table's comment, `collect_inventory`'s docstring and the manual. Nit 4: the manual says an older and a newer indexer syncing one store rewrite it on every sync, each converging to its own view, with no duplicates and no growth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…here a lane has two trees with the same path; and what a store from #219's unlanded heads keeps (#219 delta check) The checker's four alternating syncs with no same-path pair - an unreadable lane included - wrote nothing after the first; the rewrite on every sync is the same-path pair's alone, which the older indexer keys as one. Each still converges to its own view, with no duplicates and no growth. A store written by f00ee58 or ab853b1 keeps their `<lane>:` marker as a kept row while that lane stays unreadable, and it clears at the first sync that reads the lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
…ecord with no path: is a malformed tree, never read and never the marker (#115, #219) Merged with main, CI failed two tests of #219 (lanes-index's reader) in tests/test_lanes_index.py. Both plant a current-schema sidecar with no `path:` and assert its branch appears in `lanes-edit.sh worktrees`. Since #115 such a record is not whole: the listing reads no field of it and names its row `malformed sidecar`, by the path it records or, with none, by its file. The rows were still trees, counted and never NOT READ, so #219's reader and its marker key are unchanged; only the branch text the tests looked for is deliberately no longer read. - test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker: the path-less record is now asserted as a malformed row named by its file. Its second record keeps the path of the lane's whole tree (it lacks only `upstream:`), so two trees of one lane with one path stay two rows, the clause the source can no longer reach with an empty path. - test_a_tree_whose_branch_reads_like_a_marker_is_a_tree: the forged branch is carried by a whole record (it gains a `path:`), so the branch is read and still makes no marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce closes with its operation, and a lane sidecar is valid only when whole (#252) * A lane sidecar of the current schema is read and replaced only when it is whole (#115) `lane_sidecar_schema_ok` asked one question, so a file whose first line is `schema: 1` and that held nothing else passed it: the snapshot's reader printed empty fields for it, the inventory's reader printed a row with an empty id and empty counts (which `lane_reconcile` skipped, or read as `dirty 0, unpushed 0`), and every writer renamed its own file over it. The gate keeps its name and its call sites, and now also asks a record of the current schema for every key its writer writes (not empty), the shapes the readers decide with (`state` one of the four words, every `generation` a number, a tree's `dirty` a number and `unpushed` a number or `unknown`, its `path` absolute) and the id a tree record's file name gives it. Both writers have written exactly these keys since #97 landed. The kind of record comes from where it is filed; `LSS_WHY` says what failed. A malformed record takes the unknown schema's path on both sides: - every writer refuses it, exit 1, nothing written (`set-lane-state`, `set-lane-tree` for the snapshot and the tree record, and the lifecycle follow-up of a STARTED/RESUMED/ENDED/RETIRED line); - `lane_state_read` answers 10, `lane-state` exits 10 and says which of the two 10s it is, and `lane-reconcile` reads the state as `MALFORMED`, verdict `indeterminate`; - `lane-trees` carries a tree record as a row named by its file, every field empty, schema `<malformed>`; `lane-reconcile` reports it `malformed-sidecar`, counted toward recovery; `worktrees` lists it as `malformed sidecar`. `lane_sidecar_put` asks the same of its temporary file before the rename, so a record cut short as it was written is removed rather than renamed into place, and `set-lane-tree` refuses a `--dirty` or `--unpushed` that is not a count with 64, so no writer files a record its own readers refuse. No lock line moves: the four `if ! lane_sidecar_schema_ok` lines, every `acquire_lock`/`lock_try`/`release_lock` and the lock blocks are byte-identical; only the refusal messages beside them change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * set-lane-tree's fence refuses a poll filed under an operation the lane has finalized (#114) `SWAPPING -> SWAPPED` keeps the generation and the operation by design, so the fence's two comparisons accepted a poll from operation O after O's own finalizer had run, and the delayed reading replaced the inventory the commit point closed. Every other transition advances the generation, so `SWAPPED` is the one state in which a matching pair names a finished operation: a fenced `set-lane-tree` now reads the lane's state with the pair, under the same mutex, and is refused with 7 where it is `SWAPPED`. #114 also offered "require SWAPPING". That would refuse a write the suite makes on purpose (repoRC-5, section 11(c): a lane RUNNING after a resume records under its current generation and operation), so the narrower rule is the one taken. An observation filed under no transition is still an unfenced `set-lane-tree`. #114's second item, the partial observation, has been a 64 since 8.12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * lane-reconcile keeps the status of every read it rests on, and a failed one is never an empty one (#113) - The holder ids: `lane_session_ids_read` unions the published row's and this checkout's session ids and answers 1 where either copy could not be read (an empty published register, or a register file that is there and cannot be opened). That is `HOLDER unknown` and `indeterminate`, never an empty set that `live_holder` answers 8 for and the verdict table reads as a crash. - `branch.<b>.remote` in `lane_tree_now` gets the `case` its `merge` sibling has: a read that failed is 3 (`lane-tree-now` 1, `set-lane-tree` refuses), never the fabricated local upstream `./<merge>`. - `git worktree list --porcelain` in the lane's checkout keeps its status. A failure makes the verdict `indeterminate` and names it; a recorded directory that is NO repository registers nothing and stays an absence (`lane_no_repository`, `lane-worktrees`' `no_repository`, #170 G5). - `lane-trees`' 10 for an inventory directory that cannot be listed, which this read still swallowed after 8.12 and #157 item 4e, and a lane worktree root that cannot be listed or searched, make the verdict `indeterminate` too. #113's second item, the unreadable sidecar, has been its own row since 8.12. Per-record failures stay rows counted toward recovery and leave the verdict as it was, which is the line T018 (#246) draws for an unreadable leg. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test #113, #114 and #115 in the lane-start sandbox, every case red at 87a5e9d tests/test_lifecycle_fail_open_113_114_115.py runs the real lane-start and lanes-edit.sh in the sandbox of tests/test_lane_start_claude_current.py, as tests/test_deferred_97_findings_157.py does. A read that failed is injected by a `git` on PATH that passes every call through to the real one except the one FAKE_GIT_FAIL names; the permission fixtures skip under root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document #113, #114 and #115 in the manual and in the change's design and tasks docs/README-lanes.md: the verdict table's MALFORMED and failed-read rows, the fence's SWAPPED refusal, the whole-record rule and the counts' shapes, the remote read, `malformed-sidecar`, and the reads whose failure is `indeterminate`. design.md: decisions 26-28. tasks.md section 7: where each remaining item was taken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * lane-reconcile reads the holder ids through main's session_ids_checked (#67), not a copy of it (#113) #67 landed on main with `session_ids_checked`: the published-union-local ids, 0 with them, 8 for none and 1 where either copy of the register could not be read, on a `register_text_checked` that reports a failed `git show` or `cat` itself rather than inferring it from an empty render. That is the read #113 item 1 needs, so `lane_reconcile` asks it and `lane_session_ids_read` goes: one implementation. An 8 goes on to `live_holder` as before; a 1 is `HOLDER unknown` and `indeterminate`, as it was. The comments that named two callers of the checked read now name three. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * lane-reconcile names a failed registrations read in the sweep's words, so lane-end still relays #170 G5's reason (#113) The sweep asks lane-reconcile before its own reads and keeps the first refusal. With #113 item 4 that first refusal is now the reconciliation's, so a lane checkout whose .git is mode 000, or whose .git file points nowhere, refused (2) as before but named "the worktrees git registers in <dir> could not be listed" where test_lane_helpers.sh's #170 G5 cases (and lane-end's relay of them) require "the worktree registrations of <dir> could not be read". CI run 39141918371 failed those three cases on 33515f8. The reconciliation now says it in lane-worktrees' words, with the exit status after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A snapshot under a directory named trees is read as a snapshot, and set-lane-tree refuses a blank field with 64 (#115, review r1 of #252) lane_sidecar_schema_ok told a snapshot from a tree record by where it is filed, and asked */trees/*.yaml first. A case pattern's * matches /, so under a control root with any directory named trees above it every snapshot was asked a tree's keys and refused: lane-start wrote none, set-lane-state exited 1, and lane-reconcile said no-state, the verdict a launcher goes past. The snapshot's arm now comes first; no tree file is named lane-state.yaml, since a tree id is c<cksum>-... set-lane-tree refuses a --branch, --head, --upstream, --checkout or --writer that is only blanks with 64, before the lock, as it refuses a count that is not one. It used to fail at the rename with exit 1. lane_no_repository asks git in the C locale, as lane-worktrees' git_env does, so a translated git cannot turn an absence into a failed read. worktree_rows' note no longer says a row's path always begins with /: a row of a record that is not whole keeps the path the record names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test a control root under trees, a blank set-lane-tree field and the C-locale read, each red at 36520b9 (#113, #115) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document the malformed snapshot's 10, the lifecycle 64s, the kept path of a malformed row, and why a record's lane: is not compared (#115) The manual's main exit table names the malformed snapshot under 10 and the lifecycle arms' 64s. A <malformed> lane-trees row keeps the path the record names, which may be relative, and not every field empty. Decision 28 says which arm the validator asks first and why a record's lane: is not compared: rename-lane leaves it naming the old lane until the next write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #219's two path-less sidecar cases read #115's whole-record rule: a record with no path: is a malformed tree, never read and never the marker (#115, #219) Merged with main, CI failed two tests of #219 (lanes-index's reader) in tests/test_lanes_index.py. Both plant a current-schema sidecar with no `path:` and assert its branch appears in `lanes-edit.sh worktrees`. Since #115 such a record is not whole: the listing reads no field of it and names its row `malformed sidecar`, by the path it records or, with none, by its file. The rows were still trees, counted and never NOT READ, so #219's reader and its marker key are unchanged; only the branch text the tests looked for is deliberately no longer read. - test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker: the path-less record is now asserted as a malformed row named by its file. Its second record keeps the path of the lane's whole tree (it lacks only `upstream:`), so two trees of one lane with one path stay two rows, the clause the source can no longer reach with an empty path. - test_a_tree_whose_branch_reads_like_a_marker_is_a_tree: the forged branch is carried by a whole record (it gains a `path:`), so the branch is read and still makes no marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brett Heap <service@opensoft.dev> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>



Lane openRepoTools-4's follow-ups to the derived index, under the TAKEOVER lines on #166 and #173 (lane openRepoTools-3's claims, taken with its consent). Brett Heap lifted the closeout direction for these deferred issues.
Refs #173 (the finding, and items 2, 3 and 4 of the fourth-review comment). Item 1 of that comment is skipped, so the issue stays open.
Refs #166 (items 1, 3 and 4). Item 2 is skipped on its open question, so the issue stays open.
Items
Line numbers are on this branch's head.
tests/test_lanes_index.py)--indexread says the rows are held, can only pass under option 1.lanes-indexUNREAD_BRANCH,is_unread_marker,unread_key,unread_record,unread_line(1847–1881),collect_inventory(1934–1947),run_inventory(2080–2125),cmd_status(2366–2373),_export_worktrees(2506–2509), module docstring (41–52)test_a_first_sync_that_cannot_read_a_lane_says_not_read_in_the_index(new);test_an_inventory_that_cannot_be_read_keeps_its_rows(extended);test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker(new, review round);test_a_tree_whose_branch_reads_like_a_marker_is_a_tree(new, delta check)lanes-edit.shworktree_rows(~13154) is in lane openRepoTools-3's unpublishedfeat/triad-lane-toolingregion (~13000–13350)--sourcegets the same check, since it had the same defect.lanes-indexcmd_sync(2189–2202);this_workstationis asked once a process (1781–1795)test_a_source_is_refused_before_the_lock_is_asked_for;test_an_inventory_sync_asks_for_the_workstation_once(review round)SqliteStore.rowsleakssqlite3.Errorlanes-indexSqliteStore.rows(1131–1143)test_a_damaged_table_under_valid_provenance_is_unusable_not_a_tracebacklanes--worktreesfooter (368–373)test_the_index_read_of_the_worktrees_is_the_source_read(extended)exportreads one snapshotlanes-indexSqliteStore.read_image(1145–1167),PostgresStore._query_many(1328–1342),provenance/rowsand their query helpers (1386–1428),PostgresStore.read_image(1430–1441),cmd_export(2428–2440)test_an_export_is_one_commits_image_when_a_sync_lands_mid_read[wal-60.0]and[delete-5.0],test_a_postgres_export_is_one_repeatable_read_transactionholdskeyed by canonical repositorywhoignores such lines today. The fix also needslanes/repos.tsvindexed, as schema 2 or an additive schema-1 change.history <lane> --indexdrops LOG_AWK's closing count on stderrlanes-edit.shlane_log_eventsindex branch (3157–3164)test_the_index_read_is_byte_identical_to_the_source_read(extended: stderr is compared for every read, theread:line removed)psqlhard-codes the markertests/test_lanes_index.pyFAKE_PSQLtest_the_fake_psql_decodes_echo_as_psql_doesThe manual changed where it states these behaviours:
docs/README-lanes.md3435–3445 (stderr parity, the flag's two own stderr lines, and the one-snapshot export) and 3567–3601, 3618–3621 (the key forms, the unread marker, how an indexer from before #173 reads it and what two indexers on one store do, and the footer). All of it is before the ~3830 boundary.Neighbours
lanes: only the footer at 368–373. It stays clear of lane openRepoTools-5's ~520 and the other lane-4 writer's ~566.lanes-edit.sh: onlylane_log_eventsat 3157–3164. It stays clear ofregister_text(~3202),archive_text(~3238–3260),row_of_lane(~4568), lane 3's ~13000–13350 and ~16940–16960.Design notes
worktreesrow with an emptypathandbranch=unreadable inventory (<why>), the shape the issue suggested. A marker is its key:is_unread_markerisrecord_id == unread_key(lane), where the key is<lane>:plus a US plusunread, and nothing else decides it. No tree's key can be that, since no path in a US-separated line carries a US, and a same-path tree's suffix is<US><n>. The path and branch can't decide it either. A tree's path can be empty: a current-schema sidecar withtree:and nopath:gives one. And a hand-made sidecar's branch can readunreadable inventory (…); both stay trees. The UNREAD line is a plain pass-through, solanes-edit.sh worktrees --indexandlanesrender it unchanged. Where the lane's earlier rows are kept, the reason says so, and that is how the index read says the rows are held. On a first sync no suffix is added, so the index read is the source read byte for byte. The schema stays 1, so an indexer from before lanes-index inventory follow-ups from Copilot rounds 3 and 4 on #171: unread-lane answers, partial listings, foreign-source marks, SQLite read errors, footer remedy #173 sharing a Postgres store reads a marker as aTREEwith an empty path and counts it as a worktree. An older and a newer indexer syncing one store each converge to their own view, with no duplicates and no growth. They rewrite the store on every sync only where a lane has two trees with the same path, which the older indexer keys as one. With no such pair, an unreadable lane included, a sync after the first writes nothing. A store written by the unlanded heads f00ee58 or ab853b1 keeps their<lane>:marker as a kept row while that lane stays unreadable; it clears at the first sync that reads the lane. The manual says all of this.collect_inventorysince Derived index: a worktrees table mirroring every lane's #97 inventory under inventory:<Workstation>, nudged by every inventory write, and lanes --worktrees to read it with or without --index #171, and the second replaced the first. A later one is now keyed with its place among them,<lane>:<path><US><n>numbered from 2. The read is sorted, so a place holds while those trees keep their order, and a replay writes nothing. A change that reorders them (a renamed branch, say) moves both keys, and that sync upserts both.history --index. The index branch keeps the export's own closing count line, whichcmd_exportwrites straight after each log's warnings. That avoids a third copy of LOG_AWK's sentence.export --worktreesstill readsall_provenanceand then each inventory source in separate reads. Derived index follow-ups from #164: one read snapshot for export; holds keyed by canonical repository; history --index stderr count #166 namedcmd_exportof the register, and--worktreescame later, with Derived index: a worktrees table mirroring every lane's #97 inventory under inventory:<Workstation>, nudged by every inventory write, and lanes --worktrees to read it with or without --index #171.Tests
Local, under
flock /tmp/openrepotools-pytest.lock. The first 20-minute wait timed out, so this PR opened with CI as the record. The run then got the lock again at 19:04Z.git archiveof main f87fd5a (OPENREPOTOOLS_CODE_ROOT,LANES_EDITpointed into it). 9 failed, 9 of 9, each at its intended assertion:the export mixed two commits' rows, for both[wal-60.0]and[delete-5.0]BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY;not in the export's scripta refused source left a markTracebackinstatusinventory NOT READ … kept from the last syncbeside the held rowsno worktree is inventoriedafter a first syncFAKE_PSQLprints the marker for all four of the new test's\echolines. The four existing Postgres tests pass on main with the decoding fake, so the indexer's real\echo '\036LANES-INDEX-NEXT'decodes to the marker its parser reads.tests/test_lanes_index.pyat f00ee58 gave 78 passed and 1 failed. The failure was my own stderr comparison:history --indexadds a no-fetch note by design (thehistoryarm setsLANES_NO_FETCH=1where the index answered). ab853b1 sets that note aside on both sides, as theread:line is. After that, the 9 tests gave 9 passed.Review round, 5bf29d9 (findings 1–4 of the review at ab853b1). Both runs were local, under
flock /tmp/openrepotools-pytest.lock, which was taken at 22:11Z on 10-17.git archiveof it asOPENREPOTOOLS_CODE_ROOT): 2 failed, 2 of 2.test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker: the index read printedinventory NOT READ: feat/ywhere the source printed two trees with no path. The second had replaced the first.test_an_inventory_sync_asks_for_the_workstation_once:workstationwas asked twice.tests/test_lanes_index.pygave 81 passed.testsgave 1426 passed and 9 skipped;tests-no-submodulegave 1206 passed and 229 skipped;guard-launch-modepassed. These totals can't be compared with the ab853b1 run's: its base was a74c7fc (Install from the code an adopted assembly pins, resolved to one commit, and keep a single repository's install on today's reads, words and network needs. #191) and this run's was aaa7bc8, so ci: run the test suite on Linux only #227 is the base change between the two.Delta check, 0ac41f7 (nits 1–5 of the delta check at 5bf29d9). This ran local, under the lock, taken at 04:11Z on 10-18, with every
LANES_*andTMUX*unset.test_a_tree_whose_branch_reads_like_a_marker_is_a_treeexported a hand-made empty-path tree with branchunreadable inventory (forged by hand)asinventory NOT READ: forged by hand.tests/test_lanes_index.pyandtests/test_repo_hygiene.pygave 234 passed.testsgave 1548 passed and 19 skipped;tests-no-submoduleandguard-launch-modepassed.Doc fix, c255e18 (the checker's DELTA OK on 0ac41f7, with one doc correction). The manual now says an older and a newer indexer on one store rewrite it only where a lane has two trees with the same path, and says what a store from the unlanded heads keeps. This commit is documentation only.
tests/test_lanes_index.pyandtests/test_repo_hygiene.pygave 234 passed, under the lock, with everyLANES_*andTMUX*unset.CI. Linux
testson head ab853b1: success, run 38452710904. Linuxtestsgave 1426 passed and 9 skipped.tests-no-submodule,tests-windows,parse-macosandguard-launch-modepassed;tests-macoswaits for thereadylabel. Run 38450237715 on f00ee58 failed on the one stderr comparison that ab853b1 corrects (1 failed, 1205 passed intests-no-submodule).🤖 Generated with Claude Code
Summary by Sourcery
Harden lane index reads and synchronization so exported data is consistent, unread inventories remain explicit, and source behavior stays aligned with indexed reads.
Bug Fixes:
Enhancements:
Documentation:
Tests: