Skip to content

tests: the bump-leg.py and family.py reachability rows tag a descendant of the commit --to names, so a tag above it reads as one that reaches it and a tag below it keeps the plain words, and --points-at for --contains no longer survives (#1019) - #1107

Merged
brettheap merged 2 commits into
mainfrom
fix/1019-points-at-row
Oct 23, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

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

Goal

No test held that a tag at a DESCENDANT of the refused commit counts as a tag that reaches it, so --points-at in place of --contains survived in bump-leg.py and family.py (mutant M9 of #988's review, filed as #1019). This adds the rows the issue asks for to the two reachability test files, and no production code changes. Opened as a draft with no milestone: Brett Heap's ruling of 2026-10-21T10:32:04Z ("leave the follow-ups out of v1.2.0") held it until the v1.2.0 tag, and it is now on the v1.2.1 milestone.

What still held on main

Measured on main 2a119f28 before any change, and checked again on 16f9a0e81 and 7e00834c (the tag questions did not change, only their line numbers):

  • scripts/bump-leg.py _remote_tag_reaches and scripts/family.py _origin_tag_reaches list the tags with git for-each-ref --contains <commit> --format=%(objectname) refs/tags/. No other copy of either exists (the --contains in on_a_remote_branch and _on_a_remote_branch asks about branches and is not this question).
  • The mutant (--contains -> --points-at on that one line in each script) SURVIVES every row on main: test_bump_leg_reachability_words.py 34 passed, test_family_bump_reachability_words.py 20 passed, 54 rows, as the issue says.
  • The production code is right: the new rows pass on unmutated main. So they do not "fail on main"; they fail on the mutant, which is the issue's own acceptance ("With --points-at, that row must fail"). No defect was found.

What changed (two test files, 197 insertions, 4 deletions, after round 2)

tests/test_bump_leg_reachability_words.py:

tests/test_family_bump_reachability_words.py:

  • made_on(holder, env, parent, name): a commit never pushed, made in the mount on top of parent, through commit_in_mount (which checks parent out first), so the holder stays clean.
  • test_a_tag_at_a_descendant_of_the_commit_reaches_it: the same three kinds (3 rows), both bump --dry-run and the real bump refuse member-to-local-only in the tag words, with nothing moved and the mount's own tag as it was.
  • test_a_tag_at_an_ancestor_of_the_commit_alone_keeps_the_plain_words: the control (1 row).

No assert joins conditions with and or or; each premise is its own assert. No README, docs, AGENTS.md or handbook text names these tests or their row counts (grep), so none changes.

The mutants (rebased tree, 7e00834c's line numbers: bump-leg.py 1205, family.py 8869; each mutant restored, sha256 checked equal to HEAD)

Mutant Rows before (main's) With these rows
--points-at in bump-leg.py survives: 34 of 34 pass killed: 6 failed, 36 passed (all six descendant rows; the two controls pass, as they should)
--points-at in family.py survives: 20 of 20 pass killed: 3 failed, 21 passed (all three descendant rows)
--merged in bump-leg.py (extra: a check read the other way round) survives: 34 of 34 pass killed: 8 failed (6 descendant rows and both controls)
--merged in family.py (extra) survives: 20 of 20 pass killed: 4 failed (3 descendant rows and the control)

The failure is the one the issue names: the plain words ("is reachable from no branch of ...") where the tag words ("and a tag of ... reaches it, but no branch ... does") are expected. Logs: r-mut-points-at-bump.log, r-mut-points-at-family.log, r-mut-merged-bump.log, r-mut-merged-family.log (and mut-main-*.log for the 34 and 20 on 2a119f28).

Runs (through run9.sh, EMAIL=service@opensoft.dev, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null, gh stubbed, at most two at once, never the full suite)

On the pushed head 01fcb259:

  • git 2.43.0 (/usr/bin/git): the two test files, 66 passed (42 + 24; 34 + 20 before).
  • git 2.43.0: the trio tests/test_repo_hygiene.py, tests/test_agents_md_codes.py, tests/test_cli_reference.py and tests/test_update_shape_kept_helper.py, 329 passed.

On 1201680f (the same two files on top of 16f9a0e8, before 7e00834c, whose three files no row of mine touches or runs):

  • git 2.43.0: the trio alone, 170 passed; the two test files that import helpers from mine (test_bump_leg_fetch_refspecs.py, test_family_bump_fetch_refspecs.py), 102 passed; the two test files, 42 and 24 passed.
  • git 2.47.1 ($Q/v115/w764/git247/bin/git first on PATH, git --version printed git version 2.47.1): the two test files, 66 passed.

No script changed, so no other test runs a changed script.

Merge-trees

Round 2 (rv1107, NIT 2; NIT 1, the title, was the coordinator's)

  • The NIT. The docstrings of the two descendant rows said "Every row before this one tagged the commit itself, so ... survived all 54 of them". 54 counts the two files together (34 + 20), several earlier rows tag nothing (the no-origin and prune rows), and a count goes stale with the next row.
  • The fix, docstrings only. Each now says only what holds: "No row before this one tags a descendant of the commit: the tags in them are at the commit itself or below it, so --points-at in place of --contains, the mutant M9 of bump-leg.py and family.py: the tag check compares the commits tags point to, so a lightweight or another annotated tag at the same commit reaches it (#982) #988's review, survived every row of this file." No row, assert or helper changed. The 54 in "What still held on main" above is the two files together, as it says.
  • One commit on top of 01fcb259, a fast-forward push, no rebase: head ee48b5fe9693ebda099d1c0aadaca7bdc16b91c1, read back with git ls-remote. Its tree is 615e571bb62fd1cf481e4152278b0e3628bab059.
  • Runs (git 2.43.0, through run9.sh, EMAIL=service@opensoft.dev): tests/test_bump_leg_reachability_words.py 42 passed, tests/test_family_bump_reachability_words.py 24 passed. They ran on 7186f3cf, the same tree as the pushed head; only the commit message was shortened after.
  • Merge-tree against a re-fetched origin/main (6da757cb, merge base 7e00834c): exit 0, tree 453566df33c886cf7fc2b1c39af02acbbff365dc. Main moved by nine files, none of mine; its scripts/bump-leg.py change (9 lines added) leaves _remote_tag_reaches and its --contains line alone (now line 1214).

Closes #1019

🤖 Generated with Claude Code

Summary by Sourcery

Strengthen reachability tests for bump-leg and family so descendant-tag handling is validated and incorrect points-at or reverse-reachability implementations are caught.

Enhancements:

  • Expand bump-leg and family reachability coverage to verify that tags on descendant commits are reported as reaching the target, while tags on ancestors do not change the plain refusal wording.

Tests:

  • Add parametrized reachability tests covering annotated and lightweight tags across local and remote repositories, including controls for ancestor tags, dry-run behavior, and preservation of repository state.

… names, so a tag above it is told as one that reaches it and a tag below it keeps the plain words, and `--points-at` in place of `--contains` no longer survives (#1019)

`_remote_tag_reaches` (bump-leg.py) and `_origin_tag_reaches` (family.py)
list the tags that reach a refused commit with `git for-each-ref --contains`:
a tag at the commit or at any commit made on top of it. Every row tagged the
commit itself, so swapping `--contains` for `--points-at`, which lists only a
tag AT the commit, survived all 54 rows of
tests/test_bump_leg_reachability_words.py and
tests/test_family_bump_reachability_words.py (mutant M9 of #988's review).

Each file now has a row where the leg (or mount) and the remote each tag a
DESCENDANT of the commit, made apart, and both runs give the tag words (bump-leg
through `origin` and through `--local-remote-dir`), and a control where they tag
only the commit's ANCESTOR and the plain words stay. No production code changes.

Closes #1019

Lane: openreposhape-5 (openRepoShape-5)
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

This test-only PR closes the reachability coverage gap in bump-leg and family by modeling tags on descendants and ancestors of a local-only commit, validating correct refusal wording and state preservation across execution paths, and killing the surviving --points-at mutation.

File-Level Changes

Change Details Files
Add descendant-tag reachability coverage to ensure tags on descendants of the refused commit are reported as reaching it.
  • Extend commit-fixture helpers to create commits on top of arbitrary parents.
  • Exercise annotated and lightweight tag combinations through both bump-leg execution paths.
  • Assert tag-based refusal wording, unchanged refs, and no repository movement.
  • Verify premises exclude exact-point tags, branches, and remote-tracking refs.
tests/test_bump_leg_reachability_words.py
tests/test_family_bump_reachability_words.py
Add ancestor-tag control cases to preserve the distinction between tags above and below the target commit.
  • Assert a tag on the parent does not reach a child commit absent from the remote.
  • Preserve the existing plain reachability wording for both dry-run and real bump flows.
  • Check offline state and repository contents remain unchanged.
tests/test_bump_leg_reachability_words.py
tests/test_family_bump_reachability_words.py
Strengthen mutation coverage for the production reachability query without changing production code.
  • Ensure replacing --contains with --points-at fails all descendant-tag cases.
  • Cover both bump-leg and family implementations and multiple tag object types.
  • Retain compatibility checks for existing helper-importing and hygiene test suites.
tests/test_bump_leg_reachability_words.py
tests/test_family_bump_reachability_words.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1019 Add reachability-test rows in bump-leg.py’s two routes and family.py where tags point to a descendant of the pinned commit, and verify the tag-reaches wording (so replacing --contains with --points-at fails those rows). ✅
#1019 Add a control in each test area showing that a tag only at an ancestor does not reach the pinned commit and the plain wording remains. ✅

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 #1107 (#1019) at 01fcb259, for lane openRepoShape-5: READY

Findings: no MUST, no SHOULD.

  • NIT 1, the title. It begins "bump-leg.py and family.py:", but no line of either script changes.
  • NIT 2, tests/test_bump_leg_reachability_words.py:355 and tests/test_family_bump_reachability_words.py:476. The docstrings say "Every row before this one tagged the commit itself, so ... survived all 54 of them."
    • 54 is the two files together (34 + 20); each file held 34 or 20 on its own.
    • Several earlier rows (the no-origin and prune rows) tag nothing.
    • The count goes stale with the next row.
    • Scenario: a reader counting this file's rows finds 34 and doubts the docstring.
    • Suggest "all 34 of this file's (54 with family's)", or drop the count.

Checked and true

  1. The issue's mutant, --points-at in place of --contains, at bump-leg.py:1214 (line 1205 at 7e00834c) and family.py:8869.

    • It survives main's rows (6da757cb): bump-leg 34 passed, family 20 passed. That is the issue's 54.
    • The new rows kill it.
      • bump-leg, on the predicted merge: 6 failed, 36 passed. The six failures are the descendant rows, both routes. The controls pass.
      • family, at the head: 3 failed, 21 passed. The three failures are the descendant rows.
      • These are exactly the writer's numbers. The failure text gives the plain words where the tag words are expected.
    • I also re-ran the writer's --merged mutant in family.py: 4 failed (the 3 descendant rows and the control), 20 passed. Same as the body.
  2. The rows.

    • Descendant rows. Their setup checks:

      • above^ is made;
      • the two tags are two objects, and both peel to above;
      • for-each-ref --points-at made refs/tags/ is empty on both sides, which is what makes --points-at fail;
      • a tag of the remote contains made, but no branch and no remote-tracking ref does.

      They then expect the tag words, no "cannot fetch", the own tag unchanged and nothing moved. Each asserts what its name says.

    • Controls.

      • Setup: the commit named is the child and the tag is at its parent. The remote holds the parent but not the child, and no tag and no remote-tracking ref of the leg (or mount) contains the child.
      • They expect local_only(child).
      • They are true controls: they pass on the real code and under --points-at, and fail under --merged.
    • Helpers.

      • The rows reuse the files' own helpers: tag_as, pushed, only_a_tag_reaches, assert_one_refusal, tag_reaches, local_only, contains, commit_in_mount, both_runs, private_origin.
      • made_in_the_leg's default on="HEAD" keeps the old behaviour. Its one other importer, tests/test_bump_leg_fetch_refspecs.py, does not pass on.
    • AST scan.

      • The bump-leg file has 89 asserts and the family file 77. None joins conditions with and or or.
      • Both files are at most 79 wide, their existing width. The added lines are ASCII.
      • No top-level name is defined twice.
  3. Three mutants of my own. Each was applied alone and restored, with its sha256 checked equal to HEAD.

    mutant where result
    M1: compare the remote's tag list only with the commit (in held -> == commit.lower()) bump-leg.py:1227 6 failed, 36 passed: only the descendant rows, so main's 34 rows miss it too
    M2: the same family.py:8881 3 failed, 21 passed: only the descendant rows
    M4: drop the ^{} peel (set(tags) | set( -> set(tags) or set() bump-leg.py:1219 14 failed, 28 passed: 10 of the bump-leg.py and family.py: the tag check compares tag objects, not the commits they point to, so a lightweight tag against an annotated one of the same name at the same commit gets the plain words (follow-up to #937) #982 rows and 4 descendant rows; leg-lightweight-remote-annotated passes, as it should

    No mutant I ran survived. M1 and M2 are a second gap that only the new rows close.

  4. Runs, through run9.sh:

    tree git result
    head 2.43 66 passed in 191.57s
    head 2.47.1 (git --version read 2.47.1 at $Q/v115/w764/git247/bin/git; run9.sh's prepended folders hold only gh and git-filter-repo) 66 passed in 314.89s
    predicted merge aff80287 (01fcb259 + 6da757cb) 2.43 66 passed in 204.70s
  5. Merges:

    against result
    6da757cb clean, tree 0d1c5ad74e3cab67e019a7f69912782201973627
    7e00834c clean, tree 3a852e87 (the head's own tree)
    16f9a0e81 clean
    family.py: a failed remove's by-hand lines spell a leg mounted at a path that is not UTF-8 as a printf word of octal escapes ending in a slash, so git -C <member>/<leg> and submodule update -- <leg> typed in any POSIX shell name that leg (#1086) #1098 dad7961a (draft, touches family.py) clean, tree 46484a22
    shape_materialize.py: env_commit's docstring says the split commit's message spells a path byte that is not UTF-8 as _shown does, an escape, and no longer says it names it with a ? in its place, which #900 replaced (#1018) #1104 clean

    No other open PR touches the two test files.

  6. Title and body.

    • The title is 237 characters, ASCII.
    • closingIssuesReferences is [1019] alone, and the only closing keyword is Closes #1019. body-guard.py exits 0.
    • Body numbers I reproduced:
      • 195 and 4.
      • 34, 20 and 54.
      • 42 + 24 = 66.
      • 6 failed / 36 passed; 3 failed / 21 passed; 4 failed under family --merged.
      • Lines 1205 and 8869 at 7e00834c; tree 3a852e87.
  7. Sonar: gate OK, 0.0% new duplication, 0 open issues.

Could not verify

  • M3 (family --contains plus --merged on the same commit) was ready but not run, because of the checkpoint. So I made three mutants of my own, which meets the brief's "at least three" but not the common rules' four.
  • These body numbers were not reproduced: bump-leg --merged 8 failed; the 329 (the trio plus test_update_shape_kept_helper.py); the 102 importer rows; the 170 at 1201680f; the 8 open PRs at the time of writing.
  • tests/test_bump_leg_fetch_refspecs.py imports the changed made_in_the_leg, but I did not run it on the merge.
  • git 2.55 (CI).

Checklist candidates

  • A test-only PR's title names the test files or tests:.
  • A docstring states no row count that spans files.
  • When reviewing a reachability check, also mutate the comparison to "the commit only" (M1/M2).

(Posted by lane openRepoShape-5's coordinator. rv1104-1107 is lane 5's independent opus reviewer; it wrote none of #1107. The PR stays a DRAFT until the v1.2.0 tag; its NITs are taken before it leaves draft.)

Lane: openreposhape-5 (openRepoShape-5)

@brettheap brettheap changed the title bump-leg.py and family.py: rows tag a descendant of the commit --to names, so a tag above it is told as one that reaches it and a tag below it keeps the plain words, and --points-at in place of --contains no longer survives (#1019) tests: bump-leg.py's and family.py's reachability rows tag a descendant of the commit --to names, so a tag above it reads as one that reaches it and a tag below it keeps the plain words, and --points-at in place of --contains no longer survives (#1019) Oct 21, 2026
@brettheap brettheap changed the title tests: bump-leg.py's and family.py's reachability rows tag a descendant of the commit --to names, so a tag above it reads as one that reaches it and a tag below it keeps the plain words, and --points-at in place of --contains no longer survives (#1019) tests: the bump-leg.py and family.py reachability rows tag a descendant of the commit --to names, so a tag above it reads as one that reaches it and a tag below it keeps the plain words, and --points-at for --contains no longer survives (#1019) Oct 21, 2026
…_words.py: the descendant rows' docstrings say no earlier row of their file tags a descendant, and no longer count "all 54" rows of both files (#1019)

rv1107's NIT 2: "survived all 54 of them" counted the rows of the two files together
(34 + 20), several of those rows tag nothing (the no-`origin` and prune rows),
and the number goes stale with the next row. Each docstring now says only what
holds: no row before the descendant row tags a descendant of the commit, so
`--points-at` in place of `--contains` survived every row of its file.
Docstrings only.

Refs #1019

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 #1107 (#1019) at ee48b5fe, for lane openRepoShape-5: READY

Findings: one optional NIT. It does not need another round.

What changed and what I checked

  • The commit. One commit on top of 01fcb259 (a fast-forward). It changes only the two test files, 4 lines added and 3 removed in each. Its message says "Refs tests: no reachability row tags a descendant of the pinned commit, so --points-at in place of --contains survives in bump-leg.py and family.py #1019", which is not a closing keyword.
  • NIT 2 is fixed. "all 54" is gone from both files. The only other "all 54" in tests/ is an unrelated sentence in test_bootstrap_branch_bytes.py:528.
  • The new claims are true. I listed every tag each row makes in both files:
    • Every other tag is at the commit itself, or below it: the remote's v-same is at the pin, and the control's tag is at the parent.
    • The descendant rows are the only ones in either file that tag a descendant.
  • Nothing but docstrings changed.
    • With docstrings blanked, both files' ASTs are identical to round 1's (101224 and 80813). The only docstring that differs in each is test_a_tag_at_a_descendant_of_the_commit_reaches_it's.
    • The asserts are the same: 89 and 77, none with and or or.
    • The widths are still at most 79. The added lines are ASCII and at most 78 wide.
  • NIT 1 is fixed. The new title begins tests:. It is 250 characters, ASCII.
  • Merge. With main 6da757cb it is clean, tree 453566df (the body's tree). Of the 16 open PRs, only tests: the bump-leg.py and family.py reachability rows tag a descendant of the commit --to names, so a tag above it reads as one that reaches it and a tag below it keeps the plain words, and --points-at for --contains no longer survives (#1019) #1107 itself touches the two test files.
  • The refreshed body is true.
    • The stat against 7e00834c is 197 added and 4 removed.
    • The head's tree is 615e571b.
    • The commit the writer ran on, 7186f3cf, has the same tree 615e571b and the same parent 01fcb259, so its runs count for the pushed head.
    • Main moved by 9 files since 7e00834c, with bump-leg.py at 9 lines added and the --contains line now at 1214.
    • closingIssuesReferences is [1019], and the only keyword in the text is Closes #1019. body-guard.py exits 0.

Could not verify

  • The writer's 42 passed and 24 passed. I did not re-run them: the change is docstrings only and the AST is identical. Round 1 measured 66 at the head on 2.43 and on 2.47.1, and on the merge.
  • git 2.55 (CI).

(Posted by lane openRepoShape-5's coordinator. rv1104-1107 is lane 5's independent opus reviewer; it wrote none of #1107. The PR stays a DRAFT until the v1.2.0 tag.)

Lane: openreposhape-5 (openRepoShape-5)

@sourcery-ai

sourcery-ai Bot commented Oct 23, 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 4 hours and 42 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit b055bb7 into main Oct 23, 2026
5 checks passed
@brettheap
brettheap deleted the fix/1019-points-at-row branch October 23, 2026 19:06
brettheap added a commit that referenced this pull request Nov 8, 2026
…s message spells a path byte that is not UTF-8 as `_shown` does, an escape, and no longer says it names it with a `?` in its place, which #900 replaced (#1018) (#1104)

`env_commit`'s docstring in `scripts/shape_materialize.py` says what the split commit's message does with a `root` or `drop` path that holds a byte that is not UTF-8, as the code does since #900. It said the message names such a path "with a `?` in place of that byte, a path that does not exist". That was true before #900, and it is not true now. A reader of the docstring is told about a `caf?.md` the tool no longer writes.

Found by lane 1's writer on #900 and filed as #1018. Lane 5 took it outside v1.2.0, under Brett Heap's ruling of 2026-10-21T10:32:04Z.

Measured on main `2a119f281` and again on `16f9a0e81` (main after #1087), with a small script that calls: `adopt-project.py`'s own `_split_message` and `_shown`, then `env_commit` and a real `git commit`, read back with `cat -A`.

- The issue's claim held: the docstring (about line 2103 when filed, line 2268 on `2a119f281`, line 2349 on this branch) still said the message names the byte with a `?`.
- The code does not. `_shown("caf\udce9.md")` is `` `caf\udce9.md` ``, and the split message for a `root` and a `drop` entry named that holds no lone surrogate. Through `env_commit` and `git cat-file commit HEAD`, `cat -A`:

  ```
  DROPPED (in no leg and no longer here):$
    `caf\udce9.md`$
  STAYS IN THE ASSEMBLY ROOT:$
    README.md$
    `caf\udce9.md`$
  ```
- What stays true of `env_commit` itself: a message that holds the lone surrogate, handed to it directly, is still written as `?`. `cat -A` reads `Name: caf?.md$`. So the `errors="replace"` paragraph is kept, and what changes is who it describes.
- #1087 (main `16f9a0e81`) changed `scripts/shape_materialize.py` by 128 lines, none of them in `env_commit`, `COMMIT_COMMAND` or the message, and changed nothing in `adopt-project.py` (0 diff lines). The rebase onto it was clean and the docstring needed no other change.

One file, `env_commit`'s docstring, in two paragraphs: the `errors="replace"` paragraph (round 1, 11 lines added and 7 removed) and the sentence above it (round 2, below; 3 added and 2 removed). Together 14 added and 9 removed against main `6da757cb`. The `errors="replace"` paragraph now says:

- encoding, a character UTF-8 cannot carry is written as `?`, and nothing raises after both legs are pushed, as before;
- `adopt-project.py` refuses a lone surrogate in a path it extracts (`entry_path_problem`), and a `root` or `drop` entry that holds one is listed in the split message but never raw: the message names each path as `plan`'s report does (`_shown`, #900), so the byte 0xE9 reads as an escape with no surrogate in it, `` `caf\\udce9.md` ``, the path the source holds;
- written raw it would be a `?`, `caf?.md`, a path that does not exist, which is what `env_commit` still writes for a lone surrogate it is handed.

The backslash is doubled in the source, as the neighbouring docstrings write `\\udcc3`. A single `\u` in a non-raw docstring would be an escape that makes a lone surrogate. Measured: the docstring holds no character from U+D800 to U+DFFF and holds the six characters `\udce9`. The longest docstring line is 78 characters.

**The code is unchanged.** A script compares the AST of `scripts/shape_materialize.py` with every docstring blanked: the branch's file is identical to `origin/main`'s after the rebase onto `16f9a0e81` (round 1), and the merge of this head into `origin/main` `6da757cb` is identical to `6da757cb`'s file (round 2).

**No pin moves.** `scripts/shape_materialize.py` is not a pinned copy. `COPIED_VERBATIM`, `COPIED_FROM_SHAPE`, `FAMILY_COPIED_VERBATIM` and `FAMILY_COPIED_FROM_SHAPE` do not list it, and `templates/assembly-root/scripts/validate-pins.py` says of it "which no project carries". So no `upstream-changed` row, no digest, no assembly copy and no `tests/test_pinned_copy_text.py` entry changes.

**No other text pins these words.** Searched in `tests/`, `README.md`, `docs/`, `docs/handbook.html`, `AGENTS.md` and `templates/`: no test reads `env_commit`'s docstring (`__doc__`, `getdoc`), and none of them holds "in place of that byte" or "a path that does not exist" for this. The other places that name the `?` are about this function's behaviour or about history, and are still true: `_split_message`'s docstring says `caf\\udce9.md` "was `caf?.md`" (before #900), `_refused_by_policy` already says the message "lists it as `_shown` does (#900)", and `tests/test_adopt_spelled_names.py` quotes the message as it was on `c7a3ff16`. The `?` in `argv_text`, `trailer_line` and the commit-trailer text is about a `--trailer`, not the split message.

No rows and no mutants: the change is a docstring, the code is the same AST, and no test pins the docstring. So there is no row to fail on main and nothing to mutate.

All with `$Q/v110/run9.sh`, `EMAIL=service@opensoft.dev`, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`, the stub `gh`, a fake token, git 2.43.0 (`/usr/bin/git`).

Two shards, run before the rebase, at the `2a119f281` base with this one-file change in the tree. Together they are every `tests/*.py` that names `shape_materialize` or `env_commit` (86 files, including `tests/test_adopt_spelled_names.py` and `tests/test_adopt_entry_paths.py`, which run `env_commit` and the #900 message) and `tests/test_agents_md_codes.py`:

| run | files | result |
|---|---|---|
| shard A | 43 | 2873 passed, 4 skipped in 1707.51s, rc=0 |
| shard B | 44 | 4611 passed, 31 skipped in 2462.01s, rc=0 |

After the rebase onto `16f9a0e81`, on the committed head `e32b2703d733f6c60c1b2cba2d62d164d93d7b9a` (`dirty=0`): the trio, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py`, **170 passed in 55.67s**, rc=0. The docstring-only change needed no new shards: the shards' counts are runs at the `2a119f281` base, and #1087 did not touch `env_commit`.

Round 2, on the committed head `de89aa81b1533e32f1007f991aeae05cd915b36d` (`dirty=0`): the same trio, **170 passed in 61.77s**, rc=0. The round 2 commit is one docstring sentence, so it needed no new shards either.

git 2.47.1: no run. The change adds no test file of its own, so there is none to run on it.

`git merge-tree --write-tree HEAD <other>` with HEAD at `de89aa81b1533e32f1007f991aeae05cd915b36d`, after a re-fetch of `origin main` with `-c gc.auto=0 -c maintenance.auto=false` (round 2, about 14:03Z on 2026-10-21):

| against | result |
|---|---|
| `origin/main` `6da757cbe9a4eb66d60c34f38dcc2b332d175e8b` | clean, rc 0, tree `15c11b6c959fc4c7540105d5d62e3f961c8e27fd` |
| #1105 `fix/1048-makefile-follow-up-gnu-read-order` `5a4de7f763b5a3c32adce9614a3a539508416eb4`, an open PR that touches `scripts/shape_materialize.py` | clean, rc 0, tree `db05aead67052643942e35a9689d9dd4a0873fda` |
| #1118 `fix/1089-named-link-skip-tests` `c60066cbeec18b8b6c926e254ed4bee6b9a486c2`, the other open PR that touches `scripts/shape_materialize.py` | clean, rc 0, tree `a40df3b63c32c2a3d8f4c40587b094940c8d6567` |

Read with `gh pr list --state open --json number,headRefName,headRefOid,files`: 16 open PRs, three of them (this one, #1105 and #1118) touch `scripts/shape_materialize.py`, the only file this change touches. The other 13 (#1120, #1119, #1117, #1111, #1109, #1107, #1106, #1102, #1101, #1098, #1097, #1091, #1088) touch no file this change touches.

The same command with HEAD at the reviewed head `e32b2703` gives rv1104's trees: `de31d857` against `6da757cb` and `195ffe19` against #1105. The round 1 merge against #1090 is moot: #1090 has landed in `6da757cb`.

Held as a draft until the v1.2.0 tag; it is now on the v1.2.1 milestone.

- **NIT 1, `scripts/shape_materialize.py` lines 2340-2343, a line round 1 did not change.** The paragraph above the reworded one said the adoption's split message "names each entry path as the source spells it". Since #900 that is true of a printable path alone: `_split_message` lists any other through `_shown`, as the escape the next paragraph describes. It now says "as the source spells it (a printable one; see below)". One new commit on top of `e32b2703`, `de89aa81b1533e32f1007f991aeae05cd915b36d`, 3 lines added and 2 removed, docstring only, pushed as a fast-forward (no rebase, the PR is open).
  - Measured: the AST with docstrings blanked is identical to `e32b2703`'s and, merged into `6da757cb`, to `6da757cb`'s. The docstring holds no lone surrogate, still holds the six characters `\udce9`, and its longest line is 78 characters.
  - Searched `tests/`, `README.md`, `docs/`, the handbook mirror, `AGENTS.md`, `templates/` and the scripts for "names each entry path", "as the source spells" and the new words: only `tests/test_adopt_entry_paths.py` line 233 matches, a test's docstring about `execute` handing each path on as the source spells it, which is another sentence and pins nothing here.
- **NIT 2, this body.** It named main as `16f9a0e81` and #1090 as the one open PR that touches the file. #1090 has landed. Main is `6da757cb`, and the merge-trees above are redone against it, against #1105 and against #1118, a draft that opened since and also touches the file.

Review: rv1104-1107, lane 5's independent opus reviewer, which wrote none of #1104: READY at `de89aa81`, H0 of this landing (issuecomment-6318111413), with no finding; NIT 1 and NIT 2 of the round before are fixed. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 de89aa8 passed tests, tests-git-bash and SonarCloud; the head 92285d4 is a clean update-branch merge of main b66d8f0, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 6bd2128 over main b66d8f0: the repo_hygiene, agents_md_codes, cli_reference sets, 171 passed. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green".

Closes #1018

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Nov 8, 2026
…r through a tag it lacks, is told so, a tag of a tree or a blob it lacks is refused by name, and rows hold `YAML_NOT_TEXT_RE`'s exponents of two digits (#1103) (#1131)

The three things rv1090's review of #1090 (#912) left (#1090 issuecomment-6316009648): a row that holds `YAML_NOT_TEXT_RE`'s exponents of two or more digits, a true refusal where a leg with no `origin` holds a tag without its commit, and no `else` that nothing reaches. This is #1103. Brett Heap's ruling of 2026-10-21T10:32:04Z held it until the v1.2.0 tag, and it is now on the v1.2.1 milestone.

Three commits: `9d14c29a` on main `6da757cb`, a merge of main `038dafba` (clean), and fix round 1 `a4beec9e`, which answers rv1131's review (#1131 issuecomment-6321276436, READY with two NITs): see `## Fix round 1`. Together they change `scripts/bump-leg.py`, `tests/test_neutral_pin_entry_one_spelling.py` and `tests/test_bump_leg_reachability_words.py`. The sections down to `## Runs` are the first commit's, as rv1131 reviewed them.

Each claim was measured on main `6da757cb` by name; the issue's lines are at `d5389eb6`. All three held, at the same names.

- **The SHOULD held.** `YAML_NOT_TEXT_RE` (`scripts/shape_materialize.py:538-543`) has two exponent groups, `(?:[eE][-+][0-9]+)?` and `(?:[eE][-+]?[0-9]+)?`, and every exponent in `YAML_FLOATS` (`tests/test_neutral_pin_entry_one_spelling.py:92`) has one digit.
  - rv1090's R2 changes `[0-9]+` to `[0-9]` in both groups.
  - Applied to main, R2 passes all 208 rows of the spelling file, `test_scaffold_pin_and_reuse.py` and `test_adopt_pins.py` that `-k "spell or entry or name or rule or stock or every"` selects.
  - The head before this change quotes `.5e-10`, `.5E+10`, `.5_e-10`, `.5e10` and `.05E-12` correctly, so this is a missing row, not a defect.
- **NIT 1 held.** `_unreachable_refusal` (`scripts/bump-leg.py:1239`) was called by the probe on a leg that holds the tag `v1`'s object but not its commit.
  - With `has_origin=False` and either `fetched`, it said `bump-leg-commit-not-on-remote: <tag> is not in the leg's own object store, and the leg's mount, <leg>, has no remote named `origin` to fetch it from, ...`. That is untrue: `cat-file -t` says `tag`.
  - With `has_origin=True` it said `opensoft/openGlass has no branch containing <tag>`, which is true.
- **NIT 2 held.** A line trace of `peeled_target` (`scripts/bump-leg.py:979`) ran over a commit, a tag, a tag of a tag, a tree, a blob, a tag of either, a tag and a tag of a tag held without their commit, and an id the leg lacks.
  - `names a {kind}` was reached by the tree and the blob, and `is an annotated tag of a {names}` by the tag of a tree and the tag of a blob.
  - `else: what = "is an annotated tag that names no commit"` was reached by nothing.

- **`YAML_FLOATS`** gains `.5e-10`, `.5E+10`, `.5_e-10` and `.5e10`, as the issue asks.
  - It also gains `.5.5e-10`. All four of those reach the second pattern (`\.[0-9_]+`), so a cut to the first group's exponent alone still passed them. Only the first pattern, YAML 1.1's as written, takes `.5.5e-10`. Each group is now held on its own (R2a and R2b below).
  - The list's comment no longer says `--pin` admits every name in it: `.e+5`, `.5e+3` and `.5E+10` hold a `+`, which `PIN_ARG_RE` refuses, so it says a plain scalar can spell them.
  - Each new name runs through the two rows that take `YAML_FLOATS`: double-quoted and read back, and double-quoted by the rule alone. That is 10 new rows.
- **`_unreachable_refusal`**: where the leg has no `origin` and `object_kind(id)` is `tag`, the refusal keeps its code, `bump-leg-commit-not-on-remote`. Its words are now:
  - `<tag> is an annotated tag the leg's own object store holds without the commit it names, and the leg's mount, <leg>, has no remote named `origin` to fetch that commit from, so whether a branch of <repository> reaches it is not known`
  - `Remediation: check the tag -- the commit it names belongs to the LEG, not to the assembly root -- and push that commit to a branch of the leg first if it is not pushed yet; give <leg> back a remote named `origin`, ...` (an em dash in the tool, as in the words beside it).
  - An id the leg does not hold keeps its words, and so does every case with an `origin`.
  - Fix round 1 gives two rarer held tags this branch caught their own true words: a tag through a tag the leg lacks, and a tag of a tree or a blob.
  - No other text pins the old sentence. `grep` of `tests/`, `README.md`, `docs/`, the handbook and `AGENTS.md` finds it only in `bump-leg.py`, and `AGENTS.md`'s codes table is unchanged.
- **`peeled_target`**: the three-way `if`/`elif`/`else` is now one expression: `is an annotated tag of a {names}` for a tag, `names a {kind}` otherwise, with a comment saying why nothing else is left. The words for a tree, a blob and a tag of either are unchanged, by the same trace on the head. `shape_materialize.not_a_commit`, which `commit_on_forge` uses, still says "an annotated tag that names no commit"; it is not touched.

- `tests/test_bump_leg_reachability_words.py`, `test_a_tag_a_leg_with_no_origin_holds_alone_is_told_it_is_held`.
  - The setup: a tag of a commit pushed nowhere is made in a clone of the leg's remote, and only the tag object goes into the leg (`git hash-object -t tag -w`). The leg's `origin` is renamed to `upstream`, then both runs go through `assert_one_refusal`.
  - It asserts the note and the two lines exactly, through `told_no_origin`, and that the old sentence is not in stderr.
  - It reuses the file's `private_remote`, `without_origin`, `runner` and `origin_back`.
- **On main's code** (the two test files copied onto `6da757cb`, git 2.43): 1 failed, 159 passed. The one failure is that row, which got main's `REFUSED bump-leg-commit-not-on-remote: <tag> is not in the leg's own object store`.
  - The 10 float rows pass on main, as they should, because main's rule is right. They are there to kill R2, below.
- No assert joins conditions with `and` or `or`.

Each mutant was applied alone to a scratch clone holding this commit's files, then restored and compared with the worktree.

| Mutant | Rows run | Result |
| --- | --- | --- |
| R2: `[0-9]+` to `[0-9]` in both exponent groups | the spelling file | killed: 10 failed, 115 passed (all 10 new rows) |
| R2 on main's rows | the 208 rows above | survives: 208 passed |
| R2a: the first group only | the spelling file | killed: 2 failed (`.5.5e-10`, both rows) |
| R2b: the second group only | the spelling file | killed: 4 failed (`.5_e-10` and `.5e10`, both rows) |
| N1: the held-tag branch removed, so the old words answer | `-k "no_origin or held"` in the reachability file | killed: 1 failed, 3 passed |

All runs go through lane 5's `run9.sh`: a stub `gh` first on PATH, a bogus token, every `LANES_*` variable cleared, `EMAIL=service@opensoft.dev`, `GIT_SSH_COMMAND=false` and `GIT_CONFIG_GLOBAL=/dev/null`.

- **The two changed test files on the head:**
  - git 2.43: 160 passed;
  - git 2.47.1 (a git 2.47.1 build first on PATH; `git --version` printed `git version 2.47.1`): 160 passed.
- **git 2.43, every test file that runs `bump-leg.py`.** The commit changes production code in `scripts/bump-leg.py` only. `scripts/shape_materialize.py` is unchanged: only its test rows were added, and those ran on both gits above. So the related-file runs take the test files that name `bump-leg` or `bump_leg`, and the files that name only `shape_materialize` are not needed.
  - The set is `grep -rl -E 'bump-leg|bump_leg' tests --include='*.py'`: 56 files, sorted and split into two halves. It holds `tests/test_bump_leg_reachability_words.py` again, and `tests/test_repo_hygiene.py` and `tests/test_agents_md_codes.py`.
  - shard a, 28 files: 1038 passed, 1 skipped, 0 failed;
  - shard b, 28 files: 2324 passed, 1 skipped, 0 failed;
  - together 3362 passed, 2 skipped, 0 failed. Both skips are rows for another host: one needs a disk that ignores case (macOS, Windows), in `tests/test_bump_leg.py`, and one is Windows' own stdout default, in `tests/test_stdout_encoding.py`.
- **git 2.43, the trio** `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py`: 170 passed.

Commit `a4beec9e`, on the merge of main `038dafba` into the branch (`ae830e2b`, clean, tree `10c2372fe578`). It answers rv1131's review (#1131 issuecomment-6321276436).

### What changed

- **NIT 1: the no-`origin` words for a held tag were untrue in two rarer cases.** The branch keyed on `object_kind(id) == "tag"` alone, so every held tag `peeled_target` cannot peel was told the leg holds it "without the commit it names". Each case now reads the tag chain from the tags' own headers, `git cat-file tag`'s `type` and `object` lines, one tag at a time while the leg holds the next (`tag_chain_end`, new). The peel (`<id>^{}`) says nothing where the leg lacks an object on the way.
  - **(a) A tag through a tag the leg lacks**, with the commit at the end held or not: still `bump-leg-commit-not-on-remote`, now in these words:
    - `<tag> is an annotated tag the leg's own object store holds without a tag it leads through, <inner tag>, and the leg's mount, <leg>, has no remote named `origin` to fetch that tag from, so what that tag names, and whether a branch of <repository> reaches it, are not known`
    - `Remediation: check the tag -- the tags it leads through, and what they name, belong to the LEG, not to the assembly root -- and push them to the leg first if they are not pushed yet, and any commit they name to a branch of it; give <leg> back a remote named `origin`, ...` (an em dash in the tool, as in the words beside it).
  - **(b) A tag, or a tag of a tag, of a tree or a blob the leg lacks** names no commit, so `peeled_target` now refuses it `bump-leg-target-not-a-commit`, in the words a tag of a held tree or blob already had: `--to <tag> is an annotated tag of a tree in the leg (<repository>), not a commit`. That is with or without an `origin`, since no fetch makes a tree a commit.
    - Measured first: for the same tag with its tree or blob held, main and `9d14c29a` both refuse it `bump-leg-target-not-a-commit` in those words. The new refusal is byte for byte the same as main's for the same tag id held with its tree or blob, in all 6 (leg, id) pairs of (b) below.
    - With an `origin`, such a tag was refused `bump-leg-commit-not-on-remote`, "<repository> has no branch containing <tag>", with "check the commit". That is the one with-`origin` answer this round changes.
  - **The issue's own case is as it was:** a tag held without its commit, and a tag of a tag with both tags held, keep the head's words, byte for byte.
  - Neither code is added or removed. Neither code, nor any changed sentence, appears in `README.md`, `docs/`, the handbook or `AGENTS.md`; `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py` pass.
  - `peeled_target` and `_unreachable_refusal` keep their docstrings' accounts true: the #912 paragraph now says a tag held without THE COMMIT it names, and each function says what (a) and (b) are now told.
  - A cycle of tags made with `refs/replace/` is never walked: git's own peel never leaves one (measured, git 2.43: `rev-parse <tag>^{}` was still running at a 10 s timeout), and the walk is reached only where the peel failed.
- **NIT 2: mutant M5 survived.** `.5.5E-10` is added to `YAML_FLOATS`: only the first exponent group takes it, and only with its `E`. The list's comment says so.

### Words compared with main's and the head's tool

The probe called `peeled_target` and `_unreachable_refusal` from main `038dafba`, the head `9d14c29a` and this head on 15 legs x 10 ids x 4 `(has_origin, fetched)`, as rv1131's did, with legs for (a) and (b) added. Each id was asked as typed (`raw`) and, where `peeled_target` returned, as it returned (`flow`). That is 1578 answers from main and the head, and 1554 from this head, where 6 (leg, id) pairs are refused before the verdict.

| Pair | The same | Differ |
| --- | --- | --- |
| `9d14c29a` vs this head | 1532 of 1578 | 46: (a) 16, (b) 30, and nothing else |
| main vs this head | 1492 of 1578 | 86: those 46, the issue's case (12) and 28 `raw` asks |

- **(a):** 4 legs, `has_origin=False` only. Each holds the outer tag without the inner one: a tag of a tag of a commit, with the commit or without it, and a tag of a tag of a tree, with the tree or without it.
- **(b):** 6 (leg, id) pairs, `peeled_target` and the four verdicts each: a tag of a tree and a tag of a blob, each alone and under a held tag of it, and those two tags of a tag, all held without the tree or blob.
- **The 28 `raw` asks** ask `_unreachable_refusal` directly about a held tag of a tree or blob, which the flow never does: `peeled_target` refuses it first, with the tree or blob held or not. They keep the head's words.
- **Every other answer**, every id with an `origin` but (b)'s, every missing id and every held commit keeps main's words.

### Rows, failing on `9d14c29a` and on main

In `tests/test_bump_leg_reachability_words.py`, in the file's style, through `assert_one_refusal` (both runs, exit 2, the same words, nothing written). They reuse `private_remote`, `without_origin`, `runner`, `told_no_origin`, `refusal` and `origin_back`. Two helpers, `pushed_nowhere` and `written_into_the_leg`, are split out of `held_without_its_commit`, which keeps what it did.

- `test_a_tag_through_a_tag_the_leg_lacks_is_told_which_tag_it_lacks[commit-held, commit-missing]`: (a). The leg holds only the outer tag; the commit is the leg's own pin, or one pushed nowhere. The note and the two lines are asserted exactly, and "without the commit it names" is not in stderr.
- `test_a_tag_of_a_tree_or_a_blob_the_leg_lacks_is_refused_by_name[...]`: (b), 6 rows. The kinds are a tag of a tree, a tag of a blob, and a tag of a tag of a tree with both tags held. Each runs with the leg's `origin` and without it. The tree and the blob are pushed nowhere, so no fetch brings them, and the row checks that afterwards. The two lines are asserted exactly, with the note where there is no `origin`.
- **On `9d14c29a`'s `bump-leg.py`:** 8 failed, 59 passed (git 2.43; the rows of `tests/test_bump_leg_reachability_words.py` and `tests/test_bump_leg_dry_run_truth.py`). The 8 are exactly the 8 new rows: the 2 of (a) and the 6 of (b). The old file was put back by sha256 afterwards.
- **On main `038dafba`'s `bump-leg.py`:** 9 failed, 58 passed (the same two files, git 2.43). The 9 are those 8 and the issue's row, `test_a_tag_a_leg_with_no_origin_holds_alone_is_told_it_is_held`. The old file was put back by sha256 afterwards.
- **`.5.5E-10`** adds 2 rows, one to each test that takes `YAML_FLOATS`. They pass on main and on the head, as they should: the rule is right, and they are there to kill M5.
- No assert joins conditions with `and` or `or`. Added lines are at most 79 characters wide. The only non-ASCII character is the em dash in the tool's own words.

### Mutants

Each was applied alone in the worktree, at this commit. Its rows were run, then the file was restored and checked against the commit by sha256. For `bump-leg.py` the rows are `tests/test_bump_leg_reachability_words.py` and `tests/test_bump_leg_dry_run_truth.py`; for `shape_materialize.py`, the spelling file.

| Mutant | Result | Rows that failed |
| --- | --- | --- |
| M5: `shape_materialize.py`, the first exponent group's `[eE]` cut to `[e]` | killed: 2 failed, 125 passed | `.5.5E-10`, in `test_a_name_yaml_takes_for_no_text_is_double_quoted` and `test_the_rule_quotes_it_whatever_the_projects_reader_reads` |
| NM1: `names = names or tag_chain_end(submodule, target)[0]` cut to `names = names`, so the header is never read where the peel says nothing | killed: 6 failed, 61 passed | the 6 rows of (b) |
| NM2: the walk's `tag = named` step replaced by a second `return`, so the chain is never followed past its first tag | killed: 2 failed, 65 passed | the 2 `tag of a tag of a tree` rows of (b) |
| NM3: (a)'s branch made dead, `if names == "tag":` to `"tags"` | killed: 2 failed, 65 passed | the 2 rows of (a) |
| NM4: a blob not refused, `("tree", "blob")` to `("tree",)` | killed: 4 failed, 63 passed | the 2 `tag of a blob` rows of (b), and `tag of a blob` for `local-remote-dir` and `origin` in `test_a_tree_a_blob_or_a_tag_of_either_is_refused_by_name_in_both_runs` |
| NM5: (a)'s note names the outer tag, `{named}` to `{commit}` | killed: 2 failed, 65 passed | the 2 rows of (a) |
| NM6: the held-tag words given with an `origin` too, `not has_origin and` cut | killed: 2 failed, 65 passed | `test_a_tag_the_leg_holds_without_its_commit_is_not_on_the_remote`, for `local-remote-dir` and `origin` |
| NM7: the header key read is `tag`, not `type` | killed: 8 failed, 59 passed | the 8 new rows |

No mutant survived. After each, `scripts/shape_materialize.py` and `scripts/bump-leg.py` were restored from the commit and their sha256 checked: `a37e3d83fbcda475...` and `d6822093b1aa4b0e...` both matched, with the worktree clean.

### Runs

All go through lane 5's `run9.sh`, as above, at `a4beec9e`.

- **The two changed test files** (`tests/test_neutral_pin_entry_one_spelling.py` and `tests/test_bump_leg_reachability_words.py`): 170 passed, which is the first commit's 160 and this round's 10 new rows.
  - git 2.43: 170 passed;
  - git 2.47.1 (a git 2.47.1 build first on PATH; `git --version` printed `git version 2.47.1`): 170 passed.
- **git 2.43, the path guard and the trio**: `tests/test_path_guard_hygiene.py`, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py`: 219 passed.
- **git 2.43, every test file that runs `bump-leg.py`**, picked as above (`grep -rl -E 'bump-leg|bump_leg' tests --include='*.py'`): 57 files, which are the 56 files of the first commit's set and `tests/test_path_guard_hygiene.py`, which main gained since. Sorted and split in two:
  - shard a, 29 files: 1094 passed, 1 skipped, 0 failed;
  - shard b, 28 files: 2325 passed, 1 skipped, 0 failed;
  - together 3419 passed, 2 skipped, 0 failed. The two skips are the same two rows for another host as before: `tests/test_bump_leg.py` (a disk that ignores case) and `tests/test_stdout_encoding.py` (Windows' own stdout default).
- Every run in this section was on `a4beec9e` itself, committed, with the worktree clean (`dirty=0` in each log's START line).

Refreshed for fix round 1 (rv1131's body NIT). `git merge-tree --write-tree` of the head `a4beec9e` was run against origin/main, re-fetched (`-c gc.auto=0 -c maintenance.auto=false`), and against each open PR that touches these files:

| Against | Head | Result |
| --- | --- | --- |
| origin/main, re-fetched at 15:16Z on 2026-10-22 | `038dafba9dbe` | clean, tree `f571322318b1`, the head's own tree: the head holds main by its merge |
| #1107 `fix/1019-points-at-row` (DRAFT) | `ee48b5fe9693` | clean, tree `7383abd73770` |
| #1157 `fix/1100-refspec-followups` (DRAFT) | `a35a97ea52f4` | clean, tree `de32065b7371` |

- #1107 touches `tests/test_bump_leg_reachability_words.py`. It gives `made_in_the_leg` an optional `on=` and adds rows after `test_a_tag_of_a_tag_the_two_share_reaches_it`.
  - The rows here call neither `made_in_the_leg` nor anything #1107 changes. The merged file parses, and no name in it is defined twice.
- #1157 touches `scripts/bump-leg.py`, `scripts/shape_materialize.py` and two refspec test files, so it shares only `scripts/bump-leg.py` with this PR.
  - Its two hunks there are inside `_leg_fetch_refspecs`, at lines 1109 to 1146 of main's file. This PR's hunks in that file are at main's lines 976 to 1043 (`tag_chain_end`, new, and `peeled_target`) and 1250 to 1289 (`_unreachable_refusal`), so the two do not meet and the merge is clean.
- None of the other 34 of the 37 open PRs touches these three files.
- The branch is no longer behind main: the merge `ae830e2b` brought in every file main gained since `6da757cb` (among them #1111's `tests/test_path_guard_hygiene.py`). None of them is one of these three files, and the runs above stand on the head's own tree.

rc1131 re-checked fix round 1 at `a4beec9e` and found it READY with four NITs (#1131, issuecomment-6347256815). The commit `55c30f20f005` mends the first three, in `scripts/bump-leg.py` and `tests/test_bump_leg_reachability_words.py`. NIT 4, the title, is not in a commit. Nothing the tool does changes: the production edit is a comprehension that gives the same dict and a longer docstring.

### What changed

- **NIT 1, SonarCloud python:S7494 at `bump-leg.py:999`.** In `tag_chain_end`, `dict(line.split(" ", 1) for line in lines if " " in line)` is now `{key: value for key, sep, value in (line.partition(" ") for line in lines) if sep}`, the form rc1131 gave.
  - **Same dict:** `tools/equiv2.py` compares the two on every list of up to two lines of up to five characters, and every list of up to three lines of up to three characters, over `a`, `b`, a space and a tab (2478716 distinct lists: the script runs the two ranges one after the other, and the 7311 lists of up to two lines of up to three characters sit in both, so it makes 2486027 comparisons, as rc1131's count of the union shows), and on a real tag header with a continuation line, a line with no space and a `type ` line with a trailing space: equal in all. The commit message says "every list of up to three lines"; the coverage is the one in this sentence (`equiv.py`'s first pass, 3864591 lists, drew its three-line lists from a prefix of the strings).
  - **The rule:** SonarQube's local analysis of a standalone file holding both forms (project `opensoft_openRepoShape`, python, MAIN) reports one issue, python:S7494 MINOR, at the line of the old `dict(...)` form, and none at the comprehension.
  - **SonarCloud's own analysis, read through the SonarQube API** (rc1131's reading, issuecomment-6352247887, posted 2026-10-22T22:00:11Z; lane 5 read the same two answers again between 12:05Z and 12:08Z on 2026-10-23): for PR 1131 the API lists exactly one issue, python:S7494 at `scripts/bump-leg.py:999` (MINOR, created 2026-10-22T13:31:04Z), and its status is CLOSED, so there are 0 open issues. The quality gate is OK on all five conditions (new reliability, security and maintainability ratings 1, new duplicated lines density 0.0, new security hotspots reviewed 100.0). The API gives no commit, so it does not say which analysis it read: it shows the issue closed, not that the analysis was of `55c30f20`.
- **NIT 2, the walk of two headers.** `TAGS_OF_NO_COMMIT` has a fourth kind, `tag of a tag of a tag of a tree`: three tags, all held, and the tree pushed nowhere, run with and without an `origin` like the other kinds. Its values are now `(what, depth)`, and the row makes `depth - 1` tags over the first one (`outer1-w1131`, `outer2-w1131`) where it made the one `outer-w1131` for the second kind; no other row reads the dict.
- **NIT 3, `tag_chain_end`'s docstring.** It now says `("tag", <id>)` is also what it returns "through a tag whose header misstates what it names (`type tag` over an `<id>` the leg holds as something else, which only a malformed tag has)".

### Rows, failing on main

Both rows of the new kind (`test_a_tag_of_a_tree_or_a_blob_the_leg_lacks_is_refused_by_name[tag of a tag of a tag of a tree-origin]` and `[...-no-origin]`) fail on main's `bump-leg.py` (`961640c6`) and pass on the head's before this commit (`a4beec9e`'s). The two test files that hold rows for `peeled_target` (`tests/test_bump_leg_reachability_words.py` and `tests/test_bump_leg_dry_run_truth.py`) run 69 rows:

| `bump-leg.py` from | Result | Failing rows |
| --- | --- | --- |
| main `961640c6` | 11 failed, 58 passed | the 9 rows of fix round 1, and the 2 new rows |
| `a4beec9e` (this PR before the commit) | 69 passed | none: the new rows hold on the code as it was |
| `55c30f20` | 69 passed | none |

With main's file the new rows fail on the code of the refusal, as the other rows of the kind do: the row expects `REFUSED bump-leg-target-not-a-commit`, and the run with an `origin` prints `bump-leg-commit-not-on-remote: testorg/Atlas-spec has no branch containing <the tag>` (main's `peeled_target` returns the tag as typed where its peel cannot be read, because the leg lacks the tree, and the run goes on to the reachability verdict), and the run without one prints the `git fetch origin ... has no remote named origin` note and goes the same way.

### Mutants

Each was applied alone to `scripts/bump-leg.py`, the 69 rows run on git 2.43.0, and the file restored from `HEAD`; all 11 restores matched (`bl=1a7a69d38f737dce`, `dirty=0`). The mutants are in `tools/mutate.py`.

| Mutant | Result | Rows that failed |
| --- | --- | --- |
| NX1 (rc1131's): the walk reads at most two headers, `while True:` as `for _ in range(2):` | **killed**: 2 failed, 67 passed | the 2 rows of the new kind: the walk stops at `tag`, and the refusal is `bump-leg-commit-not-on-remote`, not `bump-leg-target-not-a-commit` |
| NX0: the walk reads one header | killed: 4 failed | the 2 rows of the third kind and the 2 of the new one |
| NM2 (fix round 1's): `tag = named` replaced by a return | killed: 4 failed | the same 4 rows |
| NX2 (rc1131's): no check that the leg holds the next tag | killed: 2 failed | the 2 rows of `test_a_tag_through_a_tag_the_leg_lacks_is_told_which_tag_it_lacks` |
| C3: the comprehension's `key: value` as `value: key` | killed: 10 failed | the rows that read a tag's header |
| C6: the comprehension keeps the lines with no space (`if not sep`) | killed: 10 failed | the same 10 rows |
| NX3: the walk reads at most three headers | **survives**: 69 passed | none |
| C1: the `if sep` filter dropped | survives: 69 passed | none |
| C2: `rpartition` for `partition` | survives: 69 passed | none |

- **NX3 survives by construction.** A walk bounded at three headers gives the code's answer on every chain of up to three tags, and the longest chain the rows hold is three. Any bound survives a chain of that length, so a row of four tags would only move the survivor to a bound of four. NX1 is the mutant the review named, and it is killed.
- **C1 and C2 are equivalent for every header git writes.** The `type` and `object` lines each carry exactly one space and each appears once, and nothing reads another key. C1 keeps a line with no space as a key with an empty value, which `.get("type", "")` and `.get("object", "")` already say of a missing key. C2 splits at the last space, which for a line with one space is the first. Where they could differ is a hand-forged header that repeats a bare `type` line after `type tag`; no row forges one.

### Runs

Every run went through `run9.sh` with `EMAIL=service@opensoft.dev`, on `55c30f20` itself, committed, with the worktree clean (`dirty=0` in each log's START line). Logs are in `$Q/v124/n1131/`, where `$Q` is the lane's scratchpad.

| Run | git | Result |
| --- | --- | --- |
| `tests/test_bump_leg_reachability_words.py`, `tests/test_neutral_pin_entry_one_spelling.py`, `tests/test_bump_leg_dry_run_truth.py`, `tests/test_path_guard_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py`, `tests/test_pinned_copy_text.py` (`g243.log`) | 2.43.0 (`/usr/bin/git`) | 402 passed |
| the first three of those files (`g247.log`) | 2.47.1 (first on PATH; `git --version` printed `git version 2.47.1`) | 196 passed |

- The 57 files that run `bump-leg.py`, which fix round 1 ran in two shards (3419 passed), were not run again: the edit is inside `tag_chain_end`, and the rows that reach it are the two files above, which ran in full. CI's `tests` job on the pushed head is the full-suite run.
- `tests/test_path_guard_hygiene.py` passes: the commit adds no `is_file`, `is_dir`, `exists`, `is_symlink` or `os.path` guard.
- No added line is wider than 79 characters, none is non-ASCII, and no added assert joins conditions with `and` or `or`.

### Merge-trees at `55c30f20`

`git merge-tree --write-tree` of the head against origin/main, re-fetched (`-c gc.auto=0 -c maintenance.auto=false`), and against each open PR that touches one of this PR's three files. Of the 41 open PRs listed at 20:09Z (`gh pr list --json number,headRefName,headRefOid,files`), only #1157 and #1107, besides this one, touch them.

| Against | Head | Result |
| --- | --- | --- |
| origin/main, re-fetched before the push | `961640c681b5` | clean, tree `4d7968be5547`: the head's tree and `docs/release-notes/v1.2.0.md`, the one file main gained since `038dafba` (#1155) |
| #1157 `fix/1100-refspec-followups` (DRAFT) | `368b87e9be87` (re-measured at 2026-10-23T12:06:38Z, after #1157's NIT round, which changes only `tests/test_family_bump_fetch_refspecs.py`; `baa19972a271` gave the same clean merge with tree `bd6ebc5cd3af`, and `a35a97ea52f4` when this round began) | clean, tree `e036c02d6a12` |
| #1107 `fix/1019-points-at-row` (DRAFT) | `ee48b5fe9693` | clean, tree `a72145c05633` |

- This commit's hunks in `scripts/bump-leg.py` are at lines 979 to 1003, in `tag_chain_end`; #1157's against main are in `_leg_fetch_refspecs`, from line 1105. They do not meet.
- The other 38 of the 41 touch none of the three files.

### After the second re-check

rc1131 found this round READY at `55c30f20f` (#1131, issuecomment-6352247887), with two NITs of its own, both in this body and neither in the code. This body now answers both: the count of lists in NIT 1's equivalence check (2478716 distinct lists, 2486027 comparisons), and SonarCloud's own analysis (python:S7494 CLOSED, 0 open issues, gate OK, no commit named by the API). The one other change is the #1157 row of the merge-trees table, which moved when #1157's NIT round added a commit. The head `55c30f20f` is unchanged.

Review: lane 5's independent re-check, by a reviewer that wrote none of #1131: READY at `55c30f20` (issuecomment-6352247887), with rc1131's NITs 1 to 3 fixed. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 55c30f2 passed tests, tests-git-bash and SonarCloud; the head db8533a is a clean update-branch merge of main 0d5704b, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 8fe39a5 over main 0d5704b: the bump_leg_reachability_words, neutral_pin_entry_one_spelling, repo_hygiene, agents_md_codes, cli_reference sets, 351 passed. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green".

Closes #1103

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

tests: no reachability row tags a descendant of the pinned commit, so --points-at in place of --contains survives in bump-leg.py and family.py

1 participant