Repository navigation
validate-manifest.py, the naming gate and setup-project.py: a chain link no tree answers for is offered what each caller reads, so the root's validator and setup's step (4) no longer offer --link-source, which neither takes, and the gate spells every --link-source value and path it prints - #744
Conversation
…ink no tree answers for is offered what each caller reads, so the root's validator and setup's step (4) no longer offer `--link-source`, which neither takes, and the gate spells every `--link-source` value and path it prints (#661) A chain link whose own tree nothing could read is declared-unverified, and its WARNING offers what would let that tree be read. Since #634 the scaffold offers its own sources (`link_pins_from_trees(..., remedies=)`). The root's validate-manifest.py and setup's step (4) still printed the naming gate's words, "a sibling checkout, SHAPE_PIN_SOURCE_<LINK>, or --link-source <link>=<path>", and neither takes `--link-source`. L2-68: validate-manifest.py offers what it reads, SHAPE_PIN_SOURCE_<LINK> (unless two links share it, #275) and a checkout beside the root, keyed by `pin_product_key`. It passes them as `remedies=` only where the root's repo_shape.py signature takes the keyword (`inspect.signature`), so beside an older library (pre-#634, v1.0.1) it runs and keeps that library's words; an unguarded `remedies=` there was a TypeError. L2-75: the naming gate takes `--link-source-label=FLAG`, the caller's name for the bare source it passes, and `--link-remedy LINK=TEXT`, the caller's words for a holder, passed on as `remedies=` under the same guard. With a label the unreadable line says `(FLAG)`, the not-used NOTE names it, no line says the value was read as a PATH, and the NOTE lists no keyed `--link-source`. Setup's step (4) passes `--link-source-label=--pin-source` and one `--link-remedy` per holder of a chain of two links or more, in the scaffold's own words: its `_link_remedies` moves, unchanged, to shape_materialize.scaffold_link_remedies, which both load. The test helper's `re.sub` of the gate's offer is gone. L2-69: the gate spells with its own `_shown` the key `read_as_path` names, `_also_a_directory`'s value, the path of a variable or a checkout beside the project that could not answer (and that checkout's `how`), `no_path_refusal`'s value, and the not-used NOTE's value and the value that replaced it. docs/cli.md is regenerated for the gate's two options. Both shape copies already differ from v1.0.1 on main, so no file is added to a project's v1.1.0 update; scripts/repo_shape.py is untouched. Lands after #634. Closes #661 Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 4 hours and 8 minutes by commenting |
Reviewer's GuideThe PR aligns chain-link warnings across the root validator, naming gate, scaffold, and setup by supplying caller-specific remedies and labels, safely rendering every link-source value/path, and conditionally supporting older repo-shape libraries without changing their call contract. Sequence diagram for caller-specific chain-link remediessequenceDiagram
participant Setup as setup-project.py
participant Gate as validate-repository-naming.py
participant Library as repo_shape.py
participant Scaffold as shape_materialize.py
Setup->>Scaffold: scaffold_link_remedies(chain)
Scaffold-->>Setup: remedies keyed by pin_product_key
Setup->>Gate: naming_declarations(..., --link-source-label=--pin-source, --link-remedy=LINK=TEXT)
Gate->>Gate: _link_remedy(value)
Gate->>Gate: _library_takes_remedies()
alt library accepts remedies
Gate->>Library: link_pins_from_trees(..., remedies=remedies)
else older library
Gate->>Library: link_pins_from_trees(...)
end
Library-->>Gate: chain findings and warnings
Gate-->>Setup: caller-labelled warnings using supplied remedies
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…and its label's help say only what is true, the validator's docstring covers a shared variable, and a qualified first link pins the validator's `repo_basename` rv661 on #744 at f5e1867, CHANGES REQUIRED: - S9073, tests/conftest.py: `assert offered and offered[0].endswith(...)` in `assert_step_four_speaks_in_the_plans_words` is two asserts now, one per condition. No other added assert joins conditions. - validate-repository-naming.py's docstring said every value and path it prints is `_shown`. It is now about the `--link-source` values and paths, and the path of a variable or a checkout beside the project it names, and it says a caller's FLAG and words, a chain link's name and the `--project` and `--policy` paths are printed as given. - The `--link-source-label` help said "a line about that value names it by FLAG", which the `link-source-no-path` REFUSED line and the NOTE's `--link-source <link>=PATH` (chains that begin at two links) do not do. It now names the two lines that do: the link-source-unreadable WARNING and the not-used NOTE. The docstring names the two that keep the gate's own flag. The code is unchanged; setup reaches neither line. docs/cli.md is regenerated. - validate-manifest.py's `_link_remedies` docstring said every holder is offered both sources; a holder whose variable another link shares is offered the checkout alone (#275), as the code does. - tests/test_chain_remedy_callers.py: a `qualified` row, `referent_chain: [opensoft/openXdox, openDox]`, so the validator reading the chain without `repo_basename` (rv661's M4, which 373 tests let live) fails. - shape_materialize.py: the comment above the repo_shape import names `scaffold_link_remedies` as the reader of `pin_source_env_name`, `pin_source_env_shared` and `repo_basename`. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-2, 2026-10-16T19:14:28Z) #744 round 2: rv661's findings, fixed at
Nothing else changed. E1-E5 are held for #459. Verification at
The re-pin is unchanged: assembly roots re-pin |
|
main gained 25 pull requests since de92614: #634, #699, #671, #719, #701, #717, #700, #702, #722, #720, #725, #721, #728, #705, #727, #718, #744, #743, #749, #740, #737, #734, #742, #751 and #736. No conflict: AGENTS.md, adopt-project.py and docs/cli.md merged as text, and README.md and docs/handbook.html are unchanged by the merge. README.md is 1473 lines, its cap; AGENTS.md 494, its cap; every handbook marker is current; `render-cli-reference.py --check` is ok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es every reason THIS checkout cannot answer, and the notes are current to main bf1f099 with the 25 pull requests since de92614 (#563, #683) - W683 and RV-f's N4 on #742: README's exit-3 list (Keeping a project's shape current, :1208-1220) and its handbook twin (`doctor`) named only a pin whose commit this checkout does not carry. They now say, as shape-doctor.py's EXIT CODES does: a pin's commit it lacks, a partial clone missing what is compared (#683), a read that fails (`update-upstream-unreadable`), a crashed validator of its own (#278, #410), or its own naming policy not loading (#678). +1 line, with tests/readme-cap.d/683.txt; README.md is 1474, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 1). The two `Keeping a project's shape current` markers move: `doctor` text and marker, `current` marker only. - The notes, "What changed since v1.0.1", one entry per pull request from `git log --first-parent de92614..bf1f099`, each from its squash commit and the issues GitHub says it closes: - adopt-project.py: #699, #719, #749, #727, #717, #728 (with #621's hint), #743 + #720 + #718, #722, #721; - update-shape.py, bump-leg.py and a project's own scripts: #702 (with the pinned validators), #634; - scaffold, setup, family and the shared readers: #725, #671, #744; - shape-doctor.py: #742, #701, #737, #751, #736, #734, #700 + #705, #740; - install, CI and the docs: the exit-3 sentence above. - Counts (99 pull requests since bfed143; 109 of 135 milestone issues closed at 10:28Z), the "current to main" sha and the open list (26) are at main bf1f099, still placeholders for tag time. Left for round B: lane 5's eight lines (#630 issuecomment-6227129879), whose pull requests #726, #747, #682, #724, #746, #745 and #738 are open drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's 28 new commits since 8062810 merged with no conflict. Four of them touch a file this branch changes: #744, #728, #671 and #634 edit scaffold-project.py, and #728 and #671 edit scripts/family.py too, each in other functions than the clone URL's remedy, its refusal and the README values. Nothing this branch changes is a pinned copy, so nothing was re-pinned. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d A3 (three SHOULDs, four nits), and notes entries for the seven pull requests landed since bf1f099, current to main 2000488 (#563, #683) Round A4, from lane 2's review (#630 issuecomment-6233033833, READY): - SHOULD 1: the doctor's exit-3 list names "a validator of its own that crashed or could not be run", so the case of one that cannot be given its `PYTHONPATH` (shape-doctor.py's EXIT CODES, #278) is listed too: README.md:1208-1222, its handbook twin (`doctor`) and the notes' docs line. The paragraph re-wraps to one more line: tests/readme-cap.d/683.txt is +2, and its text now says which causes the EXIT CODES block names and that #742 added the partial clone and #701 the naming policy. README.md is 1475 lines, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 2). Both `Keeping a project's shape current` markers move to c728c970: `doctor` text and marker, `current` marker only. - SHOULD 2: #727's entry names the leg description, whose em dash stopped every `execute` against GitHub at the first leg. - SHOULD 3: #749's entry names `source-ambiguous`, for an `org/repo` that is also a directory here. - Nits: the doctor section's three near-duplicate pairs are merged (#636 with #737, #632 with #700 and #705, #586 with #736); #718 has its own bullet; #634's entry names `git_out`'s spelling for every caller and #744's the gate's `--link-source-label` and `--link-remedy`; the header comment says 2026-10-17 at main 2000488. Wave B1, `git log --first-parent bf1f099..2000488`: #706 (#664), #703 (#696), #670 (#646), #682 (#648), #726 (#667), #730 (#666) and #724 (#665), each from its squash commit; #726's and #682's from lane 5's lines (#630 issuecomment-6227129879), #682's with the unmerged `.gitmodules` its last round added, and #724's with lane 5's note that v1.0.0's test-hygiene line on the generator's `.pyc` files closes. The counts (106 pull requests; 116 of 135 milestone issues closed at 11:53Z), "current to main" and the open list (19) are at main 2000488, still placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efusals and `family-operation-in-progress` name a path as `shown_source` names it, so an ESC or a line break in a path no longer reaches the terminal (#692) (#731) #622's folder refusals printed a path as it was. `family.py init --into <a file named wd-ESC[31mRED> --dry-run` gave `REFUSED family-folder-cannot-be-made: the family folder .../wd-^[[31mRED/Orbit cannot be made: ...` through `cat -v`. A name with a line break and then `WARNING forged-line: pwned` printed that line as a line of its own. R612's `family-operation-in-progress` printed the mark's path across two lines for a holder whose path holds a newline. Each path is now spelled as `shape_materialize.shown_source` spells a `--pin-source`: as it is, and as Python's `repr` spells it where a character in it is not printable. - `scripts/shape_materialize.py`: both answers of `folder_cannot_be_made` (a file or a bad stat in the way, and a folder this user cannot write in), the folder in `refuse_unmakeable_folder`, and the folder in `make_folder`. - `scaffold-project.py`: `scaffold-target-exists`. - `scripts/family.py`: `family-target-exists`, and the mark's path in `family-operation-in-progress` (`where.as_posix()`). A printable path is named byte for byte, so no existing row changes and `why.endswith(CANNOT_WRITE_IN)` in `refuse_unmakeable_folder` still holds. No help text changed, so `docs/cli.md` is untouched. No README, handbook or template sentence says how a refusal names a path (`git grep` of `shown_source`, `target-exists` and `folder-cannot-be-made` in AGENTS.md, README.md, `docs/handbook.html` and the templates found none), so no doc changes. - **`shown_source`, not `shown_path`.** Since #634, `shape_materialize.shown_source` returns `repo_shape.shown_path(source)` (`scripts/shape_materialize.py`: `return shown_path(source)`), so the seven call sites this change adds (four in `shape_materialize.py`, one in `scaffold-project.py` and two in `family.py`) spell a path as `shown_path` does. Renaming them to `shown_path` stays out of this PR: it would only move imports in `scaffold-project.py` and `family.py` and reword docstrings in `family.py`, `shape_materialize.py` and the test file, and the review's N1 left it optional. I did not take it. The predicted merge with main works with the alias: `family.py --help` and `scaffold-project.py --help` exit 0 on it, and `shown_source` and `shown_path` return the same string for an ESC name, a line-break name and a plain path. - **`shown_source` is imported by name into each file's existing `from shape_materialize import (...)` block.** The first commit put it on a line of its own after the block; lane 4's #660, #688 and #693 add an import at that same place, and `merge-tree` reported add/add conflicts in `scripts/family.py` and `scaffold-project.py`. The second commit moves the name into the block, and those conflicts are gone. - **"The holder in the other refusals of the same block" is one refusal.** The `# What the holder is in the middle of, and what its sparse checkout leaves out` block holds `family-operation-in-progress`, `family-sparse-excluded` and `member-path-unaddable`. Only the first prints a path of the holder's. `family-sparse-excluded` names `members/<Project>` and `.gitmodules`, and `member-path-unaddable` names `members/<Project>`. - **No scaffold row for `--local-remote-dir`.** A control character there is refused earlier as `scaffold-unwritable-value`, which already names the path with `repr`. The unit test covers `make_folder`, which only a race reaches by the command line. `tests/test_dry_run_parity.py`: - `SCAFFOLD_CASES` gains five rows: ESC and line break in the `--work-dir` name, ESC in a folder name above it, and ESC and line break in a work dir that holds a file named `Atlas` (`scaffold-target-exists`). - `FAMILY_CASES` gains five rows: ESC and line break in `--work-dir`, ESC in `--into` (`the family folder`), and ESC and line break in a work dir that holds a file named `Orbit` (`family-target-exists`). - Two rows join `UNWRITABLE_FOLDER_AT` (ESC in the scaffold's and in `init`'s `--work-dir`, a folder this user cannot write in). - Rows named `ESC-...` and `line-break-...` are skipped on Windows, whose file names cannot hold them (`POSIX_NAMES`, `case_params`). Every expectation spells the path with `repr`, and not with `shown_source`. - `assert_refused` now fails any stderr line that starts with `WARNING forged-line`. - Three unit tests: `test_each_folder_question_spells_a_name_that_is_not_printable` (ESC, line break; `folder_cannot_be_made`, `refuse_unmakeable_folder` and `make_folder` by themselves), `test_a_folder_this_user_cannot_write_in_is_spelled_safe_to_show`, and `test_a_printable_name_is_named_byte_for_byte` (a name with a space and an accented letter stays unquoted). `tests/test_family_refuse_up_front.py`: `test_a_holder_path_with_a_newline_still_shows_its_merge` asserted the raw newline in the refusal (`"Ink\nRouter/.git/worktrees/" in ran.stderr`). It is now `test_a_holder_path_with_a_control_character_still_shows_its_merge`, over a line break, an ESC and a forged line, and asserts the one-line `repr` form (`refusal()` fails unless the stderr is two lines). It is the one existing test this change had to edit. Results at head `ab55c29f` (Python 3.12.3, Linux): - `tests/test_dry_run_parity.py tests/test_dry_run_asks_remotes.py tests/test_family_dry_run.py tests/test_family_init_landing.py tests/test_repo_hygiene.py tests/test_pin_source_unreadable.py tests/test_scaffold_leg_path_collisions.py tests/test_scaffold_reuse_seeded_legs.py`: **494 passed**. - `tests/test_family.py tests/test_family_refuse_up_front.py tests/test_family_init_values.py tests/test_family_add_refusals.py`: **356 passed, 19 skipped** (the skips are `test_family_init_values.py:219` and `:291`, template-quoting premises). - Not run: the full suite (the coordinator's gate), and macOS and Windows (CI is Linux-only since #597). The new hostile-name rows are POSIX only by design. On the base code: the review overlaid this head's two test files on main `2fac9790` and ran `tests/test_dry_run_parity.py` and `tests/test_family_refuse_up_front.py` with `-k "dry_run_parity or control_character"`. The result is **20 failed, 87 passed** (76 deselected, 43.5 s). The 20 are exactly the new and edited tests: the 5 scaffold and 5 `init` rows, the 4 unwritable-folder rows, the 3 unit tests (`test_each_folder_question_spells_a_name_that_is_not_printable` in two cases, and `test_a_folder_this_user_cannot_write_in_is_spelled_safe_to_show`), and the 3 cases of the edited `test_a_holder_path_with_a_control_character_still_shows_its_merge`. The one new test that passes on main's code is `test_a_printable_name_is_named_byte_for_byte`, by design: a printable name is named byte for byte on both. An earlier draft of this body said 21 failed, 2 passed. That run was on `8062810d` with a wider selection, and it held a scaffold row for ESC in the local remote dir's name (`ESC-in-the-local-remote-dir-name`) that this head's tests no longer have; its second pass was the existing `test_the_refusal_quotes_gh_s_first_line_and_escapes_what_is_unprintable`, which also passes in the new run, one of the 87. `family.py init` and `scaffold-project.py --dry-run` against a file named `wd-ESC[31mRED`, a file named `wd-\nWARNING forged-line: pwned`, and a work dir with a file `Atlas` or `Orbit` in it, run with a stub `gh` and no real `gh`: - On main, through `cat -v`: `... the family folder /.../wd-^[[31mRED/Orbit cannot be made: /.../wd-^[[31mRED is a file, not a folder`, and a stderr line `WARNING forged-line: pwned cannot be made: ...` of its own. - On this head: `REFUSED family-folder-cannot-be-made: the family folder '.../wd-\x1b[31mRED/Orbit' cannot be made: '.../wd-\x1b[31mRED' is a file, not a folder`, and one line for the forged name: `--work-dir '.../wd-\nWARNING forged-line: pwned' cannot be made: '...' is a file, not a folder`. `scaffold-target-exists` and `family-target-exists` are one line each in the same form. Each spelled site reverted alone in a throwaway clone at `19d126cd` (the call sites are the same at `ab55c29f`); the new tests that fail for each: - `folder_cannot_be_made`, the `_not_a_folder` answer: 8 failed (six CLI rows and the two unit tests). - `folder_cannot_be_made`, the `CANNOT_WRITE_IN` answer: 5 failed (the four unwritable rows and the unit test). - `refuse_unmakeable_folder`: 12 failed. - `make_folder`: 2 failed (the unit test only). - `scaffold-target-exists`: 2 failed (its two rows). - `family-target-exists`: 2 failed (its two rows). - `family-operation-in-progress`: 3 failed (line break, ESC, forged line). `git merge-tree --write-tree` of this head `ab55c29f` against origin/main `20c8873b` and against every ref on origin that could meet it, fetched 2026-10-16: the 41 branches (`fix/*`, `l3-*`, `wip/*`, `docs/*` and `release/v1.0`) and the 55 PR heads from #630 up. That is 96 refs. (The same list was run for #730's head `7f2bb36b`.) - **origin/main `20c8873b`: clean** (the predicted tree `00b5b24c`, which differs from main in these five files only: 216 lines added, 25 removed). - **#630's head `6fd33a93`: clean.** It conflicted with main in `docs/handbook.html` when this body was first drafted, and no longer does, with or without this change. This change touches no README or handbook. - **The refs that edit one of this change's five files**, and so could meet it, all merge clean with this head: `fix/646`, `fix/651`, `fix/654`, `fix/660`, `fix/661`, `fix/684`, `fix/686`, `fix/688`, `fix/689` and `fix/693`, the heads of #670, #725, #728, #743 and #744, and the heads of the landed #634, #638, #639, #640, #643 and #671. #634 has landed (`30676c90`), so the `fix/660` note about a conflict with it is gone. - **Four refs conflict with origin/main on their own**, and so with this head: `docs/release-notes-v1.0.0` and `release/v1.0`, and the pre-squash heads of the landed #637 and #641. Their conflicts do not come from this change. Every other ref, 92 of the 96 in all, is clean with this head, including the refs that conflict with main in `templates/family-root/scripts/validate-family.py` (#682, #745, #747, `fix/648`, `fix/681`, `fix/704`) or in `shape-doctor.py` (#742, `l3-683`) on their own. So do the old heads of the landed #638 and #639, and lane 3's `l3-677-pins-hand-step-wip` branch, and each of them merges clean with this head too. - **Tests on the predicted merge**, run by the review on the merge with main `2fac9790`: `test_dry_run_parity`, `test_family_refuse_up_front`, `test_pinned_copy_text`, `test_pin_source_unreadable`, `test_dry_run_asks_remotes`, `test_family_dry_run` and `test_family_init_landing`: 445 passed in 215 s. `test_family`, `test_family_init_values`, `test_family_add_refusals`, `test_derived_id_refusals`, `test_repo_hygiene`, `test_naming_policy`, `test_scaffold_leg_path_collisions` and `test_scaffold_reuse_seeded_legs`: 758 passed, 19 skipped in 243 s. I did not re-run them on `20c8873b`; the merge is clean, and the two commits since `2fac9790` (#700 and #702) change only `shape-doctor.py`, `templates/assembly-root/scripts/validate-pins.py` and four test files, none of them a file of this change. - Before the second commit, `fix/660`, `fix/688` and `fix/693` each conflicted with this head in the import region of `scripts/family.py` or `scaffold-project.py` (a separate import line each side added at the same place). That is why the import joins the existing block. #634 is on main, and this change does not edit `shown_source` or its docstring in `scripts/shape_materialize.py`. The review approved with nits. Its S1, the stale base-run numbers and merge lines, is taken here, in this body: no code changed, and the head is still `ab55c29f`. Its N1, renaming the call sites to `shown_path`, is not taken (see "Decisions and their evidence"). It also re-ran 5 of the 7 mutation probes on `ab55c29f` and got the same failure counts as above (12, 2, 3, 2 and 2), and ran 144 hostile-name dry runs (16 names in 9 scenarios): on main 69 rows put a control character on stderr and 6 print a forged line of their own; on this head all 144 exit 2 with no traceback, no control character and no forged line. - `scripts/family.py` `init`'s plan prints `folder` and `landing` lines with the path raw (`print(f"landing {holder} ...")`, `print(f"folder {folder} ...")`). On a dry run or a refused run the plan is on stdout before the refusal, so `WARNING forged-line: pwned/Orbit (--work-dir: no family folder)` still appears there. It is the plan, not a refusal, and not one of the issue's lines. - Other refusals that name a holder or folder path raw, none of them in the issue's lines: `family-holder-detached` and `family-on-tracking-branch` (`{family.root}`), `family-index-locked` (`lock.as_posix()` in the detail), `family-landing-in-standard` (`{holder}`, `{checkout}`, `{beside / family / family}`), `family-holder-already-here` (`{into}`), and `family-remote-exists` and `scaffold-remote-exists` (`{target}`, a local remote path). 1. **family.py `init`'s plan prints the holder and folder paths raw.** `scripts/family.py` `landing` and `folder` rows (about :1756-1759) print `holder` and `folder` as they are, so a forged line or an ESC reaches stdout in a `--dry-run` and in a run refused after the plan. Direction: spell them with `shown_source`. 2. **The same raw spelling in the refusals listed above.** `family-holder-detached`, `family-on-tracking-branch`, `family-index-locked`, `family-landing-in-standard`, `family-holder-already-here`, `family-remote-exists` and `scaffold-remote-exists` each print a path the person supplied or git keeps. Direction: the spelling this change gives its sites. Review: rv692, lane 5's independent review, approved ab55c29 with nits, and its body fix (S1) is published. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 ab55c29 passed tests, tests-git-bash and SonarCloud; the head 13f643d is a clean update-branch merge of main 431c8cf, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 4327947 over main 431c8cf: the dry_run_parity, family_refuse_up_front, repo_hygiene, agents_md_codes, cli_reference sets, 330 passed. Landed on Brett Heap's word of 2026-10-17T10:59:39Z, relayed by lane 3 on #473: "merge lane 5's approved v1.1.0 drafts". Closes #692 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-2 (openRepoShape-2)
Claim: #661 (comment)
Lands after #634 (on main as 30676c9). This branch is cut from it. It must land before the v1.1.0 tag: DB8 option 3 and DB9 option b of the checklist triage of 2026-10-16, which Brett Heap approved at 12:14:51Z, and D6, one re-pin for v1.1.0.
A chain link whose own tree nothing could read is
declared-unverified, and its WARNING offers what would let that tree be read. Since #634 the scaffold offers its own sources (link_pins_from_trees(..., remedies=)). The two other callers still printed the naming gate's words, "a sibling checkout, SHAPE_PIN_SOURCE_, or --link-source =", and neither of them takes--link-source:validate-manifest.py, on everymake validate, bootstrap and CIvalidaterun of a root that records a chain. Typed as printed, the offer givesunrecognized arguments.--pin-sourceit hands the gate(--link-source). Typed after--, the offer ends step (5) in the scaffold'sunrecognized arguments, exit 2.Each caller now offers the sources it reads. The gate also names every
--link-sourcevalue and path it prints as_shownnames one (L2-69).What changed
templates/assembly-root/scripts/validate-manifest.py(L2-68; a shape copy)._link_remedies(chain)builds this validator's own words. It readsSHAPE_PIN_SOURCE_<LINK>(rule 2 ofresolve_link_source, unless two links share the variable, scripts/repo_shape.py:pin_source_env_nameturns every character outside[A-Z0-9_]into_, so the neutral productsopen-glass,open_glassandopen.glassshare oneSHAPE_PIN_SOURCE_OPEN_GLASS, and a project that pins two of them gets aneutral-pin-source-unreadableFINDING (exit 1) for whichever one the variable does not name #275) and a checkout beside the root (rule 3). So each holder is offeredSHAPE_PIN_SOURCE_OPENXDOX, or a checkout of openXdox beside this root, would let it be read, or, where another link shares the variable, the checkout alone:a checkout of open-x beside this root would let it be read. Its docstring says both. The links are read as_chain_link_findingsreads them, byrepo_basename, so a link recordedopensoft/openXdoxis offered asopenXdox. The entries are keyed bypin_product_key, the key_chain_link_findingslooks a holder up by (scripts/repo_shape.py: the chain readers identify a link, a declared pin and a--link-sourcekey bycasefold()(twelve calls,:1207to:2428), where #275's fix (PR #409, 6494714) makes a product's identitypin_product_key(lower(), the pin file's own fold), soopenßandopenssare two products to the pin validator and one link to the chain: a holder's tree that declaresopenssverifies a chain that saysopenß, a project that pinsopenssverifies a chain that begins atopenß, a--link-source openss=answers for the linkopenß,link_pins_from_treeskeeps one entry for the pair, and a chain that names both is refused as a cycle #444).scripts/repo_shape.py(a half-applied update,update-shape.py apply --accept-local). Before Pinned shape copies: AGENTS-shape.md'sCONTRACTS_DIRsentence is false for every scaffolded root, and the chain WARNING offers remedies the scaffold lacks (one shape bump before the v1.1.0 tag) #610,link_pins_from_treeshad noremedies=and raisedTypeError(measured onde926142and on v1.0.1). So_library_takes_remedies()asks the function's own signature (inspect.signature), andremedies=is passed only where the signature names it. Beside an older library the WARNING keeps that library's words, with exit 0 and no traceback.pin_product_key,pin_source_env_name,pin_source_env_shared) are all in v1.0.0's library, so update-shape.py:applyrefuses to keep an--accept-localcopy of a helper that lacks a name a copy it writes imports from it, before anything is written, so a keptscripts/repo_shape.pyno longer leavesmake bootstrapdying on an ImportError after the validators passed (#536) #562's kept-helper check still lets an older one stay.scripts/validate-repository-naming.py(L2-75, L2-69; a shape copy).--link-source-label=FLAGis the caller's own name for the bare--link-sourceit passes. Setup passes--pin-source. The value starts with-, so it is given after an=. With a label:link-source-unreadablesays(FLAG), not(--link-source). The gate replaces the library's label, so an older library needs nothing.FLAG <value>.="was read as a PATH". The caller's flag is always a path, and setup spells a<word>=value./<word>=.link-source-no-pathREFUSED line, and the NOTE's advicename the link it is, as --link-source <link>=PATHwhere the chains begin at more than one link. Setup reaches neither (see Not changed).--link-remedy LINK=TEXT(repeatable) gives the caller's words for what would let LINK's tree be read.=. A value with no=, no link or no words is refused by argparse, exit 2.pin_product_key(link); the last value for a link wins.remedies=only when some were given and the library's signature takes them, so a project's own run calls the library exactly as before, beside any library._chain_link_findingsis unchanged. It already looks a holder's entry up bypin_product_key, and a holder with no entry keeps the gate's own words._shown(Pinned shape copies: AGENTS-shape.md'sCONTRACTS_DIRsentence is false for every scaffolded root, and the chain WARNING offers remedies the scaffold lacks (one shape bump before the v1.1.0 tag) #610):read_as_path's key,_also_a_directory's value (in the sentence and in the./command), the path of aSHAPE_PIN_SOURCE_<LINK>or a checkout beside the project that could not answer, and the path inside that checkout's(sibling checkout (…)),no_path_refusal's value, and the "not used" NOTE's value and its replacement. A printable value is named byte for byte as before._shown. Every--link-sourcevalue and path the gate prints, and the path of aSHAPE_PIN_SOURCE_<PRODUCT>or of a checkout beside the project it names, is_shown. A caller's FLAG and the words it offers for a link, a chain link's name, and the--projectand--policypaths are printed as given (see Not changed).setup-project.py(L2-75).naming_declarations(passthrough, link_remedies=None): for a chain of two links or more, step (4) also hands the gate--link-source-label=--pin-sourceand one--link-remedy=<link>=<words>per holder. This happens whether or not a source was named, because--pin-source ''still needs the offer.referent_chain_links).link_remediesthe output is exactly setup-project.py: step (3) refuses a parent nobody can write in and a drive that is not there, step (4) verifies a chain from--pin-source,--pinno longer takes a flag as its value, and step (5) says a printed plan was refused (#608) #623's._mainpassesscaffold_link_remedies(shape_root), a deferred import of the checkout'sscripts/shape_materialize.py, asshape_scriptsdoes forrepo_shape.scripts/shape_materialize.py,scaffold-project.py._link_remedies(Pinned shape copies: one bump, so AGENTS-shape.md is true of every root, the scaffold's chain WARNING offers only its own sources and spells--pin-source, the root's not-used note spells it too, and THE LOCKSTEP RULE'sbumpruns (#610) #634) moves, unchanged, toshape_materialize.scaffold_link_remedies. The comment aboveshape_materialize.py'srepo_shapeimport now names it as the reader ofpin_source_env_name,pin_source_env_sharedandrepo_basename.--pin-source, the root's not-used note spells it too, and THE LOCKSTEP RULE'sbumpruns (#610) #634's three tests stay as they are. Its now-unusedpin_product_key, pin_source_env_name, pin_source_env_sharedimport goes.shape_materialize.pyis in no copy list. Its own rule keeps a helper only the tools call out of the pinnedrepo_shape.py.docs/cli.mdis regenerated: the gate's two new options.validate-manifest.py's and setup's--helpare unchanged.What step (4) says now, measured through
setup-project.py:----pin-source '', 2 links(a sibling checkout, SHAPE_PIN_SOURCE_OPENXDOX, or --link-source openXdox=<path> would let it be read)(SHAPE_PIN_SOURCE_OPENXDOX, or a --pin-source clone of openXdox that holds its project.yaml, would let it be read)--pin-source ' 'WARNING link-source-unreadable: (--link-source) could not answer for openXdox: …… (--pin-source) could not answer…--pin-source openXdox=layer, absent(--link-source), then "It was read as a PATH:./openXdoxis not a link…"openXdox=layer (--pin-source) could not answer…, and no "read as a PATH"… or --link-source openYdox=<path> would let it be read(SHAPE_PIN_SOURCE_OPENYDOX would let it be read), the plan's own line byte for byte--pin-source ''--link-source--pin-source <tree>NOTE --link-source <tree>: not used — … (a keyed --link-source, SHAPE_PIN_SOURCE_<PRODUCT>, …)NOTE --pin-source <tree>: not used — … (SHAPE_PIN_SOURCE_<PRODUCT>, or a checkout beside the project).Decisions the issue left open
remedies=only where the function takes it", checked withinspect.signature.CONTRACTS_DIRsentence is false for every scaffolded root, and the chain WARNING offers remedies the scaffold lacks (one shape bump before the v1.1.0 tag) #610). A library that lacks the keyword is called exactly as before.try/except TypeError, which would swallow an unrelatedTypeErrorfrom inside the call and call it again.pin_source_env_nameturns every character outside[A-Z0-9_]into_, so the neutral productsopen-glass,open_glassandopen.glassshare oneSHAPE_PIN_SOURCE_OPEN_GLASS, and a project that pins two of them gets aneutral-pin-source-unreadableFINDING (exit 1) for whichever one the variable does not name #275 rule.remediesas an attribute rests on_chain_link_findings' privategetattr, which onlyLinkPinsis promised._link_remedies, and a second copy of it would drift. So the function moved to the one module both the scaffold and setup can load, and a test holds the three names (scaffold-project.py's, setup's andshape_materialize's) to one function.--pin-source ''(the plan refuses it aspin-source-no-path) and a run with no--pin-sourcekept the gate's offer. setup-project.py: step (3) refuses a parent nobody can write in and a drive that is not there, step (4) verifies a chain from--pin-source,--pinno longer takes a flag as its value, and step (5) says a printed plan was refused (#608) #623's 13--link-source=rows intest_the_declarations_are_read_as_the_scaffold_reads_themare unchanged, because the label goes with the words, not with the source.--help(noargparse.SUPPRESSanywhere), sodocs/cli.mdgains them.Tests
New file
tests/test_chain_remedy_callers.py, 30 tests:test_the_manifest_gate_offers_the_sources_it_reads(two-links, three-links, qualified)validate-manifest.py: exit 0, each holder's WARNING exactly, in its own words, no--link-source.qualifiedrecords[opensoft/openXdox, openDox], which is offered by the nameopenXdoxtest_each_source_the_manifest_gate_offers_reads_the_link(variable, beside-the-root)test_a_shared_variable_is_not_offered_by_the_manifest_gate(lowercase, mixed-case)_link_remediesfor two holders sharing a variable, keyed bypin_product_keytest_the_new_copies_run_beside_an_older_library(pre-634de926142, v1.0.1)--link-remedy, beside each olderrepo_shape.py(read withgit show, asserted to hold noremedies): exit 0, no traceback, the older wordstest_the_gate_offers_each_holder_the_callers_wordsOPENYDOX=)test_a_holder_given_no_words_keeps_the_gates_own,test_the_last_words_for_a_link_wintest_words_that_are_not_link_equals_text_are_refused(no-equals, no-link, no-words)argument --link-remedy: … is not LINK=TEXTtest_a_labelled_bare_source_is_named_by_its_label,test_a_labelled_source_holding_an_equals_is_not_said_to_be_a_path,test_an_unused_labelled_source_is_noted_by_its_labeltest_a_key_read_as_a_path_is_spelled,test_a_keyed_value_that_is_also_a_directory_is_spelled,test_a_variable_the_gate_could_not_read_is_spelled,test_a_checkout_beside_the_project_the_gate_could_not_read_is_spelled,test_a_value_that_names_no_path_is_spelled,test_an_unused_link_source_and_its_replacement_are_spelled; and the controltest_a_printable_value_is_named_as_typedSetup's tests (shared helpers in
tests/conftest.py, one-call wrappers in both setup files):tests/conftest.pyassert_step_four_speaks_in_the_plans_words(5 cases: three-links-first-tree, three-links-no-source, blank, holds-an-equals, variable-first)--link-sourcein step (4) or on stderr; the plan's words; the--pin-sourcelabel; the plan's own WARNING line byte for byte. One assert per condition (S9073)tests/conftest.pyassert_an_empty_pin_source_is_the_plans_to_refusere.subof the gate's offer is gone: it now asserts no--link-sourceat all, and the plan's words in step (4)tests/test_setup_project_py.py,tests/test_setup_sh.pytest_step_four_speaks_in_the_plans_wordssetup-project.pyand throughsetup.shtests/test_setup_project_py.pytest_each_holder_is_given_the_plans_words(6)--link-remedyper holder, the chain read as the scaffold reads it, nothing for one linktests/test_setup_project_py.pytest_step_four_and_the_plan_share_one_set_of_wordsshape_materialize's names are one functiontests/test_naming_policy.pytest_the_projects_own_manifest_gate_keeps_its_warning_for_a_tree_it_reached--link-source30676c90with only the tests): 46 failed and 3 passed. The 3 are the controls:test_each_source_…[variable, beside-the-root]andtest_a_printable_value_is_named_as_typed.f5e1867d, each by a named test, and the files restored byte for byte afterwards (sha256 checked).TypeErrorbeside both older libraries.--link-remedynot passed on;--link-remedyvalues taken._shownalwaysrepr.test_naming_policy,test_link_source_chain_defaults,test_pinned_copy_text,test_pin_source_env_collisionsandtest_chain_link_fold;test_manifest_validatorandtest_doctor_validator_verdicts;test_cli_reference;test_setup_project_pyandtest_setup_sh;test_scaffold_manifest_values,test_neutral_pin_validatorandtest_template_text_placeholders;test_repo_hygiene;test_branch_left_bump_leg_setup(bump-leg.py and setup-project.py: thegit switch -cline says under it what to do where an earlier act left that branch, soalready existsis no dead end (#607's sentence, in the two tools it left) #657's) andtest_doctor_next_is_a_command;test_scaffold_leg_path_collisions;test_update_shape*.pyfiles andtest_shape_doctor.After that run, an unused
import shutilwas dropped from the new file (29 passed again).git merge-treeof this head with it is clean. The predicted merge was committed in a scratch tree and passed 532 tests in 7 files: the new file,test_naming_policy,test_pinned_copy_text,test_link_source_chain_defaults,test_derived_id_refusals,test_scaffold_manifest_valuesandtest_cli_reference.scripts/render-cli-reference.py --check: ok.Round 2 (rv661), at
657cb233:validate-manifest.py's_link_remedieswithoutrepo_basename(rv661: 373 tests let it live). Against the two rowsf5e1867dshipped it still lives (2 passed, the control). Against the newqualifiedrow it fails on the WARNING list (test_chain_remedy_callers.py:107), and against the whole new file it fails too. V2, G5 and E3 from round 1 are killed again, each on its assertion. Files restored byte for byte (sha256 checked).test_naming_policy,test_pinned_copy_text,test_link_source_chain_defaults,test_manifest_validator,test_cli_reference,test_setup_project_pyandtest_setup_sh.test_repo_hygiene,test_template_text_placeholders,test_scaffold_manifest_values,test_neutral_pin_validator,test_doctor_validator_verdicts,test_pin_source_env_collisions,test_chain_link_fold,test_scaffold_leg_path_collisions,test_doctor_next_is_a_commandandtest_branch_left_bump_leg_setup.scripts/render-cli-reference.py --check: ok at this head, and ok on the predicted merge with main3726ada2(git merge-tree --write-tree, clean).f5e1867ddid not have (main's two inshape_materialize.py).The re-pin
Because of this PR, assembly roots re-pin
scripts/validate-manifest.pyandscripts/validate-repository-naming.py; family holders re-pin nothing new; against v1.0.1 both already differ at main.COPIED_VERBATIMholdsMANIFEST_VALIDATOR,COPIED_FROM_SHAPEholdsVALIDATE_NAMING). A family holder's lists (FAMILY_COPIED_VERBATIM,FAMILY_COPIED_FROM_SHAPE) hold neither.git diff --quiet v1.0.1 origin/main -- templates/assembly-root/scripts/validate-manifest.pyand-- scripts/validate-repository-naming.pyboth exit 1 (re-measured at55c6ba7d). So this adds no file to a project's v1.1.0 update.scripts/repo_shape.pyis untouched.Not changed (known limits)
--projectand--policypaths, are printed as given.--referent-chainor a manifest, not from--link-source. They appear in "could not answer for ", in the keyed(--link-source <link>=...), after "names no path for", inread_as_path's list of links, and in the library's<holder>in every chain WARNING.--projectand--policypaths are not--link-sourcevalues either.--link-sourcevalues and paths only, and the gate's module docstring now says so.--link-sourceunder a label. Thelink-source-no-pathREFUSED line (--link-source ''` names no path) and the NOTE's advice where the chains begin at more than one link (name the link it is, as --link-source <link>=PATH). Setup reaches neither: it gives step (4) no--link-sourcefor an empty last--pin-source(setup-project.py: step (3) passes an unwritable parent or a missing drive, and step (4) shows a chain the plan verifies as[declared-unverified]#608), and it passes one--referent-chain, so the chains begin at one link. A label in those two lines would need words of its own, because the caller's flag cannot name a link. The help and the docstring say which lines take the label. rv661's mutant M3, under which the label also replaces a keyed value's(--link-source <link>=...), still lives: setup passes no keyed--link-source, so no test gives a label and a keyed value together.validate-manifest.pyasks for no reasons (scripts/validate-repository-naming.py:_link_sources_from_argssplits every--link-sourcevalue at its first=and nothing reports a value no chain link used, so a link tree under a directory nameda=b, or behind a mistyped key, is silently skipped (declared-unverified, exit 0), andrepo_shape.link_manifest_pathlets a source this user cannot enter (keyed,SHAPE_PIN_SOURCE_<LINK>or a sibling checkout) end the naming gate in aPermissionErrortraceback before Python 3.14 #338 kept it so). A checkout beside the root that is there but cannot be read as a declaration is still "not reachable here", and is now offered "a checkout of openXdox beside this root" while one is there.with_reasons=Trueis in every library since v1.0.0, so a change would be skew-safe. It is scripts/validate-repository-naming.py:_link_sources_from_argssplits every--link-sourcevalue at its first=and nothing reports a value no chain link used, so a link tree under a directory nameda=b, or behind a mistyped key, is silently skipped (declared-unverified, exit 0), andrepo_shape.link_manifest_pathlets a source this user cannot enter (keyed,SHAPE_PIN_SOURCE_<LINK>or a sibling checkout) end the naming gate in aPermissionErrortraceback before Python 3.14 #338's decision andtest_the_projects_own_manifest_gate_keeps_its_warning_for_a_tree_it_reachedpins it.<shape parent>/<link>), which step (5)'s scaffold never reads (link_pins_from_trees(chain, None, ...)). Read, not run.--pin-sourceas not used, a NOTE the plan does not print. It now speaks setup's words. Whether setup should pass the bare source then is setup-project.py: step (3) passes an unwritable parent or a missing drive, and step (4) shows a chain the plan verifies as[declared-unverified]#608's design question.Closes #661
🤖 Generated with Claude Code
Summary by Sourcery
Align chain-link remediation messages across manifest validation, naming checks, and setup planning with the sources each caller actually reads.
New Features: