Skip to content

T017: A tree record that names no absolute path makes its lane UNREAD, so a sync and a reconcile keep the lane's rows instead of swapping the tree's row for one keyed by the record's id (opensoft/openRepoTools#302) (part of opensoft/openRepoTools#186) - #6

Merged
brettheap merged 1 commit into
mainfrom
fix/pathless-tree-record-302
Nov 8, 2026

Conversation

@brettheap

@brettheap brettheap commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

What this is

This is the T017 continuation, in this leg (opensoft/openRepoTools-code), of the opensoft/openRepoTools branch fix/pathless-tree-record-302 at 01c83b70ebf3, for opensoft/openRepoTools#302 (part of opensoft/openRepoTools#186). The original tree and branch stay as the archived original (lane 4's decision, opensoft/openRepoTools#186 comment 6548343086). One commit here, 4ed3fd2ad6fe, carries it.

It carries that branch's own change against 3e97ae5, the head of fix/control-root-unsearchable-291 it is stacked on (so against the previous PR of this chain), with no merge of main, for the code leg's paths: lanes-edit.sh (+41 −5, worktree_rows and its two comment blocks only), lanes-index (+9 −5, comments only), tests/test_lanes_index.py (+24 −18) and the new tests/test_pathless_tree_record_302.py (+518). Its one other path, docs/README-lanes.md (+22 −9), is the spec leg's, in the paired PR. lane-start and lane-handoff have no diff.

What was wrong and what changed

What was wrong

A tree record is the file <control root>/trees/<id>.yaml that set-lane-tree writes. One that is read and names no absolute path (no path: line, an empty path:, a relative path:, or a record of another schema with none) is listed by worktrees as a TREE row keyed by the record's id, or by its relative text, where the path would be. The inventory index keys each tree <lane>:<path>, so a sync took that row for the tree and removed the row the record had last given under the tree's real path: its path, branch, counts and last commit. Nothing said the lane was not read.

Found by the writer of the opensoft/openRepoTools#291 fix, who put it to Brett Heap as a ruling and recommended a separate v1.2.0 issue (ruling 3 of opensoft/openRepoTools#291). It is at main before opensoft/openRepoTools#291. This change is stacked on opensoft/openRepoTools#291's and changes one function of lanes-edit.sh, worktree_rows.

A record with no path: line is not a whole record (since opensoft/openRepoTools#252, opensoft/openRepoTools#115), and the helper lists it under its own id. worktree_rows makes it a TREE row with the branch malformed sidecar and puts the record's id in the path column, on purpose: the header comment says the id stands where the path would be. The indexer keys the tree <lane>:<id>, so the row the record last gave, under its real path, is not in the listing. A sync removes it and writes the id-keyed row. worktree_rows printed no UNREAD line for the lane, so run_inventory did not hold it.

The writer's probe at 3e97ae5 (a sandbox estate, one lane and one tree per case, a first sync, one record changed, then a second sync) printed 1 upserted, 1 removed and no UNREAD for every shape below, and removed the tree's own row:

  • no path: line: the new row is repoB-1:c0-repoB-1, branch malformed sidecar;
  • an empty path:: the same;
  • a schema 2 record with no path:: the same key, branch unknown schema 2;
  • a relative path:: the key is the relative text, branch malformed sidecar;
  • a schema 2 record with a relative path: and an empty file, which the issue's rule reaches.

Controls at 3e97ae5: a malformed record or a schema 2 record that names an ABSOLUTE path keeps its key, and its row is rewritten in place (1 upserted, 0 removed); a whole record is upserted in place; lane-trees exits 0 with its <malformed> row in every case.

What changed

  • A second list beside part F's. worktree_rows collects wr_nopath next to wr_unrec (the ids A lane control root that cannot be searched reads as "no trees", so an inventory sync removes its rows (from #289's review) openRepoTools#291's part F collects). In the <malformed> arm and in the other-schema arm, case "${t_p:-}" in /*) asks the record's own path BEFORE the id stands in for it. The order matters in the second arm: lane_trees_list gives a record of another schema the id it claims, so a claim that is itself absolute (tree: /x/y) would pass an ask made after the fallback.
  • One UNREAD line per lane. The indexer keeps one reason per lane (unread[lane] = why), so a second line would replace the first. A lane with unreadable records and records that name no absolute path says both: part F's clause, then ; , then these records under <root>/trees name no absolute path, so which tree each records is not known: <ids>. That is lane 3's wording (finding 522): it does not say "read" inside the indexer's own "its inventory could not be read (...)" prefix.
  • What stays. The record's row stays in the listing, for a person to read. A record that is not whole or of another schema but names an absolute path keeps its key, is rewritten in place and holds nothing (the issue's line, lane 3's ruling R2). lane_trees_list, lane_sidecar_schema_ok, lane-trees, lanes, lane-start and lane-handoff are untouched, and lanes --worktrees now prints its NOT READ line and footer for such a lane.
  • The cost is the lane's. One such record holds every row of its lane, the tree's own and any a later sync would write, until the record is put right. A test pins it; the manual states it (the paired spec PR).
  • tests/test_lanes_index.py (test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker): it no longer files a record with no path:, since its lane now reads as not read. It keeps the record that is not whole but names the whole tree's path; its counts go from 7 to 6 and from 3 to 2. The marker's key rule for a row with no path stays pinned by the forged-line tests beside it.
  • lanes-index: comments only, at UNREAD_BRANCH and in collect_inventory (no code in lanes-index changes).
  • Lane 3's two tests (the original's last commit, in the test file alone): its review of the code found that no case had two or more records in one lane that name no absolute path (761), and that the per-lane reset in worktree_rows was unpinned, because every case put its record in repoB-1, which is read last (762). test_the_line_names_every_record_that_names_no_absolute_path and test_a_record_that_names_no_absolute_path_holds_its_own_lane_and_no_other close both.

Not in this PR

Old to new: the receipt

The original's own non-merge commits in opensoft/openRepoTools (3e97ae5..01c83b7), 4 in all, are carried by one commit in this leg, 4ed3fd2ad6fe. The list is the commit's own body, and it equals the original's non-merge commits read from the chain's refs/root/ ref (checked).

e094080de29f Add the tests that a tree record that is read and names no absolute path holds its lane's rows in the inventory index, as part F holds a record that could not be read (#302)
d035807239e7 lanes-edit.sh worktrees names a lane UNREAD beside a tree record that was read and names no absolute path, so a sync keeps the lane's rows instead of swapping the tree's row for one keyed by the record's id (#302)
e7f86b6fe02d The README and the lanes-index comments no longer say a sidecar with no path: is a tree with an empty path, and the README says what a record that names no absolute path costs its lane (#302)
01c83b70ebf3 Add the tests that the UNREAD line names every tree record that names no absolute path, and that the clause holds its own lane and no other (#302)

The subjects are the original's, byte for byte; a bare (#N) in them names the root issue.

Resolutions

None. The patch applied without conflict, at offsets only: lanes-edit.sh +97 and +118 lines, from opensoft/openRepoTools#295's and opensoft/openRepoTools#301's commits below it in this chain.

The pairing

The manual's part of this change (docs/README-lanes.md, +22 −9) is the commit 7c80dc826965 on fix/pathless-tree-record-302 in opensoft/openRepoTools-spec, its own PR, opensoft/openRepoTools-spec#9 (base fix/control-root-unsearchable-291). The code leg's checks read the manual through OPENREPOTOOLS_SPEC_ROOT: tests/test_repo_hygiene.py reads docs/README-lanes.md (call sites at lines 1373, 1404, 1423, 3139 and 3572 of the code leg's main; 1404 is the tuple of surfaces that a loop reads through doc()) and tests/test_test_roots.py pins which manual a run reads. The code leg's own CI (OPENREPOTOOLS_COMPOSED: '0', no spec leg beside the checkout) skips those checks, so they bind only in a composed run or with the variable set to a spec clone. A search of the manual lines this change touches for the strings those checks pin (the rung-count heading, the two settlement quotes, every lane on this workstation, the exit-code section's two markers and lane-end <lane> --retire) finds none: read, not run. So the two PRs are one change in two repositories, and they land together, as a pair. The code PR goes first, so that the manual never describes behaviour the code lacks: this PR first, then its spec PR, opensoft/openRepoTools-spec#9. The state that order passes through, the code chain's tip beside the spec leg's main, passed -k test_repo_hygiene in the independent review (run (e) under Tests: 223 passed, 14 skipped; that run read the chains' tips, not this commit alone). The other order's state, the spec tip beside the code leg's main, was not run. I found no check that turns either order red; that is a reading, not a run.

Tests

Run by the CODE writer, in a sandbox clone, with env -i (HOME, XDG_* and TMPDIR inside the sandbox), OPENREPOTOOLS_CODE_ROOT set and OPENREPOTOOLS_COMPOSED=0, as the leg's CI sets it. Each targeted run read this branch's own commit in its own worktree; that commit was 2ca415e7cc76 before the message amend, and the amend kept its tree, so the results stand for 4ed3fd2ad6fe.

run exit result time
bash -n on lanes-edit.sh, lane and lanes, at every commit of the chain 0 all ok -
py_compile on every changed .py file and on lanes-index 0 all ok -
run.sh -k "test_pathless_tree_record_302 or test_lanes_index" 0 116 passed (18 + 98, split inferred) 498.22 s

shellcheck, over the whole chain (main to the tip): errors 0 to 0 and warnings 108 to 108 (lane 14 to 14, lanes 8 to 8); notes up by 3, 2 and 2, all SC1003 on the literal '\' patterns of opensoft/openRepoTools#301's commit.

The pair runs below are of the two chains' tips, so they cover this commit with every later commit of its chain; apart from the targeted run above, no run was made at this commit alone. They are lane 4's independent paired review, 2026-10-31 (the letters are the review report's own), with OPENREPOTOOLS_SPEC_ROOT set to the spec clone shown, env -i, TMPDIR=/tmp and a 54-character XDG_STATE_HOME.

run code + spec exit counts time
(a) -k test_repo_hygiene code tip 5f2995d43893 + spec tip 8104f2cf965c 0 223 passed, 14 skipped, 2944 deselected 11.1 s (33 s wall)
(b) -k test_repo_hygiene, baseline code main 2a057a6 + spec main 9f8b21a 0 223 passed, 14 skipped, 2772 deselected 9.2 s (10 s wall)
(c) -k lane_helpers code tip + spec tip 0 42 passed, 6 skipped; 0 FAIL in the shell suite (the wrapper asserts " passed, 0 failed"), with a 54-character state root 2188.6 s (36:28)
(e) -k test_repo_hygiene code tip 5f2995d43893 + spec main 9f8b21a 0 223 passed, 14 skipped 5.5 s

The pair runs read the chains' tips as they were before the message amend (code 5f2995d43893, spec 8104f2cf965c). The amend kept every tree (git diff from each old commit to its new one is empty), so the runs stand for the amended tips, code 2c15aa6613c3 and spec 430ea20784ac. No failure is new at the tips. Run (d), the manual-reading tests at each branch, was skipped under the review's brief, because none of the five branches' test files reads the manual.

The CODE writer's first run of -k lane_helpers at the chain's tip, with a 212-character run root, gave 5 FAILs in the shell suite (4870 ok, 5 FAIL, 3 skipped), because rc_tree_id in tests/test_lane_helpers.sh lacks the 180-character cap that tree_id_for in lanes-edit.sh has, so at that length the two names differ and the planted records are never read; the chain changes neither, and run (c) above, with the 54-character state root, gave 0 FAIL.

Not run: the suite at the leg's main for comparison, bash 3.2, macOS and the assembly's composed check; the submodule was not initialized, so 6 tests skip.

Test files this commit adds or changes:

  • tests/test_pathless_tree_record_302.py, new (+518): 18 cases from 10 test functions at the root head, on A lane control root that cannot be searched reads as "no trees", so an inventory sync removes its rows (from #289's review) openRepoTools#291's estate helpers. Eight shapes, one case each (the four the issue probed, the two its rule reaches, and finding 523's two); the reads and the reconcile (lanes --worktrees, lanes --index --worktrees, lane-trees exit 0, reconcile --source inventory); the cost (a second tree's change and a tree filed meanwhile stay out of the index); one line for both kinds; four controls that hold before and after; and lane 3's two (761, 762).
  • tests/test_lanes_index.py, changed (+24 −18): the one existing test test_a_tree_with_no_path_is_a_tree_and_not_an_unread_marker, as What changed says.

The original's own record, from the root repository, at the root heads and not repeated in this leg: lane 3's red run at 3e97ae5 (28 failed, 15 passed for this file's first 13 cases and opensoft/openRepoTools#309's together; this file's were 9 red and 4 green, as the design predicted); 18 passed for -k test_pathless_tree_record_302 at 01c83b7; five mutants, each killed (lane 4's M3, M4 and M8, and lane 3's mA and mB, which the last two tests kill alone). In the shell suite -k lane_helpers gave 4889 ok and 2 FAIL, both lane-start register-row asserts whose expected dir sits under the run root (opensoft/openRepoTools#304).

Release notes (v1.2.0)

Proposed in lane 4's post to lane 3 (opensoft/openRepoTools#302 comment 6354092305). Not established: whether lane 3 accepts the line; it has not commented on it. Lane 3 ruled (opensoft/openRepoTools#301 comment 6352342484) that each line goes in the PR description.

A tree record that names no absolute path no longer swaps its tree's row in the index for one keyed by the record's id. worktrees names the lane UNREAD, so lanes-index sync and reconcile --source inventory keep the lane's rows and say why. One such record holds every row of its lane until it is put right. (opensoft/openRepoTools#302)

The line is here, in the code PR of the pair, and only here: the paired spec PR, opensoft/openRepoTools-spec#9, repeats none.

Review

READY, lane 4's independent paired review of both chains, 2026-10-31. It found no MUST and no SHOULD against the commits, and three NITs. Report sha256: 4ac9e5441affc6aeac48e6d73419580f6786440dc0e5dd32722cc40b8070a682. The review read the chains before the message amend (tips 5f2995d43893 and 8104f2cf965c, the same trees as the amended tips).

The original's recorded review, from the root repository; the comment ids are copied from the root draft, which lists them as the comments of opensoft/openRepoTools#302, and were not re-read (no network):

Landing

It lands only on Brett Heap's word, PR by PR; nothing in this text is a landing.

Lane: openRepoTools-4
Claim: opensoft/openRepoTools#302 (comment 6349903174)

🤖 Generated with Claude Code

Summary by Sourcery

Keep inventory lanes intact when tree records do not identify an absolute path, and clearly report the records preventing a reliable tree mapping.

New Features:

  • Treat tree records without an absolute path as unreadable lane state during inventory operations, preserving the lane’s existing rows and reporting the affected records.
  • Keep malformed and unknown-schema records visible while preventing sync and reconcile operations from replacing the tree row with an id- or relative-path-keyed row.

Bug Fixes:

  • Prevent sync and inventory reconciliation from removing a tree’s established row when its record has no absolute path.
  • Ensure lanes containing multiple pathless records report all affected record ids in a single unreadable status line.

Enhancements:

  • Preserve existing behavior for complete records and malformed or unknown-schema records that specify an absolute path.
  • Add comprehensive coverage for pathless, relative-path, empty, unknown-schema, multi-record, reconciliation, healing, and lane-isolation scenarios.

Tests:

  • Add regression tests covering pathless tree records across inventory listing, sync, reconciliation, status reporting, and recovery.
  • Update inventory index tests to reflect that pathless records mark their lane unreadable rather than acting as ordinary tree rows.

…lane UNREAD, so a sync and a reconcile keep the lane's rows instead of swapping the tree's row for one keyed by the record's id

T017 continuation, in opensoft/openRepoTools-code, of the
opensoft/openRepoTools branch fix/pathless-tree-record-302 at
01c83b70ebf3a91adea40cf76fe4f1d8a2eecf48 (issue
opensoft/openRepoTools#302). It carries that branch's own change against
3e97ae5, the head of fix/control-root-unsearchable-291 it is stacked on,
for the code leg's paths: lanes-edit.sh (worktree_rows and its two
comment blocks), lanes-index (comments only), tests/test_lanes_index.py
and tests/test_pathless_tree_record_302.py. Its one other path,
docs/README-lanes.md (+22 -9), belongs to the spec leg and is not in
this commit. worktree_rows collects the ids of tree records that were
read and name no absolute path beside the ids
opensoft/openRepoTools#291's part F collects, asks the record's own path
before the id stands in for it, and gives the lane one UNREAD line that
names both kinds.

The subjects below are quoted from the originals; a bare #N in them is
opensoft/openRepoTools#N.

Old-to-new receipt (the original's own non-merge commits; e7f86b6fe02d
also changes docs/README-lanes.md, whose part is the spec leg's):
e094080de29f Add the tests that a tree record that is read and names no absolute path holds its lane's rows in the inventory index, as part F holds a record that could not be read (#302)
d035807239e7 lanes-edit.sh worktrees names a lane UNREAD beside a tree record that was read and names no absolute path, so a sync keeps the lane's rows instead of swapping the tree's row for one keyed by the record's id (#302)
e7f86b6fe02d The README and the lanes-index comments no longer say a sidecar with no path: is a tree with an empty path, and the README says what a record that names no absolute path costs its lane (#302)
01c83b70ebf3 Add the tests that the UNREAD line names every tree record that names no absolute path, and that the clause holds its own lane and no other (#302)

Resolutions: none. The patch applied without conflict, at offsets only:
lanes-edit.sh +97 and +118 lines, from opensoft/openRepoTools#295's and
opensoft/openRepoTools#301's commits below it in this chain.

Lane: openRepoTools-4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Mark a lane UNREAD when a readable tree record names no absolute path, combining that reason with unreadable-record reasons in one lane-level line and updating the related code comments.

  • lanes-edit.sh L15022-15036, L15265-15279, L15318-15327 (+2 small edits)
  • lanes-index L1856-1859, L1955-1959

Tests

Update the existing worktree-index test and add coverage for pathless records, lane-level row retention, combined unread reasons, reconciliation, and control cases.

  • tests/test_lanes_index.py L2403-2422, L2427-2434, L2442
  • tests/test_pathless_tree_record_302.py L1-518

Tips and commands

Interacting with Sourcery

  • 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!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud

sonarqubecloud Bot commented Nov 7, 2026

Copy link
Copy Markdown

@brettheap
brettheap changed the base branch from fix/control-root-unsearchable-291 to main November 8, 2026 13:48
@brettheap
brettheap marked this pull request as ready for review November 8, 2026 13:48
@sourcery-ai

sourcery-ai Bot commented Nov 8, 2026

Copy link
Copy Markdown

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 6 days and 4 hours by commenting @sourcery-ai review. Upgrade to get a review now.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant