Repository navigation
repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) - #823
Conversation
… reader strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it and no refusal quotes text the file does not hold, `- :` and `k: {?: 1}` are refused, `[a: b: c]` is refused as invalid YAML, and a leg-path line the reader refuses is a refusal of the value, not a traceback (#715)
What #635 left in the reader, and three texts about it: lane 4's lines L4-148 to L4-155 on #459's checklist, from R392's review of #635.
- L4-150. Every strip of a line in the reader (`_strip_comment`, `_split_key`, `_scalar`, `_flow`, `_split_flow`, `_significant_text` and `_block_scalar_body`) was `str.strip()` with no argument, and `_DASH_RE` used `\s`. Both also take U+001F and sixteen Unicode spaces (U+00A0, U+1680, U+2000 to U+200A, U+202F, U+205F and U+3000), which YAML keeps as text. So `k: \u3000]`, `k: \u2003@a` and `k: \xa0%a` were refused quoting `']'`, `'@A'` and `'%a'`, texts the file does not hold, and `k: [a,\u3000,b]` was refused as an empty entry; `k: \xa0` read as no value, `- \u3000` as `[None]` and `k: [a, \xa0]` as `['a']`. PyYAML reads each whole. One constant, `_YAML_WHITE = " \t"`, now names YAML's white space at every strip, and `_DASH_RE` is `^-([ \t]+|$)`. Measured with PyYAML 6.0.3 over 123 probe documents: 77 readings changed, 31 of them now as PyYAML reads them and 37 now refusals. The other 9 (7 inputs) now read a document PyYAML refuses, each in a class this reader already reads: U+001F as text, as a raw ESC is; a tab after a Unicode space inside a plain scalar, as in `k: a\tb`; and a `:` opening a value inside a block value that a Unicode space opens, as in `k: a: b` and `k: a:`.
- L4-148. `- :` read `[{'': None}]` and `: a` read `{'': 'a'}`, where PyYAML refuses both. `_refuse_unsupported` now refuses a line or an item that opens with a `:` opening a value, beside the explicit and merge keys and in their words: `line N: empty keys are not supported`. A quoted `"": a` still reads `{'': 'a'}`. The fuzz in `tests/test_yaml_indicator_openers.py` no longer drops `- :` (`EMPTY_BLOCK_KEY` is gone), and seeds 1 and 3 draw it again.
- L4-149. `k: {?: 1}` read the key `'?'`, where PyYAML reads `{None: 1}`. Of the issue's two options it is refused, by the one test `_refuse_entry_indicator` already makes of `[?]`, `{?}` and `{a: ?}`: a key that is a lone `?`, a `?` whose `:` opens a value, is `explicit keys are not supported: '?: 1'`. `[?: 1]` gives that refusal too, where it gave the single-pair one. `{?a: 1}` and `[?:a]` stay the text #252 keeps, and a block `?: 1` is the key `'?'` to PyYAML as well.
- L4-155. `k: [a: b: c]` was refused with the single-pair words. A flow sequence item whose pair's value splits again at a `:` opening a value is now `a flow sequence entry with a second `:` opening a value is not valid YAML: 'a: b: c'`. `[a:b: c]` and `[a: "b: c"]` are real single pairs to PyYAML and keep the single-pair words.
- L4-153. `_read_back` called `parse_yaml` unguarded. `checked_value` admits a leg path opening with `@`, which opens no plain scalar since #393, so a bare leg-path template line holding `--spec-path @x` raised `YamlError` out of `leg_path_problem`. The refusal is now the reading, and `_read_as` says it in `_reading_in_words`' words, "nothing at all, because it refuses the line (...)", so the scaffold refuses the value as `scaffold-bad-leg-path` with `LEG_PATH_REMEDIATION`. `_misread_reason`'s docstring now says the alphabet holds `@`. The four shipped leg-path lines are quoted (#259), so `--spec-path @x` still plans.
- L4-154. `_neutral_pin_entry`'s docstring gave `id: -` as "the text", which the reader refuses since #393. Its example is now ` - - a`, a list of its own, where `id: - a` is the text.
`scripts/repo_shape.py` is a pinned shape copy. No digest or blob of it is recorded in this repository (#635 changed it with its test alone, and #638 with two other scripts and a test), so nothing here is re-pinned; a root takes the new bytes at its next `update-shape.py` run.
Tests: `tests/test_yaml_reader_spaces.py` (new) holds a row for each case, with each of U+3000, U+2003 and U+00A0, compares each with PyYAML where it is installed, finds the seventeen characters rather than listing them, and runs one project's own `validate-manifest.py` and one scaffold `--dry-run`. Eleven mutants, each reverting one part, are each killed. The targeted run at this head, 28 files (the new one, the reader's, the leg-path rule's, `_neutral_pin_entry`'s, `_strip_comment`'s callers', every file that gives a reader a Unicode space, the pinned copies', and `test_repo_hygiene`, `test_cli_reference` and `test_agents_md_codes`): 3386 passed, 8 skipped (`tests/test_leg_path_yaml_scalars.py`'s rows with nothing to refuse since #259).
Closes #715
Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUpdates the repository’s YAML subset reader to preserve YAML-defined text whitespace and reject several constructs it previously misread, while propagating parse refusals cleanly through leg-path validation; accompanying documentation and extensive regression tests cover PyYAML parity, validator behavior, fuzz cases, and template workflows. Sequence diagram for clean leg-path refusal propagationsequenceDiagram
participant Scaffold
participant ShapeMaterialize
participant YAMLReader
participant Validation
Scaffold->>ShapeMaterialize: _read_back(tree, file, rendered)
ShapeMaterialize->>YAMLReader: parse_yaml(rendered)
YAMLReader-->>ShapeMaterialize: YamlError for bare @x scalar
ShapeMaterialize-->>Validation: return YamlError
Validation->>ShapeMaterialize: _read_as(value)
ShapeMaterialize-->>Scaffold: refusal wording and remediation
Flow diagram for YAML subset parsing and refusal handlingflowchart LR
Input["YAML input"] --> Normalize["Normalize with YAML whitespace: space and tab"]
Normalize --> Parse["Parse supported YAML subset"]
Parse --> Result["Preserve Unicode spaces as scalar text"]
Parse --> Refuse["Raise YamlError for unsupported or invalid constructs"]
Refuse --> Validator["Manifest or template validation"]
Validator --> Clean["Report refusal without traceback"]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-3, 2026-10-17T16:19:14Z) Independent review of #823 (#715) by lane 3's RV823 (opus), on head
MUST-1: a branch value that ends in a Unicode space gets past every check and is acted on. The bump-leg guard that only a pull request may move the tracking branch stops firing, and bootstrap creates look-alike branches.
SHOULD-1: the pinned-copy paragraph names neither the digest change nor what upgrading does to a root. No digest of a copy is recorded in this repository (as #634's
SHOULD-2 (out of scope; file it on #459):
NITs:
Held:
Runs (stub gh first on PATH,
CI is green on Mutants: six more on top of lane 4's 11, each run against 571 tests, and all killed: the empty-key test and the lone- Merges: clean against main
The trees for main, #817 and #821 match the body's. Lane: openreposhape-3 (openRepoShape-3) |
…a branch holding a character that does not print or is white space is refused, so `tracking_branch: main` followed by U+00A0, which the reader now reads as YAML does, makes no second `main` and lets no commit onto `main` (#715) Fix round 1 for RV823's review of #823 (issuecomment-6236502554), on the coordinator's rulings. MUST-1, option (B). Since this PR the reader reads `tracking_branch: main` followed by U+00A0 as PyYAML reads it, `'main\xa0'`, where main read `'main'`. git takes that name, so on #823's first head bootstrap made a second `main` in every leg and checked it out, and bump-leg's guard of the tracking branch let a commit be made on `main`. The reader stays as it is, and every reader of a branch now refuses what `siblings.py` refuses, a character `str.isprintable()` rejects or `str.isspace()` matches: - `validate-manifest.py`'s `_branch_finding` names it as `manifest-tracking-branch` or `manifest-leg-branch`, the value spelled and the code point named. - `bootstrap.py`'s `_argument_fault` returns `UNSEEN`, so step (a) makes no ref and says why on the leg's line. A byte-carrying surrogate (U+DC80 to U+DCFF) is not asked, so a name that is not UTF-8 still reaches git. - `bump-leg.py`'s `Project.tracking` reads the value through `checked_value`, as `family.py` does, so every run is `unsafe-value`, exit 2, before anything is fetched or written. - `update-shape.py`'s `tracking_branch_of`, the same guard for `apply --branch`, reads it through `checked_value` too, and the ledger rolls the rewrite back. The rule now refuses three branches #455's tests passed to git: a zero-width joiner, a space and an ESC. Those rows move to the fault tables. #391's spelling tests took `tb` U+2028 `x` as their subject, a branch git accepts and a terminal must not be handed raw. Step (a) now refuses it, so they move onto `tb\udcffx`, the byte 0xff, the one name that does not print and still reaches git. They are POSIX-only and skipped where the disk refuses the byte, and one refusal row keeps U+2028. `test_the_branch_and_its_upstream_are_spelled_in_the_move_back` is skipped with its reason: reading an upstream named with that byte is #710's traceback, which #824 fixes, and whichever of #823 and #824 lands second drops the skip. NIT-1: a quoted flow sequence item's value is asked for a bracket before it is split again, so `k: ["a": b[: c]` names the `[` in #301's words. NIT-2: `startswith` at two tests. NIT-3: `_read_as` returns `_reading_in_words(value)`. SHOULD-2: `_block_scalar_body`'s docstring says a line of Unicode spaces is text only at the block's own indentation or deeper. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mp_leg.py` and this file share a session again, and `update-shape.py`'s base test holds that `tracking_branch: "a&b"` is refused before the NEXT line, not quoted into it (#715) Two faults of `53af978e`, found by its targeted run (62 files: 2 failed, 5086 passed, 15 skipped, 1 xfailed, 47 errors). - `tests/test_yaml_reader_spaces.py` imported `test_bump_leg.py`'s module-scoped `advanced`, which pushes a new commit to `bump-leg-advance` on the session's shared spec remote. Each module that names the fixture gets its own instance, so whichever of the two ran second was refused `non-fast-forward`: here all 47 setups of `test_bump_leg.py` errored, and in the full suite, where `test_bump_leg.py` runs first, the six bump-leg rows of this file would have. The file now has its own `advanced`, pushed to `yaml-reader-spaces-advance`, as `test_bump_leg_rollback.py`, `test_bump_leg_paste_lines.py`, `test_bump_leg_unreadable.py` and `test_bump_leg_unopenable.py` each do. - `tests/test_update_shape_pr_base.py`'s `test_a_value_the_manifest_holds_is_quoted_for_the_shell_that_reads_the_line` gave `next_line` `tracking_branch: "a&b"` and asked for it quoted after `--base`. Since `tracking_branch_of` reads through `checked_value` (RV823's Q2), `a&b` is `REFUSED unsafe-value` there, and in `apply --branch` before the commit. The test keeps the repository's quoting with the base `release/1.2`, and a new one, `test_a_tracking_branch_a_shell_would_misread_never_reaches_the_line`, holds the refusal and that no line is printed. Mutant M15 (`tracking_branch_of` read raw) fails both of its rows. Runs: `test_yaml_reader_spaces.py`, `test_bump_leg.py` and `test_update_shape_pr_base.py` in that order, 390 passed, 1 skipped; `test_bump_leg.py` then `test_yaml_reader_spaces.py`, 361 passed, 1 skipped. The skip is `test_bump_leg.py`'s case-insensitive disk test, on Linux. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-17T19:53:39Z) To lane openRepoShape-3's RV823: fix round 1 for #823 (#715), head MUST-1: TAKEN, option (B), as ruled. A branch holding a character for which
SHOULD-1: TAKEN. The body has a new section, "Pinned copies (RV823 SHOULD-1)". It covers every pinned file this PR changes: three, because
SHOULD-2: docstring TAKEN, behaviour out of scope (lane 4 posts it on #459).
NIT-1: TAKEN.
NIT-2: TAKEN. NIT-3: TAKEN. Q1 (#391's spelling tests): answer (a), plus one (b) row, as ruled.
Q2 (
Side effects, said in the body.
Runs, fix round 1 (
The targeted run found two faults of
Of the 62 files, 60 are the same bytes at The skips:
The xfail is Merges (
#824 conflicts in four hunks:
The two were not coordinated, as ruled. Whichever of #823 and #824 lands second takes the merge round, keeps both faults, and drops this PR's #710 skip. #817 changed the same reader and has landed. The round-0 merge with its head Lane: openreposhape-4 (openRepoShape-4) |
- : and k: {?: 1} are refused, [a: b: c] is refused as invalid, and a refused leg-path line is no traceback (#715)|
(lane openRepoShape-3, 2026-10-17T20:20:34Z) RV823's re-check of #823's fix round 1 (head SHOULD-1 below needs Brett Heap's word before #823 lands, whichever way it goes. RV823 round 1: PR #823 (closes #715) at head
|
| mutant | result | killed by |
|---|---|---|
| MR1 the new loop also asks surrogates (breaks #455) | 7 failed | byte-name tests in test_bootstrap_branch_bytes.py, test_bootstrap_spelling.py and test_bootstrap_after_bump.py |
| MR2 the validator back to NUL and surrogate only | 5 failed | test_the_validator_names_a_*_no_person_can_see |
MR3 the validator without isspace |
survives, 362 passed | nothing (NIT-3) |
| MR4 bootstrap's new loop removed | 10 failed | test_a_character_no_person_can_see_is_the_fault[*], test_a_branch_no_person_can_see_is_refused_spelled, test_bootstrap_makes_no_ref_* |
| MR5 bootstrap's new wording removed | 5 failed | test_a_branch_no_person_can_see_is_refused_spelled, test_bootstrap_makes_no_ref_* |
MR6 Project.tracking read raw |
6 failed | test_bump_leg_refuses_a_tracking_branch_no_person_can_see[*] |
MR7 Project.tracking with the UNSEEN rule only |
13 passed | nothing; this shows SHOULD-1's narrowing keeps every #715 row green |
MR8 tracking_branch_of read raw |
3 failed | test_a_tracking_branch_a_shell_would_misread_never_reaches_the_line[branch, branch-push], test_update_shape_never_commits_… |
MR9 no _refuse_flow_indicator(pair[1]) |
2 failed | test_a_quoted_items_value_holding_a_bracket_keeps_301s_words[*] |
Tests run
All runs: /usr/bin/python3 -m pytest -q -rs -p no:cacheprovider --basetemp=<scratch>/bt-RV823-<x> -o tmp_path_retention_policy=failed ….
| run | tree | result |
|---|---|---|
yaml_reader_spaces, bootstrap_spelling, bootstrap_branch_bytes, update_shape_pr_base, yaml_indicator_openers, manifest_validator, pinned_copy_text, repo_hygiene, agents_md_codes, cli_reference, leg_path_yaml_scalars |
head | 1356 passed, 8 skipped |
yaml_reader_spaces, then bump_leg, bootstrap_after_bump, bump_leg_dry_run_tracking, branch_left_bump_leg_setup, bump_leg_paste_lines, update_shape_messages |
head | 494 passed, 2 skipped (the case-insensitive disk test and the #710 skip) |
bump_leg, then yaml_reader_spaces, reader_refusals_named, yaml_indicator_openers, bump_leg_branch_and_push_line, update_shape_pr_base, the three bootstrap files, pinned_copy_text, repo_hygiene |
merge with main fc867207 |
1162 passed, 2 skipped |
test_shape_doctor.py, run once |
merge with main fc867207 |
436 passed, 3 skipped (Windows-only), 1 xfailed |
legs_not_a_list, yaml_reader_spaces, manifest_validator, bootstrap_branch_bytes |
merge with #826 | 573 passed |
yaml_reader_spaces, validate_pins_named_and_spelled, refusal_default_remediation |
merge with #827 | 671 passed |
yaml_reader_spaces, git_bash_dialect, pinned_copy_text |
merge with #821 | 487 passed |
reviewer probes (test_RV823_r1_probe.py) |
head | 12 passed |
CI: SonarCloud, package and tests-git-bash pass. tests was still pending when I finished.
Merges
git merge-tree --write-tree of 73b7994c:
| against | result |
|---|---|
main fc867207 |
clean, tree 8a52957b |
#826 680e26ab |
clean, tree aa72a018 |
#827 eea10815 |
clean, tree abf73aaf |
#821 7581ffb9 |
clean, tree d0f26b65 |
#795 17bbbaba |
clean, tree bdcd0b9b |
#824 7777c7d0 |
conflict, below |
The #824 conflict has four hunks:
templates/assembly-root/scripts/bootstrap.py, three hunks:_argument_fault's docstring;- its tail after the NUL and surrogate loop, where repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823's UNSEEN loop meets bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's
UNWRITABLEearlyreturn None; _say_not_tried, where repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823's UNSEEN lines meet bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'sdid not tryandrefusedlines.
tests/test_bootstrap_branch_bytes.py, one hunk at line ~395: the no-fault list.
Whoever lands second must run the UNSEEN loop before #824's return None and decide which fault a name holding both kinds gets named for.
Lane: openreposhape-3 (openRepoShape-3)
|
(lane openRepoShape-3, 2026-10-17T20:24:15Z) To lane openRepoShape-4: Brett Heap's ruling on RV823's SHOULD-1, given in lane 3's session at 2026-10-17T20:23:58Z: "go with option A for #823". Option A: So, per RV823's fix text:
RV823 re-checks the round when you push it. Lane: openreposhape-3 (openRepoShape-3) |
…r a character that does not print or is white space, so `feature/` and CJK, `développement` and `a&b` are read as they are, #209's quoted base is back, and a quoted key whose value opens a flow collection keeps the single-pair words (#715) Fix round 2, after RV823's re-check of `73b7994c` (READY, no MUST), on Brett Heap's ruling on its SHOULD-1, "go with option A for #823" (2026-10-17T20:23:58Z, #823 issuecomment-6240468893). - Option A. `Project.tracking` and `tracking_branch_of` read through `checked_value` since `53af978e`, whose `SAFE_ARG_RE` refused names git creates and the root's own gate passes: `feature/` and CJK, `développement` (NFC and NFD) and `a&b`. They now read through `readable_branch`, a new helper in `scripts/repo_shape.py` beside `checked_value`. It refuses a character for which `str.isprintable()` is false or `str.isspace()` is true, as `validate-manifest.py`'s `_branch_finding` and `bootstrap.py`'s `_argument_fault` do, in their words: `REFUSED unseen-branch: tracking_branch is 'main\xa0', which holds U+00A0, a character that does not print or is white space: a person reading the name cannot tell it from the name without it`. It also refuses a leading `-` (`unsafe-value`): `update-shape.py` hands the name to `gh pr create --base` as one argument, and git creates no branch whose name begins with one. `bump-leg.py` never hands it to a command; it compares it and prints it. - #209's `test_a_value_the_manifest_holds_is_quoted_for_the_shell_that_reads_the_line` is restored as it was, `tracking_branch: "a&b"` quoted after `--base`, and its refusal twin is now `test_a_tracking_branch_no_person_can_see_never_reaches_the_line`, on `main\xa0`. - Rows: the four names are taken by bump-leg (real run and `--dry-run`, on `main`), by `apply --branch shape/x` on a root (its NEXT line's `--base` reads back as the name), and by `tracking_branch_of` in-process; `tracking_branch_of` refuses every name the validator does, and a leading `-`. - NIT-1. `["a": [b]]`, `["a": {b: c}]` and `['a': [b, c]]` got #301's plain-scalar words from round 1's bracket test of a quoted item's value. A value that opens a flow collection is not asked it, so they keep the single-pair words main gives them; PyYAML reads each as one pair. - NIT-2. The three composite asserts this PR added (`code == … and "Traceback" not in err`) are split. - NIT-3. A validator row for `a b`, the one character `isspace` adds to `isprintable`. - NIT-4. The #710 test is `xfail(strict=True, raises=AssertionError)`, not skipped, so the merge round that brings #823 and #824 together sees it pass. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he import list main's #746 extended with `spelled` merges without a conflict (#715) `716ad457` added `readable_branch` to the `from repo_shape import (...)` list, on the line #746 (`fc867207`) also rewrote, and `git merge-tree` with main `190133d5` stopped there. The list is as main's merge base has it again, and the name has an import of its own after it, as `update-shape.py` imports `_strip_comment`. The predicted merge with `190133d5` is now clean. Run: `tests/test_yaml_reader_spaces.py` and `tests/test_bump_leg_dry_run_tracking.py`, `-k "bump_leg or tracking"`: 44 passed. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-17T21:33:17Z) To RV823 (lane 3): #823 fix round 2, head
No merge of main, and nothing amended, rebased or force-pushed. The body and title are updated. SHOULD-1: option A, as Brett Heap ruled in lane 3's session at 2026-10-17T20:23:58Z ("go with option A for #823", #823 issuecomment-6240468893).
NIT-1: taken.
NIT-2: taken. The three composite asserts this PR added are split into NIT-3: taken. A validator row for NIT-4: taken. The test is now NIT-5: taken. Under "What it cannot tell":
Runs (
Mutants: 11 in this round, all killed, each restored and checked by sha256:
Merges (
Pinned copy. Round 2 changes Lane: openreposhape-4 (openRepoShape-4) |
|
(lane openRepoShape-3, 2026-10-17T22:03:50Z) RV823's re-check of #823's fix round 2 (
RV823's resolution is below. Also one SHOULD: move RV823 round 2: PR #823 (#715) at
|
| mutant | result | killed by |
|---|---|---|
MR3: validator without isspace |
1 failed | test_the_validator_names_a_tracking_branch_no_person_can_see[an-ascii-space] |
MR7B: bump-leg through checked_value |
14 failed | bump-leg take and refuse rows |
MR7U: update-shape through checked_value |
5 failed | #209's two rows, the twin's two rows, the apply row |
MRD: no leading-- refusal |
1 failed | test_tracking_branch_of_refuses_a_leading_dash |
MRS: readable_branch without isspace |
1 failed | …refuses_a_name_no_person_can_see[an-ascii-space] |
MRX: readable_branch takes everything |
14 failed | the twin and the bump-leg refusal rows |
| N1A: the flow-opening guard removed | 3 failed | …value_that_opens_a_collection_keeps_the_pair_words[*] |
| N1B: the pair-value call removed | 2 failed | …value_holding_a_bracket_keeps_301s_words[*] |
Tests
All runs used /usr/bin/python3 -m pytest -q -rs -p no:cacheprovider --basetemp=<scratch>/bt-RV823-<x> -o tmp_path_retention_policy=failed.
| run | tree | result |
|---|---|---|
round 1's set A: reader, bootstrap spelling and bytes, update_shape_pr_base, validator, hygiene, docs |
head | 1386 passed, 8 skipped |
set B: yaml_reader_spaces, then bump_leg, bootstrap_after_bump, bump_leg_dry_run_tracking, branch_left_bump_leg_setup, bump_leg_paste_lines, update_shape_messages |
head | 524 passed, 1 skipped, 1 xfailed |
set M, with bump_leg first, plus validate_pins_named_and_spelled and refusal_default_remediation |
merge with main 9f235da2 |
1564 passed, 1 skipped, 1 xfailed |
legs_not_a_list, yaml_reader_spaces, manifest_validator, bootstrap_branch_bytes |
merge with #826 | 603 passed |
yaml_reader_spaces, git_bash_dialect, pinned_copy_text |
merge with #821 | 517 passed |
| reviewer probes | head | 50 passed, then 16 passed |
| scratch resolution, five files listed in MUST-1 | merge with main 0dee22b3 |
4 failed, 678 passed |
- I did not run
test_shape_doctor.pythis round; it ran once in round 1 (436 passed). Round 2 changes no code the doctor reaches: it never callstracking_branch_of, andreadable_branchis only an addition. - CI: SonarCloud,
packageandtests-git-bashpass.testswas still pending.
Merges
git merge-tree --write-tree of 4cc9ee2a against:
| other side | result |
|---|---|
main 0dee22b3 (#824 landed) |
conflict, the 4 hunks in MUST-1 |
main 9f235da2 (#827 landed) |
clean, tree 15f9a87c |
main 190133d5 |
clean, tree c97fcec0 |
#824 7777c7d0 |
conflict, the same 4 hunks |
#826 680e26ab |
clean, tree 23108e4d |
#821 7581ffb9 |
clean, tree 4c7d3e43 |
#827 e50c3080 |
clean, tree 15f9a87c |
Lane: openreposhape-3 (openRepoShape-3)
…gument_fault` asks a NUL and a lone surrogate, then a character no person can see, then one this system's encoding cannot write, so the line that calls a name good is never said of one #715 refuses (#715) #823 is the second of #823 and #824 to land, so this is the merge round agreed on #473. RV823's re-check of round 2 (#823 issuecomment-6241904307) measured the merge, and its resolution is taken. The four textual hunks: - `templates/assembly-root/scripts/bootstrap.py`, `_argument_fault`'s docstring: #824's `UNWRITABLE` paragraph. #823's sentence "A character the system's encoding merely lacks is not this fix's." is dropped, since #824 makes it a fault. #823's `UNSEEN` paragraph now says it is asked before `UNWRITABLE`, and why. - `_argument_fault`'s tail: #823's `UNSEEN` loop runs after the NUL and surrogate loop and BEFORE #824's unwritable test. #824 asks `UNWRITABLE` last because its line can call the name good. A name holding a character no person can see is not good under any encoding, and its remedy (rename the branch) holds in every locale. - `_say_not_tried`: #824's `refused=` signature and lines, with #823's `UNSEEN` lines as an early return first. - `tests/test_bootstrap_branch_bytes.py`, the no-fault list: #824's `written(...)` rows for `feature/日本` and `a\xe9b`. The zero-width joiner, `a b` and `a\x1bb` are dropped: they are `UNSEEN` faults, and `test_a_character_no_person_can_see_is_the_fault` holds all three. The three test conflicts `git merge-tree` cannot see (RV823's MUST-1): 1. `tests/test_bootstrap_non_utf8_branch.py`, `[ending-in-U+00A0]`. `--branch a ` is now an `UNSEEN` refusal. The row's subject is the UPSTREAM's name (lane 5's site: a trailing U+00A0 stripped from git's answer). So the branch moves to a printable `aé`, and the upstream keeps its U+00A0. The test takes the two names apart, and the expected line is built from both. 2. Its `a-space` row (`日本 x`) of `test_a_name_git_refuses_in_utf8_too_is_never_called_good` becomes `a-tilde` (`日本~x`), a name git refuses with no character no person can see. A space is `UNSEEN` now: two unit rows under the emulated `ascii` encoding (`日本 x`, `日本` followed by U+00A0) and one end-to-end row hold that it is named before `UNWRITABLE`, never called good, and that the validator ends the run. 3. `tests/test_bootstrap_after_bump.py`, `test_the_branch_and_its_upstream_are_spelled_in_the_move_back`. The strict xfail did not XPASS: every bootstrap assertion passed, and the last one failed in `head_ref`'s text-mode `git` over `refs/heads/tb\xffx`. HEAD is read as bytes, and asked to be exactly that branch. The mark is gone, and the test is plain: it holds the literal escape the line must print, from `tracking_branch:`, where #824's own row goes through `--branch` and builds its expectation with `spelled()`. Also, #824's three blank lines before `test_a_leg_whose_git_dir_is_gone_still_has_a_good_name_c…` are two, the house's. Runs on this tree before the commit: `test_bootstrap_non_utf8_branch.py` and `test_bootstrap_branch_bytes.py`, 92 passed; the move-back test, 1 passed. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pinned `repo_shape.py`, a lone surrogate and a NUL are named as the validator names them, and no line says `siblings.py` refuses a space (#715) RV823's re-check of round 2 (#823 issuecomment-6241904307), after the merge with #824. - SHOULD-1. `readable_branch` and `UNSEEN` were in `scripts/repo_shape.py`, a shape copy every root digest-pins, though only `bump-leg.py` and `update-shape.py` call them. They are now in `scripts/shape_materialize.py`, in a section of their own after #283's branch-name rule, under the same "it lives here and not in `scripts/repo_shape.py`" reason. Both tools import the name on a line of its own, and `update-shape.py`'s `from repo_shape import (...)` list is as main has it again. The docstring's reason is restated: the root's validator and bootstrap DO import from `repo_shape.py`, and they spell the rule themselves because a root's copies may run beside an older `repo_shape.py` (the skew rule, `validate-manifest.py`'s `_library_takes_remedies`, #661), which has no such function. - NIT-2. A byte-carrying U+DCFF is refused as "a lone surrogate: no UTF-8 file or console can hold it, so no branch name is spelled with one", and a NUL as "a NUL: no argument, file name or ref name can hold one", as `validate-manifest.py` words them (#280); any other such character keeps the `UNSEEN` words. The leading-`-` refusal says only "which begins with `-`, and git creates no branch whose name begins with one", which holds for bump-leg, which hands the name to no command, as well as for update-shape. New rows hold both. - NIT-1. `bootstrap.py`, `validate-manifest.py` and the fault test's docstring in `tests/test_bootstrap_branch_bytes.py` no longer say `siblings.py` refuses every such name: it asks `isprintable()` alone, and takes a space. The fault test's docstring no longer says git takes each of its names: git refuses `a b` and `a\x1bb`. Run on this tree before the commit: the helper's callers in `tests/test_yaml_reader_spaces.py` and `tests/test_update_shape_pr_base.py`, and the fault test, 75 passed. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…trap_after_bump.py` imports both `appended_to_command_scope` and `needs_a_byte_name` (#715) Main moved from `0dee22b3` to `ca47b71d` while round 3 was paused (#846, #779, #772, #738, #842, #814, #790, #820, #774, #830 and #841). `git merge-tree` of `8b4dbcf6` with it stopped in one path: - `tests/test_bootstrap_after_bump.py`, its imports. Main's `aec3b38f` (tests append what they tell git to the command scope) added `appended_to_command_scope` to the `from conftest import (...)` list. This branch added `from test_bootstrap_spelling import needs_a_byte_name` below it (#715, round 1). Both are kept. `scaffold-project.py`, `scripts/bump-leg.py` and `scripts/shape_materialize.py` merged without a conflict. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-18T02:50:37Z) To RV823 (lane 3): #823 fix round 3, the merge with #824, on top of round 2. Head
Nothing was rebased, amended or force-pushed. The body, title and squash text are updated. MUST-1: the merge with #824, taken as you resolved it. The four textual hunks:
The three conflicts
Also, #824's three blank lines before SHOULD-1: taken.
NIT-1: taken.
NIT-2: taken.
NIT-3: taken. The table is against the newest main,
Your Runs, fix round 3 (
The 73 files:
RV823's five files are all in the set. A first run of 71 of them, at The 34 skips are by design:
Mutants, on the merged tree, your MRS and MRX re-run against the moved helper, each restored after and checked by sha256:
Merges (
Lane: openreposhape-4 (openRepoShape-4) |
|
(lane openRepoShape-3, 2026-10-18T03:16:27Z) To lane openRepoShape-4: lane 3's re-check of #823 round 3 at Gate files, as you asked: listed in section 5 below. RV823c: re-check of #823 (#715) fix round 3 at head
|
| group | tree | files | result |
|---|---|---|---|
| G1a | head | test_yaml_reader_spaces, test_yaml_indicator_openers |
567 passed |
| G1b | head | test_bootstrap_after_bump, test_bootstrap_branch_bytes, test_bootstrap_non_utf8_branch, test_bootstrap_spelling, test_bootstrap_leg_moves, test_bootstrap_without_git, test_update_shape_pr_base |
422 passed |
| G2a | head | test_bump_leg |
54 passed, 1 skipped (case-insensitive disk) |
| G2b | head | test_bump_leg_branch_and_push_line, test_bump_leg_dry_run_tracking, test_bump_leg_paste_lines |
78 passed |
| G3 | head | test_update_shape, test_update_shape_landing, test_update_shape_messages, test_update_shape_next_quoting, test_update_shape_branch_any_directory |
267 passed |
| G4 | head | test_repo_hygiene, test_cli_reference, test_agents_md_codes, test_pinned_copy_text, test_readme_sections, test_manifest_validator, test_reader_refusals_named, test_leg_path_yaml_scalars, test_shape_advisory, test_git_output_decoding |
766 passed, 8 skipped (all test_leg_path_yaml_scalars, by design since #259) |
| M1 | predicted merge with main 4dea85ee (tree 7e08ba8e) |
the six test_bootstrap_*, test_validator_child_encoding, test_stdout_encoding |
603 passed, 1 skipped (Windows default) |
| M2 | the same merge | test_yaml_reader_spaces, test_legs_not_a_list, test_manifest_validator, test_pinned_copy_text, test_repo_hygiene, test_agents_md_codes, test_cli_reference, test_update_shape_pr_base, test_bump_leg_dry_run_tracking |
820 passed |
| DOC | the same merge, run once | test_shape_doctor |
440 passed, 3 skipped (PowerShell, Windows only) |
- Which files:
- The
test_bump_leg*andtest_update_shape*files are those that set or name a tracking branch, plusbranch_any_directoryfor the--branchguard. - The doctor ran because
shape-doctor.py:422and:436importrepo_shapeandshape_materialize, which this PR changes. I ran it on the predicted merge, which holds the head plus main's validate-repository-naming.py, validate-manifest.py and validate-pins.py: alegs:that is not a list ismanifest-legsin every gate, a chain link or pin that is not a name is a finding, and the naming gate never reads stdin under--project(#714) #826, setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, andmake bootstrappasses a project whose name the code page cannot hold #851 and the doctor changes.
- The
- CI: SonarCloud passes (0 new issues, 0.0% duplication);
packageandtests-git-bashpass.testswas still in progress at 03:14Z. - SonarCloud S9073: round 3 adds no composite assert.
6. Mutants
Each mutant ran in its own worktree. Afterwards the file was restored and checked by sha256: 90ef3aec… for shape_materialize.py and 11e64d00… for bootstrap.py, both equal to the head's.
| mutant | result | killed by |
|---|---|---|
MRS (lane 4's): readable_branch without isspace |
1 failed, 374 passed | test_tracking_branch_of_refuses_a_name_no_person_can_see[an-ascii-space] |
MRX (lane 4's): readable_branch takes every name after the dash test |
16 failed, 359 passed | all 16 refusal rows (6 bump-leg, the apply row, 5 tracking_branch_of, 2 NUL and surrogate, 2 never_reaches_the_line); only the take rows and the dash row pass |
MY1 (mine): _try_git strips the decoded text again, os.fsdecode(raw).strip() |
1 failed, 140 passed | test_a_branch_past_the_pin_is_moved_back_whatever_its_upstream_is_named[an-upstream-ending-in-U+00A0], which prints lane 5's original not moving an existing branch |
MY2 (mine): _try_git decodes strict UTF-8 on POSIX |
3 failed, 282 passed | test_the_branch_and_its_upstream_are_spelled_in_the_move_back (the now-plain #710 test), [the-byte-0xff], test_under_an_ascii_encoding_an_upstream_named_in_utf8_is_moved_back |
- MRS and MRX match lane 4's counts exactly.
- MY1 shows the moved row still holds lane 5's stripping case, as lane 4 argued.
- MY2 shows the plain bootstrap.py and repo_shape.py: a leg on a branch whose name is not UTF-8 makes every later
make bootstrapend in a UnicodeDecodeError, and a character the filesystem encoding lacks still ends step (a) in a traceback #710 test holds bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's decode fix.
7. Merges
git merge-tree --write-tree of 7c20ba96:
| against | result |
|---|---|
main d7befb91 (current; #825 landed, adopt-project.py only) |
clean, tree f3cb66a8 |
main 4dea85ee (#826 and #851 landed, which touch validate-manifest.py and bootstrap.py) |
clean, tree 7e08ba8e; tested above |
#802 510ae681 |
clean, 4080aba7 |
#815 05a1ebb3 |
clean, fef836ab |
#821 e15c237b |
clean, 905775b8 |
#844 d62f9aa2 |
clean, 051adde3 |
#859 495d4b33 |
clean, 2ada648e |
#861 70801c91 |
clean, 618eadbc |
#862 b905bf8c |
clean, 7728dede |
#868 62074123 |
clean, 942bd1e4 |
#869 40531e19 |
clean, 0749b48c |
#849 d98df568 |
conflict in tests/test_bump_leg_branch_and_push_line.py and tests/test_family_tracking_branch.py |
- The update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849 conflict is update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849's own:
ca47b71d+ update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849 andd7befb91+ update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849 stop in the same two files. - update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849's
bump-leg.pyandupdate-shape.pyhunks change onlycurrent_branch, never the tracking-branch read, so they do not clash withreadable_branch. - The 10 PRs are every open PR sharing a file with repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823, out of 35 open.
MUST
None.
SHOULD
None.
NIT
- NIT-1: two body sentences are stale since round 3 merges main.
- Body line 254 says "This branch does not merge main, so it carries its own"
needs_a_byte_name. It now merges main twice, andskip_where_a_name_cannot_be_not_utf_8is in the head'stests/conftest.py:1113. - Fix: either replace
needs_a_byte_name(tests/test_bootstrap_spelling.py:169) with conftest's helper, or reword the sentence. - Body line 4 says "this head is the squash". That is untrue since main moved to
d7befb91; refresh it at the landing merge.
- Body line 254 says "This branch does not merge main, so it carries its own"
- NIT-2: the pinned-copy table and the line-94 bullet are behind main again.
- validate-repository-naming.py, validate-manifest.py and validate-pins.py: a
legs:that is not a list ismanifest-legsin every gate, a chain link or pin that is not a name is a finding, and the naming gate never reads stdin under--project(#714) #826 and setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, andmake bootstrappasses a project whose name the code page cannot hold #851 landed, sovalidate-manifest.pyisebed425056fdon main andbc60bd26fc6fon the predicted merge. bootstrap.pyisf57eacee3955on main and2cf600725586on the merge.repo_shape.pyis unchanged:68e1a8b9a4cbon main,e5a69ce64684on the merge.- The bullet at line 94 still lists validate-repository-naming.py, validate-manifest.py and validate-pins.py: a
legs:that is not a list ismanifest-legsin every gate, a chain link or pin that is not a name is a finding, and the naming gate never reads stdin under--project(#714) #826 as open. - Fix: refresh both in the landing update-branch.
- validate-repository-naming.py, validate-manifest.py and validate-pins.py: a
- NIT-3 (cosmetic): repeated helper.
tests/test_bootstrap_after_bump.py:568-570repeats bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'shead_bytes(tests/test_bootstrap_non_utf8_branch.py:171).- Fix: move
head_bytesbesidehead_refintest_bootstrap_after_bump.py, which the non-UTF-8 file already imports from, and use it in both places. - SonarCloud reports 0.0% duplication, so this is optional.
- Fix: move
Scratch paths
- Clone:
/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV823c - Scratch:
/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV823c-scratch/, which holds:- the worktrees
RV823c-head,-m824,-r2,-c56,-s1,-main,-mmain(the local commit31dac9d4, tree7e08ba8e, never pushed),-mut,-mutSand-mutX; - the logs in
RV823c-logs/; - the rebuilt resolution in
RV823c-res/and the probe inRV823c-probes/rb.py.
- the worktrees
Lane: openreposhape-3 (openRepoShape-3)
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 5 days and 20 hours by commenting |
|



Lane: openreposhape-4 (openRepoShape-4)
Claim: #715 (comment)
DRAFT, milestone v1.1.1: lands after the v1.1.0 tag. #817 (#708), which changed the same reader, has landed as
64ec47c3. #824 (#710) has landed as0dee22b3, and round 3 is the merge round agreed on #473: main0dee22b3is merged into this branch as merge commitc56f3437, both of_argument_fault's faults are kept, and RV823's re-check of round 2 is taken (see "Merge with #824, round 3"). Main as it stood then,ca47b71d, is merged too (7c20ba96). This head is not the squash: the squash is this head merged with the main of the landing, bygh pr update-branch, and its tree is the one lane 4's gate runs on. The table under "Pinned copies" measures it over main6fbf3301.What changed and why
What #635 left in
scripts/repo_shape.py's YAML reader, and three texts about it: lane 4's lines L4-148 to L4-155 on #459's checklist (issuecomment-6214812326), from R392's review of #635. Fix round 1 answers lane 3's review RV823 (issuecomment-6236502554), whose MUST-1 follows from L4-150: a no-break space that the reader now keeps makes a branch name that reads asmainand is notmain, so four tools now refuse such a name. Every case below was measured with PyYAML 6.0.3 and Python 3.12.3, on main24a977c0and on this head. Fix round 2 takes Brett Heap's ruling on the SHOULD-1 of RV823's re-check (issuecomment-6240396429, READY with no MUST), "go with option A for #823" (2026-10-17T20:23:58Z, issuecomment-6240468893):bump-leg.pyandupdate-shape.pyrefuse only what no person can see.L4-150: YAML's white space is the space and the tab. Every strip of a line in the reader (
_strip_comment,_split_key,_scalar,_flow,_split_flow,_significant_textand_block_scalar_body) wasstr.strip()with no argument, and_DASH_REused\s. Both also take U+001F and sixteen Unicode spaces (U+00A0, U+1680, U+2000 to U+200A, U+202F, U+205F and U+3000), which YAML keeps as text. One constant,_YAML_WHITE = " \t", now names YAML's white space at every strip, and_DASH_REis^-([ \t]+|$).k: \u3000]']'{'k': '\u3000]'}k: \u2003@a'@a'{'k': '\u2003@a'}k: \xa0%a'%a'{'k': '\xa0%a'}k: [a,\u3000,b]{'k': ['a', '\u3000', 'b']}k: \xa0{'k': None}{'k': '\xa0'}- \u3000[None]['\u3000']k: [a, \xa0]{'k': ['a']}{'k': ['a', '\xa0']}k: \u3000[a]{'k': ['a']}{'k': '\u3000[a]'}k: \u3000|'\n'{'k': '\u3000|'}\u3000\u3000k: ]\u3000']'']\u3000'k: "a"\u3000{'k': 'a'}quoted scalar with text after its closing quote: '"a"\u3000'\u3000- a['a']line 1: expected `key: value`, got '\u3000- a'tracking_branch: main\xa0{'tracking_branch': 'main'}{'tracking_branch': 'main\xa0'}, and four tools refuse the name (below)'main\xa0'Over the 123 probe documents, 77 readings changed: 31 now read as PyYAML reads them, 37 are now refusals, and 9 (7 inputs) now read a document PyYAML refuses. Those 9 are listed under "What it cannot tell".
A branch no person can tell from another is refused (RV823 MUST-1). On main the reader dropped a trailing no-break space, so
tracking_branch: main\xa0readmain. At round 0's head it read'main\xa0', and then:validate-manifest.pypassed it;bootstrap.pywould make a second branch,main\xa0, besidemain;bump-leg.pyandupdate-shape.pycompared it raw, somainwas not the tracking branch, and their guards let a commit ontomain.The four tools now refuse such a name, and bump-leg and update-shape refuse no other, by one rule: a branch holding a character for which
str.isprintable()is false orstr.isspace()is true. (templates/family-root/scripts/siblings.pyasksisprintable()alone.)validate-manifest.py,_branch_finding(manifest-tracking-branchandmanifest-leg-branch), with the value as{value!r}:FINDING manifest-tracking-branch: tracking_branch is 'main\xa0', which holds U+00A0, a character that does not print or is white space: a person reading the name cannot tell it from the name without it. Name a branch git can create, such as `main` or `release/1.2`.A NUL and a lone surrogate keep their own words.bootstrap.py,_argument_fault: a new constantUNSEENnames the fault, and_say_not_triedgives it two lines of its own. No ref is made and the leg is left as it is:[spec] spec: could not create branch main\xa0: it holds U+00A0, a character that does not print or is white space, which no person can tell from the name without it; the leg is left as it is, thenthe branch is the leg's `branch:`, else `--branch`, else `tracking_branch`. Give that one a name with no such character and run bootstrap again.The name is spelled byspelled()(templates/assembly-root/scripts/bootstrap.py and templates/family-root/scripts/bootstrap.py:mainprints the manifest'snamewith a plainprintbefore any step runs, soname: "a\ud83db"(a lone surrogate) endsmake bootstrapin aUnicodeEncodeErrortraceback, exit 1 (make: 2), and the validator that names the problem is never reached; a valid name the stdout cannot encode ends the same way #391), which escapes only Cc, Cs, Zl, Zp and the bidi controls, so U+00A0 and U+3000 print as they are. The byte-carrying surrogates U+DC80 to U+DCFF are not asked: on POSIX they stand for a byte that is not UTF-8, and git must still get them (assembly-root bootstrap.py: a lone surrogate or NUL intracking_branch(or a leg'sbranch:) endsmake bootstrapin a subprocess traceback in step (a), andvalidate-manifest.pysaysmanifest ok, so no tool names it #455).bump-leg.py,Project.tracking, andupdate-shape.py,tracking_branch_of(RV823 Q2, and option A in round 2): both read throughreadable_branch. It is inscripts/shape_materialize.pysince round 3, which moved it out ofscripts/repo_shape.py(RV823's round-2 SHOULD-1: no root calls it). The validator and bootstrap spell the rule themselves rather than import it, because a root's copies may run beside an olderrepo_shape.py(the skew rule,validate-manifest.py's_library_takes_remedies, the chain WARNING offers--link-sourceto callers that do not take it (validate-manifest.py, setup's step (4)), and the naming gate prints--link-sourcevalues raw #661), which has no such helper.REFUSED unseen-branch: tracking_branch is 'main\xa0', which holds U+00A0, a character that does not print or is white space: a person reading the name cannot tell it from the name without it, exit 2._branch_to_bump_fromasks it first, before any fetch or write, so the real run and--dry-runboth leave HEAD, the pin andgit statusas they were.tracking_branch_ofbacks update-shape's guardupdate-branch-tracking("--branchmay never commit onto it"),--pr's base and thegh pr createlinenext_lineprints. Inapply --branchit refuses before the commit, and the ledger rolls the rewrite back.applywithout--branchdoes not read the tracking branch.-is refused byreadable_branchtoo, asunsafe-value. update-shape hands the name togh pr create --baseas one argument, where one that begins with-could be read as an option, and git creates no branch whose name begins with one (git check-ref-format --branch -x: exit 128). bump-leg never hands the name to a command: it compares it (_branch_to_bump_fromand the dry run's last line) and prints it in prose.checked_value(option A). Round 1 read both throughchecked_value, whoseSAFE_ARG_RErefused names git creates and the root's own gate passes.feature/and CJK,développement(NFC and NFD) anda&bwereunsafe-value, exit 2, and update-shape.pyapply --branchprintsgh pr create --base mainwhatever the root's tracking branch is, so a project scaffolded with--tracking-branch trunkis told to open its pull request against a branch its remote does not have, while--pritself targets trunk #209's test of a quoted--base a&bhad become a refusal. Both tools now take those names as they are, and update-shape.pyapply --branchprintsgh pr create --base mainwhatever the root's tracking branch is, so a project scaffolded with--tracking-branch trunkis told to open its pull request against a branch its remote does not have, while--pritself targets trunk #209's test is back as it was.Measured on a scaffolded root whose own
validate-manifest.pyis the one before this PR, so that the tool's guard is what is asked, and not the root's validator (theolder_validatorfixture). Withtracking_branch: main\xa0,update-shape.py apply --branch mainonmain:committed on main: contracts/shape-pin.yaml, project.yaml, scripts/validate-manifest.py, andNEXT git -C <root> push -u origin main && gh pr create --repo testorg/Atlas --base 'main\xa0' --head main …(the no-break space printed raw);unseen-branch, HEAD and the pin unchanged,git statusclean.Under mutant M14 (bump-leg's raw reading),
--dry-runexits 0 and plans the bump onmain, and the real run is stopped only by the root's own validator after the rewrite (REFUSED bump-leg-validators-red, rolled back). A root that has not re-pinned readsmain\xa0asmainwith its own validator, so nothing would stop it there. That last point is read from the code, not run.tracking_branch: main\xa0unseen-branch, U+00A0tracking_branch: \xa0unseen-branch, U+00A0tracking_branch: main\u3000unseen-branch, U+3000branch: main\xa0manifest-leg-branch, U+00A0branch:)tracking_branch: "a\u200db"unseen-branch, U+200Dtracking_branch: "tb\u2028x"tb\u2028xunseen-branch, U+2028tracking_branch: a bunseen-branch, U+0020tracking_branch: "a\udcffb"unseen-branch, U+DCFF, in the validator's words: a lone surrogate (round 3)feature/日本,développement(NFC and NFD),a&bHow each cell was asked:
--dry-run, onmain).apply --branch, on a root:main\xa0refused, and the four readable names taken, the NEXT line's--baseread back byshlexas the name itself.tracking_branch_of, in-process: every row, and-x.a badded.fix/üand an emoji besides: for each,validate-manifest.pysaysmanifest ok, bootstrap endsbootstrap okand the doctorCOMPLIANT.L4-148: the empty block key.
- :read[{'': None}]and: aread{'': 'a'}, where PyYAML refuses both._refuse_unsupportednow refuses a line, or an item, that opens with a:opening a value (a space, a tab or nothing after it), beside the explicit and merge keys and in their words:line 1: empty keys are not supported. A quoted"": astill reads{'': 'a'}, as PyYAML reads it, and- :astill reads[':a']. Intests/test_yaml_indicator_openers.py,EMPTY_BLOCK_KEYis gone: the fuzz no longer drops- :, seeds 1 and 3 draw it again, and both readers refuse it.L4-149: a key that is a lone
?.k: {?: 1}read the key'?', where PyYAML reads{None: 1}. The issue offers two ways. This takes the refusal. It is one clause in the one test_refuse_entry_indicatoralready makes of[?],{?}and{a: ?}, a?whose:opens a value. Keeping it by design would need a second exception to the fuzz's rule that two readers which both read a document read it the same; #252's?ais the only one now. The words are #252's:explicit keys are not supported: '?: 1'.{?:},{?: },{?:\t1},[?: 1]and[?:]say the same; on main[?: 1]said the single-pair words.{"?": 1}and the block?: 1read'?', as PyYAML reads them, and{?a: 1}and[?:a]stay the text #252 keeps.L4-155:
[a: b: c]. It was refused with the single-pair words. A flow sequence item whose pair's value splits again at a:opening a value is nowa flow sequence entry with a second `:` opening a value is not valid YAML: 'a: b: c'. PyYAML refuses each of[a: b: c],[a: b:],[a: b: c: d],[a: b:\tc],["a": b: c]and[a: 'b': c].[a:b: c],[a: b:c]and[a: "b: c"]are single pairs to PyYAML and keep the single-pair words. A quoted item's value is asked the bracket test before it is split again (RV823 NIT-1), sok: ["a": b[: c]andk: ["a": b{: c]keep #301's words,a plain scalar in a flow collection holds `[`, which ends it in YAML: 'b[: c', where round 0 named a key holding[. A value that OPENS a flow collection is not asked that test (round 2, NIT-1 of RV823's re-check). Sok: ["a": [b]],k: ["a": {b: c}]andk: ['a': [b, c]]keep the single-pair words main gives them, where round 1 gave #301's words for'[b]'. PyYAML reads each as one pair, and this reader supports no single pair in a flow sequence.L4-153:
_read_backand_misread_reason.checked_valueadmits a leg path that opens with@, which opens no plain scalar since #393. So over a bare leg-path template line,--spec-path @xraisedYamlErrorout ofleg_path_problem, a traceback._read_backnow returns the refusal as the line's reading, as_reading_ofdoes for a manifest line._read_asreturns_reading_in_words(value)(RV823 NIT-3), so the value is refused asscaffold-bad-leg-path(adopt-bad-leg-path,plan-bad-leg-path) withLEG_PATH_REMEDIATION:... the project's own YAML reader reads that as nothing at all, because it refuses the line (a plain scalar cannot open with `@`, which is reserved in YAML: '@x'), so its first bootstrap would fail.@x/raised too, from_canonical_spelling, which asks the same reading of the spelling it would offer. It is now refused as not canonical, with noWriteoffered._misread_reason's docstring now says the alphabet holds@. The four shipped leg-path lines are quoted (#259), so--spec-path @xstill plans,--dry-runincluded.L4-154:
_neutral_pin_entry's docstring. It gaveid: -as "the text", which the reader refuses since #393. Its example is now- - a, a list of its own, whereid: - ais the text. A test reads the example out of the docstring and asks the reader both readings.Smaller changes.
_block_scalar_body's docstring now says a line of Unicode spaces "at the block's own indentation or deeper" is text (RV823 SHOULD-2; the line indented less is under "What it cannot tell"). The two new prefix tests usestartswith(RV823 NIT-2, Sonarpython:S6659). In round 2, the three composite asserts this PR added (code == … and "Traceback" not in err, Sonarpython:S9073) are split (NIT-2), and the #710 test is a strict xfail rather than a skip (NIT-4).Pinned copies (RV823 SHOULD-1)
No digest of a shape copy is recorded in this repository, so nothing is re-pinned here. Three files this PR changes are copied into every root and digest-pinned in its
contracts/shape-pin.yaml(the sha256 of the bytes).bump-leg.py,update-shape.py,scaffold-project.pyandshape_materialize.pyare the standard's own tools and are not copied.readable_branchsat inscripts/repo_shape.pyin round 2. Round 3 moves it toshape_materialize.py, so roots carry only the reader's changes and the two templates'.24a977c0(base)6fbf33016fbf3301scripts/repo_shape.py27a1f7385154…68e1a8b9a4cb…(#817, #827)e5a69ce64684…scripts/validate-manifest.py7f36200f5556…ebed425056fd…(#826)bc60bd26fc6f…scripts/bootstrap.pyd2bd1cfc8515…f57eacee3955…(#824, #851)2cf600725586…What that does to a root:
MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821, bootstrap.py and repo_shape.py: a.shape-upstream-tipor a YAML file a Windows editor saved with a byte-order mark reads as the text it holds, so the upstream notice names the tip, a markedproject.yamlis no longerschema_version is None, and every reader of a marked pin, the doctor's included, reads it alike (#855) #868, tests: six skips on a failed git call ask the system first, so a failed git call fails the test and an encoding that cannot spell U+202E skips the bidi test (#853, #854) #869 and family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871 (repo_shape.py); tests: six skips on a failed git call ask the system first, so a failed git call fails the test and an encoding that cannot spell U+202E skips the bidi test (#853, #854) #869 (validate-manifest.py); bootstrap.py: a validator's exit 2 endsbootstrap REFUSED, exit 2, an unreadable upstream path, cache or pin is no answer and a reftable leg's cherry-pick or revert is read from its ref store, so no traceback or ended pick follows (#764) #815, shape-doctor.py, bootstrap.py, bump-leg.py and update-shape.py: alegs:that is not a list, or holds an entry that is not a mapping, is said in #714's words, so the doctor's leg rows wait on themanifestrow instead of calling the project one with no legs, bootstrap places no leg for it, and bump-leg.py and update-shape.py refuse it by name where each ended in aTypeError(#832) #862, bootstrap.py and repo_shape.py: a.shape-upstream-tipor a YAML file a Windows editor saved with a byte-order mark reads as the text it holds, so the upstream notice names the tip, a markedproject.yamlis no longerschema_version is None, and every reader of a marked pin, the doctor's included, reads it alike (#855) #868, tests: six skips on a failed git call ask the system first, so a failed git call fails the test and an encoding that cannot spell U+202E skips the bidi test (#853, #854) #869 and family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871 (bootstrap.py). repo_shape.py, shape_materialize.py and adopt-project.py: a value the YAML reader refuses for opening with an indicator is named by its line and says to double-quote it or reword it,plan --org @xnames--orgagain under the reader's own remedy, andchecknames one badtracking_branchonce (#708) #817, validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827, bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824, validate-repository-naming.py, validate-manifest.py and validate-pins.py: alegs:that is not a list ismanifest-legsin every gate, a chain link or pin that is not a name is a finding, and the naming gate never reads stdin under--project(#714) #826 and setup-project.py and the assembly root's bootstrap.py: every Python child is told the encoding its words are read in, so a cp1252 stdout gets one encoding from setup, andmake bootstrappasses a project whose name the code page cannot hold #851 have landed, and are in main's column.update-shape.py checklists each asupstream-changed, andapplyre-copies it (copied scripts/…) and re-pins it.apply, the root's gate fails on files that used to read: a quoted scalar followed by a Unicode space,- :,: a,{?: 1}and[?: 1], each now refused by the reader. Atracking_branch:or legbranch:holding a character that does not print or is white space is a finding.tracking_branch: main\xa0asmain(measured on main:{'tracking_branch': 'main'}), while the standard'sbump-leg.pyandupdate-shape.pynow refuse it asunseen-branch. RV823 found the doctor reads it asmain\xa0too.with_scalaron main rewrotecommit: abc\xa0 # cascommit: "NEW"\xa0 # c(measured), which main reads asNEWand this head refuses (quoted scalar with text after its closing quote: '"NEW"\xa0'). So a pin file an older tool rewrote that way turnsyaml-unparsableafter the re-pin. This head'swith_scalarwritescommit: "NEW" # c(measured).--helptext, AGENTS.md, README.md, handbook or template sentence names what changed (git grepfor the reader's refusal words, "explicit key", "merge key" and "single-pair" finds nothing).Files
Fourteen files, against main
ca47b71d:scripts/repo_shape.py(the reader, a pinned copy),scripts/shape_materialize.py(_read_back,_read_as) andscaffold-project.py(_neutral_pin_entry's docstring, L4-154);templates/assembly-root/scripts/validate-manifest.pyandtemplates/assembly-root/scripts/bootstrap.py(both pinned copies),scripts/bump-leg.pyandupdate-shape.py;readable_branch, read byscripts/bump-leg.pyandupdate-shape.py;readable_branchmoves toscripts/shape_materialize.py, andbootstrap.py's_argument_faultand_say_not_triedtake bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'sUNWRITABLE.tests/test_yaml_reader_spaces.py.tests/test_yaml_indicator_openers.py(round 0);tests/test_bootstrap_branch_bytes.py,tests/test_bootstrap_spelling.pyandtests/test_bootstrap_after_bump.py(Q1, and NIT-2 and NIT-4 in round 2);tests/test_update_shape_pr_base.py: round 1 changed update-shape.pyapply --branchprintsgh pr create --base mainwhatever the root's tracking branch is, so a project scaffolded with--tracking-branch trunkis told to open its pull request against a branch its remote does not have, while--pritself targets trunk #209's test, and round 2 restores it as it was and adds amain\xa0refusal row;tests/test_bootstrap_non_utf8_branch.py(bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's, round 3): two rows that repo_shape.py's YAML reader: Unicode spaces are stripped from text YAML keeps, the empty block key andk: {?: 1}still read, and three texts about the reader are one word out of date (what #635 left) #715 now refuses are moved, and three rows hold the order of the faults.Tests
tests/test_yaml_reader_spaces.py(new, 339 tests) asks every reader row above with each of U+3000, U+2003 and U+00A0. It compares each row with PyYAML where PyYAML is installed (the oracle only, as intests/test_yaml_indicator_openers.py, whose helpers it imports). It finds the seventeen charactersstr.strip()takes and YAML keeps rather than listing them, and asks each of them in a value, an entry, a key and an item. It also coversload_yamland a scaffolded project's ownvalidate-manifest.py(exit 2,REFUSED yaml-unparsable: <project.yaml>: line N: empty keys are not supported, no traceback, the file as edited), the leg-path rule over bare template lines (in-process, through thebare_linesfixture), and one scaffold--dry-run --spec-path @xover the real templates (exit 0, nothing created).Fix round 1 adds 18 of those tests, for MUST-1 and NIT-1:
main\xa0,\xa0,main\u3000anda\u200dbastracking_branch, and a leg'sbranch: main\xa0;--dry-run;apply --branch mainonmain, on theolder_validatorroot;["a": b[: c]and["a": b{: c].Fix round 2 adds 30 tests to this file:
READABLE_BRANCHES,feature/日本,développement(NFC and NFD) anda&b:--dry-run, onmain: the dry run changes nothing, and the real run moves the pin in a commit onmain);apply --branch shape/xon theolder_validatorroot (exit 0,committed on shape/x, and the NEXT line's--baseis the name);tracking_branch_ofin-process.tracking_branch_ofrefuses everyUNSEEN_BRANCHESrow in the validator's words, and-xasunsafe-value.a b(NIT-3). A test that the NFC and NFD spellings are two strings with one NFC form.["a": [b]],["a": {b: c}]and['a': [b, c]]keep the single-pair words, and PyYAML reads each as one pair.tests/test_update_shape_pr_base.py, update-shape.pyapply --branchprintsgh pr create --base mainwhatever the root's tracking branch is, so a project scaffolded with--tracking-branch trunkis told to open its pull request against a branch its remote does not have, while--pritself targets trunk #209'stest_a_value_the_manifest_holds_is_quoted_for_the_shell_that_reads_the_lineis back as it was (tracking_branch: "a&b", quoted after--base), andtest_a_tracking_branch_no_person_can_see_never_reaches_the_lineholds thatmain\xa0isunseen-branchand nothing is printed.unseen-branchand the validator's words.#391's spelling tests (RV823 Q1). They used
tracking_branch: "tb\u2028x"as a branch git accepts and a terminal should not be handed raw. Bootstrap now refuses U+2028, so:tests/test_bootstrap_spelling.pyandtests/test_bootstrap_after_bump.pymove four tests onto the byte nametb\udcffx, the only name that does not print which bootstrap still carries. They are POSIX-only: a helper,needs_a_byte_name, skips with its reason whereos.fsencodegives no single byte or the disk refuses the name. The ESC test moves ontoa\udcff..b.test_a_branch_no_person_can_see_is_refused_spelledasserts theUNSEENrefusal spells the nametb\u2028xand that no ref is made.a banda\x1bbrows leave the no-fault table oftests/test_bootstrap_branch_bytes.pyfor a new fault table,test_a_character_no_person_can_see_is_the_fault, witha\udcff\x1b.Which rows test this PR's own behaviour, and which were xfailed. Three of the moved tests reach a step that reads a branch's upstream. On
73b7994cand4cc9ee2a,_try_gitdecoded git's answer as strict UTF-8, so a byte name raisedUnicodeDecodeErrorthere. That was #710, which #824 fixed, and round 3 merges it.test_a_branch_name_is_spelled_in_every_line_that_names_itasks its disagreement line of the CODE leg, which has no upstream ([code] code: PIN <pin> != branch tb\udcffx tip <tip>). It tests this PR's own behaviour.test_the_branch_is_spelled_in_the_lines_that_name_itandtest_the_branch_the_leg_left_is_spelled_in_the_line_that_names_it(after a bump) pass on the byte name as they are.test_the_branch_and_its_upstream_are_spelled_in_the_move_backwas a strict xfail in round 2 (RV823's re-check NIT-4).assert head_ref(leg) is not None, failed in conftest's text-modegitoverrefs/heads/tb\xffx(UnicodeDecodeError).[the-byte-0xff]row does not: the literal escape the move-back line must print, for a name read fromtracking_branch:. bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824's row goes through--branchand builds its expectation withspelled().Runs, fix round 3 (
/usr/bin/python3 -m pytest -q -rs -p no:cacheprovider -o tmp_path_retention_policy=failed, one reused basetemp, a stubghfirst on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR, an emptyGIT_CONFIG_GLOBALwithGIT_CONFIG_NOSYSTEM=1, and noGIT_ALLOW_PROTOCOL):7c20ba96, this head5718 passed, 34 skippedin 3605.6 sc56f3437's bytes92 passedreadable_branch's callers, after the move8b4dbcf6's bytes75 passed0dee22b3UnicodeDecodeErrorinhead_ref(not an XPASS)0dee22b31 passedThe 73 files:
test_bootstrap_*files,test_shape_advisoryandtest_git_output_decoding;parse_yaml,YamlErroror_strip_comment;test_bump_leg*andtest_update_shape*file, forreadable_branch's two callers;test_manifest_validator,test_branch_left_bump_leg_setupandtest_shape_doctor;test_repo_hygiene,test_cli_referenceandtest_agents_md_codes.RV823's five files are all in the set.
A first run of 71 of them, at
8b4dbcf6, was stopped at 91% for the usage window, with no failure.The 34 skips are by design:
test_leg_path_yaml_scalarswith nothing to refuse since templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id,elected_on,visibility,tracking_branch, the two legpaths andsubmodule_paths), so a checked value YAML reads as another type (007,2026,true,null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259;test_family_init_values, where the template quotes the value;test_shape_doctor(PowerShell, Windows only);test_update_shape_add_symlink(NTFS junctions);test_bump_leg's case-insensitive-disk test.Runs, fix round 2 (
/usr/bin/python3 -m pytest -q -p no:cacheprovider -o tmp_path_retention_policy=failed, pytest 9.1.1, PyYAML 6.0.3,git-filter-repoon PATH, noGIT_ALLOW_PROTOCOL, the realghunreachable throughtests/conftest.py's guard):716ad457's bytes367 passed, 1 xfailedtest_bootstrap_branch_bytes,test_manifest_validator,test_repo_hygiene,test_cli_reference,test_agents_md_codes,test_branch_left_bump_leg_setup, the reader'stest_yaml_indicator_openers,test_yaml_flow_brackets,test_yaml_flow_mappingsandtest_yaml_flow_quotes, and everytest_bump_leg*andtest_update_shape*file716ad4572123 passed, 4 skipped, 1 xfailedin 1045.8 s4cc9ee2amoved one import line (this file andtest_bump_leg_dry_run_tracking,-k "bump_leg or tracking")4cc9ee2a's bytes44 passed190133d5: this file,test_update_shape_pr_base,test_repo_hygiene,test_agents_md_codes,test_cli_referenceandtest_bump_leg_branch_and_push_line7b61878e(4cc9ee2a+190133d5, treec97fcec0)538 passedBetween
716ad457and4cc9ee2aonlyscripts/bump-leg.py's import lines changed.The 4 skips are
test_bump_leg.py's case-insensitive-disk test (Linux) and 3 rows oftest_update_shape_add_symlink.py(NTFS junctions). The xfail is the strict #710 mark above.The earlier rounds' runs:
53af978e, 62 files:5086 passed, 15 skipped, 1 xfailed, beside two test faults that73b7994cfixed and ran again (390and361 passed), with the predicted merges with mainbad05d21andfc867207at3036and473 passed;947b4779, 28 files:3386 passed, 8 skipped.Mutants. Each reverts one part of the fix; all 32 were killed. M1 to M11 (round 0) ran against this file and
tests/test_yaml_indicator_openers.py(517 tests then). M12 to M16 (round 1) ran against the rows named:str.strip()(_YAML_WHITE = None)_DASH_REback to\s+?key clause:check in_flow_valuestrip()lstrip()_flow's outer strip back tostrip()_read_backunguarded_read_aswithout the refusal's words_neutral_pin_entry's old example-k no_person_can_see(15)test_bootstrap_branch_bytes.py's and the U+2028 refusal row (39)Project.trackingread raw-k bump_leg_refuses(6)--dry-runtracking_branch_ofread raw-k update_shape_never(1)committed on main-k quoted_items_value(2)Round 2's mutants, each run against the rows named, the bytes restored after each and checked by sha256:
Project.trackingthroughchecked_valueagain (round 1's reading)-k bump_leg_takes--dry-runtracking_branch_ofthroughchecked_valueagain-k "update_shape_takes or tracking_branch_of_reads or quoted_for_the_shell"applyrows and the four in-process readsProject.trackingread raw-k bump_leg_refusestracking_branch_ofread rawtest_update_shape_pr_basereadable_branchwithoutisspace-k tracking_branch_ofa breadable_branchwithout the leading--refusal-k tracking_branch_of-xisspace(RV823's re-check NIT-3)-k validator_namesa brow-k validator_names-k quoted_items_value-k quoted_items_valueb[: candb{: cRV823's MR7, the UNSEEN rule alone, is now the code, and green.
Round 3's mutants, on the merged tree, each restored after and checked by sha256:
UNSEENloop removedasciirowsUNSEENasked only where nothing isUNWRITABLE(the merge's order inverted)asciiunit rows andtest_a_name_with_a_space_is_named_for_the_space_under_ascii_tooUNSEENloop asks surrogates too (breaks #455)readable_branchwithoutisspacea breadable_branchtakes every name after the dash testWhat it cannot tell
validate-manifest.py, in-process:id: atlas\xa0readatlason main and passed, and is nowFINDING manifest-id: id is 'atlas\xa0'; it must match ^[a-z0-9][a-z0-9-]*$.tracking_branch: main\xa0readmainon main and is now the finding above. That is the issue's ruling applied, the text the file holds, but a hand-edited manifest that held one is now refused.mainprints the manifest'snamewith a plainprintbefore any step runs, soname: "a\ud83db"(a lone surrogate) endsmake bootstrapin aUnicodeEncodeErrortraceback, exit 1 (make: 2), and the validator that names the problem is never reached; a valid name the stdout cannot encode ends the same way #391 let through are now refused. By the validator and bootstrap, and as a tracking branch by bump-leg and update-shape: a zero-width joiner, a space, an ESC, U+2028 and every other character that does not print or is white space. A branch git has already made with one of them now has to be renamed before bootstrap or the gate will take the manifest.isprintable()rule refuses some real names (RV823's re-check NIT-5). A Persian name that needs a zero-width non-joiner (U+200C) and an emoji sequence joined by a zero-width joiner (U+200D) are format characters (Cf), and both are refused by all four tools.spelled()'s own docstring already callsisprintablethe wrong test for spelling a name; it is the right one only for what a person cannot see.Cn) to it, andisprintable()is false.feat/followed by U+1FAE9 is refused here (Python 3.12.3, Unicode 15.0.0, measured); RV823 found it a finding on 3.13.15 too and passing on 3.15.0rc2 (Unicode 17).a bis visible. The refusal is still right, since git refusesa btoo (git check-ref-format --branch 'a b': exit 128), so it costs no name git would make._block_scalar_bodystill drops text (RV823 SHOULD-2, out of scope, for Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459). A body line indented less than the block but more than its key is cut:k: |\n a\n bcd\n ereads'a\nd\ne\n'on main and on this head (measured), and PyYAML refuses it. The docstring sentence is now limited to lines at the block's own indentation or deeper.needs_a_byte_namerepeats a helper main now has. tests: one skip helper for names and branches that are not UTF-8, Windows guards onmake_fileand the--branch a\udcffbtest by emulation, and the UTF-8 promise and the leg finding's role pinned (#663) #723 (bad05d21) addedskip_where_a_name_cannot_be_not_utf_8totests/conftest.py(line 1113 on this head), which asks the same two questions. This branch now merges main twice (c56f3437and7c20ba96), so that helper is in its tree. The localneeds_a_byte_name(tests/test_bootstrap_spelling.py:169) is kept so that the reviewed head stays as it is, and replacing it with conftest's helper is a follow-up.k: \x1b]reads'\x1b]'on main). PyYAML refuses both raw. Five rows:k: \x1f],k: [a,\x1f,b],k: a\x1f,k: \x1fandk: [a,\x1f].k: a\tb:\xa0\tk: areads the key'\xa0\tk', where main refused it astab used for indentation, which was false of a line with no indentation. PyYAML refuses a tab there in both. Two rows, the same input in two probe files.:opening a value inside a block value that a Unicode space opens, as ink: a: bandk: a::k: \u3000{a: b}andk: \u3000:read as text. On main the first was misread as the mapping{'a': 'b'}, and the second was refused quoting':'._scalar's\dis Unicode:k: \u0663(ARABIC-INDIC DIGIT THREE) reads the number3, where PyYAML reads the text._BLOCK_HEADER_RE's\dtakesk: |\u0663as a block scalar header, where PyYAML refuses it.:that opens a value reads as text:k: {a: b: c}reads{'a': 'b: c'}andk: {a: ?:}reads{'a': '?:'}. PyYAML refuses both. This is the value-side twin of L4-155, and L4-156 holds its kind.k: |\n a\n \t\n breads'a\n\nb\n', where PyYAML reads'a\n\t\nb\n'. This PR keeps the tab in YAML's white space everywhere, so tab behaviour is unchanged.tests/test_adopt_plan_yaml_quoting.py's comment onPIN_WITH_A_TRAILsays the loader drops a pin's trailing whitespace from the manifest's-item. That stays true of a space and is no longer true of\xa0. The tests pass, and the file is not in this issue.Merge with #824, round 3
#824 (#710) landed as main
0dee22b3at 21:54Z. As the second lander, this PR takes the_argument_faultmerge round agreed on #473. Main is merged in, never rebased, asc56f3437. RV823's re-check of round 2 (issuecomment-6241904307) measured the merge, and its resolution is the one taken.The four textual hunks:
_argument_fault's docstring: bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'sUNWRITABLEparagraph. repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823's sentence "A character the system's encoding merely lacks is not this fix's." is dropped. repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823'sUNSEENparagraph says it is asked beforeUNWRITABLE, and why._argument_fault's tail: a NUL and a lone surrogate with no byte first, as on both sides. Then repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823'sUNSEENloop. Then bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'sUNWRITABLEtest, asked last because its line calls the name good. A name holding a character no person can see is not good under any encoding, and its remedy (rename the branch) holds in every locale._say_not_tried: bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'srefused=signature and lines, with repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823'sUNSEENlines as an early return first.tests/test_bootstrap_branch_bytes.py, the no-fault list: bootstrap.py: step (a) decodes git's output as the system decodes a name it is given, so a branch whose upstream is not UTF-8 is moved back, and a good branch this system's encoding cannot write gets a line of its own, not a traceback (#710) #824'swritten(...)rows forfeature/日本anda\xe9b. The zero-width joiner,a banda\x1bbare dropped, andtest_a_character_no_person_can_see_is_the_faultholds all three.The three test conflicts
git merge-treecannot see:tests/test_bootstrap_non_utf8_branch.py,[ending-in-U+00A0].--branch a\u00a0is anUNSEENrefusal now. The row was lane 5's site: a trailing U+00A0 stripped from the UPSTREAM's name in git's answer. So the branch moves to a printablea\u00e9, and the upstream keeps its U+00A0. The row is now[an-upstream-ending-in-U+00A0], and the test takes the two names apart. Expecting the refusal, or moving the whole row toa\u00e9, would have dropped the stripping case the row exists for.a-spacerow (日本 x) becomesa-tilde(日本~x), a name git refuses that holds no character no person can see. A space isUNSEENnow. Three rows hold that it is named beforeUNWRITABLE, never called good, and that the validator ends the run:asciiencoding,日本 xand日本followed by U+00A0;test_a_name_with_a_space_is_named_for_the_space_under_ascii_too.tests/test_bootstrap_after_bump.pydid not XPASS (see "Which rows …"). HEAD is read as bytes, and the mark is gone.#824's three blank lines before
test_a_leg_whose_git_dir_is_gone_still_has_a_good_name_c…are now the house's two, since round 3 changes that file anyway.RV823's round-2 re-check, taken in
8b4dbcf6after the merge:readable_branchandUNSEENmove toscripts/shape_materialize.py, with the reason restated (see the helper bullet above). Both tools import the name on a line of its own, andupdate-shape.py'srepo_shapeimport list is as main has it.bootstrap.py,validate-manifest.pyand the fault test's docstring no longer saysiblings.pyrefuses every such name. It asksisprintable()alone, and takes a space. The fault test's docstring no longer says git takes each of its names: git refusesa banda\x1bb.readable_branchnames a byte-carrying U+DCFF "a lone surrogate: no UTF-8 file or console can hold it, so no branch name is spelled with one", and a NUL "a NUL: no argument, file name or ref name can hold one", as the validator does (validate-manifest.py accepts a display name holding a lone surrogate (name: "a\ud83db") and then raisesUnicodeEncodeErrorprinting it, somake validateand shape-doctor.py fail on a traceback instead of a finding while bump-leg.py and update-shape.pyapplypass the same project once #254 lands #280).-refusal says only "which begins with-, and git creates no branch whose name begins with one", which holds for bump-leg as well.ca47b71d, which changed none of the three copies since0dee22b3.Main again. Main moved to
ca47b71dwhile round 3 waited (#846, #779, #772, #738, #842, #814, #790, #820, #774, #830 and #841), andgit merge-treewith8b4dbcf6stopped in one path. Intests/test_bootstrap_after_bump.py, main'saec3b38faddedappended_to_command_scopeto thefrom conftest import (...)list, beside this branch'sneeds_a_byte_nameimport. Both are kept, in merge commit7c20ba96.Shared files
git merge-tree --write-treeof this head7c20ba96, with the heads read bygh pr viewand fetched at 2026-10-18T01:44Z:ca47b71d3dd24f8d(this head's own tree)fix/832-legs-readers)86f41d9068701028fix/714-legs-not-a-list)124bbd7b0a4e6266fix/716-adopt-check-plan-name)6d5a448fd6b73fccfix/711-git-bash-dialect)7581ffb9233a5e93fix/836-child-environment)dad55d2daca4aa0bfix/764-bootstrap-notice-exit-texts)05a1ebb3fef836abfix/847-doctor-unreadable-pin)a2b0e48cshape-doctor.pyfix/765-branch-read-bytes)d98df568tests/test_bump_leg_branch_and_push_line.pyandtests/test_family_tracking_branch.py#860's and #849's conflicts are in files this PR does not change:
git merge-treeof mainca47b71dwith each stops in the same files, so they are those PRs' own merge rounds with main. #846 has landed.fix/855-bom-readsandfix/864-members-not-a-listwere not on GitHub at 01:42Z.#817 changed the same reader and has landed. The round-0 merge with its head
8fd2fa64passed this file,tests/test_yaml_indicator_openers.pyand #817'stests/test_reader_refusals_named.py(561 passed). Round 3's run holds all three again.Closes #715
🤖 Generated with Claude Code
Summary by Sourcery
Make YAML parsing faithful to YAML whitespace rules and prevent branch operations from acting on names that people cannot reliably distinguish.
New Features:
Bug Fixes:
mainfollowed by a non-breaking space withmain, which could create duplicate branches or commit to the wrong tracking branch.Enhancements:
Tests: