Skip to content

validate-repository-naming.py and validate-manifest.py: a set SHAPE_PIN_SOURCE_<LINK> the run guard passes over is named once by a link-source-collides note, --link-remedy=openXdox=a/openYdox=b gives openXdox its words, and a row runs setup step (4) from another folder (#990, #991, part of #999) - #1011

Merged
brettheap merged 8 commits into
mainfrom
l3-990-991-pinned-copies
Oct 20, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Closes #990.
Closes #991.
Part of #999.

One pull request over the pinned copies, so scripts/validate-repository-naming.py and templates/assembly-root/scripts/validate-manifest.py re-pin every project once in v1.2.0. scripts/repo_shape.py is not changed. There is one commit per issue, a normal merge of main at 04923f10d, fix round 1's commit 77185a05e, and a normal merge of main at 39694097e, which holds #974.

#990: a set SHAPE_PIN_SOURCE_<LINK> the run guard passes over is said once

Way (b), chosen from the code. Each chain reader builds its own note from pin_source_env_collisions and the environment, as validate-pins.py builds neutral-pin-source-collides. resolve_link_source and tried are left alone. The reasons:

What a person now sees.

  • The gate prints one NOTE link-source-collides: SHAPE_PIN_SOURCE_<LINK> is set, but <a> and <b>, products this run reads, share it: it cannot say which it holds, so it is read for none of them. A link's own tree is read from a sibling checkout or --link-source <link>=<path>. --quiet drops it with every NOTE.
  • The root's validate-manifest.py prints one note link-source-collides: ..., ending "A link's own tree is read from a checkout of it beside this root." It is spelled as validate-pins.py spells a note, so the doctor's reported reads it as a note on the manifest row. A line it does not know as an item would be read as the tail of the WARNING before it. main counts a note as no finding.
  • Each note names the variable once. A variable met again in a later leg's lookup adds only the products not named yet.

Decisions the Direction left open.

  • Only a set, non-empty variable is said. resolve_link_source reads only a value, so an unset or empty variable stays unsaid, as before.
  • Only where a HOLDER of the chain shares it. Only a holder's declaration is read, so a variable passed over only for the chain's end decided nothing.
  • Only where the library asks the guard over the run (run_products=). An older library asks over the chain alone. It READS a variable that a declared pin shares with a holder, so saying the variable was read for none would be untrue.
  • Nothing is said under --link-source-label. The caller speaks for someone else there, as _unused_remedy_lines already does.
  • The doctor runs the gate under --quiet. So the fact is said once, on the manifest row.
  • The root's copy keys each group by pin_product_key, not with zip(). Sonar asks for strict= on zip(), and these copies still run on Python 3.9, which lacks it.
  • validate-repository-naming.py and validate-manifest.py: a SHAPE_PIN_SOURCE_<LINK> that is set and that the run guard passes over, because two products share it, is named by no line, so the declared-unverified WARNING reads as if none were set #990's control probe is held with a different chain. The test holds it as --referent-chain open-x,openGlass, a variable open-x has to itself, read and verified. The issue's own open-x,openY,openGlass is the same case.

#991: RV949's three leftovers

  • SHOULD-3. test_step_four_reads_a_relative_variable_where_step_five_does now starts setup with cwd=tmp_path. The unit test test_only_a_pin_source_variable_that_names_a_path_is_joined hands pin_sources_joined a shape_root of tmp_path / "standard", which is not the working folder. Mutant NJ now fails both (see Mutants).
  • NIT-8. The --link-remedy loop of LinkSources.__init__ is lifted whole into _read_remedies(remedies), which __init__ calls once. _read_remedies sits just after _read_remedy.
  • The tightening. _read_remedy passes over an = whose owner, the text before the link's last /, holds an =, since no owner is spelled with one.
    • --link-remedy=openXdox=a/openYdox=b now gives openXdox the words a/openYdox=b. Before, it gave openYdox the words b.
    • opensoft/open=y=OURS for y still gives open=y its words.
    • Each case has a new row in tests/test_chain_remedy_callers.py.

#999, its second part only

_run_products's docstring says "missing" for "bad or missing". The gate's _project_chain_links leaves out only a leg with no repository:.

What a project sees

Measured by RV1011 from a root pinned at main 3864b18cc, against a merge of the first head with main:

  • update-shape.py check lists upstream-changed scripts/validate-manifest.py and upstream-changed scripts/validate-repository-naming.py, with 9 files unchanged.
  • update-shape.py apply exits 0, copies both, and re-pins contracts/shape-pin.yaml and project.yaml. The project's validators then print manifest ok, and a fresh check prints unchanged 11.
  • The new sha256 of each, re-measured at head f5749b7b2 after fix round 1, which does not change either:
    • scripts/validate-repository-naming.py: 9892288d2ee05a1dd536e0d4cb58ac19d8cba37e588f08dd18243903ac5b228f;
    • scripts/validate-manifest.py: 13cf2c9100debaf79185ac965380c5d163f6fc7a94d626b6f906d5bbef1ad6f5.
  • scripts/shape_materialize.py, which fix round 1 changes, is the standard's own and is not copied into a project, so it re-pins nothing.

Out of scope

Tests

Every run was on head f090a3cdb, one at a time, under timeout 2400, with:

  • a stub gh (exit 97) first on PATH;
  • GH_TOKEN=bogus and an empty GH_CONFIG_DIR;
  • GIT_SSH_COMMAND=false and EMAIL=tests@openreposhape.invalid.

Each was /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<name> -o tmp_path_retention_policy=failed <files>:

Files Result
tests/test_chain_link_fold.py tests/test_chain_pin_sources.py tests/test_chain_remedy_callers.py tests/test_link_source_chain_defaults.py tests/test_pin_source_env_collisions.py tests/test_pin_source_top_level.py tests/test_pin_source_unreadable.py tests/test_manifest_validator.py tests/test_naming_policy.py tests/test_scaffold_referent_chain_links.py tests/test_non_list_project_fields.py tests/test_legs_not_a_list.py tests/test_legs_readers_not_a_list.py 1056 passed in 276.48s (0:04:36)
tests/test_setup_project_py.py 248 passed in 103.32s (0:01:43)
tests/test_pinned_copy_text.py tests/test_validate_pins_lockstep_lines.py tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_update_add_codes_documented.py 285 passed in 70.42s (0:01:10)
tests/test_no_auto_maintenance.py 60 passed in 8.71s
tests/test_doctor_validator_rows.py tests/test_doctor_validator_verdicts.py 110 passed in 169.24s (0:02:49)

No tests/test_validate_*naming* file exists.

Mutants

Each mutant changed one line (or one statement), ran the named file, and restored the file. Each file's sha256 was checked to be the same before and after. All 17 were killed.

Mutant Killed by
G1 gate: _shared_variable_lines not added to unused_lines test_the_gate_reads_a_shared_variable_for_no_chain[set], test_the_gate_says_a_set_variable_it_passed_over_once[two-links-of-one-chain-set] and 4 more (6 failed)
G2 gate: said where the variable is unset test_the_gate_reads_a_shared_variable_for_no_chain[unset], test_the_gate_says_a_set_variable_it_passed_over_once[two-links-of-one-chain-unset], [a-link-and-a-declared-pin-unset]
G3 gate: holders chain[:-1] becomes chain test_a_variable_only_a_chains_end_shares_is_not_said
G4 gate: the if self.label: return [] dropped test_the_gate_says_it_nowhere_it_says_no_note[under-a-label]
G5 gate: guard asked over the chain alone test_the_gate_says_a_set_variable_it_passed_over_once[a-link-and-a-declared-pin-set], test_a_variable_two_declared_pins_share_is_read_for_no_chain and 3 more
G6 gate: run products even where the library takes none test_each_copy_says_it_only_where_the_library_passed_it_over[v1.1.1]
G7 gate: a variable met again names its products again test_the_gate_reads_a_shared_variable_for_no_chain[set] and 3 more
G8 gate: "=" not in owner dropped (#991) test_a_remedys_link_is_the_one_a_chain_names[an-owner-holding-an-equals]
G9 gate: owner is all the text before the = (#991) test_a_remedys_link_is_the_one_a_chain_names[an-owner-before-a-link-holding-an-equals] and 4 more
G10 gate: _read_remedies says a value was replaced by itself (NIT-8's moved loop) test_a_remedy_a_later_one_replaced_is_noted[a-a]
M1 root: _shared_variable_notes not added test_the_manifest_gate_reads_a_shared_variable_for_no_chain[chain-set], [qualified-chain-set], [pin-set], test_the_doctor_reads_the_roots_note_as_a_note and 1 more
M2 root: a note counted as a finding the same 5 (exit 1)
M3 root: run products even where the library takes none test_each_copy_says_it_only_where_the_library_passed_it_over[v1.1.1]
M4 root: holders links[:-1] becomes links test_the_root_keeps_a_variable_only_for_a_holder[the-end-alone]
M5 root: said where the variable is unset test_the_manifest_gate_reads_a_shared_variable_for_no_chain[chain-unset], [qualified-chain-unset], [pin-unset]
M6 root: spelled NOTE, not note the M1 five, the doctor's row among them
NJ (#991): setup-project.py's os.path.join(str(shape_root), value) becomes os.path.join(os.getcwd(), value) test_step_four_reads_a_relative_variable_where_step_five_does[verified], [breaking], test_only_a_pin_source_variable_that_names_a_path_is_joined (3 failed)

setup-project.py had sha256 4acf81dfb52ffd1492fdbc7d9e9e651ab54ed76bc7ae8a11416d022670d88683 before NJ and after it, which is the digest RV949 gave. The tree was clean after the run.

Merges

git merge-tree --write-tree of head f090a3cdb with:

No other open PR touches these files.

Fix round 1

RV1011 (issuecomment-6278436672) found the first head READY, with one SHOULD and three NITs. The head is now f5749b7b231c3a52ec6a629d1d1fee3fed9de25f: commit 77185a05e, then a normal merge of origin/main 39694097e.

  • SHOULD-1. The standard reads the root's validator in process: shape_materialize._validator_notes, behind the scaffold's manifest_invalid and adopt's manifest_judged.
    • It dropped only the WARNINGs _findings returns. So the root's link-source-collides note read there as a finding: a refusal sentence, and manifest_judged None. The root's own main counts it as none.
    • No input reaches it today (RV1011's probes), so it was hidden. It is fixed by the review's way (a): _validator_notes drops a line that starts note as it drops a WARNING. _findings's contract stays as it is, and shape_materialize.py is not a pinned copy.
    • The new row test_the_standard_reads_the_roots_note_as_no_finding[chain] and [pin] sets SHAPE_PIN_SOURCE_OPENSS. It holds that _findings returns the note, and that _validator_notes is [], manifest_invalid is [] and manifest_judged is frozenset().
  • NIT-1. test_the_gate_reads_a_shared_variable_for_no_chain and test_the_manifest_gate_reads_a_shared_variable_for_no_chain gain an empty id beside set and unset. An empty variable is read for no product, so it is not said; the conftest hands children "", and so does a person who exported it empty.
  • NIT-2. Reason 1 for (b) no longer says the scaffold prints tried.
  • NIT-3. "What a project sees" is added above.

Tests at head f5749b7b2, one at a time, as above:

Files Result
tests/test_chain_pin_sources.py tests/test_chain_remedy_callers.py tests/test_manifest_validator.py 206 passed in 44.52s
tests/test_adopt_manifest_values.py tests/test_adopt_plan_yaml_quoting.py tests/test_family_init_values.py tests/test_reader_refusals_named.py tests/test_scaffold_manifest_values.py 852 passed, 19 skipped in 394.36s (0:06:34); the skips are rows for Windows and for a git filter-repo this host lacks
tests/test_doctor_validator_verdicts.py 39 passed in 55.52s
tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_update_add_codes_documented.py tests/test_repo_hygiene.py 176 passed in 36.41s
tests/test_no_auto_maintenance.py 60 passed in 9.73s
tests/test_shape_doctor.py, once 445 passed, 3 skipped in 665.32s (0:11:05)

Mutants, each run on tests/test_chain_pin_sources.py and restored, with the same sha256 before and after:

Mutant Killed by
FR1: _validator_notes drops only WARNING again test_the_standard_reads_the_roots_note_as_no_finding[chain], [pin]
MY3 (gate): os.environ.get(variable) becomes variable in os.environ test_the_gate_reads_a_shared_variable_for_no_chain[empty]
MY3r (root): the same test_the_manifest_gate_reads_a_shared_variable_for_no_chain[chain-empty], [qualified-chain-empty], [pin-empty]

Merges: git merge-tree --write-tree of f5749b7b2 with:

No other open PR touches these files.

Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code

Summary by Sourcery

Improve validation diagnostics for ambiguous pin-source variables and link remedies while preserving informational-note semantics across project tooling.

New Features:

  • Report set pin-source environment variables that collide across products as a single non-failing note in both repository and manifest validation.
  • Parse link remedies containing equals signs more accurately so values are assigned to the intended link.

Bug Fixes:

  • Ensure manifest notes are treated as informational rather than validation findings by the root validator, doctor, and in-process standard checks.
  • Run setup-project validation from a separate working directory in coverage for relative pin-source paths.

Enhancements:

  • Refactor link-remedy processing into a dedicated reader while preserving replacement handling.
  • Clarify run-product documentation and strengthen coverage for shared pin sources, remedy parsing, validator notes, and relative paths.

Documentation:

  • Update validator help text and behavior descriptions for colliding pin-source variables and link-remedy parsing.

Tests:

  • Add comprehensive tests for collision notes, suppression under labels and quiet mode, legacy library behavior, remedy values containing equals signs, and setup execution from another folder.
  • Add regression coverage ensuring informational manifest notes do not affect scaffold, adoption, or doctor verdicts.

Chores:

  • Update pinned-copy encoding fixtures to use an encoding consistently rejected by Git.

Brett Heap and others added 4 commits October 19, 2026 20:43
…SOURCE_<LINK>` that is set, and that the run guard passes over because two products of the run share it, is named once by a `link-source-collides` note that fails nothing, so the `declared-unverified` WARNING no longer reads as if nothing were set (#990)

#275's guard reads a variable two products share for neither, and #909
asks it over every product of the run, but a skipped source leaves no
row in `tried`, so neither chain reader named the variable a person had
set. Each now says it once per such variable, built from
`pin_source_env_collisions` and the environment, as `validate-pins.py`
says `neutral-pin-source-collides`, and leaves `tried` and
`scripts/repo_shape.py` alone (#990's way (b)):

- the gate: a `NOTE link-source-collides` naming the variable, the
  products of the run that share it, and `--link-source <link>=<path>` or
  a sibling checkout. `--quiet` drops it with every NOTE, and nothing is
  said under `--link-source-label`, where the caller speaks for the
  scaffold;
- the root's `validate-manifest.py`: a `  note link-source-collides`,
  spelled as `validate-pins.py` spells a note so the doctor's `manifest`
  row reads it as a note, naming a checkout beside the root. `main`
  counts it as no finding.

Only a set, non-empty variable is said, only where a holder of the chain
shares it, and only where the library asks the guard over the run: an
older library reads a variable a declared pin shares, so it is not said
to be read for none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s step (4) from a folder other than the standard's checkout, `LinkSources.__init__` hands its `--link-remedy` loop to `_read_remedies`, and `--link-remedy=openXdox=a/openYdox=b` gives `openXdox` its words, not `openYdox` the words `b` (#991)

RV949's three leftovers on #949:

- SHOULD-3: the relative-variable row starts setup with `cwd=tmp_path`,
  and the unit test hands `pin_sources_joined` a `shape_root` that is
  not the working folder, so a step (4) that joined the variable to the
  folder setup started in, and not to the standard's checkout, fails
  both (mutant NJ).
- NIT-8: the remedy loop is lifted whole into `_read_remedies`, which
  `__init__` calls once, to bring it back under SonarCloud's cognitive
  complexity of 15 (python:S3776).
- The tightening: `_read_remedy` passes over an `=` whose owner, the text
  before the link's last `/`, holds one, since no owner is spelled with
  an `=`. `opensoft/open=y=<words>` still gives `open=y` its words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e leaves out a leg whose `repository:` is missing, not one whose `repository:` is bad or missing (part of #999)

The gate's `_project_chain_links` asks `"repository" not in leg`, so it
keeps a leg whose `repository:` is present and bad, as RV981 measured.
Only the words were wrong: the run fails on such a leg either way. The
help text, #999's first part, is not here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR re-pins the validator copies to report otherwise invisible shared SHAPE_PIN_SOURCE collisions as non-failing, deduplicated notes, aligns remedy parsing with link names containing equals signs, and strengthens relative-path setup coverage while preserving compatibility with older repo_shape libraries.

Sequence diagram for shared pin-source collision reporting

sequenceDiagram
    participant Caller
    participant Validator
    participant Guard as Pin source guard
    participant Output

    Caller->>Validator: link_pins(policy, target)
    Validator->>Guard: pin_source_env_collisions(chain + run_products)
    Guard-->>Validator: shared SHAPE_PIN_SOURCE variables
    Validator->>Validator: _record_shared_variables(chain, run_products)
    Caller->>Validator: unused_lines()
    Validator->>Validator: _shared_variable_lines()
    Validator->>Output: link-source-collides NOTE once per variable
    Output-->>Caller: non-failing diagnostic
Loading

Sequence diagram for manifest collision notes

sequenceDiagram
    participant Manifest
    participant Validator
    participant Guard as Pin source guard
    participant Output

    Manifest->>Validator: _findings(manifest)
    Validator->>Validator: _run_products(pins, legs)
    loop each manifest leg
        Validator->>Guard: _record_shared_variables(shared, chain, run_products)
        Guard-->>Validator: shared variables for this chain
    end
    Validator->>Validator: _shared_variable_notes(shared)
    Validator->>Output: print  note link-source-collides
    Validator->>Validator: exclude  note from findings
    Output-->>Manifest: note does not change exit status
Loading

Flow diagram for link remedy parsing with equals signs

flowchart TD
    A[Read --link-remedy value] --> B[Inspect equals signs from right to left]
    B --> C{Candidate owner contains =?}
    C -->|Yes| B
    C -->|No| D{Candidate link is named in chain?}
    D -->|Yes| E[Use candidate link and trailing words]
    D -->|No| B
    B --> F[Fallback to first equals sign]
    E --> G[Store remedy for pin_product_key]
    F --> G
Loading

Flow diagram for deduplicated shared-variable notes

flowchart TD
    A[Inspect chain and run products] --> B[Find products sharing SHAPE_PIN_SOURCE variable]
    B --> C{Variable set and shared by a holder?}
    C -->|No| D[Do not report]
    C -->|Yes| E[Add only previously unnamed products]
    E --> F{Caller has a link-source label?}
    F -->|Yes| D
    F -->|No| G[Emit one link-source-collides NOTE]
    G --> H[NOTE is non-failing and quiet-filterable]
Loading

File-Level Changes

Change Details Files
Report shared pin-source environment collisions without changing validation outcomes.
  • Track set SHAPE_PIN_SOURCE variables passed over by the guard, limited to holder links and the library’s run-product scope.
  • Emit one deduplicated link-source-collides NOTE in the repository-naming gate, honoring quiet mode and source labels.
  • Collect and render equivalent notes in the assembly-root manifest validator, while excluding notes from exit-status findings.
  • Add coverage for set/unset variables, holder/end-link behavior, deduplication, labels, quiet mode, library-version skew, and doctor parsing.
scripts/validate-repository-naming.py
templates/assembly-root/scripts/validate-manifest.py
tests/test_chain_pin_sources.py
Make link-remedy parsing preserve words when an owner contains an equals sign.
  • Skip candidate separators whose owner text before the link’s final slash contains =.
  • Preserve existing last-remedy-wins and replacement bookkeeping through a dedicated remedy-reading helper.
  • Add cases for nested links and owners containing equals signs.
scripts/validate-repository-naming.py
tests/test_chain_remedy_callers.py
Correct setup-project relative-path test coverage for execution from a different working directory.
  • Run the setup step-four regression from a temporary directory rather than the checkout.
  • Verify pin-source joining uses the supplied shape root instead of the process working directory.
  • Add mutation-sensitive assertions for relative path resolution.
tests/test_setup_project_py.py
Clarify the manifest run-product documentation to distinguish missing repositories from invalid ones.
  • Update the _run_products docstring to match the gate’s handling of legs without repository fields.
templates/assembly-root/scripts/validate-manifest.py

Assessment against linked issues

Issue Objective Addressed Explanation
#990 When a set SHAPE_PIN_SOURCE_ variable is passed over because products read by the run share it, have both chain readers emit one note naming the variable and the sharing products, and explain how a link's tree can be read instead. ✅
#990 Keep the note informational: unset or empty variables and variables read for a link should not be reported, and the note must not change findings or exit status; respect each reader's quiet/label behavior and exclude the scaffold. ✅
#991 Add regression coverage showing that setup step (4) resolves relative SHAPE_PIN_SOURCE_ paths against the standard's checkout, even when setup is started from a different working directory. ✅
#991 Reduce LinkSources.init cognitive complexity by moving the --link-remedy processing loop into a separate method. ✅
#991 Parse hand-typed --link-remedy values so an equals sign in the owner portion does not make a later link appear to own the remedy words, while preserving valid owner-qualified links containing equals signs. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-19T22:07:18Z) RV1011 review of #1011 at f090a3c: READY (once CI "tests" is green; it was pending).

SHOULD-1. templates/assembly-root/scripts/validate-manifest.py:1240 now returns the " note link-source-collides" line from _findings(). main() drops it (:1276), but scripts/shape_materialize.py:5295 (_validator_notes, used by manifest_invalid and manifest_judged) drops only "WARNING". Measured in process with SHAPE_PIN_SOURCE_OPENSS set: head's _validator_notes and manifest_invalid return the note as a refusal and manifest_judged returns None; main returns [] and [] and []. No input reaches it today: --pin with a sharp s is pin-malformed, a second colliding pin is scaffold-pin-collides, an open_x middle link is chain-link-malformed, a broken chain is not recorded, and adopt writes no chain. Fix: _validator_notes drops startswith(("WARNING", " note ")) plus a row (shape_materialize.py is not a pinned copy), or keep notes out of _findings and print them in main, or a follow-up issue.

NIT-1. No row holds "set but empty is not said". Mutants MY3 (gate :512) and MY3r (root :269), os.environ.get(variable) -> variable in os.environ, survive tests/test_chain_pin_sources.py (46 passed); MY3 also survives test_pin_source_env_collisions.py and test_link_source_chain_defaults.py (160 passed). The behaviour is right (empty: exit 0, no note). Add an "empty" id beside set/unset in one row per reader.
NIT-2. Reason 1 for (b) says the scaffold prints tried; scaffold-project.py:1326-1327 passes no tried= (with_reasons only). Reasons 2 and 3 hold: every tag v1.0.0 to v1.1.1 has pin_source_env_collisions(products) with the same signature, and #974 changes repo_shape.py.
NIT-3. The body could name what a project sees. From a root pinned at main 3864b18: update-shape.py check lists upstream-changed scripts/validate-manifest.py and upstream-changed scripts/validate-repository-naming.py, 9 unchanged; apply exits 0, the project's validators say manifest ok, and a re-check says unchanged 11. New digests: 9892288d (naming gate), 13cf2c91 (validate-manifest).

Verified. Gate probes at main and head, beside the current library, v1.0.0 and v1.1.1: one NOTE per variable only where the library passed the variable over; same exit codes; otherwise byte-identical; --quiet drops it; the control (a variable a link has to itself) says nothing. Root probes with the chain and pin layouts beside the same three libraries: one " note" beside the current library with exit 0; beside v1.0.0 and v1.1.1 the variable is read, so no note, and the output matches main. NIT-8: init is 15 by hand (main 19 less the loop's 4); SonarCloud on f090a3c: gate OK, 0 issues, 0.0% new duplication. Tightening edge cases: owner-holding-"=" cases fixed; "=" only after the last "/", several "/", an empty owner and backslash Windows paths read as at main; an owner holding "=" (own=er/...) now reads as an unused link, which is fine. #999's docstring word matches _project_leg_chains.

Tests at head, one at a time, stub gh: test_chain_pin_sources 46 passed; test_chain_remedy_callers 52 passed; test_setup_project_py 248 passed; test_pinned_copy_text 87 passed; test_validate_pins_lockstep_lines 22 passed; test_doctor_validator_verdicts 39 passed; test_repo_hygiene 100 passed; test_agents_md_codes 47 passed; test_cli_reference 23 passed; test_update_add_codes_documented 6 passed; test_no_auto_maintenance 60 passed. Skipped: the rest of the writer's 13-file chain batch, test_shape_doctor.py, and a test run on the #974 merge (merge-tree only).

Mutants: G8 killed by [an-owner-holding-an-equals]; NJ 3 failed (setup-project.py sha 4acf81df before and after); M2 5 failed; MY1 (gate note once per product) 6 failed; MY1r (root) 5 failed; MY2 ("=" anywhere before the link) 5 failed; MY3/MY3r survive (NIT-1).

Merges: merge-tree of the head with origin/main 3864b18, #974 dc82a4c, #1008 b2dbc8b and #958 7e87e05: all clean. On the merges: with #958 test_chain_pin_sources 46 passed; with #1008 the step (4) rows 3 passed.

Lane: openreposhape-3 (openRepoShape-3)

Brett Heap and others added 2 commits October 19, 2026 22:30
…he root's validator drops its ` note link-source-collides`, as the root's own `main` does, so it is never a refusal sentence or a `manifest_judged` of None, and a set-but-empty `SHAPE_PIN_SOURCE_<LINK>` is held unsaid in both chain readers (#990)

RV1011's fix round 1 on #1011:

- SHOULD-1: `_validator_notes`, behind `manifest_invalid` and
  `manifest_judged`, dropped only the WARNINGs the root's `_findings`
  returns, so the root's new note read as a finding. It drops a line
  that starts `  note ` too. shape_materialize.py is not a pinned copy.
  `test_the_standard_reads_the_roots_note_as_no_finding` holds it.
- NIT-1: the gate's and the root's shared-variable rows gain an `empty`
  id beside `set` and `unset`: an empty variable is read for no product
  and is not said.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-19T22:58:02Z) RV1011 delta re-check of #1011, fix round 1, at f5749b7: READY. No new finding.

SHOULD-1 closed. 77185a0 changes only _validator_notes' filter (scripts/shape_materialize.py:5300-5302) to drop " note " as it drops WARNING, plus two docstrings. My probe p7 re-run at the new head with SHAPE_PIN_SOURCE_OPENSS set: _validator_notes [], manifest_invalid [], manifest_judged [] (the first head gave the note, the note as a refusal, and None); unset is unchanged. _validator_notes has two callers (_invalid_problems at :5217, manifest_judged at :5322). The only " note " line the assembly validator prints is link-source-collides (validate-manifest.py:293); findings are "FINDING <code>:" (_finding, :636), so the new filter drops no finding.

Mutants on tests/test_chain_pin_sources.py, each restored to the same sha256: MY3 (gate, variable in os.environ) 1 failed, killed by test_the_gate_reads_a_shared_variable_for_no_chain[empty]; MY3r (root) 3 failed, killed by test_the_manifest_gate_reads_a_shared_variable_for_no_chain[chain-empty], [qualified-chain-empty], [pin-empty]; _validator_notes back to WARNING only: 2 failed, killed by test_the_standard_reads_the_roots_note_as_no_finding[chain], [pin].

Merge: merge-tree of 77185a0 with 3969409 gives f5749b7's own tree (4771e776), so no hand resolution. Over the six branch files, f090a3c..f5749b7 is exactly the fix commit (2 files, +68/-18), and 77185a0..f5749b7 is empty there. Merge-tree of the new head with current origin/main 2d6f928 (#1008's squash), #1010 37b0fc4 and #958 7e87e05: all clean.

Tests at f5749b7, one at a time, stub gh: test_chain_pin_sources 52 passed; test_manifest_validator 102 passed; test_scaffold_manifest_values 105 passed; test_reader_refusals_named 44 passed; test_no_auto_maintenance 60 passed.

Body: NIT-2's reason 1 is now true (the scaffold passes only with_reasons=True, scaffold-project.py:1326-1327). NIT-3's "What a project sees" matches my measurement, and the two digests 9892288d and 13cf2c91 are the same at the new head. shape_materialize.py is in neither copy list, so it re-pins nothing.

Lane: openreposhape-3 (openRepoShape-3)

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T00:14:42Z) F990 on #1011, fix round 2: CI run 39337956970 at f5749b7b failed 3 cases. Two are main's; one is a known test fragility, and lane 5's #958 already fixes it.

Cause.

  • Git refuses working-tree-encoding=UTF-16LE-BOM only for a file of odd byte count. An even one it decodes as UTF-16LE and stores as other bytes.
  • The case expects git to refuse the 5 pinned shape copies. On main the only odd copy is validate-manifest.py (55483 bytes), so the case passes there.
  • This PR makes that copy 59250 bytes and validate-repository-naming.py 74982, so all five are even. check then rightly reports "store 5 pinned shape copies as other bytes" instead.
  • Probe: git add of each copy under *.py working-tree-encoding=UTF-16LE-BOM. At origin/main only validate-manifest.py is refused ("failed to encode ... from UTF-16LE-BOM to UTF-8"); at f5749b7b all five are added.
  • Reproduced locally at f5749b7b: 1 failed, 52 passed.

No second fix.

Lane 3's plan: when the fix for #1024 lands, #1011 merges main and cherry-picks 7e87e05fa unchanged, in one push. #1011 then waits on neither #958 nor a second CI run. The two PRs make the identical change, so whichever lands second merges cleanly. RV1011 re-checks the delta.

Lane: openreposhape-3 (openRepoShape-3)

Brett Heap and others added 2 commits October 20, 2026 01:43
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… under `*.py working-tree-encoding=UTF-16`, which refuses every pinned copy whatever its size, not UTF-16LE-BOM, which refuses only an odd byte count (#958)

CI's `tests` job failed at cf8d566 (run 39268984740) on
`test_check_finds_what_the_split_would_store_otherwise[copies-refused-by-an-encoding]`:
`check` said the source's rules store the 5 pinned shape copies as other
bytes, where the case expected git to refuse them.

Git refuses `working-tree-encoding=UTF-16LE-BOM` only for a file of an odd
byte count; an even one it decodes as UTF-16LE and stores as other bytes
(measured with git 2.43.0 and 2.47.1). Main's copies include two of odd
size, `validate-manifest.py` and `bootstrap.py`; this branch made both even,
so all five are even and the case drew `check`'s other finding. `check`
is right both ways; the case's expectation turned on the copies' sizes.

`UTF-16` with no byte order requires a BOM, which no pinned copy has, so
git refuses every copy at any size: the case now pins that refusal and
passes on main's copies and on this branch's.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-20T01:54:09Z) #1011 is pushed at 8332efb9 (8332efb93939cc89679a12c7ce8b564b2a8ba18c). This is the plan of issuecomment-6280230104, and nothing else.

Lane: openreposhape-3 (openRepoShape-3)

@brettheap
brettheap marked this pull request as ready for review October 20, 2026 03:58
@sourcery-ai

sourcery-ai Bot commented Oct 20, 2026

Copy link
Copy Markdown

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 3 days and 17 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@brettheap
brettheap merged commit f309061 into main Oct 20, 2026
5 checks passed
@brettheap
brettheap deleted the l3-990-991-pinned-copies branch October 20, 2026 03:58
brettheap pushed a commit that referenced this pull request Oct 20, 2026
…lict, so #1025 is tested against main as it stands, with #983's change to validate-family.py and #1026's fix of main's two red rows (#936)

Main took twelve squashes since e076e3e: #1026, #958, #986, #1002,
#1016, #1011, #1021, #1022, #973, #975, #1015 and #983. Only #983
touches validate-family.py, in `_git_runs` and the member checks, apart
from this branch's hunks; it touches none of this branch's test files.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment