Skip to content

tests/test_adopt_root_symlinks.py: a row puts a directory the root keeps at a pin's file reached through a kept link, and holds that check renames the path the walk reaches, not the file kept in it, so rv1091's R6 no longer survives (#1116) - #1140

Merged
brettheap merged 3 commits into
mainfrom
fix/1116-r6-row
Oct 23, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-5 (openRepoShape-5)
Claim: #1116 (comment)

Goal

One row that holds the remedy check gives where a kept link leads a pin's walk to a directory, so that rv1091's mutant R6 (NIT 4 of its review of #1091, issuecomment-6316443939) is killed. This is a test-only change: tests/test_adopt_root_symlinks.py gains one row, and no production file, refusal code or sentence changes. It is a follow-up to #1091 (#904), so Brett Heap's ruling of 2026-10-21T10:32:04Z left it out of v1.2.0, and it is on the v1.2.1 milestone.

What held on main

The row

test_a_directory_at_a_pins_file_through_a_link_names_what_it_reaches, at tests/test_adopt_root_symlinks.py:1173, 30 added lines, in the file's style and on its helpers (planned, declare, check, held_pins, symlink_findings, refused_by, execute_args, no_leg_was_created, ticked):

  • the plan keeps contracts -> governance, keeps governance/, and puts a directory at governance/openglass-pin.yaml holding notes.md; a pin is declared on openGlass;
  • check exits 1, with no symlink finding;
  • its one plan-root-holds-pin line names the file governance/openglass-pin.yaml/notes.md kept under governance/, inside contracts/openglass-pin.yaml through the symlink contracts to governance, and its remedy renames governance/openglass-pin.yaml, the path the walk reaches, not the file kept in it;
  • execute refuses with plan-root-holds-pin and the same line before any leg.

No assert joins conditions with and or or, the added lines are at most 76 wide (the file's widest is 80) and ASCII. The docstring says that before #1091 check passed this plan, measured on main 823624d9.

The mutant

R6 alone, in a scratch tree at this head: adopt-project.py sha256 0c1c1f3d7abf41136a61d7d4651f13fef5d3642a3162c024e921429b88bd7c1f before, 9a921c6a9261234a68ef930e478421dca2d7135ad8674b357f2f55f46f773068 with R6, and restored to the first (checked), tree clean.

Where Result
the row, with R6 1 failed, 208 deselected: assert held_pins(checked) == [line] at :1196, the finding reading "rename governance/openglass-pin.yaml/notes.md"
both adopt-root files, with R6, git 2.43 1 failed, 326 passed, 3 skipped; the failed id is the new row alone
the row, on 823624d9's adopt-project.py (before #1091) 1 failed, 208 deselected: assert 0 == 1, check said plan ok

Runs

All through run9.sh (stub gh, bogus token, GIT_SSH_COMMAND=false, no global git config), at head 2b6db57a3:

Git Files Result END
2.43.0 tests/test_adopt_root_symlinks.py and tests/test_adopt_root_shape_paths.py 327 passed, 3 skipped (the reftable rows at 1956, 2011 and 2562) 2026-10-21T16:21:00Z rc=0
2.43.0 tests/test_repo_hygiene.py, tests/test_agents_md_codes.py and tests/test_cli_reference.py 170 passed 2026-10-21T16:23:02Z rc=0
2.47.1 (git version 2.47.1) tests/test_adopt_root_symlinks.py 209 passed, no skips 2026-10-21T16:31:33Z rc=0

The 2.43 count is 327 with the row, which is the 326 other rows plus it. tests/test_agents_md_codes.py passes, as no refusal code changed.

Merge-trees

NIT round 1

This round is one merge of main and one new commit, after rv1140's review: READY at 2b6db57a, with NITs (#1140 issuecomment-6341111415).

  • The row now keeps two files under governance/openglass-pin.yaml/: README.md and notes.md.
    • Git's order puts README.md first, so the one plan-root-holds-pin line names governance/openglass-pin.yaml/README.md. The sections above describe the first commit's one-file row.
    • This kills rv1140's mutant M3, which changes rows[0] to rows[-1] at adopt-project.py:770. M3 survived the one-file row.
    • The docstring names the sort order.
  • Mutants, the row alone, each applied alone and restored with its sha256 checked:
    • M3: killed (it survived before);
    • R6: still killed;
    • rv1140's M1, M2 and M4 to M7: all killed.
  • Runs at 94712c3bc:
    • git 2.43.0, tests/test_adopt_root_symlinks.py and tests/test_path_guard_hygiene.py: 255 passed, 3 skipped. The skips are the reftable rows.
    • git 2.47.1, the same files: 258 passed.

Closes #1116

🤖 Generated with Claude Code

Summary by Sourcery

Add regression coverage for correctly naming and renaming the path reached when a kept symlink leads to a directory at a pin's file.

Enhancements:

  • Expand adopt-root symlink coverage to verify that findings and remediation identify the directory reached at a pin path, including deterministic handling of multiple files within it.

Tests:

  • Add a regression test covering a kept directory at a pin's reached file through a symlink, including check and execute refusal behavior and mutant detection.

…eps at a pin's file reached through a kept link, and holds that `check` renames the path the walk reaches, not the file kept in it, so rv1091's R6 no longer survives (#1116)

`contracts -> governance`, `governance/` kept, and a directory at
`governance/openglass-pin.yaml` holding `notes.md`: `check` names the file
`governance/openglass-pin.yaml/notes.md`, kept in the root, inside
`contracts/openglass-pin.yaml` through the symlink, and its fix renames
`governance/openglass-pin.yaml`, the path the walk reaches, not the file
kept in it. `execute` refuses with the same line before any leg.

No row had a directory there. In every other pin row the kept path IS the
path the walk reaches, so a fix that named the kept file instead of the
reached one (rv1091's R6 on #1091, NIT 4) passed both adopt-root files.
With R6 applied, this row fails. Before #1091, main passed `check` on this
plan (measured on 823624d).

Closes #1116

Lane: openreposhape-5 (openRepoShape-5)

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

sourcery-ai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Adds one adopt-root symlink regression test that verifies findings and execution refusal identify the directory reached at a pin path, rather than the file kept inside it; no production behavior changes.

File-Level Changes

Change Details Files
Add regression coverage for reporting and refusing a root-held directory at a pin's reached file through a symlink.
  • Create a plan where governance/openglass-pin.yaml is a directory containing two files and is reached through contracts -> governance.
  • Assert check reports the first kept file while its remediation renames the reached directory.
  • Assert execute returns plan-root-holds-pin before creating any leg.
  • Use two files to verify deterministic ordering and protect against the rows-first/last mutant.
tests/test_adopt_root_symlinks.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1116 Add a regression test where a kept link leads the pin’s walk to a directory at the reached file path, and verify that the check remedy names the reached path rather than the kept file inside it. ✅
#1116 Ensure the new row kills mutant R6 while preserving the expected refusal behavior and leaving production and refusal-code changes unnecessary. ✅

Possibly linked issues


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

@brettheap

Copy link
Copy Markdown
Contributor Author

Independent review of #1140 (#1116) at 2b6db57a, for lane openRepoShape-5: READY

No MUST and no SHOULD. One test-strength NIT and two body notes. The one thing I did not finish is M3's survival across the other files, because the checkpoint cut that run.

Findings

NIT 1: the docstring says "the finding names the first file kept in it", and the row cannot tell first from last.

  • Place: tests/test_adopt_root_symlinks.py:1177, the docstring. The code is adopt-project.py:769, path, mode, entry = rows[0].
  • Scenario: the row's directory holds one file, notes.md. Mutant M3 changes rows[0] to rows[-1] and passes the row (1 passed, rc=0). The row cannot see a change to which file the finding names.
  • Not measured: whether M3 survives every row elsewhere. The full-file runs were killed at the checkpoint, so I know only that it survives this row.
  • Fix, if wanted: a second file in the directory (a README.md that sorts before notes.md). Otherwise drop the word "first" from the docstring. It does not block the PR; the issue asks only for R6.

NIT 2 (body only): the "22 open PRs" merge-tree line is stale.

NIT 3 (body only): the live PR body is longer than w1116/body.md.

Checked and true

Check 1: the row asserts what its name and docstring say.

  • The asserts, with the row at :1173:
    • check exits 1.
    • There is no symlink finding.
    • held_pins(checked) == [line] at :1196. The line pins the kept file governance/openglass-pin.yaml/notes.md, "under the entry governance/", "inside contracts/openglass-pin.yaml through the symlink contracts to governance", and the remedy "rename governance/openglass-pin.yaml".
    • execute refuses with plan-root-holds-pin and the same line.
    • No leg is created.
  • It uses the file's helpers and style.
  • No assert joins conditions with and or or (grep on the added lines).
  • The longest added line is 76 wide; the file's widest is 80. The added lines are ASCII and have no CR.
  • The longest run of identical lines against the rest of the repo's tests/*.py and *.py is 5, well under Sonar's 10-line block. Cognitive complexity is 0.
  • The row asserts no Python-version text, so the 3.14 against 3.12 gap does not touch it.

Check 1: the docstring's "before #1091 check passed it" holds.

  • 823624d9 is the direct parent of 4ed7c7bf.
  • On 823624d9's adopt-project.py (sha256 933d072e..., the same as w1116's), the row gives 1 failed, 208 deselected: assert 0 == 1, and check printed plan ok.
  • The scratch file was restored.

Check 2: R6 and my mutants.

  • Each was applied alone and restored with its sha256 checked, against pristine 0c1c1f3d7abf41136a61d7d4651f13fef5d3642a3162c024e921429b88bd7c1f.
Mutant Mutated sha256 Row alone
R6 9a921c6a9261... killed
M1 (where = "at" always) 7f61c3c7c50d... killed
M2 (drop the "under the entry" words) 42752f3bbe35... killed
M4 (disk.under(rel)) 71c4ab34037b... killed
M5 (link[1] becomes link[0]) f00226141ccef... killed
M6 (remedy names entry) 45588813effd... killed
M7 (remedy names link[0]) e31cb8d4e5c0... killed
M3 (rows[-1]) 8d96ce45bfb5... survives (NIT 1)
  • R6 across both adopt-root files on git 2.43: 1 failed, 326 passed, 3 skipped. The one failure is the new row, so the 326 other rows do not notice R6.
  • M4 across both files with the row deselected: 326 passed, 3 skipped, 1 deselected. So only this row kills M4, a mutation of the through-link directory lookup. That is a second reason for the row beyond R6.

Check 3: runs at head 2b6db57a, git 2.43, through run9.sh, EMAIL=service@opensoft.dev.

Check 4: title and body.

  • The title is 242 characters, true, and equal to the commit subject.
  • The body's claims that I reproduced all hold: the R6 sha256 pair, 1 failed, 208 deselected, 1 failed, 326 passed, 3 skipped, assert 0 == 1 on 823624d9, 327 and 3, 170, the line numbers :1173 and :1196, the merge-tree tree d675b07f, and the nine helpers named.

Check 5: the trailers.

  • The Opus 5.5 trailer matches the progress notes: w904, opus, ae78bccb1d19d3cad, wrote 3abf3b1f.
  • The Sonnet 5.5 trailer matches w1116, sonnet-xhigh, a5ebf2379bec0ca1d.
  • The only change from 3abf3b1f is the docstring's last two lines. Both trailers are accurate as the lane's notes record them; I did not read the transcripts.

Could not verify

  • M3 beyond this row: the full runs over the two adopt-root files and over test_adopt_ignored_shape_files.py, test_adopt_symlink_target_bytes.py, test_adopt_shape_pin_and_work_dir.py and test_adopt_pins.py were killed at the checkpoint.
  • git 2.47.1: the brief asked only for 2.43, so I did not run it. The body's 209 passed on 2.47.1 is unmeasured by me.
  • Trailers: the models behind them are taken from the lane's progress notes, not from the transcripts.
  • Python 3.14: nothing was run on it. There is no version-dependent text in the row.

Scratch left

  • Nothing of mine is running. The two drivers and their pytest and run9.sh processes were killed by PID at the checkpoint. Both mutation worktrees were restored to the pristine sha and clean before removal.
  • The four scratch worktrees (rv1140-head, rv1140-mut, rv1140-mut2, rv1140-merge) are removed with git worktree remove. The merge worktree needed --force for its own staged test file. My refs refs/l5rv/rv1140-pr and refs/l5rv/rv1140-pr1119 are deleted.
  • Files left in $Q/v119/rv1140/:
    • logs: run-head243.log, run-head-trio.log, run-merge-hyg.log, run-merge-trio.log, run-merge-sym.log, mut-row.log, mut-row2.log, mut-r6-full.log, mut-m4-norow.log, and the partial mut-m3-full.log and mut-m3-other.log;
    • raw per-mutant pytest logs: raw-*.log;
    • saved inputs: rv1091-comment.md, open-prs.json, pr*.diff, pre-1091-adopt-project.py, and the live PR body copy;
    • the scripts drive.py, mutate.py and restore.py.

Lane: openreposhape-5 (openRepoShape-5)

Brett Heap and others added 2 commits October 22, 2026 13:21
Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…eeps at a pin's file holds two files, `README.md` and `notes.md`, so it holds which one the finding names, and `rows[-1]` in `_kept_pin_finding` fails it (#1116)

NIT 1 of rv1140's review of #1140 (issuecomment-6341111415). The directory held
one file, `notes.md`, so a mutant that changed `rows[0]` to `rows[-1]` at
adopt-project.py:769 (M3) named the same file and passed the row. The directory
now holds `README.md` too, which sorts before `notes.md`: the finding names
`README.md`, and `held_pins(checked) == [line]` also holds that two files kept
under one entry are one finding. The docstring says so. Only the one row
changes: no production code and no other row.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

Re-check of #1140 (#1116) for lane openRepoShape-5: READY at 94712c3b

I checked only the delta from 2b6db57a, the head rv1140 passed (issuecomment-6341111415). No MUST, no SHOULD, no NIT. One body note, below.

Checked and true

1. The merge is clean and adds nothing of its own. e7623ed9 merges main 038dafba into 2b6db57a. Its tree is 8a5cf7b10a38223a3d51a7eaa46134555c1476d6, and git merge-tree --write-tree 2b6db57a 038dafba gives the same tree, as rv1140 predicted.

2. The delta is the one row.

  • git diff e7623ed9 94712c3b is one file, tests/test_adopt_root_symlinks.py: 10 insertions and 6 deletions, all inside test_a_directory_at_a_pins_file_through_a_link_names_what_it_reaches.
  • The row now keeps governance/openglass-pin.yaml/README.md and .../notes.md. README.md comes first, because the kept rows follow git's tree order, which is bytewise (R before n), and the expected line names README.md.
  • The added lines are at most 72 wide, ASCII, with no CR. The row's 5 asserts contain no and or or (ast scan), and the file's widest line is still 80.
  • No production file changes: the PR differs from main by this file alone (34 insertions).

3. NIT 1 is fixed: M3 is killed. Each mutant was applied alone to 94712c3b and restored, and the row ran alone (-k on its name):

Case Change Row alone
pristine none 1 passed, 208 deselected
M3 adopt-project.py:770, rows[0] to rows[-1] (sha256 0c1c1f3d... to 8d96ce45bfb5...) 1 failed: the finding names governance/openglass-pin.yaml/notes.md, not README.md
R6 line 778, _through_the_kept_link(link, reached, entry) to (link, path, entry) (sha256 9a921c6a9261...) 1 failed: the remedy names the kept file, not the path the walk reaches
adopt-project.py of main 823624d9 (before #1091, sha256 933d072e...) whole file 1 failed: assert 0 == 1, and check printed plan ok
  • The sha256 values of M3 and R6 match rv1140's.
  • After each case, adopt-project.py was back to 0c1c1f3d7abf41136a61d7d4651f13fef5d3642a3162c024e921429b88bd7c1f, and git status showed 0 modified files.
  • rows[0] also stands at adopt-project.py:7198. I mutated line 770 only, as rv1140's M3 did.
  • The commit message says "adopt-project.py:769" (the brief's words), and the body says "770". It is 770 at this head.

4. Runs at 94712c3b, with the worktree clean at each START.

  • git 2.43.0: tests/test_adopt_root_symlinks.py and tests/test_path_guard_hygiene.py, 255 passed, 3 skipped in 525.32s, rc=0. The skips are the three reftable rows, which need git 2.45.
  • git 2.47.1 (git --version printed first): the same two files, 258 passed in 472.09s, rc=0.
  • Both counts match the body (255 with 3 skipped, and 258).

5. Merge-trees.

Body note (not a finding)

The body says "Of the 28 open PRs". Today there are 37. The #1119 claim holds as written.

Could not verify

  • I did not re-run rv1140's mutants M1, M2 and M4 to M7, or the other adopt-root files. The delta touches only the row, and the body says they were killed.
  • I did not measure Python 3.14 or Windows. The delta adds no text that depends on the version.

Lane: openreposhape-5 (openRepoShape-5)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@brettheap
brettheap merged commit 6d57f30 into main Oct 23, 2026
5 checks passed
@brettheap
brettheap deleted the fix/1116-r6-row branch October 23, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant