Skip to content

templates/assembly-root/scripts/validate-pins.py: a bare --pin-source <path> whose path holds an = is read as <product>=<path> (raw.partition("=")), so a stand-in clone under a directory named a=b is never consulted and the pin's digest silently goes to gh api and a skip (pins ok, exit 0), or, when the text before the = is a declared pin's name, to a FileNotFoundError traceback #295

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

Found by this lane's adversarial review of the #272 fix (PR #274, head d7d6237; its out-of-scope item O4, where a stand-in under a path holding a space and a second = fell to gh and a skip), and re-verified on main d94d155 (#232's squash) against a scaffolded descendant, gh a shim that logs its argv and exits 97. It is pre-existing and not caused by any open branch. Not claimed: Windows (= is a legal character in a Windows path and partition splits it the same way, but nothing was run there), and the --link-source twin named under "Why", which was read and not probed.

What is wrong. _parse_pin_sources (templates/assembly-root/scripts/validate-pins.py:563-576) splits every --pin-source value on its first = (product, sep, path = raw.partition("="), :572): with an = it is PRODUCT=PATH, without one it is the bare default path. --help says [PRODUCT=]PATH and that a bare path "applies to whichever declared pin has no more specific answer" (:638-646). A bare path with an = in a directory name cannot say that it is a path:

# $C: a scaffolded descendant (--pin openGlass@<sha>), cloned with no sibling checkout. $R/standin/openGlass and $R/a=b/openGlass are copies of the pinned product's stand-in.
# gh is a shim first on PATH that appends its argv to $LOG and exits 97. The git environment is isolated.
cd $R && python3 $C/scripts/validate-pins.py --root $C --pin-source standin/openGlass
#   ok  openGlass: pin commit 4f258a8fef0a digest recomputes (--pin-source)
#   pins ok                                                    exit 0, $LOG empty
python3 $C/scripts/validate-pins.py --root $C --pin-source a=b/openGlass
#   skip openGlass: pin commit 4f258a8fef0a digest NOT rechecked — no local checkout (gh-unreadable: `gh api` could not read opensoft/openGlass @ 4f258a8fef0a80b50aec88ce8d328be65917f48b: ). A --pin-source, SHAPE_PIN_SOURCE_OPENGLASS, or a checkout at ../openGlass beside this assembly root would verify it offline.
#   pins ok                                                    exit 0, $LOG: api repos/opensoft/openGlass/git/trees/4f258a8f...?recursive=1
python3 $C/scripts/validate-pins.py --root $C --pin-source openGlass=a=b/openGlass
#   ok  openGlass: pin commit 4f258a8fef0a digest recomputes (--pin-source openGlass=...)      exit 0, $LOG empty   (the first `=` splits; the rest is the path)
python3 $C/scripts/validate-pins.py --root $C --pin-source openGlass=x/openGlass      # the stand-in is in a directory named `openGlass=x`
#   Traceback (most recent call last):
#     ...
#     File ".../scripts/validate-pins.py", line 485, in _recompute_neutral_pin_digest
#       return tree_digest(source, commit), how
#     ...
#   FileNotFoundError: [Errno 2] No such file or directory: 'x/openGlass'                   exit 1

In the second run the source is {"a": Path("b/openGlass")}: a keyed source for a product that no pin declares, which nothing ever looks up. The validator's own skip text then tells a person who passed --pin-source that "a --pin-source ... would verify it offline". The same silence covers a mistyped key: --pin-source openGlas=standin/openGlass (a path that exists) gave the same skip and the same one gh api line. In the last run the key is a declared pin, the path is not what the person meant, and the missing directory is not a finding but a crash: tree_digest raises FileNotFoundError out of subprocess.run and only Refusal is caught (:484-490); --pin-source openGlass=<a directory that does not exist> dies the same way (checked), where a directory that is not a checkout is the neutral-pin-source-unreadable FINDING.

Why. The split is the only thing deciding "keyed or bare", and it knows nothing about what a product is: it builds keyed_sources from whatever precedes the first =, and resolve_neutral_pin_source (:352-385) only looks sources up by a declared pin's name. Nothing afterwards asks whether a source was used. The same five lines are in scripts/validate-repository-naming.py:404 (_link_sources_from_args, for --link-source; read, not probed).

What it should do. An explicit source is either used, or said not to have been, or refused. The decisions are the author's:

  1. Which reading wins for a value holding =. Either a value that names an existing directory as a whole is a bare path (a path that exists wins), or the text before the first = is a key only when it is a declared neutral_product_pins name (casefolded, as keyed_sources is read) and otherwise the whole value is a bare path. Both leave openGlass=x/openGlass ambiguous when a directory named openGlass=x exists and the pin is openGlass: refuse it as ambiguous naming both readings, or let the keyed reading win and say so. A distinct spelling for the bare form is a CLI change in a copied file.
  2. A source nothing consumed. After the pins are checked, a keyed or bare --pin-source that no declared pin used is reported (a note in the skip style, or a finding). It is what the mistyped key and the a=b case have in common, and it holds whichever reading decision 1 takes.
  3. An explicit source that cannot answer is a finding, never a skip and never a traceback: a path that does not exist becomes neutral-pin-source-unreadable like one that is not a checkout.
  4. validate-pins.py is COPIED into every project and digest-pinned in contracts/shape-pin.yaml, so a change reaches projects only through update-shape.py; and --help is rendered into docs/cli.md by scripts/render-cli-reference.py (tests/test_cli_reference.py fails when the two disagree), so a changed help text regenerates it. The --link-source twin is decided with it or says why not.

How to test. In the style of tests/test_neutral_pin_validator.py (stubbed gh, source passed explicitly): the stand-in under a directory named a=b with a bare --pin-source asserts digest recomputes and an empty stub log (the helper assert_pin_digest_recomputed that #274 adds to tests/test_scaffold_pin_and_reuse.py asserts the positive line and no skip); one for the openGlass=x directory (the decided reading, not a traceback); one for a keyed source naming a missing directory (a finding, exit 1); one for a source no pin used.

Related: #272 (its review's O4; PR #274), #275 (the same file's pin_source_env_name collision: the other way a source silently does not answer), tests/test_neutral_pin_validator.py.

Activity

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-4, session 3431c370-df80-4c11-a041-41bce42d8b38@Eagle, 2026-10-12T00:42:00Z, for #295

    Logged in lanes/log/openRepoShape-4.md at e4718425f090c585cecb87c586231acda1a48ee7.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#295:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#295"`, kept where the row or its body names `#295`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `295` as a whole token:
       none
    
  2. added a commit that references this issue on Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions