Repository navigation
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
brettheap wants to merge 2 commits into
Conversation
…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>
Reviewer's GuideImplementationRender the affected refusal paths safely while preserving printable-path output, including the remediation path in
TestsExtend path-spelling consistency checks to the family-holder copies and assert that
Add end-to-end coverage for unsafe and printable paths across the refusals, older-library template copies, and malformed-pin remediation output.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
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 FindingsNIT 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
Each probed one exits 2 and puts an ESC on stderr raw, or lets a line feed forge a line. For example: Known limits names only 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 NIT 4: The body's Merges section is out of date. It names main Measured, by R36 item1. The eighteen sites. My own probe (
Results:
3.
8. The record.
4. The rows (git 2.43.0,
2. The skew rule.
5. Mutants (each alone in
6. Gates at the head, run with
7. Merge-trees. I read 32 open PRs at 16:25:20Z and ran
Files and treesEverything is in
Both worktrees are left detached at the head and clean, with no run still going: 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>
|
|
Delta check of #1310 (#1288) for lane openRepoShape-5, by rv1310b (sonnet), 6b9a4ec..daaa5ad: READY #1310 at FindingsNIT 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
NIT 2: the record is a little stale.
Measured, by R37 item1. Nothing else changed.
2. NIT 2a: the docstring is true.
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).
4. NIT 3.
5. Gates at
6. Merge-trees (
7. The record.
Files and treesEverything is in
Both trees are left detached at Lane: openreposhape-5 (openRepoShape-5) |



Goal
Fourteen missing, unreadable and wrong-kind refusals (#1288's table), and the four other refusals of
update-shape.py'scheck_pinthat #1288 read but did not probe, name the path the wayshown_pathdoes: as it is, or asreprspells it where a character in it is not printable. A U+202E, an ESC or a line feed in a typed--rootor--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, head86ab76db4, the PR for #1279): #1284 spells seven sibling refusals and adds what this build reuses,from repo_shape import shown_pathinupdate-shape.pyamong them, and #1288'sbump-leg.pysites sit beside #1284's own change there. This PR's base isfix/1279-refusals-shown. It is two commits on86ab76db4: 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_remediationinupdate-shape.py(the remediation of bothupdate-pin-rowrefusals) now spells the root asshown_pathdoes where the root does not print, and is_by_hand's line as before where it prints. #1288 names theREFUSEDline only, but that remediation puts the same root into agit -C <root> log -- contracts/shape-pin.yamlline, quoted by_by_hand, and a quote passes an ESC and a line feed through: with only theREFUSEDline 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" forupdate-pin-rowwithout 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
ghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/nullandGIT_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.shown_pathdoes, so no U+202E, ESC or line feed is raw (#1279) #1284's head86ab76db4: each of the 57 runs over a name that does not print put the U+202E or the ESC on stderr raw, or ended theREFUSEDline atwd-and began a lineWARNING forged-line: pwned/....reprspells it, on one line. The 19 printableREFUSEDlines are byte for byte the base's (compared with the probe's folder name aside).For example, the line feed under
family-manifest-unreadable(a holder's ownvalidate-family.py), before:and after:
bump-leg-root-not-a-projectbump-leg.py --root, noproject.yamlscripts/bump-leg.py:226shown_path, imported alreadybump-leg-wrong-kindbump-leg.py --root,kind: otherscripts/bump-leg.py:240shown_pathmanifest-missingvalidate-manifest.py --root, noproject.yamltemplates/assembly-root/scripts/validate-manifest.py:1431_shown(line 467), there alreadyfamily-unreadablefamily.py bump --dry-run --family-root,family.yamla listscripts/family.py:576shown_path, imported alreadyfamily-wrong-kindkind: otherscripts/family.py:580shown_pathfamily-manifest-missingbootstrap.py --root --no-fetch, nofamily.yamltemplates/family-root/scripts/bootstrap.py:1988_shown(line 374)family-manifest-unreadablefamily.yamla listtemplates/family-root/scripts/bootstrap.py:1995family-manifest-unreadablesiblings.py --root --dry-run, past abootstrap.pywhosemembers_ofreads nofamily.yamltemplates/family-root/scripts/siblings.py:1757_shown(line 340)shape-pin-missingvalidate-family.py --root,family.yamlreading, no pintemplates/family-root/scripts/validate-family.py:1850_shown(line 604)shape-pin-unreadabletemplates/family-root/scripts/validate-family.py:1858family-manifest-missingvalidate-family.py --root, nofamily.yamltemplates/family-root/scripts/validate-family.py:4832family-manifest-unreadablefamily.yamla listtemplates/family-root/scripts/validate-family.py:4841update-pin-missingupdate-shape.py check --root, no pinupdate-shape.py:917shown_path, imported by #1284update-pin-commitcommit: nothexupdate-shape.py:958shown_pathupdate-pin-digest-definition(read, not probed, in #1288)digest_definition: otherupdate-shape.py:963shown_pathupdate-pin-row(read, not probed)files: 5update-shape.py:980shown_path, and its remediation (below)update-pin-row(read, not probed)files: [5]update-shape.py:987update-pin-no-files(read, not probed)files: []update-shape.py:994shown_pathThe siblings site needs a stand-in: the holder's own
bootstrap.pyrefuses afamily.yamlthat is a list first, inmembers_of, with the same code and words (#1288's note). Probed both ways: past the stand-in, the line issiblings.py's; with the holder's ownbootstrap.py, it isbootstrap.py's, which this PR spells too. A row runs each.update-pin-row's remediation, found while measuring. With itsREFUSEDline spelled, the refusal still printed the root raw: its remediation namesgit -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_remediationnow spells it asshown_pathdoes, asvalidate-pins.py's_quotedspells 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" forupdate-pin-row.The skew rule.
templates/*/scripts/*are copies a root or a holder keeps beside ascripts/repo_shape.pythat may predateshown_path(update-shape.py apply --accept-localkeeps a copy), so none imports it.validate-manifest.pyuses the_shownit already carries. A holder'sbootstrap.py,siblings.pyandvalidate-family.pycarried 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 frombootstrap.py, which it imports from already: a holder that kept its ownbootstrap.pywould be refusedupdate-accept-local-missing-namefor the new name (#536).test_pinned_copy_text.pyholds all three toshown_path, andtest_no_pinned_script_imports_what_an_older_library_lacks(unchanged) lists all three already. The rows below also run each copy beside therepo_shape.pyof51a9c5f10, from before #1168.Duplication. The three new copies are 7 lines each (
def, a 4-line docstring, 2 statements);bootstrap.py's andvalidate-family.py's are the same 7 lines, andsiblings.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 undertests/(its import header, measured).Text that pins these lines. A grep of
README.md,AGENTS.md,docs/(the handbook mirrordocs/handbook.htmltoo) and the templates'README.mdandAGENTS*.mdfor each changed refusal's words finds none;docs/release-notes/v1.0.0.mdandv1.2.0.mdname some of the codes as the record of those releases. No doc changes.tests/test_cli_reference.pyandtests/test_agents_md_codes.pyare in the gate.Rows
92 new rows in
tests/test_refusal_paths_shown_more.py, and one row extended intests/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 oneREFUSEDline,REFUSED <code>: <the path, named as shown_path names it><the refusal's own words>, no U+202E, no ESC and no line startingWARNING forged-lineanywhere in stdout or stderr. The twoupdate-pin-rowruns also assert the remediation'sgit -Cline: 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'sbootstrap.py,siblings.pyandvalidate-family.py) beside therepo_shape.pyof51a9c5f10.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.pythroughtest_pinned_copy_columns.py'scopiesfixture (itsbootstrapimport answered by the holder'sbootstrap.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.86ab76db4AttributeError: no_shownin the holder's scripts)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, andgit statusthe same after each. 28 mutants, 28 killed, none survived.bump-leg-root-not-a-projectback to{path}bump-leg-root-not-a-project(U+202E, ESC, line break)bump-leg-wrong-kindback to{path}bump-leg-wrong-kind(U+202E, ESC, line break)manifest-missingback to{manifest_path}manifest-missing(U+202E, ESC, line break); beside the older librarymanifest-missing(U+202E, ESC, line break)family-unreadableback to{path}family-unreadable(U+202E, ESC, line break)family-wrong-kindback to{path}family-wrong-kind(U+202E, ESC, line break)bootstrap.py:family-manifest-missingback to{manifest_path}family-manifest-missing-bootstrap(U+202E, ESC, line break)bootstrap.py:family-manifest-unreadableback to{manifest_path}family-manifest-unreadable-bootstrap(U+202E, ESC, line break);family-manifest-unreadable-siblings-members-of(U+202E, ESC, line break); beside the older libraryfamily-manifest-unreadable-bootstrap(U+202E, ESC, line break)siblings.py'sfamily-manifest-unreadableback to{root / MANIFEST}family-manifest-unreadable-siblings(U+202E, ESC, line break); beside the older libraryfamily-manifest-unreadable-siblings(U+202E, ESC, line break)validate-family.py'sshape-pin-missingback to{pin_path}shape-pin-missing(U+202E, ESC, line break)validate-family.py'sshape-pin-unreadableback to{pin_path}shape-pin-unreadable(U+202E, ESC, line break)validate-family.py'sfamily-manifest-missingback to{manifest_path}family-manifest-missing-validate-family(U+202E, ESC, line break)validate-family.py'sfamily-manifest-unreadableback to{manifest_path}family-manifest-unreadable-validate-family(U+202E, ESC, line break); beside the older libraryfamily-manifest-unreadable-validate-family(U+202E, ESC, line break)update-pin-missingback to{pin_path}update-pin-missing(U+202E, ESC, line break)update-pin-commitback to{pin_path}update-pin-commit(U+202E, ESC, line break)update-pin-digest-definitionback to{pin_path}update-pin-digest-definition(U+202E, ESC, line break)update-pin-row(files:not a list) back to{pin_path}update-pin-row-not-a-list(U+202E, ESC, line break)update-pin-row(a row not a mapping) back to{pin_path}update-pin-row-entry(U+202E, ESC, line break)update-pin-no-filesback to{pin_path}update-pin-no-files(U+202E, ESC, line break)pin_rows_remediationalways_by_hand's lineupdate-pin-row-entry(U+202E, ESC, line break);update-pin-row-not-a-list(U+202E, ESC, line break)bump-leg-root-not-a-projectasrepr(str(path)), a printable path toobump-leg-root-not-a-project(printable)manifest-missingasrepr(str(manifest_path))manifest-missing(printable); beside the older librarymanifest-missing(printable)family-unreadableasascii(str(path))family-unreadable(printable)bootstrap.py_shownwithout the bidi controls (repronly for a C0 or C1 control)family-manifest-missing-bootstrap(U+202E);family-manifest-unreadable-bootstrap(U+202E);family-manifest-unreadable-siblings-members-of(U+202E); beside the older libraryfamily-manifest-unreadable-bootstrap(U+202E); the copy test (\udcff,pl\u202ean)siblings.py's_shownas the library'sspelled(escapes, no quotes)family-manifest-unreadable-siblings(U+202E, ESC, line break); beside the older libraryfamily-manifest-unreadable-siblings(U+202E, ESC, line break); the copy test (x\x1b[31mRED,a\nb,a\rb,\udcff,tab\there,pl\u202ean)validate-family.py's_shownas an escaper of the line feed and the ESC onlyfamily-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 libraryfamily-manifest-unreadable-validate-family(U+202E, ESC, line break); the copy test (x\x1b[31mRED,a\nb,a\rb,\udcff,tab\there,pl\u202ean)validate-family.py's_shownalwaysreprfamily-manifest-missing-validate-family(printable);family-manifest-unreadable-validate-family(printable);shape-pin-missing(printable);shape-pin-unreadable(printable); beside the older libraryfamily-manifest-unreadable-validate-family(printable); the copy test (plain/dir,caf\xe9,(empty))update-pin-commitasascii(str(pin_path))update-pin-commit(printable)pin_rows_remediationspells a printable root tooupdate-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
ghfirst on PATH (and the suite's ownghguard ahead of it),GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false,GIT_CONFIG_GLOBAL=/dev/null, a blank git identity, git-filter-repo 2.47.0 (CI's pin;--versionsaysa40bce548d2c) 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.pyandtests/test_mapping_and_kind_refusals_shown.py; the hygiene five andtests/test_no_auto_maintenance.py; every file undertests/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_NAMEorFORGED_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 testupdate-accept-local-missing-name. The list isgate-files.txtin 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.tests/test_refusal_paths_shown_more.py(92),tests/test_pinned_copy_columns.py(4)tests/test_pinned_copy_columns.py(51),tests/test_pinned_copy_text.py(45)tests/test_pinned_copy_text.py(43),tests/test_load_yaml_refusals.py(53)tests/test_load_yaml_refusals.py(63),tests/test_mapping_and_kind_refusals_shown.py(33)tests/test_mapping_and_kind_refusals_shown.py(7),tests/test_path_guard_hygiene.py(89)tests/test_path_guard_hygiene.py(32),tests/test_repo_hygiene.py(64)tests/test_repo_hygiene.py(37),tests/test_agents_md_codes.py(47),tests/test_cli_reference.py(12)tests/test_cli_reference.py(11),tests/test_no_auto_maintenance.py(60),tests/test_bootstrap_spelling.py(25)tests/test_bootstrap_spelling.py(96)tests/test_bootstrap_spelling.py(86),tests/test_template_text_placeholders.py(10)tests/test_template_text_placeholders.py(96)tests/test_template_text_placeholders.py(36),tests/test_family_docs_one_spelling.py(32),tests/test_readme_windows_quoting_sentence.py(28)tests/test_readme_windows_quoting_sentence.py(18),tests/test_adopt_ignored_shape_files.py(37),tests/test_adopt_root_symlinks.py(41)tests/test_adopt_root_symlinks.py(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)tests/test_assembly_root_unreadable_paths.py(78),tests/test_bootstrap_upstream_notice.py(9),tests/test_bump_leg.py(9)tests/test_bump_leg.py(46),tests/test_bump_leg_unsearchable.py(32),tests/test_bump_remedies_family_root.py(18)tests/test_bump_remedies_family_root.py(3),tests/test_doctor_unreadable_pin.py(52),tests/test_family.py(41)tests/test_family.py(72),tests/test_family_siblings_bootstrap_unreadable.py(24)tests/test_family_siblings_bootstrap_unreadable.py(33),tests/test_manifest_validator.py(63)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)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(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)tests/test_update_shape.py(87),tests/test_update_shape_apply_lines_and_headers.py(9)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)tests/test_update_shape_unreadable.py(41),tests/test_validate_family_unreadable.py(55)tests/test_validate_family_unreadable.py(29),tests/test_validate_pins_named_and_spelled.py(67)tests/test_validate_pins_named_and_spelled.py(96)tests/test_validate_pins_named_and_spelled.py(96)tests/test_validate_pins_named_and_spelled.py(85),tests/test_validate_pins_spelled_and_named.py(11)tests/test_validate_pins_spelled_and_named.py(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)tests/test_chain_pin_sources.py(9),tests/test_chain_remedy_callers.py(68),tests/test_derived_id_refusals.py(19)tests/test_derived_id_refusals.py(44),tests/test_doctor_members_spelled.py(9),tests/test_doctor_next_is_a_command.py(43)tests/test_doctor_next_is_a_command.py(18),tests/test_doctor_unreadable_paths.py(78)tests/test_doctor_unreadable_paths.py(54),tests/test_dry_run_asks_remotes.py(32),tests/test_dry_run_parity.py(10)tests/test_dry_run_parity.py(96)tests/test_dry_run_parity.py(17),tests/test_family_add_refusals.py(65),tests/test_family_bootstrap_stops_named.py(14)tests/test_family_bootstrap_stops_named.py(35),tests/test_family_bump_remove_rollback.py(31),tests/test_family_gitmodules_values.py(30)tests/test_family_gitmodules_values.py(96)tests/test_family_gitmodules_values.py(21),tests/test_family_holder_remedies_compose.py(54),tests/test_family_holder_states.py(21)tests/test_family_holder_states.py(21),tests/test_family_init_landing.py(19),tests/test_family_init_values.py(56)tests/test_family_init_values.py(73),tests/test_family_member_branch_not_utf8.py(4),tests/test_family_remove_gitmodules.py(19)tests/test_family_remove_gitmodules.py(16),tests/test_family_tracking_branch.py(28),tests/test_legs_not_a_list.py(52)tests/test_legs_not_a_list.py(60),tests/test_naming_policy.py(36)tests/test_naming_policy.py(96)tests/test_naming_policy.py(96)tests/test_naming_policy.py(36),tests/test_scaffold_init_empty_values.py(36),tests/test_scaffold_manifest_values.py(24)tests/test_scaffold_manifest_values.py(89),tests/test_scaffold_referent_chain_links.py(7)tests/test_scaffold_referent_chain_links.py(37),tests/test_stdout_encoding.py(48),tests/test_update_shape_pin_source.py(11)tests/test_update_shape_pin_source.py(25),tests/test_validate_family_typed_states.py(43),tests/test_validate_workflow_gitmodules_urls.py(28)tests/test_validate_workflow_gitmodules_urls.py(96)tests/test_validate_workflow_gitmodules_urls.py(96)tests/test_validate_workflow_gitmodules_urls.py(96)tests/test_validate_workflow_gitmodules_urls.py(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)tests/test_update_shape_kept_helper.py(96)tests/test_update_shape_kept_helper.py(33)Skips, each named:
tests/test_adopt_root_symlinks.py:1960: the reftable backend needs git 2.45 or latertests/test_adopt_root_symlinks.py:2015: the reftable backend needs git 2.45 or latertests/test_adopt_root_symlinks.py:2596: the reftable backend needs git 2.45 or latertests/test_bump_leg.py:697: needs a disk that ignores case (macOS, Windows): on this one a second spelling is a second filetests/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 ittests/test_family_init_values.py:301: this template quotesid:: the derived id reads backtests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that onetests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows hosttests/test_stdout_encoding.py:292: the default this is about is Windows' owntests/test_bump_leg.py:697: needs a disk that ignores case (macOS, Windows): on this one a second spelling is a second filetests/test_family_bootstrap_stops_named.py:1330: this git's update exits 128 here, where git 2.43.0's crashes (exit 139)tests/test_family_bootstrap_stops_named.py:837: this git's update exits 128 here, where git 2.43.0's crashes (exit 139)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 ittests/test_family_init_values.py:301: this template quotesid:: the derived id reads backtests/test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and its file includes that onetests/test_shape_doctor.py:10516: the PowerShell line is printed, and powershell.exe exists, only on a Windows hosttests/test_stdout_encoding.py:292: the default this is about is Windows' ownOther runs:
86ab76db4+ this head's two test files106f92ff9, 13 calls, 1132 ids7029b3a9b(merge commit9fb4687a2, an object only)The predicted merge. The head with main
106f92ff9(fetched fresh; its commit is dated2026-11-10T12:56Z):git merge-tree --write-treethengit commit-tree, an object only (merge commit9fb4687a2, tree7029b3a9b; 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 mainb607223b4): 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 owntest_refusal_paths_shown_more.py,test_pinned_copy_columns.pyandtest_validate_family_unreadable.py; and the five that main's two latest commits changed (3add78e1c:test_adopt_pins.py, which runs a clone'sbootstrap.pyandvalidate-manifest.py, and, as controls,test_adopt_pin_row_values.pyandtest_adopt_plan_value_order.py;106f92ff9:test_adopt_relative_submodule_url.py, which runs a clone'sbootstrap.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 main3add78e1cand are the same ids;106f92ff9changed none of those seven files. Main also changedtests/conftest.pysince the merge base; every call here runs with it. A first run of the same list against main3add78e1c(1079 ids, 12 calls, tree51ced7527) passed too; main moved while this PR was being gated, and the list was run again at106f92ff9.No call of the predicted merge skipped a row.
One run with
LANES_LANEexported (#1299's lesson: a row of #1269 failed in any shell that exports it):tests/test_refusal_paths_shown_more.pyat this head on git 2.43.0 under a runner that exportsLANES_LANE=openRepoShape-5(every other run here unsets everyLANES_*): 92 passed in 17.96s.The round after rv1310's review
rv1310 found this PR READY at
6b9a4ece5with 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.pyandtests/test_pinned_copy_text.py, and no production file. The PR is two commits now (6b9a4ece5, thendaaa5ad8a). The Rows, Mutants and Runs sections above are as measured at6b9a4ece5; this section is what the round adds.51a9c5f10already definesshown_path(git show 51a9c5f10:scripts/repo_shape.py | grep -c 'def shown_path'gives 1). So the 16test_each_copy_spells_the_path_beside_an_older_libraryrows show that each copy refuses with the path spelled beside a library from before scripts/repo_shape.py:load_yamlnames the file with{path}as it was typed in its three refusals, so a bidi control, an ESC or a line feed in a--plan,--rootor--policypath reaches the terminal, whereshown_pathand_shownspell one (see #1152, #1115) #1168, and do not forbid a copy from importingshown_path: rv1310's mutant R1 (bootstrap.py's_shownreplaced byfrom repo_shape import shown_path as _shown) passes all 92 rows and the copy test, and onlytest_no_pinned_script_imports_what_an_older_library_lacks[family-bootstrap]kills it. The docstring now says so and names that row. Docstring only. Before: "THE SKEW RULE: each copy beside arepo_shape.pyfrom before scripts/repo_shape.py:load_yamlnames the file with{path}as it was typed in its three refusals, so a bidi control, an ESC or a line feed in a--plan,--rootor--policypath reaches the terminal, whereshown_pathand_shownspell one (see #1152, #1115) #1168, as a root or a holder has one that kept its own (update-shape.py apply --accept-local). The copy spells the path with its own_shown, so it runs, refuses by name, and spells the path as it does beside this checkout's library." After: "EACH COPY REFUSES WITH THE PATH SPELLED beside arepo_shape.pyfrom before scripts/repo_shape.py:load_yamlnames the file with{path}as it was typed in its three refusals, so a bidi control, an ESC or a line feed in a--plan,--rootor--policypath reaches the terminal, whereshown_pathand_shownspell one (see #1152, #1115) #1168, as a root or a holder has one that kept its own (update-shape.py apply --accept-local): it runs, refuses by name, and spells the path as it does beside this checkout's library. That library (51a9c5f10) already definesshown_path, so these rows do not forbid a copy from importing it. The row that does istest_pinned_copy_text.py'stest_no_pinned_script_imports_what_an_older_library_lacks."apply --accept-local scripts/repo_shape.pycan keep an old helper that a newly copied file imports from, andmake bootstrapthen fails with ImportError #536 choice. New:tests/test_pinned_copy_text.py::test_siblings_keeps_its_own_shown_and_takes_none_from_bootstrap, after the skew row, in its style (it readssiblings.pybyast, throughPINNED_IMPORTERS). It holds, by itself, thatsiblings.pydefines exactly one module-leveldef _shown, that nofrom bootstrap importnames_shown, at module level or inside a function, and that nothing readsbootstrap._shown. At 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 ofcheck_pinname the path asshown_pathdoes (#1288) #1310's head the choice was held only through the stand-in's missing_shown. It passes at this head.MEMBERS_UNREAD, which hard-codedsiblings.py's import list frombootstrap.py, is nowmembers_unread(): it reads the names fromsiblings.py's ownfrom bootstrap import ...byast(all butmembers_of, which the stand-in defines), so the stand-in re-exports whateversiblings.pytakes. A name a later change adds is answered by the holder's realbootstrap.py, and the 8 siblings rows keep running and testing what they say, where before they failed with anImportErrorand tested nothing.Mutants of the round, each applied alone in the detached tree at this head (
siblings.pychanged, the tests run, the file put back, its sha256 andgit statuschecked after each), git 2.43.0,-rs:from bootstrap import _shown as showninside_shownfrom bootstrap import _shownin place of the defimport bootstrapandreturn bootstrap._shown(path)_shownto give)siblings.py'sfrom bootstrap importgainsDEFAULT_MEMBERS_DIR, a namebootstrap.pydefines6b9a4ece5's rows file (the hard-coded list)ImportError: cannot import name 'DEFAULT_MEMBERS_DIR' from 'bootstrap'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 followingsiblings.py.No production file changed.
git diff --stat 6b9a4ece5 HEADlists the two test files alone (55 insertions, 19 deletions); the seven production files have the same sha256 at both commits, andgit diff 6b9a4ece5 HEADoutside the two test files is 0 bytes.Runs at
daaa5ad8a(tree84019f7a6de2), through the lane's runner as above (git-filter-repo 2.47.0,--versionsaysa40bce548d2c; 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, underflock):tests/test_no_auto_maintenance.py:222x2, "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one".tests/test_refusal_paths_shown_more.pytests/test_pinned_copy_text.pytests/test_pinned_copy_columns.pytests/test_load_yaml_refusals.pytests/test_mapping_and_kind_refusals_shown.py,tests/test_agents_md_codes.pytests/test_path_guard_hygiene.pytests/test_repo_hygiene.pytests/test_cli_reference.py,tests/test_no_auto_maintenance.pyOne run of the new file with
LANES_LANEexported (run10-lane.sh, #1299's lesson), git 2.43.0: 92 passed in 20.21s.Merges
git merge-tree --write-treeof the headdaaa5ad8a(tree84019f7a6de2) 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:fix/1279-refusals-shown, this PR's base)86ab76db484019f7a6de2, this head's own (an ancestor)dc9a0bcc73fffccbd6c23fix/1285-ascii-locale-family-rows,test_pinned_copy_text.py)bd84004f3f83555bf5839fix/1128-shown-default-ignorables,test_pinned_copy_text.py)ce1dd661f6a662aa489cel3-1256-member-head-unreadable,validate-family.py)1962f7498e43b74a6ae97fix/1215-rev-list-dashdash,update-shape.py)cf07ba0d1895f9c637a55fix/1287-siblings-precedence-row-and-fold,siblings.py)f51349796d6b8e8ff060cThe 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 torepo_shape.pybut does not changeshown_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 changedvalidate-manifest.pyandvalidate-family.pyand added none.Known limits
shown_pathdoes, so no U+202E, ESC or line feed is raw (#1279) #1284. The base isfix/1279-refusals-shown. Once repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, update-shape.py and the root's validate-manifest.py: not-a-mapping and wrong-kind refusals name the file asshown_pathdoes, so no U+202E, ESC or line feed is raw (#1279) #1284 lands, this PR is restacked onto main by three merges (repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, update-shape.py and the root's validate-manifest.py: not-a-mapping and wrong-kind refusals name the file asshown_pathdoes, so no U+202E, ESC or line feed is raw (#1279) #1284's final head, its squash with conflicts resolved to this branch's side in repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, update-shape.py and the root's validate-manifest.py: not-a-mapping and wrong-kind refusals name the file asshown_pathdoes, so no U+202E, ESC or line feed is raw (#1279) #1284's own files, then main), with no rebase.family-root-missing,family-members-unreadable,siblings-script-missingprint the holder's path as typed andmember-unknownpastes it raw, so a bidi control, an ESC or a line feed in it reaches the terminal (follow-up to #1288) #1317 (scripts/family.pyand the family root'sbootstrap.pyandvalidate-family.py):family-root-missing(Family.openand thesiblingssubcommand),family-members-unreadable,siblings-script-missingand themember-unknownremedy name or paste the holder's path as typed. update-shape.py:_by_hand's lines, the NEXT lines ofcheckandapply,update-root-missing,update-upstream-not-a-repoandupdate-branch-not-utf8print--rootas typed, so a bidi control, an ESC or a line feed in it reaches the terminal raw (follow-up to #1288) #1318 (update-shape.py):_by_hand's other lines, the NEXT lines ofcheckandapply,update-root-missing,update-upstream-not-a-repoandupdate-branch-not-utf8print--rootas typed. Measured at this head by rv1310 (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 ofcheck_pinname the path asshown_pathdoes (#1288) #1310 issuecomment-6773301382, NIT 1) and by n1310f: an ESC in--family-rootor--rootreaches stderr, or a line feed forges a line. Neither issue is closed by this PR; both stay open after it lands, and the one closing line below names 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 still print the path as typed, so a bidi control, an ESC or a line feed in it reaches the terminal raw (follow-up to #1279) #1288 alone.bootstrap.py, as no real run reachessiblings.py's own line: the holder'sbootstrap.pyrefuses first, in the same words, and that line is spelled too.update-pin-rowremediation for a root that does not print cannot be pasted as typed: it names the root asreprspells it.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:
Enhancements:
Tests: