Repository navigation
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 as shown_path does, so no U+202E, ESC or line feed is raw (#1279) - #1284
brettheap wants to merge 7 commits into
Conversation
…ame the file as `shown_path` does, so a U+202E, an ESC or a line feed in a typed path is spelled, and validate-pins.py, family.py and shape-doctor.py read either spelling (#1168) `load_yaml` named the file with the bare `{path}` in `yaml-unreadable` (twice) and `yaml-unparsable`, so a bidi override, an ESC or a line feed in a `--plan`, `--path-policy`, `--policy`, `--project` or `--root` path reached the terminal raw from `adopt-project.py check` and `plan`, `validate-repository-naming.py`, `bump-leg.py`, `family.py bump`, `update-shape.py check` and `validate-manifest.py`. Each now names it as `shown_path` does: `repr` for a path that is not printable, byte for byte for one that is. `policy-unreadable`, the next line of `NamingPolicy.load`, does the same. Every reader that finds the path at the start of the detail by its text asks both the spelled and the raw prefix, so it reads a `repo_shape.py` from before this change or after it: `validate-pins.py`'s `_load` (a root re-copies its pinned files one row at a time), `family.py`'s member manifest (the swap to `<repo> @ <commit>:project.yaml`), and in `shape-doctor.py` `naming_script_refusal` and the `manifest-kinds` row's `less_the_path`. `link_declarations`' own swap is unchanged and reads as it did. Rows: tests/test_load_yaml_refusals.py. Closes #1168 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l runs in is named as typed and spelled by `adopt-project.py check --plan`, and `_load`'s row builds its file before, not inside, `pytest.raises` (#1168) rv1274 and rv1274b found two things in the rows of #1274, each with no change to production code. Every tool row typed its path under `tmp_path`, which is absolute, so a `load_yaml` that named `Path(path).absolute()` (rv1274's X5) passed all of them. The new row, `test_a_path_typed_relative_is_named_as_typed_and_spelled` (U+202E, ESC and a line feed), types `<name>/adoption-plan.yaml` and runs `adopt-project.py check --plan` with that folder's parent as the working directory: the refusal names the path as typed, still relative, as `repr` spells it. SonarCloud python:S5778 (MAJOR) flagged `unparsable(root / "project.yaml")` inside the `pytest.raises` block of `test_the_gates_load_takes_either_prefix` beside `gate._load`. The file is made first, so only `gate._load` runs inside the block; the row's ids and verdicts are the same. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ing.py, update-shape.py and the root's validate-manifest.py: not-a-mapping and wrong-kind refusals name the file as `shown_path` does, so no U+202E, ESC or line feed is raw (#1279) #1168 (#1274) spelled the file in `load_yaml`'s three refusals and in `policy-unreadable`. The refusals on the next line of each reader, where the YAML parsed but is a list or declares another kind, still named the path raw: a bidi override reordered the line, an ESC reached the terminal, and a line feed began a line of the path's choosing. Each is spelled now: `policy-wrong-kind`, the naming gate's `manifest-unreadable`, `bump-leg-manifest-unreadable` (its other site was spelled already), `path-policy-unreadable`, `path-policy-wrong-kind`, `update-pin-unreadable` and the root's `manifest-unreadable`. The two copied gates use the `_shown` each already carries, so no pinned copy imports a new name. A printable path is named byte for byte as before. `adopt-project.py`'s `plan-unreadable` is left to #1177, which spells it with `_shown`; `validate-pins.py`'s `manifest-unreadable` names `project.yaml` and no path. `shape-doctor.py`'s `naming_script_refusal` docstring and two doctor rows in `tests/test_load_yaml_refusals.py` follow `policy-wrong-kind`: the raw row builds the line a root's older library prints. New rows in `tests/test_mapping_and_kind_refusals_shown.py`. Closes #1279 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUISpell paths safely in the not-a-mapping and wrong-kind refusals so control characters cannot forge or reorder terminal output, while preserving printable-path behavior and copied-script compatibility.
TestsExtend doctor refusal coverage for safely spelled and legacy raw policy paths, and preserve the older-library spelling behavior.
Exercise each affected refusal with unsafe and printable paths, including copied gates with older libraries and the already-spelled inaccessible-root refusal.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Independent review of #1284 (#1279) for lane openRepoShape-5, by rv1284 (opus), at 771667f: READY There is no MUST. There is one SHOULD and three NITs, listed below. All seven refusals now name the path the way How I worked: I made the detached trees FindingsSHOULD 1. Nothing tests the spelling at
NIT 1. The doctor docstring names the wrong source for a raw line.
NIT 2. The older-library rows cannot catch a
NIT 3. Parts of the body are stale.
What I checkedEach site, probed at the base
The two sites left alone, probed under all four names at the base and the head:
Rows. My 51 ids are the same set as n1279's (md5 equal).
Mutants. Each was applied alone and its files put back. Every sha256 matched and the tree was clean after each.
Each M is killed by its own site's rows only. SonarCloud (read only): quality gate OK. New-code duplication is 0.0% against the 3% limit, 203 new lines, 0 new issues. CI on Body:
Item 4: other refusals that print the path raw (for the follow-up issue)I probed each at the head, at the predicted merge with main, and validate-family again at main The sites n1279 listed:
More of the same kind I found, all raw:
GatesBoth at
The skips are the same on both:
Merge-treesAll with git 2.47.1, heads read fresh, all clean.
The PR's branch still reads Process note, not against the PR: n1279's Everything is in
Lane: openreposhape-5 (openRepoShape-5) |
…bump-leg-manifest-unreadable` for a root this user cannot search; two docstrings name this checkout's older library, not a root's, as what prints a raw `policy-wrong-kind` (#1279) rv1284 (independent review of #1284, READY at 771667f) found one SHOULD and three NITs, all fixed here and nothing else changed. SHOULD 1: `_manifest_unopened_refusal`, the second site of `bump-leg-manifest-unreadable`, was spelled with `shown_path` already, and no row ran it under a path holding U+202E, an ESC or a line feed: with it put back to a raw `{path}`, every file of the gate still passed. A new row over `NAMES` runs `bump-leg.py --root` over a root at mode 000, through `test_bump_leg_unsearchable.py`'s `unsearchable_root`, and asserts exactly `REFUSED bump-leg-manifest-unreadable: <the manifest, named as repr spells it>: cannot be read: Permission denied`. It passes on #1274's head too: it pins a spelling that was right. NIT 1: `naming_script_refusal` reads only the naming script of the checkout the doctor runs from, which imports that checkout's library, so a raw `policy-wrong-kind` prefix appears only where that library is older than its doctor. A root's library never reaches the function. The docstring of the function and of the changed raw row said "a root's `repo_shape.py`". NIT 2: the older-library rows of the new test file run beside a library that already has `shown_path`, so they cannot catch a gate that imports it; the file's docstring now credits `test_pinned_copy_text.py`'s `test_no_pinned_script_imports_what_an_older_library_lacks` for that import. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Re-check of #1284 (#1279) for lane openRepoShape-5, by rv1284b (sonnet), at 0270d48: READY There is no MUST or SHOULD. There is one NIT, and it was already in the body at I used the trees Findings1. Nothing else changed.
2. SHOULD 1 is fixed and the row is sound.
3. NIT 1 is fixed and the claim is right.
4. NIT 2 is fixed and the credit is right.
5. NIT 3 and the body hold.
NIT (not blocking, already there at GatesAll at
The 13-file totals equal the body's claim of 1723 tests, 1718 passed and 5 skipped. The skips are the same on both gits:
Predicted merge: Merge-treesAll use git 2.47.1 with
The PRs in the table are all of the open PRs that touch the 9 files of Logs and scripts are in Lane: openreposhape-5 (openRepoShape-5) |
…prints through `spelled()`, so a name with a line feed or an ESC byte is one row and one `naming-unclassified` name, with its column worked out from the spelled form (#1267) (#1271) **#1267: `validate-repository-naming.py` prints every name it tables or refuses through `repo_shape.spelled()`, so a name that holds a line feed or another control byte stays on one line and in one table row, and the finding for `Atlas` plus a line feed cannot read as the finding for a valid `Atlas`.** **This PR was stacked on #1266 (#1202) and was restacked onto main after #1266's squash.** Its base was `fix/1202-naming-fullmatch` (#1266's head `09697e9c5`); its base is `main` now, and #1266 is on main as the squash `28be96557`. It is a DRAFT. The change is three commits written on top of `09697e9c5`, `4b1653d8b`, the review round `4fa9f78a6` and the round after rv1271b's delta check `76e99ec86` (see "Review round" and "The round after rv1271b's delta check"), and the restack's merge of #1266's squash, `2d9250c57` (see "The restack after #1266's squash"). Against main it is 3 files, +416 and -19 (`git diff --shortstat origin/main..HEAD`): `scripts/validate-repository-naming.py` (+31 -15), the new `tests/test_naming_gate_spelled.py` (+376, 39 rows), and `tests/test_naming_policy_line_break.py` (+9 -4: two of #1266's rows now assert the spelled form, see "What changed"). It is the follow-up lane 5's independent review of #1266 filed as #1267 (NIT 2, #1266 issuecomment-6724498649). #1266 makes `Atlas` plus a line feed a name of no family; this one is about how the gate then prints that name. rv1271 (opus, independent) found this PR READY at `4b1653d8b` with six NITs, none blocking (#1271 issuecomment-6726218829). The round is one commit on it, `4fa9f78a6`: 2 files, +39 and -20 (`scripts/validate-repository-naming.py` +13 -10, `tests/test_naming_gate_spelled.py` +26 -10). NITs 1 to 5 are done. NIT 6 was left for the restack, and the restack has done it (see "The restack after #1266's squash"). | NIT | What rv1271 found | What was done | |---|---|---| | 1 | The module docstring said two things that cannot both be true: a chain link and a caller's words are "printed as given", and "every repository name this prints" is spelled | **Docstring only** (the script's syntax tree outside the docstring is equal to `4b1653d8b`'s, proved with `ast`). It now says one thing: every name the gate classifies or refuses is spelled, and a sentence that quotes one is spelled whole, so a chain link or a caller's `--link-remedy` words inside it are spelled too, while the `CHAIN` line, `link-source-unreadable` and the NOTEs print a chain link as given. The old "printed as given" lines no longer list a chain link or the words of a remedy | | 2 | Two rival escapers survived every row: X1 (escape only a line feed and an ESC) and X3 (`repr(value)[1:-1]`) | **Four new rows**, below. X1 now fails 2 rows and X3 fails 2, each mutant applied alone and restored with a sha256 check | | 3 | A surrogate name's row changed, against "byte for byte as before" | **Body only.** "What changed" now says the name's bytes are unchanged and its label moved from character 40 to 35 | | 4 | The skips were explained wrongly: the body said the reasons "were not printed" | **Body only.** "Runs" names each reason and says why the first gate's logs hid them (`run10.sh`'s `-rs`, then the gate's `-rf`: pytest keeps only the last `-r`) | | 5 | The Merges section was stale | **Body only.** "Merges" is re-measured at the round's head against main fetched fresh | | 6 | `closingIssuesReferences` was `[]` while the base was #1266's branch | **Left for the restack, now done:** the PR was restacked onto main, and `closingIssuesReferences` reads #1267 (see "The restack after #1266's squash") | **NIT 1, measured.** At the round's head, with a chain link `openX<ESC>dox` and `--link-remedy 'openXdox=run<TAB>make <ESC>[31mfetch'` (`logs/docstring-claims-head.log`, `logs/note-claims-head.log`): the stderr `WARNING <name>: <sentence>` and `--explain`'s `WARNING` spell the link (`openX\x1bdox`) and the remedy words (`run\tmake \x1b[31mfetch`); `--explain`'s `CHAIN` line, the `link-source-unreadable` WARNING (`could not answer for openX<ESC>dox`) and the NOTEs that name a link (`no name classified here reads openX<ESC>dox's own declaration`, `links: openX<ESC>dox, openDox`) print the link with its ESC byte. The values of `--link-source` and `--link-remedy` go through `_shown`, as the docstring's paragraph above the new one says. The "Limits" bullet on chain links still holds as written; the remedy words are the half it did not name. **NIT 2, the rows.** `SPELLED_ITSELF` in `tests/test_naming_gate_spelled.py` adds two names to the finding row and the table row on the shipped contract (the file is 36 rows now). The expected spellings are written out by hand, as the file's others are: | Name | Spelled | Pins | |---|---|---| | `Atlas` plus a CR (`ends-in-cr`) | `Atlas\r` | the CR: a gate that escapes only a line feed and an ESC prints it raw | | `At\las` plus a line feed (`backslash`) | `At\las\n` | the backslash: `spelled` prints it as it is, so `repr` doubles it (and an escaper of every non-ASCII character does too); the line feed beside it keeps the row failing on the base | A ZWJ was not used for the table row: lane 3's #1217 pads by terminal columns and a ZWJ (category Cf) takes none, so its row would change on the restack (`ZERO_COLUMN_CATEGORIES` at `352dcbc96`). A backslash is ASCII and one column. - **At the head:** the 4 rows pass (`tests/test_naming_gate_spelled.py` 36 passed, `logs/new-file-head.log`). - **On the base** (`09697e9c5`'s script): the 4 fail, the base prints the CR raw and splits the backslash name's row and finding at its line feed (`logs/run-base.log`, `logs/new-names.log`). **On main** (`dff8fd4ba`): the same 4 fail (`logs/run-main-gate-spelled.log`). - **Under X1** (`spelled` replaced by a local escaper of a line feed and an ESC): **2 failed**, the finding row and the table row of `ends-in-cr`; the other 40 of the 42 core tests pass. **Under X3** (`repr(str(value))[1:-1]`): **2 failed**, the finding row and the table row of `backslash`; the other 40 pass. Each applied alone to the script in `n1267-mut` and restored from git, `restored x1-narrow sha256 ok`, `restored x3-repr-unquoted sha256 ok` (`logs/mutants-core.out`, `logs/mut-core-x1-narrow.log`, `logs/mut-core-x3-repr-unquoted.log`). No other row fails under either mutant. rv1271b (sonnet, independent) found this PR READY at `4fa9f78a6` with no MUST or SHOULD and two NITs (#1271 issuecomment-6726914536). The round is one commit on it, `76e99ec86`: 2 files, +44 and -15 (`scripts/validate-repository-naming.py` +5 -2, `tests/test_naming_gate_spelled.py` +39 -13). Both NITs are done and nothing else changed: the script's syntax tree outside its module docstring is equal to `4fa9f78a6`'s (proved with `ast`, `logs/ast-delta.log`), and the test file adds three ids. | NIT | What rv1271b found | What was done | |---|---|---| | 1 | Five rival escapers pass every row, because no name holds a character outside LF, CR, ESC and a backslash: y1 (C0 controls only), y2 (category Cc only), y3 (`spelled` without the bidirectional controls), y4 (an `isprintable` test) and y5 (a table of LF, CR and ESC) | **Three new ids**: a name with U+202E (RLO) in the finding row and the table row, and a name with U+200D (ZWJ) in the finding row only. Each of y1 to y5, applied alone and restored with a sha256 check, now fails at least one | | 2 | The module docstring's first paragraph says every `--link-remedy` value this prints is shown as it is or by `_shown`, but the `declared-unverified` WARNING prints the remedy's words spelled, without quotes | **Docstring only.** The paragraph names that one exception | **NIT 1, the rows.** The names are written in the test source as `chr(0x202E)` and `chr(0x200D)`, never as raw characters, and the expected spellings by hand, as the file's others are. Both are asked of the shipped contract, where the file's other `SPELLED_ITSELF` names are: | Name | Printed as | Rows | Pins | |---|---|---|---| | `At`, U+202E, `las` (`rlo`) | `At\u202elas` | the finding, the table row | a bidirectional control that reorders the text around it and is no C0 or C1 control: an escaper that misses it (y1, y2, y3, y5) prints it raw | | `At`, U+200D, `las` (`zwj`) | the name itself, the joiner as it is | the finding only | a format character `spelled` leaves alone, as its docstring says: an `isprintable` test (y4) writes it `\u200d` | The ZWJ is in the finding row only. #1236 (landed on main as `81cbe9c82`) pads the table by the columns a name takes, and a ZWJ (category Cf) takes none, so a table row holding one would depend on that pad; the RLO name prints as ASCII (`\u202e` is six columns) and is safe in both. The ZWJ row passes on main and on the base **by design**: those print the joiner as it is, which is the text `spelled` gives, so it cannot fail there. It is a row that kills a rival, not one that fails on the base (see "Failing on the base"). The other two ids, `finding[rlo]` and `table[rlo]`, fail on both. **Each rival, measured** (`probes/mutate.py`, `logs/mutants-y.out`; each applied alone to the gate's call in `n1267-mut` at `76e99ec86`, in place of the import of `spelled`, the new file run, the script restored with a sha256 check, `9cc39a2eafb0` each time, porcelain 0 after each): | Rival | What it escapes | Rows failing (of the 39) | Killed by | |---|---|---|---| | y1 | C0 controls only | 2 | `finding[rlo]`, `table[rlo]` | | y2 | category Cc only (C0, DEL, C1) | 2 | `finding[rlo]`, `table[rlo]` | | y3 | every category `spelled` escapes, but no bidirectional controls | 2 | `finding[rlo]`, `table[rlo]` | | y4 | what `str.isprintable` refuses | 1 | `finding[zwj]` | | y5 | LF, CR and ESC by a table | 2 | `finding[rlo]`, `table[rlo]` | No other row fails under any of the five, which is the proof that each passed every row of `4fa9f78a6`. rv1271b's other rivals and rv1271's X1 and X3 are still killed (`logs/mutants-rest.out`): y6 (every non-ASCII character too) 9, y7 (C0 as `\xNN`) 25, y8 (`ascii()` without quotes) 11, y9 (one-lining) 37, X1 4, X3 3. **These six counts are over the new file's 39 rows alone.** The Mutants table below counts over the 45 core tests, the new file's 39 rows and the 6 updated rows of `tests/test_naming_policy_line_break.py`, and is the first round's table re-measured at this head. Over the 45, X1 is 4 and X3 is 3 as here (they are rows of that table), and rv1271c measured the four y rivals that are not in it as y6 9, y7 31, y8 11 and y9 43: y7 and y9 each count 6 more over the 45 (y7 25 and 31, y9 37 and 43). **NIT 2, the docstring.** The paragraph now says the one exception: the WORDS of a `--link-remedy` where a `declared-unverified` WARNING offers them stand inside its sentence and are spelled with it, so a TAB or an ESC in them is `\t` or `\x1b`, without quotes. Measured at the head (`probes/docstring-claim.py`, `logs/docstring-claim-head.log`): `--link-remedy 'openXdox=run<TAB>make <ESC>[31mfetch'` read by a chain gives `WARNING InkxDox: declared-unverified: openXdox declaring a pin on openDox is a fact in openXdox's own tree, which is not reachable here (run\tmake \x1b[31mfetch). The recorded chain still classifies.`, with no raw TAB or ESC in the output; a remedy no holder used is a NOTE whose VALUE goes through `_shown`, quotes included (`NOTE --link-remedy 'openQdox=run\tmake \x1b[31mfetch': not used ...`), as the paragraph still says. **The runs of this round.** Head `76e99ec86`, dirty=0, Python 3.12.3, git-filter-repo 2.47.0 (`a40bce548d2c`), through `run10.sh` with its `-rs` and no other `-r`, so a skip names its reason. The files are the brief's: the new file (39), `test_naming_policy_line_break` (77), `test_naming_policy` (264), `test_chain_link_fold` (17), `test_legs_not_a_list` (112), the hygiene five (`test_path_guard_hygiene` 121, `test_repo_hygiene` 101, `test_agents_md_codes` 47, `test_cli_reference` 23, `test_pinned_copy_text` 87) and `test_no_auto_maintenance` (60): 948 tests in 9 runs per git. Four single files hold more than 100 tests (`test_naming_policy`, `test_legs_not_a_list`, `test_path_guard_hygiene`, `test_repo_hygiene`); each took `flock` on one lock, so no two runs of more than 100 tests overlapped, and no other run held more than 100. | Run | git | Result | |---|---|---| | the 11 files (9 runs) | 2.43.0 (`/usr/bin/git`) | **946 passed, 2 skipped, 0 failed** | | the 11 files (9 runs) | 2.47.1 (`tools/git247/bin/git`) | **946 passed, 2 skipped, 0 failed** | | the new file alone, before the commit | 2.43.0 | 39 passed | The 2 skips are the same on both gits: `tests/test_no_auto_maintenance.py:222` x2 ("the session began with `GIT_CONFIG_GLOBAL` set, and its file includes that one"): `run10.sh` exports `GIT_CONFIG_GLOBAL=/dev/null` for every run, which is the lane's rule for a gate, and the two rows of that test skip under it. Logs: `gate/gate-243-1.log` to `-9.log`, `gate/gate-2471-1.log` to `-9.log`. **Not run:** the full suite, a real Windows host and macOS; the first commit's 37-file gate under "Runs" stands for the rest, since the round changes a docstring and the new test file only. **The restack's `_print_classification` hunk, pre-checked.** #1236 landed on main as `81cbe9c82` while this PR waited for #1266. The restack onto main will conflict at one line, and only there (see "Merges"). I built the resolution as an object-only commit, with no branch, no ref and never pushed: `git merge-tree --write-tree` of this head and main `30272026e`, then `git commit-tree` over the resolved file, the one `print` line read as `print(f" {_padded(_printed(spelled(name)), 32)} {label}{also}")` and main's comment above it kept. That commit is `0cfaedf3f` (tree `24e4b4e3`). Checked out detached in `n1267-mut`, through the gate runner, in 4 runs: the new file with `test_chain_link_fold.py` (39 + 19 = 58 tests), `test_pinned_copy_columns.py` (55), `test_siblings_family_name.py` (154, under `flock`, as the one file that needs more than 100) and `test_naming_policy_line_break.py` (77): **344 passed** (`logs/resolved-main-a.log` to `-d.log`). The mut tree was put back at `76e99ec86`, clean, its script's sha256 equal to the branch's. #1266 (#1202, this PR's base) landed on main as a squash, and GitHub retargeted this PR to `main`. The restack was done by hand, in the writing tree `l5-1267-naming-spelled`, as three merges (the route lane 5's `restack3.sh` takes: the parent's final head, the parent's squash, then main), because one conflict is in a file #1266 never touched. The merge commit carries the identity the lane's rules give it. The PR's own change is the one it was: its diff against main is the same 435 changed lines as before the restack, except the one print line resolved below, and no row or test changed. | Step | What | Result | |---|---|---| | H66, #1266's final head | `09697e9c52f35c9aadbafdebb88e47950105f474` | already an ancestor of the branch (`git merge-base --is-ancestor` says yes: the branch was built on it), so no merge and **no commit** | | S, #1266's squash | `28be9655772602597028e7936c6fb0649130801d` (its parent is main `30272026e`, its tree `8af55167`) | `git merge --no-ff S`: the merge commit **`2d9250c57f8d571ac4280a297927cf51d07be1a6`** (parents `76e99ec86` and S); **two conflicts, both the expected ones** (below) | | main | `28be96557`, fetched fresh with `-c gc.auto=0 -c maintenance.auto=false fetch --no-write-fetch-head`; it is S itself, main's tip | `git merge origin/main`: **"Already up to date."** No commit | **The two conflicts, and no other** (`git merge-tree --write-tree --name-only S 76e99ec` named the same two before the merge): - **`tests/test_naming_policy_line_break.py`, add/add.** The file is #1266's own, and this PR changed 13 lines of it (+9 -4: the two rows that now assert the spelled form). Resolved by taking the branch's side whole (`git checkout --ours`, then `git add`). Nothing is lost: S's copy of the file is #1266's own (blob `b6a75d9bf`, equal to the one at `09697e9c5`), and the branch's copy is that blob plus the 13 lines. The result's blob is `5885627f8c27426bbbcfff1e49e42bc88770809a`, **equal to `76e99ec86`'s**. - **`scripts/validate-repository-naming.py`, one hunk, `_print_classification`'s print line,** where main's #1236 (`81cbe9c82`, the display-width pad) meets this PR's `spelled`. The branch's side was `print(f" {spelled(name):<32} {label}{also}")`; main's side is the two comment lines and `print(f" {_padded(_printed(name), 32)} {label}{also}")`. Resolved by hand, as the body has said all along: spell first, then pad what is printed, with main's comment kept: `print(f" {_padded(_printed(spelled(name)), 32)} {label}{also}")`. The resolved file's blob is `c568e33b86ca922ade043a856bf47b060fd1f3ae`, **equal to the file in the object-only commit `0cfaedf3f` (tree `24e4b4e3`) that the round before pre-checked**, and main's file is the same as it was then (blob `4bfafa631` at `30272026e` and at S), so the pre-check stands for the head. Neither side was taken wholesale. The merge commit's message names S, both files and how each was resolved. **Verified at `2d9250c57`** (git 2.43.0; the changed lines are in `changed-old.txt` and `changed-new.txt` in the writer's folder): - `git diff --name-only origin/main HEAD` lists exactly `scripts/validate-repository-naming.py`, `tests/test_naming_gate_spelled.py` and `tests/test_naming_policy_line_break.py`: 3 files, +416 and -19, with `scripts/validate-repository-naming.py` +31 -15, the new test file +376, `tests/test_naming_policy_line_break.py` +9 -4. `scripts/repo_shape.py` is the same blob (`852d272ef`) at the head, at S and at `09697e9c5`: the restack leaves it to #1266. - The `-U0` changed lines of `git diff origin/main HEAD` (435) equal those of `git diff 09697e9 76e99ec` (435), line for line, **except the one resolved print line**. Before: `- print(f" {name:<32} {label}{also}")` and `+ print(f" {spelled(name):<32} {label}{also}")`. Now: `- print(f" {_padded(_printed(name), 32)} {label}{also}")` and `+ print(f" {_padded(_printed(spelled(name)), 32)} {label}{also}")`. - `git merge-tree --write-tree origin/main HEAD` exits 0 and gives **`24e4b4e3255e9fcd55abcdec217f577ccb22270d`, the head's own tree**: nothing is left to merge, and the tree is the one of `0cfaedf3f`. **The runs of the restack.** Head `2d9250c57`, dirty=0, Python 3.12.3, git-filter-repo 2.47.0 (`a40bce548d2c`), through `run10.sh` with its `-rs` and no other `-r`, so a skip names its reason. The files are the brief's: the new file (39), `test_naming_policy_line_break` (77), `test_naming_policy` (264), `test_chain_link_fold` (19 at the head; 17 at `76e99ec86`, and the two more rows are main's #1236, `81cbe9c82`), `test_legs_not_a_list` (112), `test_pinned_copy_columns` (55), `test_siblings_family_name` (154), the hygiene five (`test_path_guard_hygiene` 121, `test_repo_hygiene` 101, `test_agents_md_codes` 47, `test_cli_reference` 23, `test_pinned_copy_text` 87) and `test_no_auto_maintenance` (60): 1159 tests in 11 runs per git. Five single files hold more than 100 tests (`test_siblings_family_name`, `test_naming_policy`, `test_legs_not_a_list`, `test_path_guard_hygiene`, `test_repo_hygiene`); each took `flock` on one lock, so no two runs of more than 100 tests overlapped, and no other run held more than 100. | Run | git | Result | |---|---|---| | the 13 files (11 runs) | 2.43.0 (`/usr/bin/git`) | **1,157 passed, 2 skipped, 0 failed** | | the 13 files (11 runs) | 2.47.1 (`tools/git247/bin/git`) | **1,157 passed, 2 skipped, 0 failed** | The 2 skips are the same on both gits: `tests/test_no_auto_maintenance.py:222` x2 ("the session began with `GIT_CONFIG_GLOBAL` set, and its file includes that one"): `run10.sh` exports `GIT_CONFIG_GLOBAL=/dev/null` for every run, which is the lane's rule for a gate, and the two rows of that test skip under it. Logs: `gate/gate-243-1.log` to `-11.log`, `gate/gate-2471-1.log` to `-11.log`. **Not run:** the full suite, a real Windows host and macOS. **The new rows on main's own script.** With #1266 on main, main's `validate-repository-naming.py` (S's, blob `4bfafa631`) was swapped into `n1267-mut` at the head (its `scripts/repo_shape.py` is the same blob as main's), and the two test files run in two runs (`logs/swap-main-a.log`, `logs/swap-main-b.log`): `tests/test_naming_gate_spelled.py` gives **37 failed, 2 passed**, and `tests/test_naming_policy_line_break.py` gives **6 failed, 71 passed**. The 2 passes are `test_a_plain_valid_name_prints_exactly_as_it_did` (the plain-name control) and `test_the_finding_prints_the_spelled_name_on_one_line[zwj]` (the ZWJ finding row, which prints the same on main by design, see "The round after rv1271b's delta check"); the 6 failures of #1266's file are the 6 rows this PR updated to assert the spelled form. The script was restored from git with a sha256 check (`f504b0e48b4a`, the head's) and the tree was clean after. **The record after the push.** `git ls-remote origin refs/heads/fix/1267-naming-spelled` reads `2d9250c57f8d571ac4280a297927cf51d07be1a6`; `gh pr view 1271` reads base `main`, head `2d9250c57f8d571ac4280a297927cf51d07be1a6`, `closingIssuesReferences` `[1267]` and `mergeable` `MERGEABLE`. Sections elsewhere in this body that say "on main (without #1266)" or call `09697e9c5` the base describe those commits as they were measured, before the restack; they are not re-measured, and the Mutants and Runs sections still describe the heads they name. rv1271c (sonnet, independent) found this PR READY at `76e99ec86` and again at the restack `2d9250c57`, with no MUST or SHOULD and three NITs (#1271 issuecomment-6759403546). This round takes the first two. It is one commit on `2d9250c57`, `c56753ba7`: 1 file, +4 and -4 (`tests/test_naming_gate_spelled.py`, four `#` comment lines). NIT 3 is informational (rival edits the new file's rows cannot tell from `spelled`, which the library's own tests kill) and needs nothing. Nothing was merged into the branch: main is `28be96557` still, an ancestor of the head, and the PR's diff against it is the same 3 files, +416 and -19. | NIT | What rv1271c found | What was done | |---|---|---| | 1 | Four `#:` comments in `tests/test_naming_gate_spelled.py` write an escape with a doubled backslash. A doubled backslash is right in a string literal or a docstring, where Python reads it as one, and wrong in a comment, where it reads literally. Two of the four read correctly at `4fa9f78a6` and became wrong at `76e99ec86`, when the paragraph was re-wrapped | **Comments only.** The four comments write one backslash. Lines 80 and 81 read as they did at `4fa9f78a6` (its lines 72 and 73); lines 87 and 98 are the comments the RLO and ZWJ rows added | | 2 | The sentence that gives `y6 9, y7 25, y8 11, y9 37, X1 4, X3 3` counts over the new file's 39 rows, while the Mutants table counts over the 45 core tests, and the body says neither | **One sentence**, in "The round after rv1271b's delta check": it says the six counts are over the new file's 39 rows, that the Mutants table counts over the 45 core tests, and what the same rivals count over the 45 | **NIT 1, the four lines** (`git diff -U0 2d9250c c56753b`, the whole diff of the round; the lines are code text and show a backslash as the file has it): ``` @@ -80,2 +80,2 @@ NAMES = [pytest.param(*ENDS_IN_LF, id="ends-in-lf"), -#: double the backslash: `spelled` writes a CR as `\\r` and leaves a backslash -#: as it is (the line feed beside it is still `\\n`), so the name holding it +#: double the backslash: `spelled` writes a CR as `\r` and leaves a backslash +#: as it is (the line feed beside it is still `\n`), so the name holding it @@ -87 +87 @@ WITH_BACKSLASH = ("At\\las" + LF, "At\\las\\n") -#: `\\u202e`, and a rival that escaped only the C0 controls, only the category +#: `\u202e`, and a rival that escaped only the C0 controls, only the category @@ -98 +98 @@ SPELLED_ITSELF = [pytest.param(*ENDS_IN_CR, id="ends-in-cr"), -#: `str.isprintable` would write it as `\\u200d`. It takes no column, so only +#: `str.isprintable` would write it as `\u200d`. It takes no column, so only ``` **The round is comments only, proved** (`probes/prove-comments-only.py`, run over the two committed blobs, `logs/prove-comments-only-committed.log`; the file's sha256 is `bac49172...` before and `9b52a2cb...` after): - `ast.dump` of the file, with `include_attributes=False`, is equal before and after (48,502 characters each); with the node positions it is equal too. - A `tokenize` comparison: 2,337 tokens on each side, and **4 differ, all 4 COMMENT tokens**. The tokens that are not comments are equal in type and text; four NL tokens, the line ends after the shortened comments, stand one column to the left. - `git diff -U0` shows 8 changed lines, every one a `#` line, in three hunks (lines 80 and 81, line 87, line 98). - No comment holds a doubled backslash now: 0 `COMMENT` tokens by `tokenize` (4 before), and 0 lines with a `#` before a doubled backslash by a text search (`probes/grep-comments.py`, `logs/grep-comments-after.log`). The doubled backslashes that remain are in string literals and docstrings, where Python reads them as one (`"Atlas\\n"`, the module docstring, the finding test's docstring), and are untouched. **NIT 2, the sentence.** It is the last sentence of the paragraph in "The round after rv1271b's delta check" that gives the counts of rv1271b's other rivals and rv1271's X1 and X3. The numbers were true and are not changed; the sentence now names what they count over. **The runs of this round.** Head `c56753ba7`, dirty=0, Python 3.12.3, git-filter-repo 2.47.0 (`a40bce548d2c`), through `run10.sh` with its `-rs` and no other `-r`, so a skip would name its reason. Three files, each run alone: the new file (39) and the two single files that hold more than 100 tests (`test_repo_hygiene` 101, `test_path_guard_hygiene` 121). Each of those two took `flock` on one lock, so no two runs of more than 100 tests overlapped, and the two gits ran side by side (two runs at most): 261 tests per git, 6 runs. | Run | git | Result | |---|---|---| | the new file alone | 2.43.0 (`/usr/bin/git`) | **39 passed** | | the new file alone | 2.47.1 (`tools/git247/bin/git`) | **39 passed** | | `tests/test_repo_hygiene.py` alone, under `flock` | 2.43.0 | **101 passed** | | `tests/test_repo_hygiene.py` alone, under `flock` | 2.47.1 | **101 passed** | | `tests/test_path_guard_hygiene.py` alone, under `flock` | 2.43.0 | **121 passed** | | `tests/test_path_guard_hygiene.py` alone, under `flock` | 2.47.1 | **121 passed** | 0 failed and 0 skipped in all six. Logs: `gate/gate-243-1.log` to `-3.log` and `gate/gate-2471-1.log` to `-3.log`. **Not run:** the rest of the 11-file gate. The round changes four comment lines, proved above, so rv1271c's gate at `2d9250c57` stands for the rest: 1,157 passed and 2 skipped on both gits (`tests/test_no_auto_maintenance.py:222` x2, "the session began with `GIT_CONFIG_GLOBAL` set, and its file includes that one"). Also not run: the full suite, a real Windows host and macOS. Measured at `09697e9c5` (Python 3.12.3, git 2.43.0): the gate printed a name raw. - `validate-repository-naming.py` given `Atlas` plus a line feed: exit 1, stdout `' Atlas'` and then `' NO FAMILY'` on the next line (the row split in two), stderr `FINDING naming-unclassified: Atlas` and then a blank line, which reads exactly as the finding for a valid `Atlas`. - `At`, a line feed, `las`: the same split. - `At`, ESC, `[31mlas`: the ESC byte reached the terminal in the row and in the finding. - A name with a CR (`Atlas-spec` plus a carriage return) printed the same way, the CR raw in the row and in the finding. On main (without #1266) `Atlas` plus a line feed is not refused at all: it exits 0, classified as `Atlas`, with its row split in two (#1266's body has that measurement). Every `print` of the script, and every string it builds from a name, at the base (`09697e9c5`), with what became of it. Ten changed: | # | Site (base line) | What it prints | Row | |---|---|---|---| | 1 | `_print_classification` (1297) | the plain listing's row, ` <name padded to 32> <label>` | table rows, classified-name row | | 2 | `_no_family_lines` (791) | `--explain`'s header for a name of no family | explain NO FAMILY | | 3 | `_describe` (955) | `--explain`'s header for a classified name | explain classified | | 4 | `_referent_claim_note` (831) | `[a CLAIM: needs a declared pin on openRouter or openxRouter ...]`, the referents derived from the name | claim note | | 5 | `_referent_lines` (935) | `--explain`'s `WARNING <sentence>`; the sentence quotes the name | explain WARNING | | 6 | `_referent_lines` (937) | `--explain`'s `OVERLAP <reason>`; a broken chain's reason quotes the name | explain OVERLAP | | 7 | `_classify_targets` (1321) | the stderr line `WARNING <name>: <sentence>`; the sentence quotes the name again | stderr WARNING | | 8 | `_leg_target` (1112) | `FINDING <name>: declared role 'code' but the name is the 'assembly' form ...` | role finding | | 9 | `_leg_target` (1119) | `FINDING <name>: <broken chain's reason>`; the reason quotes the name | broken-chain finding | | 10 | `_emit_reports` (1358) | `FINDING naming-unclassified: <name>, <name>` | the finding rows | Sites 1 to 4, 8 and 10 spell the name (1 pads the SPELLED name: `f" {spelled(name):<32} ..."`, so the width is worked out from the printed form). Sites 5, 6, 7 and 9 spell the WHOLE string: the sentence `resolve_referent` builds says the name again (`it ends at openQ, but <name> would need openDox or openxDox`), so spelling only the prefix leaves the name raw in the same line. (A mutant that spells only the name, M7b and M9b, is killed.) **Not changed, and why** (each prints no repository name): - `print(f"WARNING {warning}")` (1347) and `print(f"FINDING {finding}")` (1354), `print("\n".join(_describe(...)))` (1317): they emit the strings above, spelled where they are built. - `sources.unread_lines()` and `unused_lines()` (WARNING and NOTE, 1350 and 1352): link-source values and paths through `_shown`, chain links as given, as the module docstring has said since #610. - `print(str(exc))` (1457, 1473), `no_path_refusal()` (1482), `REFUSED naming-no-target` (1500): the policy or manifest path and fixed words. - `_pins_field_findings`, `_chain_field_findings`, `_legs_field_findings`, `_topic_finding`, and `_leg_target`'s `manifest-leg` and `a leg has no repository:` lines: values through `!r` or `_shown`. - `--explain`'s `CHAIN` line (a chain link is a neutral product's name, printed as given; a manifest's links are checked printable by `_not_a_name`, #714), the family rows (the policy's ids and patterns) and the policy's notes. - **`scripts/validate-repository-naming.py`:** `spelled` is imported from `repo_shape` and applied at the ten sites; the module docstring says so beside the `_shown` paragraph, and that a name with no control prints as it did. `spelled` is in every kept library: `scripts/repo_shape.py` at v1.0.0, v1.0.1, v1.1.0, v1.1.1 and v1.2.0 each define it, with the same text (the sha256 of the AST source of `spelled` and of its two constants, `ESCAPED_CATEGORIES` and `ESCAPED_BIDI_CONTROLS`, is equal in all five and at the head), so the import is not skewed (`tests/test_pinned_copy_text.py`'s `NEWER_THAN_A_KEPT_LIBRARY` names only `shown_path` and `host_shell_dialect`, and passes unchanged). - **No refusal code, flag, command or exit status changes.** `naming-unclassified` stays; only the name in its words is spelled. A name without a line feed, CR, ESC or other control prints as before (the plain-name row, and the 3,563 gate tests below), with one change in a ROW and none in the name: a name with a lone surrogate (`Atl\udcffas`, in `test_legs_not_a_list.py`) prints its own bytes as before (`spelled` writes `\udcff`, as stderr's `backslashreplace` did), but its label moved from character 40 to character 35, because the column is now worked out from the spelled name and so lines up with every other row's (measured on `09697e9c5`, `4b1653d8b` and the round's head: `logs/surrogate-label.log`; rv1271's NIT 3). - **`tests/test_naming_policy_line_break.py` (#1266's file), two rows updated:** `test_the_naming_gate_refuses_a_leg_repository_that_ends_in_a_line_feed` (3 rows) and `test_the_naming_gate_refuses_an_argv_name_that_ends_in_a_line_feed` (3 rows) asserted `FINDING naming-unclassified: Atlas`, the raw form (the line feed ended the first line). They assert `FINDING naming-unclassified: Atlas\n` now (a backslash and an `n`), which is what the issue asks. The docstrings say so. No other row of the file changed. - **What pins the printed forms (grepped first).** `tests/`, `README.md`, `docs/` (the handbook included) and `AGENTS.md` for `naming-unclassified`, `NO FAMILY`, `declared role`, `also_matches` and the table's columns: README, docs and AGENTS name the command only, never a printed name with a control; the tests that pin the table or the finding use printable names (`test_chain_link_fold.py`, `test_naming_policy.py`, `test_setup_project_name_edges.py` with its trailing space), which `spelled` leaves alone, or `Atl\udcffas` (`test_legs_not_a_list.py`), which `spelled` writes as `\udcff` exactly as stderr's `backslashreplace` did. The files that run the gate or read its output all pass (Runs). `tests/test_naming_gate_spelled.py` (new, 39 rows). The expected spellings are written out by hand (`Atlas\n`, `At\nlas`, `At\x1b[31mlas`, `Atlas\r`, `At\las\n`, `At\u202elas`; the joiner's name prints as it is), not computed by the library under test. - **On the shipped contract (19 rows):** for each of the issue's three names, the finding is exactly three stderr lines (the finding on one line with the spelled name, then the two lines a plain unclassified name gets), the stdout is exactly one row ` <spelled padded to 32> NO FAMILY`, and `--explain`'s header is ` <spelled>: NO FAMILY` with every later line indented; one run of all three plus a plain `Atlas` gives four rows with their labels in one column and one finding naming all three; a name on stdin (an ESC; stdin strips a line feed) is spelled too; and a control row, a run of five plain names, prints exactly as it did. Two more names pin `spelled` itself (rv1271's NIT 2; the finding row and the table row of each, 4 rows): `Atlas` plus a CR, which `spelled` writes `\r` (a gate that escaped only a line feed and an ESC prints it raw), and `At\las` plus a line feed, written `At\las\n` (a backslash prints as it is; `repr` would double it). Two more pin it against the rivals rv1271b found (its NIT 1; the RLO name in the finding row and the table row, the ZWJ name in the finding row only, 3 rows): `At`, U+202E, `las`, written `At\u202elas` (a bidirectional control no C0 or C1 escaper writes), and `At`, U+200D, `las`, which prints as it is (a format character an `isprintable` test would write). - **On a fork's contract (20 rows):** the shipped contract classifies none of the three names (#1202), so sites 3 to 9 print a name only when a fork's patterns take an ESC and a line feed. The rows read the shipped contract with its 12 character classes `[A-Za-z0-9]` widened (`loosened()` asserts the count), and ask: the listing row and the header of a classified name (3 + 3 rows), the claim note's derived referents (3), the stderr WARNING (2), `--explain`'s WARNING and OVERLAP (2 + 2), the role finding (3) and the broken-chain finding (2). A descendant name ends in its product, which the direct pin reads, so the control is in its stem there (inner line feed and ESC); the other rows ask all three names. ### Failing on the base The base is `09697e9c5`'s script (sha256 `fc5c9f81c953`), swapped into `n1267-mut` (detached at the round's head `76e99ec86`, so the new file and the updated rows are the branch's) and restored with a sha256 check (`9cc39a2eafb0`, the branch's script; `logs/swap-base.log`, `logs/swap-base.out`): - `tests/test_naming_gate_spelled.py`: **37 failed, 2 passed**. The two that pass are the plain-name control (it must: nothing about a plain name changed) and the ZWJ finding row, which passes on the base and on main by design (`spelled` leaves U+200D as it is, so their raw print is already the text it expects; the row kills the `isprintable` rival y4, see "The round after rv1271b's delta check"). The 37 fail because the base prints the raw bytes: the finding row ends its first line at the line feed (`assert ['FINDING naming-unclassified: Atlas', '', ...] == [...]`), the table row is `' Atlas\n ... NO FAMILY\n'` where `' Atlas\\n ... NO FAMILY\n'` is expected, the ESC rows carry the byte, `--explain`'s header is `' Atlas'` and the stderr WARNING and the broken-chain finding carry the raw name twice. The 4 rows of the first round fail the same way: the base prints the CR raw in the row and the finding, and splits the backslash name's row and finding at its line feed (`logs/new-names.log`). The 2 RLO rows fail because the base prints U+202E raw. - `tests/test_naming_policy_line_break.py`: the 6 updated rows fail (**6 failed** in this run; the file's other 71 rows passed when it was run whole with the first commit, and neither the file nor the base has changed), because the base prints `Atlas` where the spelled `Atlas\n` is now asserted. - **On main, what each row does:** the new file is **37 failed, 2 passed** at main `438cfad53` (fetched at 04:21Z on 2026-11-10, still without #1266, with main's `repo_shape.py` swapped in as well; `logs/swap-main.log`, `logs/swap-main.out`; main's two files are the same at `30272026e`, main when this body was written), the same 37 as on the base, and the same two pass. Rows for the two names without a trailing line feed fail as above. Every `ends-in-lf` row fails first on the exit code (`assert 0 == 1`): main classifies `Atlas` plus a line feed as `Atlas`, exit 0, with its row split in two. `tests/test_naming_policy_line_break.py` is **40 failed, 37 passed** on main (measured at `9729315f1` with the first commit; it is #1266's file and has not changed since), the number #1266's own body gives. The PR needs #1266's change, and has it: #1266 landed as `28be96557` and the PR was restacked onto main. Each applied alone to the branch's `scripts/validate-repository-naming.py` in `n1267-mut` (detached at the round's head `76e99ec86`), run against `tests/test_naming_gate_spelled.py` (39 rows) and the 6 updated rows of `tests/test_naming_policy_line_break.py` (45 tests). The counts are re-measured at this head: the round's three ids add to M1, M1w, M10, M11, X1, X2, X3, X4 and X8, and nothing else moves. Each is restored with a sha256 check (`restored ... sha256 ok` after each, and the script's bytes equal the branch's at the end, porcelain 0; `logs/mutants-core.out`): | Mutant | Rows failing | Killed by | |---|---|---| | M1: site 1 prints the raw name (`{name:<32}`) | 16 | the 6 table rows, the 4-row run, stdin, the 3 classified-listing rows, the stderr WARNING (2), the role finding (3) | | M1w: site 1 spells the name but pads by the RAW name's length (`32 - len(name)`) | 16 | the same 16: the escape is longer than the byte it writes, so every label stands 1 or 3 columns off (5 for the RLO name) | | M2: site 2 raw | 3 | the 3 `--explain` NO FAMILY rows | | M3: site 3 raw | 7 | the 3 classified-header rows, and 4 more that read the header line (2 claim rows, 2 `--explain` WARNING rows) | | M4: site 4 raw (the claim note's referents) | 3 | the 3 claim rows | | M5: site 5 raw (`--explain` WARNING) | 2 | the 2 `--explain` WARNING rows | | M6: site 6 raw (`--explain` OVERLAP) | 4 | the 2 OVERLAP rows and the claim rows whose OVERLAP line splits (inner line feed, trailing line feed) | | M7: site 7 raw (stderr WARNING) | 2 | the 2 stderr WARNING rows | | M7b: site 7 spells the name only, not the sentence | 2 | the same 2 (the sentence says the name again) | | M8: site 8 raw (role finding) | 3 | the 3 role-finding rows | | M9: site 9 raw (broken-chain finding) | 2 | the 2 broken-chain rows | | M9b: site 9 spells the name only | 2 | the same 2 | | M10: site 10 raw (`", ".join(unclassified)`) | 14 | the 6 finding rows (every name with a control: the ZWJ's prints as it is), the 4-row run, stdin, and #1266's 6 updated rows | | M11: `spelled` replaced by `repr` | 45 | all 39 rows of the new file, plain control included, and the 6 updated rows (the quotes and the doubled backslash are not the spelling) | | X1 (rv1271's): `spelled` is a local escaper of a line feed and an ESC only | 4 | the finding row and the table row of `ends-in-cr` (first round) and of `rlo` (this round: U+202E is neither) | | X3 (rv1271's): `spelled` is `repr(str(value))[1:-1]` | 3 | the finding row and the table row of `backslash` (first round), and the finding row of `zwj` (`repr` writes U+200D as an escape and `spelled` does not) | rv1271's other mutants are killed too, with the rows of the rounds: X2 (escape every non-ASCII character; the backslash rows and the ZWJ finding row kill it) 11, X4 (spell the whole `naming-unclassified` block, its line breaks too) 15, X5 (site 9 spells the reason and not the name) 2, X6 (site 7 spells the sentence and not the name) 2, X7 (the claim note spells its first referent only) 3, X8 (pad the raw name, then spell it) 16. X1 and X3 survived every row of the first commit (rv1271's NIT 2); the first round's 4 rows kill them, and the new ids fail them too (X1 4, X3 3, above). rv1271b's y1 to y5 survived every row of the first round; this round's 3 ids kill them (the table in "The round after rv1271b's delta check"). No survivor. Python 3.12.3 (CI runs 3.14; the rows assert lists and lines, nothing that depends on a `repr` or an `ast.dump`), through lane 5's `run10.sh` (gh stub first on PATH, bogus token, empty `GH_CONFIG_DIR`, blank git identity, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`), git-filter-repo 2.47.0 (CI's pin; `git filter-repo --version` prints `a40bce548d2c`), at head `4b1653d8b`, dirty=0. The gate list is the 37 test files that name `validate-repository-naming` or pin its printed forms (`grep -l`), this PR's three, `tests/test_naming_policy.py`, and the hygiene five (`test_path_guard_hygiene`, `test_repo_hygiene`, `test_agents_md_codes`, `test_cli_reference`, `test_pinned_copy_text`): 3,567 tests in 28 runs, none of more than 100 tests beside another (one exception, below). | Run | git | Result | |---|---|---| | the gate list (28 runs) | 2.43.0 | **3,563 passed, 4 skipped, 0 failed** | | the gate list (28 runs) | 2.47.1 (`tools/git247/bin/git`, built from kernel.org's tarball) | **3,560 passed, 7 skipped, 0 failed** (3 more skips than the 2.43.0 leg: `test_adopt_check_throwaway_repos.py:1041`, below) | | `tests/test_naming_gate_spelled.py` alone, before the commit | 2.43.0 | 32 passed | | the naming files, `test_naming_policy`, the hygiene five and `test_chain_link_fold`, on the PREDICTED merge of current main (`51a9c5f10`) and the branch (`merge-tree` and `commit-tree`: an object, no ref) | 2.43.0 | **769 passed** in 92.43s | **The skips, by reason.** rv1271 measured them. The first gate's logs hid them because `run10.sh` passes `-rs` and the gate runs then passed `-rf`, and pytest keeps only the LAST `-r` option, so no reason was printed. On 2.43.0 the 4 skipped are `tests/test_shape_doctor.py:10516` x3 ("the PowerShell line is printed, and powershell.exe exists, only on a Windows host") and `tests/test_adopt_check_throwaway_repos.py:482` x1 ("needs a disk that ignores case: `git init` wrote no `core.ignorecase=true` in the work directory, so that disk keeps case; the rows above write the setting into the throwaway repository by hand instead"). On 2.47.1 the 7 are those 4 and `tests/test_adopt_check_throwaway_repos.py:1041` x3 ("this git's `git clone` takes `init.defaultBranch = a..b`, as 2.55's does: the split clones there"); 2.43.0's clone does not take it, so its rows run. I ran those two files again at the round's head with `-rs` alone, on both gits, and the same lines print (`logs/skips-243.log`, `logs/skips-2471.log`). **A slip in the load rule (first gate):** the 106-test run of the #1236 check below ran while the gate's slot A held a run of more than 100 tests; it passed. **The review round's runs.** Head `4fa9f78a6`, dirty=0, Python 3.12.3, git-filter-repo 2.47.0 (`a40bce548d2c`), through `run10.sh` with its `-rs` and no other `-r`, so a skip would name its reason. The files are the brief's ten: the new file, `test_naming_policy_line_break`, `test_naming_policy`, `test_chain_link_fold`, `test_legs_not_a_list` and the hygiene five (`test_path_guard_hygiene`, `test_repo_hygiene`, `test_agents_md_codes`, `test_cli_reference`, `test_pinned_copy_text`): 885 tests in 8 runs per git, none of more than 100 tests beside another. | Run | git | Result | |---|---|---| | the ten files (8 runs) | 2.43.0 (`/usr/bin/git`) | **885 passed, 0 skipped, 0 failed** | | the ten files (8 runs) | 2.47.1 (`tools/git247/bin/git`) | **885 passed, 0 skipped, 0 failed** | | `tests/test_naming_gate_spelled.py` alone, before the round's commit | 2.43.0 | 36 passed | Logs: `gate/gate-243-*.log`, `gate/gate-2471-*.log`, `gate/slotA.out`, `gate/slotB.out`. The round changes the script's docstring and the new test file only, so the first commit's 37-file gate above stands for the rest. **A slip in the load rule:** the resolved-#1236 check (Merges) ran its 154-test chunk while the skip re-run above held a 152-test file; both passed. **Not run:** the full suite, a real Windows host and macOS. `git merge-tree --write-tree --name-only <other> c56753b` (git 2.43.0) at 2026-11-10T05:01:53Z, against main (fetched fresh at 05:01:28Z and again at 05:01:53Z, before the push: `28be96557` both times; a third fetch at 05:04:12Z, after it: still `28be96557`) and every open PR that touches one of this branch's files (32 open when listed; `live/open-prs.json`, `logs/merge-trees.txt`, `probes/merge-trees.sh`): | Against | Head | Result | |---|---|---| | main | `28be96557` (#1266's squash, main's tip) | **clean**, exit 0, tree `723bb5ad`, the head's own tree: main is an ancestor of the head | | #1284 (lane 5's #1279; `scripts/validate-repository-naming.py`) | `0270d48d7` | clean, tree `2eeb4848` | | #1274 (`scripts/repo_shape.py`) | `136b38d12` | clean, tree `70f33a48` | | #1249 (`scripts/repo_shape.py`) | `3146c3ca5` | clean, tree `e4a8454a` | | #1171 (`scripts/repo_shape.py`) | `ce1dd661f` | clean, tree `52ca1d68` | Of the open PRs only #1284 touches one of the three files this branch changes against main. #1274, #1249 and #1171 touch `scripts/repo_shape.py`, which this branch carried while it was stacked on #1266 and does not carry now; they stay in the table because the earlier Merges listed them. #1266 landed: its head `09697e9c5` is an ancestor of this branch and is no longer a row. **The conflict this body has named all along is resolved in the head, and main no longer conflicts.** #1236 landed on main as `81cbe9c82` (lane 3's #1217 display-width pad) while this PR waited for #1266. The merge of #1266's squash met it at `_print_classification`'s `print` line and nowhere else, and the line was resolved as this body said it would be: `print(f" {_padded(_printed(spelled(name)), 32)} {label}{also}")`, with main's comment kept (see "The restack after #1266's squash"). - **Sites 3 to 9 print a control only under a fork's contract.** The shipped patterns take `[A-Za-z0-9]` and hyphens, so on the shipped data a name with a control is never classified and only sites 1, 2 and 10 print it. The other sites are spelled for the same reason the first three are (one rule: a repository name the gate prints is spelled), and their rows read a fork's contract. No shipped project sees them change. - **Chain links and `--pins` values are printed as given** (the module docstring's standing rule; a manifest's are checked printable, a command line's are the caller's words). So `--explain`'s `CHAIN` line prints a link raw, while the four sentences that quote it (sites 5, 6, 7, 9) are spelled whole and so escape a control in a link too. Not changed here. - **A manifest leg's `role:` prints raw** in `--explain`'s `[ADMITS ...; <role> is declared here and is not admitted]` note (`--role` is a closed choice; a manifest's value is not). It is a role, not a repository name, so it is outside #1267. Found, not fixed. - **A name read from stdin is stripped**, so a trailing line feed reaches the gate only from argv or a manifest; the stdin row asks for an ESC. - A project's own copy of `validate-repository-naming.py` reaches this through `update-shape.py`; it needs `spelled` in its `scripts/repo_shape.py`, which every kept library has. - `shape-doctor.py` and the setup, scaffold and adopt tools run the gate and quote its lines; they print what it prints, so a name with a control is spelled in their rows too. Their tests are in the gate list above. Review: rv1271, lane 5's independent opus reviewer, which wrote none of #1271: READY at `4b1653d8` (issuecomment-6726218829), with no MUST or SHOULD; its NITs 1-5 are fixed in `4fa9f78a`. Then rv1271b, lane 5's independent sonnet reviewer, which wrote none of it: READY at `4fa9f78a` (issuecomment-6726914536); its NITs 1-2 are taken in `76e99ec8`. Then rv1271c, lane 5's independent sonnet reviewer, which wrote none of it: READY at `76e99ec8`, and READY at the restack `2d9250c5` onto main after #1266's squash `28be9655` (issuecomment-6759403546), with no MUST or SHOULD. Its NITs 1-2 are taken in `c56753ba`, comments only: one file, four `#` lines, the syntax tree equal (checked by the coordinator; n1267e, W22). `c56753ba` is H0 of this landing once `tests` is green on it. CI (Brett Heap's ruling 27): H0 is the head c56753b itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 28be965; and the local gate passed on its predicted squash tree e344d88 over main 3bf35b2: the naming_gate_spelled, naming_policy_line_break, adopt_check_throwaway_repos, path_guard_hygiene, pinned_copy_text, no_auto_maintenance, repo_hygiene, agents_md_codes, cli_reference sets, 774 passed, 4 skipped. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green". Closes #1267 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…70a23c) into fix/1279-refusals-shown Step 1 of the three-merge restack of #1284 after #1274 landed as a squash. 52a140c is 136b38d (the head this branch carried) plus the pilot's refresh with main; the merge is clean and changes no file this PR owns. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…flicts resolved to this branch's side Step 2 of the three-merge restack of #1284 after #1274 landed as a squash. #1274's final head H74 is 52a140c and its squash S74 is 0354a7e; their trees are equal (2fb87fe), so main changed nothing in any file after H74. Two files conflict, both files #1274 changed, and every hunk is resolved to this branch's side: - shape-doctor.py (1 hunk): the docstring of `naming_script_refusal`. This branch's text says `policy-wrong-kind` is spelled since #1279 and a root's library never reaches the function; the squash's text says it is named raw. Resolved blob c08b1c8 equals merge 1's. - tests/test_load_yaml_refusals.py (3 hunks): the `wrong-kind` param and the docstring of `test_the_doctor_finds_its_own_policy_in_the_naming_scripts_refusal`, and the body and docstring of `test_the_doctor_still_finds_its_own_policy_named_raw`. Resolved blob da9734c equals merge 1's and the head 0270d48's. scripts/repo_shape.py auto-merged. The tree of this commit equals merge 1's (d57d76c), so the merge changes no line. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|



Goal
The refusals on the line after
load_yamlin each reader, where a file reads as YAML but is not a mapping, or is a policy of another kind, name the file asshown_pathnames a path, as #1274 (#1168) madeload_yaml's three refusals andpolicy-unreadablename it. A U+202E, an ESC or a line feed in a typed--policy,--project,--path-policyor--rootpath no longer reaches the terminal raw from these seven refusals. A printable path prints byte for byte as before.Was stacked on #1274 (
fix/1168-load-yaml-shown, head136b38d12when this PR was made, final head52a140c9e):policy-wrong-kindsits beside thepolicy-unreadableline #1274 changes inscripts/repo_shape.py, and two of #1274's doctor rows change here. #1274 landed on main as a squash,0354a7e70, at 07:54:53Z on 2026-11-10. This PR was restacked onto main by three merges, with no rebase (see The restack after #1274's squash), and its base is main. Milestone v1.2.2 (lane 3's placement).plan-unreadableinadopt-project.pywas left to #1177, the eighth row of #1279's table, and #1177 has landed: it is on main as8bcdb2c60(final head04207d95f, merged 2026-11-09T18:43:10Z), wherePlan.loadraisesf"{_shown(str(path))}: not a mapping". This PR does not touchadopt-project.py. #1284 is now restacked onto main (see The restack after #1274's squash), so all eight sites in #1279's table are spelled. The refusals that still name a path raw (rv1284's item 4) are the follow-up #1288, which this PR does not do.What held at #1274's head (measured)
Probed at
136b38d12with a stubghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=falseandGIT_CONFIG_GLOBAL=/dev/null, over a folder whose name holds U+202E, then an ESC, then a line feed (wd-\nWARNING forged-line: pwned), then a printable name. Each run exits 2.policy-unreadable, the control, says'<D>/wd-pl\u202ean/...': not a mapping. These printed the raw U+202E and the raw ESC, and the line feed ended theREFUSEDline atwd-and began a lineWARNING forged-line: pwned/...:136b38d12policy-wrong-kindvalidate-repository-naming.py --policy(akind: otherfile)scripts/repo_shape.py:2253manifest-unreadablevalidate-repository-naming.py --project(a list)scripts/validate-repository-naming.py:1140bump-leg-manifest-unreadablebump-leg.py --root(project.yamla list)scripts/bump-leg.py:236path-policy-unreadableadopt-project.py plan --path-policy(a list)scripts/path_classify.py:185path-policy-wrong-kindadopt-project.py plan --path-policy(kind: other)scripts/path_classify.py:188plan-unreadableadopt-project.py check --plan(a list)adopt-project.py:6683, left to #1177update-pin-unreadableupdate-shape.py check --root(the pin a list)update-shape.py:841manifest-unreadable(the root's)validate-manifest.py --roottemplates/assembly-root/scripts/validate-manifest.py:1438The two sites #1279 lists as not probed, probed in the same runs:
bump-leg-manifest-unreadableatscripts/bump-leg.py:589(_manifest_unopened_refusal, a root this user cannot search) already saysf"{shown_path(path)}: cannot be read: ...": spelled at the base, for all three names. Left as it is, and pinned by one new row (the SHOULD below): with it put back to a raw{path}, no row failed before.manifest-unreadableattemplates/assembly-root/scripts/validate-pins.py:2148names the literalproject.yamland no path:REFUSED manifest-unreadable: project.yaml: not a mappingunder every name. Left as it is.What changed
scripts/repo_shape.py,NamingPolicy.load:policy-wrong-kindnamesshown_path(path). Same module, no new import.scripts/path_classify.py,PathPolicy.load:path-policy-unreadableandpath-policy-wrong-kindnameshown_path(path), imported fromrepo_shape.path_classify.pyruns from a checkout of the standard beside that checkout's ownrepo_shape.pyand is not copied into a project.scripts/bump-leg.py,Project.open:bump-leg-manifest-unreadablenamesshown_path(path), which the file already imports, as its other site already did.update-shape.py,check_pin:update-pin-unreadablenamesshown_path(pin_path), imported fromrepo_shapeon a line of its own, as_split_keyandBYTE_ORDER_MARKare.shape-doctor.pycallscheck_pinwith<rev>:contracts/shape-pin.yamland discards any refusal, so it reads nothing new.scripts/validate-repository-naming.py,_project_manifest:manifest-unreadablenames_shown(path), the copy ofshown_paththis gate already carries.templates/assembly-root/scripts/validate-manifest.py,main: the root'smanifest-unreadablenames_shown(manifest_path), the copy this gate already carries.shape-doctor.py,naming_script_refusal: the docstring sayspolicy-wrong-kindis spelled now, and named raw only where this checkout's ownrepo_shape.pyis older than its doctor: the naming script the doctor reads is this checkout's and imports this checkout's library, so a root's library never reaches the function. The code is unchanged: it already asks both prefixes (repo_shape.py:load_yaml's three refusals andpolicy-unreadablename the file asshown_pathdoes, and validate-pins.py, family.py and shape-doctor.py read either spelling (#1168) #1274).tests/test_load_yaml_refusals.py: the doctor'snaming_script_refusalrow gains awrong-kindcase, and the row that heldpolicy-wrong-kindraw builds the raw line as a library from before repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, adopt-project.py, update-shape.py and the root's validate-manifest.py: eight unreadable and not-a-mapping refusals still print the path as typed, so a bidi control or an ESC in it reaches the terminal raw (follow-up to #1168) #1279 prints it, so the doctor's raw prefix stays covered. That row's docstring says the library is this checkout's own, older than its doctor, and never a root's.tests/test_mapping_and_kind_refusals_shown.py: the new rows (40). Each run is one oftest_load_yaml_refusals.py'sTOOLS, with the file it builds rewritten to YAML that parses (a list, orkind: other), so the file adds no copy of those runs. One more row overNAMESrunsbump-leg.py --rootover a root at mode 000, throughtest_bump_leg_unsearchable.py'sunsearchable_root, and asserts the exactbump-leg-manifest-unreadableline_manifest_unopened_refusalbuilds (the mode is put back in afinally; the row skips on Windows and as root, as that file's own rows do).No refusal code, remediation or word after the path changes. At the head the same probe prints each of the seven spelled, for example
REFUSED policy-wrong-kind: '<D>/wd-\nWARNING forged-line: pwned/p2/repository-naming.yaml': kind is 'other', expected 'repository-naming-policy', one line, and a printable path as it was.The skew rule.
scripts/repo_shape.py,scripts/validate-repository-naming.pyandtemplates/assembly-root/scripts/validate-manifest.pyare shape copies: every assembly root carries them andupdate-shape.py checkshows each asupstream-changeduntilapplyre-copies it. Neither copied gate imports a new name fromrepo_shape.py: each uses the_shownit already had, so a root that kept an olderrepo_shape.pyruns them as before. The rows below run both beside therepo_shape.pyof51a9c5f10(before #1168), which holds for any name that library lacks; it hasshown_pathalready, so those 8 rows cannot catch a gate that imports it, andtest_pinned_copy_text.py::test_no_pinned_script_imports_what_an_older_library_lacks(in the gate) holds that import out.policy-wrong-kindcomes from the library, so a library from before #1279 names it raw untilrepo_shape.pyis re-copied. The doctor reads either spelling, and a raw one reaches it only where this checkout's ownrepo_shape.pyis older than its doctor: the naming script it reads is this checkout's and imports this checkout's library, so a root's library never reaches it.Duplication. No new copy of
shown_pathis written.validate-manifest.py's_shown(line 467) and the naming gate's (line 156) were there at the base, besidevalidate-pins.py's (line 1518). The copied gates gain only the changed call:validate-manifest.py+2 -1,validate-repository-naming.py+1 -1. The new test file shares no run of 4 or more lines with any other file undertests/but its import header (measured, again at0270d48d7: the longest run is 5 non-blank lines, lines 37-44, againsttest_family_hand_mount_git_rm_lines.pyandtest_adopt_plan_value_order.py).Text that pins these lines. A grep of
tests/,README.md,docs/(the handbook mirrordocs/handbook.htmltoo) andAGENTS.mdfor the seven codes and for: not a mappingandkind is:README.md,docs/cli.md, the handbook andAGENTS.mdhold none of them;docs/release-notes/v1.2.0.mdnames two codes as the record of that release; the files undertests/that name a changed code are all in the gates below (the same seven files at the head and at the predicted merge with main370a23cd2, grepped again for the seven codes as whole words). Two pinned the old spelling, both changed here: the doctor docstring and its raw row.The round after rv1284's review
rv1284 (an independent review, READY at
771667f10; #1284 issuecomment-6752099605) found no MUST, one SHOULD and three NITs. This round is one commit,0270d48d7, on top of771667f10(three files:shape-doctor.py,tests/test_load_yaml_refusals.py,tests/test_mapping_and_kind_refusals_shown.py). It takes the SHOULD and NITs 1 and 2 as rv1284 measured them, refreshes NIT 3 in this body, and changes nothing else. rv1284's item 4 is filed as #1288 and is not part of this round.bump-leg.py:589,_manifest_unopened_refusal, a root this user cannot search): the spelling was right and no row ran it under a path holding U+202E, an ESC or a line feed. One new row overNAMES(4 ids) assertsREFUSED bump-leg-manifest-unreadable: <the manifest, named as repr spells it>: cannot be read: Permission denied. It passes at the base: it pins a spelling and fails nothing first. rv1284's mutant Y6 (the site put back to a raw{path}) survived every file rv1284 ran; it fails this row's U+202E, ESC and line-feed cases (3 of the 55 rows). A control, X4 (the spelling over-quoted asrepr(str(path))), fails its printable case (1 of 55).naming_script_refusal, the docstring of the changed raw row and this body's skew rule said a rawpolicy-wrong-kindcomes from "a root'srepo_shape.py". The function reads only this checkout's own naming script, which imports this checkout's library, so the raw prefix appears only where this checkout'srepo_shape.pyis older than its doctor. A root's library never reaches it. The two docstrings and the skew rule above say so; no code or assert changed (D1 still kills on the raw row).51a9c5f10, which already hasshown_path, so they cannot catch a gate that imports it. The new test file's docstring and the skew rule above credittest_pinned_copy_text.py::test_no_pinned_script_imports_what_an_older_library_lacksfor that import.--planpath incheckandexecute, and the--local-remote-dirpath inexecute'sbare, leg andsplitlines, are named with_shown, as #1154 names--outand--work-dir, so a bidi control, an escape, a line feed or a byte that is not UTF-8 in either no longer reaches the terminal as it was typed (#1152) #1177 landed as8bcdb2c60(final head04207d95f) and main is370a23cd2(the Goal, the predicted merge and Merges below are refreshed). 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 is the follow-up for the refusals that still print a path raw.Rows, and what they do at #1274's head
40 new rows in
tests/test_mapping_and_kind_refusals_shown.py, and 4 new cases (one changed row) intests/test_load_yaml_refusals.py. Run at #1274's head's code: the head's tests in the detached tree, with the base's bytes of all seven changed production files swapped in, each put back with a sha256 check.validate-manifest.pybeside the library of51a9c5f10, 3 names (6)policy-wrong-kindline, U+202E / ESC / line feed / printable (4)policy-wrong-kindline named raw, as an older library prints it (3, changed)bump-leg-manifest-unreadablefrom_manifest_unopened_refusal, U+202E / ESC / line feed / printable (4, new in this round)28 of the 55 rows fail at the base: every row with a name that is not printable, but the doctor's U+202E and ESC
wrong-kindcases, which the doctor's raw prefix found at the base, and the 3 non-printable cases of the new_manifest_unopened_refusalrow, which pins a spelling the base already had. Each row asserts the exactREFUSEDline, exit 2, no U+202E, no ESC and no line that startsWARNING forged-line.Mutants
Each applied alone in the detached tree at
0270d48d7, the 55 rows run, the file put back and its sha256 checked (all matched, the tree clean after each). 13 mutants, 13 killed, none survived. Y6 and X4 are this round's.policy-wrong-kindback to{path}wrong-kindrowmanifest-unreadableback to{path}bump-leg-manifest-unreadableback to{path}path-policy-unreadableback to{path}path-policy-wrong-kindback to{path}update-pin-unreadableback to{pin_path}manifest-unreadableback to{manifest_path}validate-manifest.pybeside the older library (3)bump-leg-manifest-unreadablealwaysrepr, a printable path toomanifest-unreadableasrepr(str(manifest_path))policy-wrong-kindasascii(str(path))wrong-kindrownaming_script_refusalasks the spelled prefix alone (code this PR leaves as it is)_manifest_unopened_refusalback to a raw{path}(rv1284's mutant)_manifest_unopened_refusalasrepr(str(path)), a printable path tooEach M is killed by its own site's rows and by no other site's; Y6 and X4 by the new row alone.
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, Python 3.12.3 (CI runs 3.14; no row asserts text that depends on the version). At most 100 tests per run.The gate list, 13 files, 1723 tests: the new file; every file under
tests/that names a changed code (test_bump_leg_unsearchable,test_doctor_validator_verdicts,test_load_yaml_refusals,test_naming_policy,test_shape_doctor,test_validate_pins_named_and_spelled); the hygiene five (test_path_guard_hygiene,test_repo_hygiene,test_agents_md_codes,test_cli_reference,test_pinned_copy_text) andtest_no_auto_maintenance.0270d48d70270d48d70270d48d70270d48d7370a23cd2(commit56bcd2c01, unreferenced)56bcd2c01Skips, each named:
Merges
git merge-tree --write-tree(git 2.43.0, and the rows for main and the four open PRs also on git 2.47.1, with the same trees) of the head86ab76db4with main, fetched fresh, and with the head of every open PR that touches one of this PR's nine files (and #1274's final head and squash), each head read fresh withgh pr list(read only; 25 open PRs). #1177 is on main as8bcdb2c60, so main's row covers it. Main is0354a7e70, which is #1274's squash, and #1274 is MERGED at its final head52a140c9e. This head contains both, so a merge of either adds nothing:0354a7e70d57d76cf5, this head's own tree (main is an ancestor)52a140c9ed57d76cf5, this head's own tree (an ancestor)0354a7e70fix/1128-shown-default-ignorables)ce1dd661f24f2d53a8fix/1212-overflow-and-long-int-said, based onfix/1114-pin-row-value-read-as)3146c3ca5ab2974fd1l3-1205-unpatterned-values-line-feed)1d7d83b34e377ce87afix/1215-rev-list-dashdash)ce9fbee6e32ceac564The other 20 open PRs touch none of this PR's nine files.
The restack after #1274's squash
#1274 (#1168, this lane's) landed at 07:54:53Z on 2026-11-10 as the squash S74 =
0354a7e70(parent150e4019b). The pilot had refreshed it first, so its final head H74 is52a140c9e(136b38d12, the head this PR's earlier text names, plus a merge of main150e4019b), not136b38d12. GitHub retargeted this PR to main, where it was CONFLICTING at0270d48d7. This round restacks it by three merges, by hand, with no rebase, and changes nothing else: no line of this PR's nine files. H74 and S74 have the same tree (2fb87fe2e), so main changed nothing in any of #1274's files after H74.52a140c9egave8ab9798fc. It was clean. It brought in main's changes up to150e4019b(93 files, 19 of them new; four of this PR's nine files,scripts/repo_shape.py,scripts/validate-repository-naming.py,update-shape.pyandshape-doctor.py, changed only by main's own commits, merged without a conflict) and no line of this PR's own change. The subject of8ab9798fcsays "with main 370a23c" by mistake: H74's second parent, the main it merged, is150e4019b, as stated here (the pushed commit message is not amended).0354a7e70gave86ab76db4, with two conflicted files,shape-doctor.pyandtests/test_load_yaml_refusals.py, both files repo_shape.py:load_yaml's three refusals andpolicy-unreadablename the file asshown_pathdoes, and validate-pins.py, family.py and shape-doctor.py read either spelling (#1168) #1274 itself changed (its five:scripts/family.py,scripts/repo_shape.py,shape-doctor.py,templates/assembly-root/scripts/validate-pins.pyandtests/test_load_yaml_refusals.py). Every hunk was resolved to this branch's side, with a script that refuses any file whose hunk count is not the one expected:shape-doctor.py, 1 hunk, the docstring ofnaming_script_refusal: this branch's text (since repo_shape.py, path_classify.py, bump-leg.py, validate-repository-naming.py, adopt-project.py, update-shape.py and the root's validate-manifest.py: eight unreadable and not-a-mapping refusals still print the path as typed, so a bidi control or an ESC in it reaches the terminal raw (follow-up to #1168) #1279policy-wrong-kindis spelled, and a root's library never reaches the function, rv1284's NIT 1) over the squash's (it is named raw). The resolved blobc08b1c8e8equals merge 1's.tests/test_load_yaml_refusals.py, 3 hunks: thewrong-kindparam of the doctor row, that row's docstring, and the body and docstring oftest_the_doctor_still_finds_its_own_policy_named_raw, this branch's text over the squash's. The resolved blobda9734cb3equals merge 1's and0270d48d7's.scripts/repo_shape.pyauto-merged. The tree of the merge commit equals merge 1's (d57d76cf5), so merge 2 changes no line.0354a7e70, fetched fresh just before this step and again before the push, is S74 itself and an ancestor of the head:Already up to date, no commit.86ab76db4:git diff origin/main HEADtouches exactly this PR's nine files, the same list asgit diff --name-only 136b38d12 0270d48d7(+234/-19 in both);-U0changed lines (281 of them, sha256108fae8ba288, hunk headers aside) are identical to those ofgit diff 136b38d12 0270d48d7, the reviewed change, with no difference. The hunk headers differ in four line numbers, each a shift main made above the hunk:scripts/repo_shape.py-2254is-2255(+1,28be96557, scripts/repo_shape.py:NamingPolicyreads a name withfullmatchat four sites, so a name that ends in a line feed matches no family andvalidate-repository-naming.pyrefuses it, andrepository-naming.yamlsays a pattern is read whole (#1202) #1266),scripts/validate-repository-naming.py-1140is-1232(+92:81cbe9c82, siblings.py and validate-repository-naming.py: the layout table, the plain listing and--explain's family rows pad a name by the columns a terminal gives it as stdout writes it, so a wide or combining name's next column starts under its heading (#1216, #1217) #1236, +76, and0aef55b93, validate-repository-naming.py: every name the gate tables or refuses prints throughspelled(), so a name with a line feed or an ESC byte is one row and onenaming-unclassifiedname, with its column worked out from the spelled form (#1267) #1271, +16),update-shape.py-184,0 +185,4is-187,0 +188,4(+3,6fa38d9e5, update-shape.py: a line with U+00A0 or U+3000 in front ofshape:orfiles:is no block header, as it is none to the reader (#1220) #1231) and-841 +845,2is-941 +945,2(+100:6fa38d9e5+3 and150e4019b, update-shape.py:prepareasks the shape pin'ssource_repository:once, with--upstreampassed too, and refuses a path or a value that is noowner/repoin the pin's name; shape-doctor.py answers as it did (#1203) #1269, +97);git merge-tree --write-tree origin/main HEADgivesd57d76cf5, the head's own tree;scripts/path_classify.py,scripts/bump-leg.pyandtemplates/assembly-root/scripts/validate-manifest.pyare byte-equal to0270d48d7's (sha256a5d5b10af9fd,6ad68db2018d,8a11b3321b78);scripts/repo_shape.py,scripts/validate-repository-naming.py,update-shape.pyandshape-doctor.pydiffer (nowb3c75e384f46,b26b75a2b40e,cb603ab32b3e,81aff0a1e353), by the main commits above and nothing else. Both test files are byte-equal to0270d48d7's.0270d48d7's (above) are ones the mutants edit. A detached tree at86ab76db4, the 55 rows, git 2.43.0,-rsalone, run namesn1279d-r1-...; each mutant applied alone and its file put back, the sha256 checked (all matched, the tree clean after each). The base swap writes main's bytes (0354a7e70) of the seven production files over the head's and keeps the head's tests: 28 of the 55 rows fail and 27 pass, the same 28 as the Rows section's table (21 site rows, 6 older-library rows and the doctor's line-feedwrong-kindcase). 13 mutants (M1-M7, X1-X3, D1, Y6, X4), 13 killed, none survived, each by exactly the rows the Mutants section names: M1 4, M2 6, M3, M4, M5 and M6 3 each, M7 6, X1 1, X2 2, X3 2, D1 2, Y6 3, X4 1.86ab76db4, the tree clean, on git 2.43.0 and on git 2.47.1 (built from kernel.org's tarball), each leg in 21 calls of at most 96 ids. It ran 2011 ids: the 1723 of the Runs section's list, collected again at this head (0 ids differ in the 13 files), and 288 more from four files main changed since136b38d12that nameshown_path(tests/test_validate_pins_spelled_and_named.py,tests/test_adopt_spelled_names.py,tests/test_bump_leg_branch_and_push_line.pyandtests/test_validate_family_unreadable.py; none names a code this PR changes). It used CPython 3.12.3, pytest 9.1.1 and git-filter-repo 2.47.0 (CI's pin,a40bce548d2c), in the probe environment,-rsalone, one basetemp per call, two calls at once. Each leg gave 2006 passed and 5 skipped. The skips, each named:tests/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_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_no_auto_maintenance.py:222skips are the probe environment's (GIT_CONFIG_GLOBAL=/dev/null); the 3tests/test_shape_doctor.pyskips are Windows-only. None is a row of this PR.tests/test_mapping_and_kind_refusals_shown.py(40),tests/test_load_yaml_refusals.py(56)tests/test_load_yaml_refusals.py(60),tests/test_bump_leg_unsearchable.py(32),tests/test_doctor_validator_verdicts.py(4)tests/test_doctor_validator_verdicts.py(35),tests/test_naming_policy.py(61)tests/test_naming_policy.py(96)tests/test_naming_policy.py(96)tests/test_naming_policy.py(11),tests/test_shape_doctor.py(85)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(96)tests/test_shape_doctor.py(76),tests/test_validate_pins_named_and_spelled.py(20)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(96)tests/test_validate_pins_named_and_spelled.py(36),tests/test_validate_pins_spelled_and_named.py(60)tests/test_validate_pins_spelled_and_named.py(56),tests/test_adopt_spelled_names.py(40)tests/test_adopt_spelled_names.py(4),tests/test_bump_leg_branch_and_push_line.py(44),tests/test_validate_family_unreadable.py(48)tests/test_validate_family_unreadable.py(36),tests/test_agents_md_codes.py(47),tests/test_cli_reference.py(13)tests/test_cli_reference.py(10),tests/test_path_guard_hygiene.py(86)tests/test_path_guard_hygiene.py(35),tests/test_pinned_copy_text.py(61)tests/test_pinned_copy_text.py(26),tests/test_repo_hygiene.py(70)tests/test_repo_hygiene.py(31),tests/test_no_auto_maintenance.py(60)Closes #1279
Lane: openreposhape-5 (openRepoShape-5)
🤖 Generated with Claude Code
Summary by Sourcery
Standardize safe path rendering across mapping and policy-kind refusals while adding regression coverage for unsafe path names.
Bug Fixes:
Enhancements:
Tests: