Repository navigation
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
Description
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 1In 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:
- 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 declaredneutral_product_pinsname (casefolded, askeyed_sourcesis read) and otherwise the whole value is a bare path. Both leaveopenGlass=x/openGlassambiguous when a directory namedopenGlass=xexists and the pin isopenGlass: 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. - A source nothing consumed. After the pins are checked, a keyed or bare
--pin-sourcethat no declared pin used is reported (a note in theskipstyle, or a finding). It is what the mistyped key and thea=bcase have in common, and it holds whichever reading decision 1 takes. - 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-unreadablelike one that is not a checkout. validate-pins.pyis COPIED into every project and digest-pinned incontracts/shape-pin.yaml, so a change reaches projects only throughupdate-shape.py; and--helpis rendered intodocs/cli.mdbyscripts/render-cli-reference.py(tests/test_cli_reference.pyfails when the two disagree), so a changed help text regenerates it. The--link-sourcetwin 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
brettheap commentedon Oct 12, 2026
CLAIMED — lane openRepoShape-4, session 3431c370-df80-4c11-a041-41bce42d8b38@Eagle, 2026-10-12T00:42:00Z, for #295
Logged in
lanes/log/openRepoShape-4.mdate4718425f090c585cecb87c586231acda1a48ee7.The three reads (lane-collision-protocol Rule 1):
validate-pins.py: a named pin source is judged by `stat()` inside its…
tests/test_neutral_pin_validator.py: an operator's own stdout handler…
Merge origin/main (f795cf7: #295 via #312, #300 via #317, #255 via #270…
Merge origin/main f795cf7 into fix/adopt-relative-submodule-url: #255'…
Merge origin/main (f795cf7) into fix/adopt-late-failure-notice
Merge origin/main 8cdca5e into fix/adopt-root-kept-symlinks: thirteen…
Both roots' scripts/bootstrap.py: `main` escapes what stdout cannot e…
validate-repository-naming.py, repo_shape.py, validate-pins.py: a bar…
validate-pins.py: a `--pin-source` key is the longest declared pin be…
tests: under a Latin-1 locale 12 of the 13 rows of #1259 skip on thei…