Skip to content

bump-leg.py, family.py, update-shape.py and the roots' validate-manifest.py, bootstrap.py, siblings.py and validate-family.py: fourteen missing, unreadable and wrong-kind refusals and four of check_pin name the path as shown_path does (#1288) - #1310

Draft
brettheap wants to merge 2 commits into
fix/1279-refusals-shownfrom
fix/1288-refusals-shown-more

Conversation

@brettheap

@brettheap brettheap commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Goal

Fourteen missing, unreadable and wrong-kind refusals (#1288's table), and the four other refusals of update-shape.py's check_pin that #1288 read but did not probe, name the path the way shown_path does: as it is, or as repr spells it where a character in it is not printable. A U+202E, an ESC or a line feed in a typed --root or --family-root, or in the folder a root or a holder sits in, no longer reaches the terminal raw from these refusals, and no longer lets a line feed begin a line of the path's choosing. A printable path prints byte for byte as before. No refusal code changes, and no word of any refusal but the path.

Stacked on #1284 (fix/1279-refusals-shown, head 86ab76db4, the PR for #1279): #1284 spells seven sibling refusals and adds what this build reuses, from repo_shape import shown_path in update-shape.py among them, and #1288's bump-leg.py sites sit beside #1284's own change there. This PR's base is fix/1279-refusals-shown. It is two commits on 86ab76db4: the build, 6b9a4ece5, and the round after rv1310's review, daaa5ad8a, which changes tests only. Milestone v1.2.2.

One change beyond #1288's table, for the reviewer to judge. pin_rows_remediation in update-shape.py (the remediation of both update-pin-row refusals) now spells the root as shown_path does where the root does not print, and is _by_hand's line as before where it prints. #1288 names the REFUSED line only, but that remediation puts the same root into a git -C <root> log -- contracts/shape-pin.yaml line, quoted by _by_hand, and a quote passes an ESC and a line feed through: with only the REFUSED line spelled, those two refusals still printed the ESC and forged a line, so no row could hold #1288's "no raw U+202E, ESC or line feed" for update-pin-row without it. The cost: for such a root the line is not a command that can be pasted as typed (no quoting could make it one). Details under "The sites" below; it is one conditional expression and a docstring paragraph, mutant M19 reverts it and X09 spells a printable root too.

The sites (measured before and after)

Real runs, with a stub gh first on PATH, GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1, over a folder named with U+202E (wd-pl<U+202E>an), then an ESC (wd-<ESC>[31mRED), then a line feed (wd-<LF>WARNING forged-line: pwned), then a printable name (wd <e-acute> and a space). 19 runs, 4 names each, 76 runs a side, every one exit 2.

For example, the line feed under family-manifest-unreadable (a holder's own validate-family.py), before:

REFUSED family-manifest-unreadable: <D>/wd-
WARNING forged-line: pwned/Acme/family.yaml: not a mapping

and after:

REFUSED family-manifest-unreadable: '<D>/wd-\nWARNING forged-line: pwned/Acme/family.yaml': not a mapping
code run site at this head spelled by
bump-leg-root-not-a-project bump-leg.py --root, no project.yaml scripts/bump-leg.py:226 shown_path, imported already
bump-leg-wrong-kind bump-leg.py --root, kind: other scripts/bump-leg.py:240 shown_path
manifest-missing a root's own validate-manifest.py --root, no project.yaml templates/assembly-root/scripts/validate-manifest.py:1431 its own _shown (line 467), there already
family-unreadable family.py bump --dry-run --family-root, family.yaml a list scripts/family.py:576 shown_path, imported already
family-wrong-kind the same, kind: other scripts/family.py:580 shown_path
family-manifest-missing a holder's own bootstrap.py --root --no-fetch, no family.yaml templates/family-root/scripts/bootstrap.py:1988 a new own _shown (line 374)
family-manifest-unreadable the same, family.yaml a list templates/family-root/scripts/bootstrap.py:1995 the same
family-manifest-unreadable a holder's own siblings.py --root --dry-run, past a bootstrap.py whose members_of reads no family.yaml templates/family-root/scripts/siblings.py:1757 a new own _shown (line 340)
shape-pin-missing a holder's own validate-family.py --root, family.yaml reading, no pin templates/family-root/scripts/validate-family.py:1850 a new own _shown (line 604)
shape-pin-unreadable the same, the pin a list templates/family-root/scripts/validate-family.py:1858 the same
family-manifest-missing a holder's own validate-family.py --root, no family.yaml templates/family-root/scripts/validate-family.py:4832 the same
family-manifest-unreadable the same, family.yaml a list templates/family-root/scripts/validate-family.py:4841 the same
update-pin-missing update-shape.py check --root, no pin update-shape.py:917 shown_path, imported by #1284
update-pin-commit the same, commit: nothex update-shape.py:958 shown_path
update-pin-digest-definition (read, not probed, in #1288) the same, digest_definition: other update-shape.py:963 shown_path
update-pin-row (read, not probed) the same, files: 5 update-shape.py:980 shown_path, and its remediation (below)
update-pin-row (read, not probed) the same, files: [5] update-shape.py:987 the same
update-pin-no-files (read, not probed) the same, files: [] update-shape.py:994 shown_path

The siblings site needs a stand-in: the holder's own bootstrap.py refuses a family.yaml that is a list first, in members_of, with the same code and words (#1288's note). Probed both ways: past the stand-in, the line is siblings.py's; with the holder's own bootstrap.py, it is bootstrap.py's, which this PR spells too. A row runs each.

update-pin-row's remediation, found while measuring. With its REFUSED line spelled, the refusal still printed the root raw: its remediation names git -C <root> log -- contracts/shape-pin.yaml, quoted for a POSIX shell by _by_hand, and a quote passes an ESC and a line feed through. Where the root does not print, pin_rows_remediation now spells it as shown_path does, as validate-pins.py's _quoted spells a word of a command it prints (#712); the line cannot be pasted as the root it names then, and no quoting could make it so. A printable root is _by_hand's line, as before. _by_hand's other lines (the rollback's hand fixes, branch -m, checkout) are left as they are. Without this, a row could not hold #1288's "no raw U+202E, ESC or line feed" for update-pin-row.

The skew rule. templates/*/scripts/* are copies a root or a holder keeps beside a scripts/repo_shape.py that may predate shown_path (update-shape.py apply --accept-local keeps a copy), so none imports it. validate-manifest.py uses the _shown it already carries. A holder's bootstrap.py, siblings.py and validate-family.py carried none, so each gains one: validate-manifest.py's code byte for byte (two statements), with a docstring that names the holder. siblings.py's is its own and not imported from bootstrap.py, which it imports from already: a holder that kept its own bootstrap.py would be refused update-accept-local-missing-name for the new name (#536). test_pinned_copy_text.py holds all three to shown_path, and test_no_pinned_script_imports_what_an_older_library_lacks (unchanged) lists all three already. The rows below also run each copy beside the repo_shape.py of 51a9c5f10, from before #1168.

Duplication. The three new copies are 7 lines each (def, a 4-line docstring, 2 statements); bootstrap.py's and validate-family.py's are the same 7 lines, and siblings.py's docstring is longer. Every other changed line changes one call. The new test file shares no run of more than 4 non-blank lines with any other file under tests/ (its import header, measured).

Text that pins these lines. A grep of README.md, AGENTS.md, docs/ (the handbook mirror docs/handbook.html too) and the templates' README.md and AGENTS*.md for each changed refusal's words finds none; docs/release-notes/v1.0.0.md and v1.2.0.md name some of the codes as the record of those releases. No doc changes. tests/test_cli_reference.py and tests/test_agents_md_codes.py are in the gate.

Rows

92 new rows in tests/test_refusal_paths_shown_more.py, and one row extended in tests/test_pinned_copy_text.py:

  • test_each_refusal_names_the_path_as_shown_path_names_it, 19 runs x 4 names (76): each asserts exit 2, no traceback, exactly one REFUSED line, REFUSED <code>: <the path, named as shown_path names it><the refusal's own words>, no U+202E, no ESC and no line starting WARNING forged-line anywhere in stdout or stderr. The two update-pin-row runs also assert the remediation's git -C line: spelled where the root does not print, shlex.join's where it does.
  • test_each_copy_spells_the_path_beside_an_older_library, 4 copies x 4 names (16): one refusal of each copy (validate-manifest.py, and a holder's bootstrap.py, siblings.py and validate-family.py) beside the repo_shape.py of 51a9c5f10.
  • test_pinned_copy_text.py::test_the_pinned_scripts_spell_a_path_as_the_library_does: the three new copies join the three it held, siblings.py through test_pinned_copy_columns.py's copies fixture (its bootstrap import answered by the holder's bootstrap.py), and a U+202E case joins its 8 (9 ids).

Every non-ASCII character in the sources is written as an escape. The ESC and line-feed rows skip by name on Windows (conftest.POSIX_NAMES), and every row skips by name where its folder name cannot be made (unsafe_folder).

Run at #1284's head: the head's two test files in the detached tree over the base's production files, git 2.43.0, -rfEs.

rows at 86ab76db4 at this head
19 refusals x U+202E, ESC, line feed (57) 57 fail pass
19 refusals, a printable path (19, the controls) pass pass
4 copies beside the older library x U+202E, ESC, line feed (12) 12 fail pass
the same, a printable path (4, controls) pass pass
the copy test (9) 9 fail (AttributeError: no _shown in the holder's scripts) pass

69 of the 92 new rows fail at the base, every row over a name that does not print, and the 23 printable controls pass.

Mutants

Each applied alone in the detached tree with this head's files, the 92 rows run (and the copy test for a mutant of a copy's _shown), git 2.43.0, -rfEs; the file put back and its sha256 checked, and git status the same after each. 28 mutants, 28 killed, none survived.

mutant killed by
M01 bump-leg-root-not-a-project back to {path} 3: bump-leg-root-not-a-project (U+202E, ESC, line break)
M02 bump-leg-wrong-kind back to {path} 3: bump-leg-wrong-kind (U+202E, ESC, line break)
M03 the root's manifest-missing back to {manifest_path} 6: manifest-missing (U+202E, ESC, line break); beside the older library manifest-missing (U+202E, ESC, line break)
M04 family-unreadable back to {path} 3: family-unreadable (U+202E, ESC, line break)
M05 family-wrong-kind back to {path} 3: family-wrong-kind (U+202E, ESC, line break)
M06 a holder's bootstrap.py: family-manifest-missing back to {manifest_path} 3: family-manifest-missing-bootstrap (U+202E, ESC, line break)
M07 a holder's bootstrap.py: family-manifest-unreadable back to {manifest_path} 9: family-manifest-unreadable-bootstrap (U+202E, ESC, line break); family-manifest-unreadable-siblings-members-of (U+202E, ESC, line break); beside the older library family-manifest-unreadable-bootstrap (U+202E, ESC, line break)
M08 siblings.py's family-manifest-unreadable back to {root / MANIFEST} 6: family-manifest-unreadable-siblings (U+202E, ESC, line break); beside the older library family-manifest-unreadable-siblings (U+202E, ESC, line break)
M09 validate-family.py's shape-pin-missing back to {pin_path} 3: shape-pin-missing (U+202E, ESC, line break)
M10 validate-family.py's shape-pin-unreadable back to {pin_path} 3: shape-pin-unreadable (U+202E, ESC, line break)
M11 validate-family.py's family-manifest-missing back to {manifest_path} 3: family-manifest-missing-validate-family (U+202E, ESC, line break)
M12 validate-family.py's family-manifest-unreadable back to {manifest_path} 6: family-manifest-unreadable-validate-family (U+202E, ESC, line break); beside the older library family-manifest-unreadable-validate-family (U+202E, ESC, line break)
M13 update-pin-missing back to {pin_path} 3: update-pin-missing (U+202E, ESC, line break)
M14 update-pin-commit back to {pin_path} 3: update-pin-commit (U+202E, ESC, line break)
M15 update-pin-digest-definition back to {pin_path} 3: update-pin-digest-definition (U+202E, ESC, line break)
M16 update-pin-row (files: not a list) back to {pin_path} 3: update-pin-row-not-a-list (U+202E, ESC, line break)
M17 update-pin-row (a row not a mapping) back to {pin_path} 3: update-pin-row-entry (U+202E, ESC, line break)
M18 update-pin-no-files back to {pin_path} 3: update-pin-no-files (U+202E, ESC, line break)
M19 pin_rows_remediation always _by_hand's line 6: update-pin-row-entry (U+202E, ESC, line break); update-pin-row-not-a-list (U+202E, ESC, line break)
X1 bump-leg-root-not-a-project as repr(str(path)), a printable path too 1: bump-leg-root-not-a-project (printable)
X2 the root's manifest-missing as repr(str(manifest_path)) 2: manifest-missing (printable); beside the older library manifest-missing (printable)
X3 family-unreadable as ascii(str(path)) 1: family-unreadable (printable)
X4 a holder's bootstrap.py _shown without the bidi controls (repr only for a C0 or C1 control) 6: family-manifest-missing-bootstrap (U+202E); family-manifest-unreadable-bootstrap (U+202E); family-manifest-unreadable-siblings-members-of (U+202E); beside the older library family-manifest-unreadable-bootstrap (U+202E); the copy test (\udcff, pl\u202ean)
X5 siblings.py's _shown as the library's spelled (escapes, no quotes) 12: family-manifest-unreadable-siblings (U+202E, ESC, line break); beside the older library family-manifest-unreadable-siblings (U+202E, ESC, line break); the copy test (x\x1b[31mRED, a\nb, a\rb, \udcff, tab\there, pl\u202ean)
X6 validate-family.py's _shown as an escaper of the line feed and the ESC only 21: family-manifest-missing-validate-family (U+202E, ESC, line break); family-manifest-unreadable-validate-family (U+202E, ESC, line break); shape-pin-missing (U+202E, ESC, line break); shape-pin-unreadable (U+202E, ESC, line break); beside the older library family-manifest-unreadable-validate-family (U+202E, ESC, line break); the copy test (x\x1b[31mRED, a\nb, a\rb, \udcff, tab\there, pl\u202ean)
X7 validate-family.py's _shown always repr 8: family-manifest-missing-validate-family (printable); family-manifest-unreadable-validate-family (printable); shape-pin-missing (printable); shape-pin-unreadable (printable); beside the older library family-manifest-unreadable-validate-family (printable); the copy test (plain/dir, caf\xe9, (empty))
X8 update-pin-commit as ascii(str(pin_path)) 1: update-pin-commit (printable)
X9 pin_rows_remediation spells a printable root too 2: update-pin-row-entry (printable); update-pin-row-not-a-list (printable)

Each site's revert is killed by its own rows and by no other site's (M07 also by the siblings run through the holder's bootstrap.py, whose line it is).

Runs

Every run through the lane's runner: a stub gh first on PATH (and the suite's own gh guard ahead of it), GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null, a blank git identity, git-filter-repo 2.47.0 (CI's pin; --version says a40bce548d2c) on PATH, CPython 3.12.3 (CI runs 3.14; no row asserts text that depends on the version), -rs, at most 96 ids a call, one basetemp per call.

The gate list, 69 files, 5793 ids: the new file; tests/test_pinned_copy_text.py, tests/test_pinned_copy_columns.py (whose fixture it now imports, the two in one call), tests/test_load_yaml_refusals.py and tests/test_mapping_and_kind_refusals_shown.py; the hygiene five and tests/test_no_auto_maintenance.py; every file under tests/ that names a code this PR touches (24); every file that runs a changed tool and holds a path that does not print (shown_path, _shown, spelt, named_as, UNSAFE_NAMES, SHOWN_NAMES, ESC_NAME or FORGED_NAME; 41); and the files that read the changed scripts' source (test_bootstrap_spelling.py, test_template_text_placeholders.py, test_family_docs_one_spelling.py, test_readme_windows_quoting_sentence.py) or test update-accept-local-missing-name. The list is gate-files.txt in the lane's notes.

Calls 1-23 ran in one session and calls 24-61 in a second, at the same head and tree (every call's log records the head and 0 modified files at its start). Each call is one gate/chunk-NN.txt, of at most 96 ids, and a call's id list is in the lane's notes.

  • git 2.43.0: 5793 ids, 5764 passed, 0 failed, 29 skipped, 0 errors.
  • git 2.47.1: 5793 ids, 5765 passed, 0 failed, 28 skipped, 0 errors.
call ids files (ids) 2.43.0 2.47.1
1 96 tests/test_refusal_paths_shown_more.py (92), tests/test_pinned_copy_columns.py (4) 96 passed in 16.43s 96 passed in 16.40s
2 96 tests/test_pinned_copy_columns.py (51), tests/test_pinned_copy_text.py (45) 96 passed in 6.20s 96 passed in 6.33s
3 96 tests/test_pinned_copy_text.py (43), tests/test_load_yaml_refusals.py (53) 96 passed in 9.67s 96 passed in 9.87s
4 96 tests/test_load_yaml_refusals.py (63), tests/test_mapping_and_kind_refusals_shown.py (33) 96 passed in 16.25s 96 passed in 16.17s
5 96 tests/test_mapping_and_kind_refusals_shown.py (7), tests/test_path_guard_hygiene.py (89) 96 passed in 15.05s 96 passed in 15.10s
6 96 tests/test_path_guard_hygiene.py (32), tests/test_repo_hygiene.py (64) 96 passed in 3.61s 96 passed in 3.52s
7 96 tests/test_repo_hygiene.py (37), tests/test_agents_md_codes.py (47), tests/test_cli_reference.py (12) 96 passed in 13.45s 96 passed in 13.07s
8 96 tests/test_cli_reference.py (11), tests/test_no_auto_maintenance.py (60), tests/test_bootstrap_spelling.py (25) 94 passed, 2 skipped in 35.73s 94 passed, 2 skipped in 36.62s
9 96 tests/test_bootstrap_spelling.py (96) 96 passed in 26.31s 96 passed in 26.38s
10 96 tests/test_bootstrap_spelling.py (86), tests/test_template_text_placeholders.py (10) 96 passed in 5.40s 96 passed in 4.88s
11 96 tests/test_template_text_placeholders.py (96) 96 passed in 0.32s 96 passed in 0.42s
12 96 tests/test_template_text_placeholders.py (36), tests/test_family_docs_one_spelling.py (32), tests/test_readme_windows_quoting_sentence.py (28) 96 passed in 62.94s (0:01:02) 96 passed in 62.30s (0:01:02)
13 96 tests/test_readme_windows_quoting_sentence.py (18), tests/test_adopt_ignored_shape_files.py (37), tests/test_adopt_root_symlinks.py (41) 96 passed in 87.67s (0:01:27) 96 passed in 87.42s (0:01:27)
14 96 tests/test_adopt_root_symlinks.py (96) 96 passed in 125.74s (0:02:05) 96 passed in 124.32s (0:02:04)
15 96 tests/test_adopt_root_symlinks.py (72), tests/test_adopt_shape_pin_and_work_dir.py (20), tests/test_assembly_root_unreadable_paths.py (4) 93 passed, 3 skipped in 84.29s (0:01:24) 96 passed in 89.23s (0:01:29)
16 96 tests/test_assembly_root_unreadable_paths.py (78), tests/test_bootstrap_upstream_notice.py (9), tests/test_bump_leg.py (9) 96 passed in 19.41s 96 passed in 19.50s
17 96 tests/test_bump_leg.py (46), tests/test_bump_leg_unsearchable.py (32), tests/test_bump_remedies_family_root.py (18) 95 passed, 1 skipped in 41.31s 95 passed, 1 skipped in 40.15s
18 96 tests/test_bump_remedies_family_root.py (3), tests/test_doctor_unreadable_pin.py (52), tests/test_family.py (41) 96 passed in 32.17s 96 passed in 31.97s
19 96 tests/test_family.py (72), tests/test_family_siblings_bootstrap_unreadable.py (24) 96 passed in 74.02s (0:01:14) 96 passed in 74.02s (0:01:14)
20 96 tests/test_family_siblings_bootstrap_unreadable.py (33), tests/test_manifest_validator.py (63) 96 passed in 18.03s 96 passed in 17.66s
21 96 tests/test_manifest_validator.py (41), tests/test_pin_validator.py (25), tests/test_refusal_default_remediation.py (20), tests/test_shape_doctor.py (10) 96 passed in 33.01s 96 passed in 33.03s
22 96 tests/test_shape_doctor.py (96) 96 passed in 113.06s (0:01:53) 96 passed in 111.58s (0:01:51)
23 96 tests/test_shape_doctor.py (96) 96 passed in 154.35s (0:02:34) 96 passed in 154.98s (0:02:34)
24 96 tests/test_shape_doctor.py (96) 96 passed in 306.66s (0:05:06) 96 passed in 305.80s (0:05:05)
25 96 tests/test_shape_doctor.py (96) 96 passed in 42.62s 96 passed in 42.40s
26 96 tests/test_shape_doctor.py (55), tests/test_shape_pin_files_not_a_list.py (28), tests/test_shape_pin_row_path.py (10), tests/test_update_shape.py (3) 93 passed, 3 skipped in 134.77s (0:02:14) 93 passed, 3 skipped in 130.94s (0:02:10)
27 96 tests/test_update_shape.py (87), tests/test_update_shape_apply_lines_and_headers.py (9) 96 passed in 116.09s (0:01:56) 96 passed in 119.59s (0:01:59)
28 96 tests/test_update_shape_apply_lines_and_headers.py (52), tests/test_update_shape_rewrite_symlink.py (29), tests/test_update_shape_unreadable.py (15) 96 passed in 61.55s (0:01:01) 96 passed in 61.42s (0:01:01)
29 96 tests/test_update_shape_unreadable.py (41), tests/test_validate_family_unreadable.py (55) 96 passed in 54.00s 96 passed in 53.31s
30 96 tests/test_validate_family_unreadable.py (29), tests/test_validate_pins_named_and_spelled.py (67) 96 passed in 35.23s 96 passed in 34.69s
31 96 tests/test_validate_pins_named_and_spelled.py (96) 96 passed in 21.84s 96 passed in 21.40s
32 96 tests/test_validate_pins_named_and_spelled.py (96) 96 passed in 19.74s 96 passed in 20.04s
33 96 tests/test_validate_pins_named_and_spelled.py (85), tests/test_validate_pins_spelled_and_named.py (11) 96 passed in 23.24s 96 passed in 21.93s
34 96 tests/test_validate_pins_spelled_and_named.py (96) 96 passed in 24.75s 96 passed in 24.93s
35 96 tests/test_validate_pins_spelled_and_named.py (9), tests/test_bump_leg_branch_and_push_line.py (44), tests/test_chain_pin_sources.py (43) 96 passed in 37.46s 96 passed in 36.61s
36 96 tests/test_chain_pin_sources.py (9), tests/test_chain_remedy_callers.py (68), tests/test_derived_id_refusals.py (19) 96 passed in 20.13s 96 passed in 19.82s
37 96 tests/test_derived_id_refusals.py (44), tests/test_doctor_members_spelled.py (9), tests/test_doctor_next_is_a_command.py (43) 96 passed in 104.37s (0:01:44) 96 passed in 103.17s (0:01:43)
38 96 tests/test_doctor_next_is_a_command.py (18), tests/test_doctor_unreadable_paths.py (78) 96 passed in 86.73s (0:01:26) 96 passed in 86.93s (0:01:26)
39 96 tests/test_doctor_unreadable_paths.py (54), tests/test_dry_run_asks_remotes.py (32), tests/test_dry_run_parity.py (10) 96 passed in 30.00s 96 passed in 28.65s
40 96 tests/test_dry_run_parity.py (96) 96 passed in 39.38s 96 passed in 38.47s
41 96 tests/test_dry_run_parity.py (17), tests/test_family_add_refusals.py (65), tests/test_family_bootstrap_stops_named.py (14) 96 passed in 138.30s (0:02:18) 96 passed in 138.53s (0:02:18)
42 96 tests/test_family_bootstrap_stops_named.py (35), tests/test_family_bump_remove_rollback.py (31), tests/test_family_gitmodules_values.py (30) 96 passed in 173.06s (0:02:53) 94 passed, 2 skipped in 171.31s (0:02:51)
43 96 tests/test_family_gitmodules_values.py (96) 96 passed in 82.83s (0:01:22) 96 passed in 83.40s (0:01:23)
44 96 tests/test_family_gitmodules_values.py (21), tests/test_family_holder_remedies_compose.py (54), tests/test_family_holder_states.py (21) 96 passed in 206.51s (0:03:26) 96 passed in 204.27s (0:03:24)
45 96 tests/test_family_holder_states.py (21), tests/test_family_init_landing.py (19), tests/test_family_init_values.py (56) 77 passed, 19 skipped in 77.02s (0:01:17) 77 passed, 19 skipped in 76.17s (0:01:16)
46 96 tests/test_family_init_values.py (73), tests/test_family_member_branch_not_utf8.py (4), tests/test_family_remove_gitmodules.py (19) 96 passed in 63.21s (0:01:03) 96 passed in 63.13s (0:01:03)
47 96 tests/test_family_remove_gitmodules.py (16), tests/test_family_tracking_branch.py (28), tests/test_legs_not_a_list.py (52) 96 passed in 78.88s (0:01:18) 96 passed in 77.05s (0:01:17)
48 96 tests/test_legs_not_a_list.py (60), tests/test_naming_policy.py (36) 96 passed in 14.39s 96 passed in 14.90s
49 96 tests/test_naming_policy.py (96) 96 passed in 11.45s 96 passed in 11.45s
50 96 tests/test_naming_policy.py (96) 96 passed in 8.63s 96 passed in 9.35s
51 96 tests/test_naming_policy.py (36), tests/test_scaffold_init_empty_values.py (36), tests/test_scaffold_manifest_values.py (24) 96 passed in 35.82s 96 passed in 35.46s
52 96 tests/test_scaffold_manifest_values.py (89), tests/test_scaffold_referent_chain_links.py (7) 96 passed in 24.79s 96 passed in 24.86s
53 96 tests/test_scaffold_referent_chain_links.py (37), tests/test_stdout_encoding.py (48), tests/test_update_shape_pin_source.py (11) 95 passed, 1 skipped in 28.50s 95 passed, 1 skipped in 28.19s
54 96 tests/test_update_shape_pin_source.py (25), tests/test_validate_family_typed_states.py (43), tests/test_validate_workflow_gitmodules_urls.py (28) 96 passed in 108.77s (0:01:48) 96 passed in 106.30s (0:01:46)
55 96 tests/test_validate_workflow_gitmodules_urls.py (96) 96 passed in 11.82s 96 passed in 10.68s
56 96 tests/test_validate_workflow_gitmodules_urls.py (96) 96 passed in 9.90s 96 passed in 9.39s
57 96 tests/test_validate_workflow_gitmodules_urls.py (96) 96 passed in 8.46s 96 passed in 8.84s
58 96 tests/test_validate_workflow_gitmodules_urls.py (96) 96 passed in 8.33s 96 passed in 8.84s
59 96 tests/test_validate_workflow_gitmodules_urls.py (9), tests/test_value_pattern_line_break.py (31), tests/test_update_shape_kept_helper.py (56) 96 passed in 33.00s 96 passed in 33.78s
60 96 tests/test_update_shape_kept_helper.py (96) 96 passed in 15.76s 96 passed in 15.27s
61 33 tests/test_update_shape_kept_helper.py (33) 33 passed in 32.41s 33 passed in 32.49s

Skips, each named:

  • git 2.43.0: 1 skipped, tests/test_adopt_root_symlinks.py:1960: the reftable backend needs git 2.45 or later
  • git 2.43.0: 1 skipped, tests/test_adopt_root_symlinks.py:2015: the reftable backend needs git 2.45 or later
  • git 2.43.0: 1 skipped, tests/test_adopt_root_symlinks.py:2596: the reftable backend needs git 2.45 or later
  • git 2.43.0: 1 skipped, tests/test_bump_leg.py:697: needs a disk that ignores case (macOS, Windows): on this one a second spelling is a second file
  • git 2.43.0: 16 skipped, tests/test_family_init_values.py:229: this template quotes the placeholder: the run is fine, and test_a_quoted_value_is_held_as_typed runs it
  • git 2.43.0: 3 skipped, tests/test_family_init_values.py:301: this template quotes id:: the derived id reads back
  • git 2.43.0: 2 skipped, tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one
  • git 2.43.0: 3 skipped, tests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows host
  • git 2.43.0: 1 skipped, tests/test_stdout_encoding.py:292: the default this is about is Windows' own
  • git 2.47.1: 1 skipped, tests/test_bump_leg.py:697: needs a disk that ignores case (macOS, Windows): on this one a second spelling is a second file
  • git 2.47.1: 1 skipped, tests/test_family_bootstrap_stops_named.py:1330: this git's update exits 128 here, where git 2.43.0's crashes (exit 139)
  • git 2.47.1: 1 skipped, tests/test_family_bootstrap_stops_named.py:837: this git's update exits 128 here, where git 2.43.0's crashes (exit 139)
  • git 2.47.1: 16 skipped, tests/test_family_init_values.py:229: this template quotes the placeholder: the run is fine, and test_a_quoted_value_is_held_as_typed runs it
  • git 2.47.1: 3 skipped, tests/test_family_init_values.py:301: this template quotes id:: the derived id reads back
  • git 2.47.1: 2 skipped, tests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that one
  • git 2.47.1: 3 skipped, tests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows host
  • git 2.47.1: 1 skipped, tests/test_stdout_encoding.py:292: the default this is about is Windows' own

Other runs:

run git tree result
the 92 rows, the head's tests over the base's production files 2.43.0 86ab76db4 + this head's two test files 69 failed, 23 passed; the copy test 9 failed
the mutants (above), 92 rows each, and the copy test for M-of-a-copy 2.43.0 this head's files 28 of 28 killed
the predicted merge of this head with main 106f92ff9, 13 calls, 1132 ids 2.43.0 7029b3a9b (merge commit 9fb4687a2, an object only) 1132 passed, 0 skipped, 0 failed

The predicted merge. The head with main 106f92ff9 (fetched fresh; its commit is dated 2026-11-10T12:56Z): git merge-tree --write-tree then git commit-tree, an object only (merge commit 9fb4687a2, tree 7029b3a9b; no ref, never pushed), checked out detached and run on git 2.43.0 in 13 calls of at most 96 ids, 1132 ids in all. The files are the seven that an earlier predicted merge ran (against main b607223b4): the four that main changed since #1284's merge base with main (0354a7e70) and that name a code or a file this PR changes (test_bootstrap_spelling.py, test_family.py, test_manifest_validator.py, test_pinned_copy_text.py), and this PR's own test_refusal_paths_shown_more.py, test_pinned_copy_columns.py and test_validate_family_unreadable.py; and the five that main's two latest commits changed (3add78e1c: test_adopt_pins.py, which runs a clone's bootstrap.py and validate-manifest.py, and, as controls, test_adopt_pin_row_values.py and test_adopt_plan_value_order.py; 106f92ff9: test_adopt_relative_submodule_url.py, which runs a clone's bootstrap.py, and, as a control, test_adopt_plan_against_its_source.py; no control names a code or a file of this PR): tests/test_adopt_pin_row_values.py (45), tests/test_adopt_pins.py (152), tests/test_adopt_plan_against_its_source.py (18), tests/test_adopt_plan_value_order.py (40), tests/test_adopt_relative_submodule_url.py (35), tests/test_bootstrap_spelling.py (207), tests/test_family.py (156), tests/test_manifest_validator.py (160), tests/test_pinned_copy_columns.py (55), tests/test_pinned_copy_text.py (88), tests/test_refusal_paths_shown_more.py (92), tests/test_validate_family_unreadable.py (84). The earlier 842 ids were collected again at the predicted tree of main 3add78e1c and are the same ids; 106f92ff9 changed none of those seven files. Main also changed tests/conftest.py since the merge base; every call here runs with it. A first run of the same list against main 3add78e1c (1079 ids, 12 calls, tree 51ced7527) passed too; main moved while this PR was being gated, and the list was run again at 106f92ff9.

No call of the predicted merge skipped a row.

One run with LANES_LANE exported (#1299's lesson: a row of #1269 failed in any shell that exports it): tests/test_refusal_paths_shown_more.py at this head on git 2.43.0 under a runner that exports LANES_LANE=openRepoShape-5 (every other run here unsets every LANES_*): 92 passed in 17.96s.

The round after rv1310's review

rv1310 found this PR READY at 6b9a4ece5 with four NITs (#1310 issuecomment-6773301382). This round takes all four. It adds one commit, daaa5ad8a, which changes two test files, tests/test_refusal_paths_shown_more.py and tests/test_pinned_copy_text.py, and no production file. The PR is two commits now (6b9a4ece5, then daaa5ad8a). The Rows, Mutants and Runs sections above are as measured at 6b9a4ece5; this section is what the round adds.

Mutants of the round, each applied alone in the detached tree at this head (siblings.py changed, the tests run, the file put back, its sha256 and git status checked after each), git 2.43.0, -rs:

mutant the 92 rows the #536 row the skew row, the copy test
R2 (rv1310's): a function-local from bootstrap import _shown as shown inside _shown 92 pass fails pass, pass
R2b (rv1310's): a module-level from bootstrap import _shown in place of the def 92 pass fails pass, pass
R2c (mine): import bootstrap and return bootstrap._shown(path) 8 fail (the stand-in has no _shown to give) fails pass, pass
N3 (mine): siblings.py's from bootstrap import gains DEFAULT_MEMBERS_DIR, a name bootstrap.py defines 92 pass: the stand-in follows pass not run
N3 against 6b9a4ece5's rows file (the hard-coded list) 8 fail, each ImportError: cannot import name 'DEFAULT_MEMBERS_DIR' from 'bootstrap' not run not run

R2 and R2b were each killed only by those 8 stand-in rows at 6b9a4ece5; now the new row is what holds them, and the rows no longer depend on the stand-in's gap. N3 is not a mutant to kill: its pass is the point of the stand-in following siblings.py.

No production file changed. git diff --stat 6b9a4ece5 HEAD lists the two test files alone (55 insertions, 19 deletions); the seven production files have the same sha256 at both commits, and git diff 6b9a4ece5 HEAD outside the two test files is 0 bytes.

Runs at daaa5ad8a (tree 84019f7a6de2), through the lane's runner as above (git-filter-repo 2.47.0, --version says a40bce548d2c; CPython 3.12.3), -rs, one basetemp per call, 0 modified files at each call's start, 8 calls a leg of at most 121 ids (a single file of more than 100 alone, under flock):

  • git 2.43.0: 744 ids, 742 passed, 0 failed, 2 skipped.
  • git 2.47.1: 744 ids, 742 passed, 0 failed, 2 skipped.
  • Skips, the same on both legs: tests/test_no_auto_maintenance.py:222 x2, "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one".
call ids files 2.43.0 2.47.1
new 92 tests/test_refusal_paths_shown_more.py 92 passed in 24.35s 92 passed in 22.15s
text 89 tests/test_pinned_copy_text.py 89 passed in 12.89s 89 passed in 11.31s
columns 55 tests/test_pinned_copy_columns.py 55 passed in 2.88s 55 passed in 3.03s
BIG-loadyaml 116 tests/test_load_yaml_refusals.py 116 passed in 16.81s 116 passed in 16.03s
mapping+agents 87 tests/test_mapping_and_kind_refusals_shown.py, tests/test_agents_md_codes.py 87 passed in 11.52s 87 passed in 11.44s
BIG-pathguard 121 tests/test_path_guard_hygiene.py 121 passed in 22.99s 121 passed in 18.29s
BIG-repohyg 101 tests/test_repo_hygiene.py 101 passed in 4.57s 101 passed in 3.25s
cli+noauto 83 tests/test_cli_reference.py, tests/test_no_auto_maintenance.py 81 passed, 2 skipped in 56.67s 81 passed, 2 skipped in 54.30s

One run of the new file with LANES_LANE exported (run10-lane.sh, #1299's lesson), git 2.43.0: 92 passed in 20.21s.

Merges

git merge-tree --write-tree of the head daaa5ad8a (tree 84019f7a6de2) with #1284's head, main and the head of every open PR that touches one of this PR's nine files, each read fresh at 2026-11-10T16:56:13Z (gh pr list, read only; 31 open PRs), on git 2.43.0 and 2.47.1, with the same trees on both:

with head result
#1284 (fix/1279-refusals-shown, this PR's base) 86ab76db4 clean, tree 84019f7a6de2, this head's own (an ancestor)
main dc9a0bcc7 clean, tree 3fffccbd6c23
#1315 (fix/1285-ascii-locale-family-rows, test_pinned_copy_text.py) bd84004f3 clean, tree f83555bf5839
#1171 (fix/1128-shown-default-ignorables, test_pinned_copy_text.py) ce1dd661f clean, tree 6a662aa489ce
#1263 (l3-1256-member-head-unreadable, validate-family.py) 1962f7498 clean, tree e43b74a6ae97
#1294 (fix/1215-rev-list-dashdash, update-shape.py) cf07ba0d1 clean, tree 895f9c637a55
#1296 (fix/1287-siblings-precedence-row-and-fold, siblings.py) f51349796 clean, tree d6b8e8ff060c

The other 24 open PRs touch none of this PR's files. #1315 (new since the review) edits test_pinned_copy_text.py's imports and one pin-source row, and the round's new row is at the end of that file, so the two do not meet. #1171 adds names to repo_shape.py but does not change shown_path, so the copies stay equal to it there. At the merge with main, each of the four copies holds exactly one _shown: main's #1282 changed validate-manifest.py and validate-family.py and added none.

Known limits

Closes #1288

Lane: openreposhape-5 (openRepoShape-5)

🤖 Generated with Claude Code

Summary by Sourcery

Make all targeted refusal diagnostics and related remediation output safely spell paths without changing refusal codes or wording.

Bug Fixes:

  • Ensure refusal messages safely render missing, unreadable, and wrong-kind paths containing non-printable or bidi characters across bump-leg, family, manifest, family-holder, validation, and shape-pin commands.
  • Prevent update-shape pin-row remediation from emitting unsafe root paths in terminal output.

Enhancements:

  • Add local path-spelling helpers to pinned template scripts so they remain compatible with older repository libraries while matching shown_path behavior.

Tests:

  • Add comprehensive coverage for unsafe and printable refusal paths, older pinned libraries, remediation output, and copy-helper consistency.

…est.py, bootstrap.py, siblings.py and validate-family.py: fourteen missing, unreadable and wrong-kind refusals and four of `check_pin` name the path as `shown_path` does (#1288)

#1279 (#1284) spelled the line after `load_yaml` in seven readers. These
refusals still named the path raw, each of a file that is not there, does
not read as a mapping, or is of another kind: a bidi override, U+202E,
reordered the line, an ESC reached the terminal as the control it is, and
a line feed began a line of the path's choosing. Measured at #1284's head
`86ab76db4`, each printed the U+202E and the ESC raw and let the line feed
forge a second line, exit 2. Each is spelled now:

- `bump-leg.py`: `bump-leg-root-not-a-project`, `bump-leg-wrong-kind`;
- `family.py`: `family-unreadable`, `family-wrong-kind`;
- `update-shape.py`: `update-pin-missing`, and in `check_pin`
  `update-pin-commit`, `update-pin-digest-definition`, `update-pin-row`
  (both sites) and `update-pin-no-files`;
- a root's `validate-manifest.py`: `manifest-missing`;
- a holder's `bootstrap.py`: `family-manifest-missing` and
  `family-manifest-unreadable`; its `siblings.py`:
  `family-manifest-unreadable`; its `validate-family.py`:
  `shape-pin-missing`, `shape-pin-unreadable`, `family-manifest-missing`
  and `family-manifest-unreadable`.

The three tools run from a checkout of the standard use `shown_path`, which
each imports already. `validate-manifest.py` uses the `_shown` it carries.
A holder's three scripts are copies kept beside a `repo_shape.py` that may
predate `shown_path` (the skew rule), so each gains its own `_shown`,
`validate-manifest.py`'s code byte for byte; `siblings.py`'s is its own, not
imported from `bootstrap.py`, since a holder that kept its own
`bootstrap.py` would be refused `update-accept-local-missing-name` for it.
`test_pinned_copy_text.py` holds the three to `shown_path`, with a U+202E
case added.

`update-pin-row`'s remediation names the root in a `git -C ... log` line,
quoted by `_by_hand`, which passed the ESC and the line feed through under a
`REFUSED` line that spelled them. Where the root does not print, that line
now spells it too, as `validate-pins.py`'s `_quoted` spells a word (#712);
a printable root is `_by_hand`'s line as before. `_by_hand`'s other lines
are left as they are.

No refusal code changes, and no word of any refusal but the path. A printable path
is named byte for byte as before. New rows in
`tests/test_refusal_paths_shown_more.py`.

Closes #1288

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.2.2 milestone Nov 10, 2026
@sourcery-ai

sourcery-ai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Render the affected refusal paths safely while preserving printable-path output, including the remediation path in update-shape.py and local _shown helpers for template copies that may use older libraries.

  • scripts/bump-leg.py L227-228, L241
  • scripts/family.py L576-581
  • templates/assembly-root/scripts/validate-manifest.py L1432-1434
  • templates/family-root/scripts/bootstrap.py L374-382, L1989-1990, L1996
  • templates/family-root/scripts/siblings.py L340-350, L1758
  • templates/family-root/scripts/validate-family.py L604-612 (+4 small edits)
  • update-shape.py L873-882, L959-964 (+4 small edits)

Tests

Extend path-spelling consistency checks to the family-holder copies and assert that siblings.py owns its helper without importing it from bootstrap.py.

  • tests/test_pinned_copy_text.py L778-795, L836-860 (+2 small edits)

Add end-to-end coverage for unsafe and printable paths across the refusals, older-library template copies, and malformed-pin remediation output.

  • tests/test_refusal_paths_shown_more.py L1-368

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 #1310 (#1288) for lane openRepoShape-5, by rv1310 (opus), at 6b9a4ec: READY

#1310 is ready to land, with no MUST and no SHOULD findings. There are four NITs: two are about the PR text, and two are about what the new tests hold.

All eight R36 items were measured, in the coordinator's order (a) 1, 3, 8; (b) 4, 2; (c) 7; (d) 5, 6. Head 6b9a4ece5c45c92993a525780014c807235b4437 matches origin, and its base is #1284 at 86ab76db4. I made no GitHub writes, ran no rm and no git config, and used no subagents.

Findings

NIT 1: Known limits leaves out some refusals that still print the path raw, next to #1288's sites. These are not regressions and are outside #1288's table. I measured them at the head with probes/probe2.py:

  • family-members-unreadable, raw in:
    • bootstrap.py:2009 and :2015 (members_of);
    • family.py:779 and :785;
    • validate-family.py:4854 (read, not probed).
  • update-root-missing, raw in update-shape.py:2544.
  • update-upstream-not-a-repo names {pin_path} raw (update-shape.py:816); read, not probed.

Each probed one exits 2 and puts an ESC on stderr raw, or lets a line feed forge a line. For example:
REFUSED family-members-unreadable: <D>/x00/LF/wd-

Known limits names only family-root-missing and _by_hand's other lines. These should be added there, or filed as follow-ups.

NIT 2: The skew rule and the #536 choice are each held by a different row than the PR text suggests.

NIT 3: The siblings stand-in hard-codes siblings.py's import list from bootstrap.py. MEMBERS_UNREAD re-exports exactly REWRITE_TARGETS, credential, member_make_environment, member_path, untraced. That list is the same today at the head, at #1296 f51349796 and at main f2a6527e9. If a later change adds a name to that import, these rows fail loudly with an ImportError, but they then test nothing.

NIT 4: The body's Merges section is out of date. It names main 106f92ff9 and 28 open PRs, read at 13:28Z. Main is now f2a6527e9, and #1315 (fix/1285-ascii-locale-family-rows) is new and touches tests/test_pinned_copy_text.py. All of these merge cleanly (item 7). Refresh the table at the next round.

Measured, by R36 item

1. The eighteen sites. My own probe (probes/probe.py, probes/analyze.py):

  • 19 runs: the 18 sites, plus siblings.py through the holder's real bootstrap.py.
  • 7 folder names each: U+202E, ESC, LF, CR, a non-UTF-8 byte 0xff, U+200B, and a printable control wd <U+00E9> and a space.
  • 133 runs at the base (in rv1310-mut) and 133 at the head, with a stub gh first on PATH, GH_TOKEN bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false and GIT_CONFIG_GLOBAL=/dev/null.

Results:

  • Every run exits 2 on both sides, with empty stdout and no traceback.
  • A raw control or forged line reaches the output in 95 base runs (CR, ESC, LF, U+202E and U+200B at every site) and in 0 head runs.
  • I compared the whole stdout and stderr, base against head, with the path and update-pin-row's remediation root replaced by a placeholder: equal in 133 of 133. So no other word of any refusal changed, and every printable run is identical byte for byte.
  • Example, update-pin-row with LF:
    • base: REFUSED update-pin-row: <D>/s17/LF/wd- (4 stderr lines);
    • head: REFUSED update-pin-row: '<D>/s17/LF/wd-\nWARNING forged-line: pwned/contracts/shape-pin.yaml': a files:row is notpath:/sha256:`` (2 lines).

3. pin_rows_remediation: right, needed, and unchanged for a printable root.

8. The record.

  • The only closing reference is line 250, Closes #1288. The other keyword hits are branch names, "hand fixes" and "resolved to this branch's side".
  • body-guard on the live body exits 0. The title is 246 characters.
  • The commit's author and committer are both Brett Heap service@opensoft.dev; its subject is 246 characters. Its trailers are Closes #1288, the Lane line and Co-Authored-By: Claude Opus 5.5.
  • There is no non-ASCII character in any added line or in the message.
  • Every line number in the body's table matches the head.
  • n1288's and n1288b's 61 logs on each leg sum to the body's totals exactly: 5764 passed and 29 skipped on 2.43.0, 5765 and 28 on 2.47.1, all at the head with 0 modified files.
  • CI at the head: tests, tests-git-bash, package and SonarCloud all SUCCESS, with 0 new issues and 0.0% duplication on new code. There are no inline review comments.
  • No doc quotes the changed words, apart from docs/release-notes/v1.0.0.md as the record of that release.

4. The rows (git 2.43.0, -rs). The head's tests ran over the base's 7 production files, swapped in by swap.sh and restored with sha256 checked:

  • 69 failed, all assertion failures, exactly the 57 + 12 unprintable ids; 23 passed, the 19 + 4 printable controls.
  • At the head, 92 passed.
  • The copy test: 9 failed at the base (AttributeError: module 'pinned_copy_text_family' has no attribute '_shown'), 9 passed at the head.
  • Skip by name: a plugin that makes mkdir refuse those names gave 69 skipped with tests/conftest.py:2501: this filesystem cannot hold the name: ..., and 23 passed.

2. The skew rule.

  • Each new _shown (bootstrap.py:374, siblings.py:340, validate-family.py:604) is the only one in its file. Its arguments and body, docstring aside, are AST-equal to validate-manifest.py:467 and to shown_path. The two statements hash the same in all four files.
  • Giving siblings.py its own copy is right. siblings.py already imports from bootstrap.py (line 241), and update-accept-local-missing-name refuses a written copy that imports a name a kept neighbour lacks.
  • test_pinned_copy_text.py pins all three copies.
  • The 16 older-library rows pass at the head; see NIT 2 for what they do and don't hold.

5. Mutants (each alone in rv1310-mut, sha256 and git status checked after every one):

  • n1288's 28 (M01-M19 and X01-X09) are all killed, with the same counts as the body's table.
  • Mine are R1, R2 and R2b, described under NIT 2.

6. Gates at the head, run with L/tools/run10.sh:

  • Versions: git 2.43.0 and 2.47.1 (L/tools/git247), git-filter-repo a40bce548d2c (venv247, CI's 2.47.0 pin), CPython 3.12.3.
  • 13 calls a leg, 1276 ids (1260 distinct). A file of more than 100 ran alone under flock.
  • 2.43.0: 1273 passed, 3 skipped, 0 failed. 2.47.1: 1273 passed, 3 skipped, 0 failed.
  • The files:
    • the new file, test_pinned_copy_text, test_pinned_copy_columns (with the copy test and the skew row, in one call), test_load_yaml_refusals and test_mapping_and_kind_refusals_shown;
    • the hygiene five and test_no_auto_maintenance;
    • my sample from n1288b's list: test_validate_family_unreadable, test_family_siblings_bootstrap_unreadable, test_shape_pin_files_not_a_list, test_update_shape_unreadable, test_bump_leg_unsearchable, test_bump_leg, test_refusal_default_remediation and test_update_shape_kept_helper.
  • Skips, the same on both legs:
    • test_no_auto_maintenance.py:222 x2: "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one";
    • test_bump_leg.py:697 x1: "needs a disk that ignores case (macOS, Windows): on this one a second spelling is a second file".

7. Merge-trees. I read 32 open PRs at 16:25:20Z and ran merge-tree --write-tree on both gits. Each gives 0 conflicts and the same tree on both:

with head result
#1284 (base) 86ab76db4 d0bf50a46, the head's own tree
main f2a6527e9 50ce90426
#1315 (new; skip helpers away from the copy test) 84e006269 8621b7d14
#1296 f51349796 ddb877215
#1294 cf07ba0d1 11631cace
#1263 1962f7498 ce4391b50
#1171 (leaves shown_path as is) ce1dd661f 44fd04539

Files and trees

Everything is in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1310/:

  • STATE.md, with each item's measured results;
  • probes/probe.py, probes/analyze.py, probes/analyze.out, probes/probe2.py, probes/probe2-head.out;
  • mutrun.py and mut/summary.txt;
  • gate.sh, logs/gate.out, and the per-call logs logs/g243-*.log and logs/g247-*.log;
  • logs/rows-baseprod.log, logs/rows-head.log, logs/rows-noname.log;
  • merge-trees.sh and merge-trees.out.

Both worktrees are left detached at the head and clean, with no run still going: /workspace/projects/openRepoShape-worktrees/rv1310-review and /workspace/projects/openRepoShape-worktrees/rv1310-mut.

Lane: openreposhape-5 (openRepoShape-5)

…der-library rows say what they hold, a new row holds that siblings.py has its own `_shown` and takes none from bootstrap (#536), and the stand-in follows siblings.py's import

rv1310's review of #1310 (#1288) found three things about what the new tests hold, and no production file changes.

NIT 2: the 16 older-library rows run each copy beside the library at `51a9c5f10`, which already defines `shown_path`, so they do not forbid a copy from importing it. Their docstring said they held the skew rule; it now says what they hold and names the row that does, `test_no_pinned_script_imports_what_an_older_library_lacks`. The #536 choice (siblings.py has a `_shown` of its own and takes none from bootstrap.py) was held only by accident, through the stand-in's missing `_shown`: a new row in test_pinned_copy_text.py reads siblings.py by `ast` and holds it by itself, against a module-level or function-local `from bootstrap import _shown` and against `bootstrap._shown`.

NIT 3: the stand-in `bootstrap.py` hard-coded siblings.py's import list. `members_unread()` now reads that list from siblings.py's own `from bootstrap import ...`, so a name a later change adds is answered by the holder's real bootstrap.py and the siblings rows keep running.

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

Delta check of #1310 (#1288) for lane openRepoShape-5, by rv1310b (sonnet), 6b9a4ec..daaa5ad: READY

#1310 at daaa5ad8a30101fd08d7a52b72966b89980e6772 is ready, with no MUST and no SHOULD findings. There are two NITs. I made no GitHub writes (reads only), ran no rm or rmdir and no git config, used no subagents, and ran no pytest while a GitHub-reaching gh was on PATH (stub first).

Findings

NIT 1: the new row holds the #536 choice for the spellings it reads, and only those. Measured with my own rivals, each applied alone in rv1310-mut and restored by sha256 (work/rv1310b/mut/summary.txt):

  • D4 replaced _shown's body with import bootstrap as boot and return boot._shown(path).
    • The new row passes it.
    • The row's third clause matches only an attribute read on a Name spelled bootstrap.
    • Only the 8 stand-in siblings rows kill it, with AttributeError: module 'bootstrap' has no attribute '_shown'.
    • Beside a kept bootstrap.py that lacks _shown it would break the skew rule.
  • D2 added from bootstrap import * after the def.
    • The new row passes it.
    • The 8 stand-in rows kill it through a SyntaxError at bootstrap.py line 7. members_unread() writes *, into the stub it generates.
    • It is harmless in behaviour and out of style: no shipped script has a star import.
  • Suggested fix: drop node.value.id == "bootstrap" from the row's third clause. siblings.py has no ._shown read at the head. Optionally add "*" not in taken.
  • The body's "holds, by itself" is true for the spelled forms.

NIT 2: the record is a little stale.

Measured, by R37 item

1. Nothing else changed.

  • git diff --stat 6b9a4ece5..daaa5ad8a lists tests/test_pinned_copy_text.py and tests/test_refusal_paths_shown_more.py: 55 insertions, 19 deletions.
  • The seven production files have the same sha256 at both commits and in the tree. Two examples are siblings.py bf126a26106c... and update-shape.py eca58c8960e8....
  • git diff 6b9a4ece5 daaa5ad8a outside the two test files is empty. git diff --check is clean, and no added line is non-ASCII.

2. NIT 2a: the docstring is true.

  • git show 51a9c5f10:scripts/repo_shape.py | grep -c 'def shown_path' gives 1.
  • 51a9c5f10 is BEFORE_1168 in tests/test_load_yaml_refusals.py.
  • R1 (bootstrap.py's _shown replaced by from repo_shape import shown_path as _shown) passes the 16 older-library rows and the 9 copy rows. It fails only test_no_pinned_script_imports_what_an_older_library_lacks[family-bootstrap] (1 failed, 6 passed).

3. NIT 2b. The new row passes at the head (33 passed in the baseline run of the row, the 7 skew rows, the 9 copy rows and the 16 older-library rows).

mutant new row 92 rows skew, copy
R2, function-local import (rv1310's) fails 92 pass pass, pass
R2b, module-level import (rv1310's) fails 92 pass pass, pass
R2c, bootstrap._shown (n1288c's) fails 8 fail pass, pass
D1, _shown defined twice (mine) fails 92 pass pass, pass
R4, from repo_shape import shown_path as _shown (mine) fails 92 pass skew [siblings] fails
D2, star import (mine) passes 8 fail pass, pass
D4, import bootstrap as boot (mine) passes 8 fail pass, pass
  • D1 is held, and holding it is reasonable. A second def is shadowed dead code.
  • D2 and D4 are NIT 1.
  • Every restore checked: the sha256 before and after match, and git status is clean.

4. NIT 3.

  • N3 (siblings.py's import gains DEFAULT_MEMBERS_DIR): 92 passed and the new row passes. The 8 siblings rows still run and pass, with no skip.
  • N3old (N3 with 6b9a4ece5's rows file): 8 fail. Each fails with ImportError: cannot import name 'DEFAULT_MEMBERS_DIR' from 'bootstrap', as the body says.
  • The stand-in refuses as before, with the same words (probe_standin.py, logs/probe-standin.out).
    • The old MEMBERS_UNREAD and the new members_unread() have equal ast.dump. Only the line wrapping of the import differs.
    • Real siblings.py --dry-run runs over U+202E, ESC, line-break and printable folder names, with the old and the new stand-in, exit 2 in all eight, and equal stdout and stderr.
    • Printable example: REFUSED family-manifest-unreadable: <D>/wd <U+00E9> and a space/Acme/family.yaml: not a mapping.
    • No raw ESC or U+202E reaches the output.
  • Excluding members_of is right.
    • It is cosmetic for behaviour: leaving it in gives the same ('Acme', []), because the stand-in's def rebinds it (probe_members_of.py).
    • Excluding it avoids importing a name only to redefine it.
  • The AST read fails loudly.
    • N3x (the import keeps only members_of) and N3z (no from-import of bootstrap) each fail the 8 siblings rows.
    • The message is AssertionError: siblings.py imports nothing but members_of from bootstrap?.
    • The message is slightly narrow for N3z.

5. Gates at daaa5ad8a, through L/tools/run10.sh, -rs, run names rv1310b-..., at most 100 ids a run. The 121-id and 101-id files each ran alone under flock.

  • Versions: git 2.43.0 and git 2.47.1 (L/tools/git247), git-filter-repo a40bce548d2c (venv247, CI's 2.47.0 pin), CPython 3.12.3. Every run started with 0 modified files.
  • Both legs, 7 calls each, the same ids:
call ids 2.43.0 2.47.1
test_refusal_paths_shown_more.py 92 passed passed
test_pinned_copy_text.py 89 passed passed
test_pinned_copy_columns.py 55 passed passed
test_path_guard_hygiene.py (alone) 121 passed passed
test_repo_hygiene.py (alone) 101 passed passed
test_agents_md_codes.py + test_cli_reference.py 70 passed passed
test_no_auto_maintenance.py 60 58 passed, 2 skipped 58 passed, 2 skipped
  • Each leg: 588 ids, 586 passed, 0 failed, 2 skipped.
  • Skips, the same on both legs: tests/test_no_auto_maintenance.py:222 x2, "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one".
  • Lane-set run (L/work/n1299/run10-lane.sh, git 2.43.0): test_refusal_paths_shown_more.py 92 passed in 23.52s, and the new row 1 passed.
  • Logs are in work/rv1310b/logs/ (gate243.out, gate247.out, g243-*.log, g247-*.log, laneset-*.log).

6. Merge-trees (merge_trees.py, read fresh at 17:04:50Z and again at 17:10:10Z). Every row is clean, with 0 conflicts and the same tree on git 2.43.0 and 2.47.1, and identical at both reads.

with head tree
#1284 (base) 86ab76db4 84019f7a6de2 (the head's own)
main dc9a0bcc7 3fffccbd6c23
#1319 (new) be62b7043 49b19ec558f9
#1315 bd84004f3 f83555bf5839
#1296 f51349796 d6b8e8ff060c
#1294 cf07ba0d1 895f9c637a55
#1263 1962f7498 e43b74a6ae97
#1171 ce1dd661f 6a662aa489ce

7. The record.

Files and trees

Everything is in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1310b/:

  • STATE.md;
  • mutrun.py, mut/summary.txt;
  • gate.sh, merge_trees.py, probe_standin.py, probe_members_of.py, probe-env.sh;
  • logs/, body-live.md, pr-final.json.

Both trees are left detached at daaa5ad8a and clean, with no run still going: /workspace/projects/openRepoShape-worktrees/rv1310-review and /workspace/projects/openRepoShape-worktrees/rv1310-mut.

Lane: openreposhape-5 (openRepoShape-5)

This branch has not been deployed

No deployments
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