Repository navigation
Description
The naming gate and validate-pins.py should read a project manifest's legs: as #443 reads referent_chain and neutral_product_pins (a non-list is a finding, not a TypeError or its characters), and a chain link that is not a name should be a finding. validate-repository-naming.py should not end in a UnicodeEncodeError or UnicodeDecodeError on a directory name that is not UTF-8. The riders in the same files ride with it.
From lane 4's checklist triage of 2026-10-16 (T4), approved by Brett Heap at 2026-10-16T13:36:07Z ("approve all triage recommendations"). One pull request covers the lines below.
Parent: #459. Milestone: v1.1.1 (a follow-up filed after Brett Heap's v1.1.1 word of 2026-10-16T13:35:28Z).
Authored after the v1.1.0 work; lands after the v1.1.0 tag unless the release owner (lane 3) says otherwise.
Lands after:
- lane 2's 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 (ready): it editsscripts/validate-repository-naming.py,templates/assembly-root/scripts/validate-pins.pyandscripts/repo_shape.py. - lane 2's the chain WARNING offers
--link-sourceto callers that do not take it (validate-manifest.py, setup's step (4)), and the naming gate prints--link-sourcevalues raw #661 (filed, v1.1.0): the naming gate's--link-sourcespelling (read_as_path,unread_lines,no_path_refusal,unused_lines) andvalidate-manifest.py. - lane 3's validate-pins.py and shape-doctor.py: one leg's gitlink off its pin beside another leg's
pin-digest-mismatchsays "First clear that finding" and the digest names no line, so everybump-leg.pyeither finding points at is rolled back and the one step that works, putting the gitlink back by hand, is never named #677 (filed, v1.1.0) and its draft pull request validate-pins.py and shape-doctor.py: one leg's gitlink off its pin beside another leg's digest or workflow reference names the step that comes first, that gitlink put back by hand, and then the other leg'sbump-leg.pyline, so neither finding says "First clear that finding" over a finding no line can clear first (#677) #702:validate-pins.py. - validate-pins.py: an unsearchable
contracts/still ends in a PermissionError, an unreadable pin is named by its absolute path twice, and a workflow name or a pin entry's control character reaches stderr raw #712, which changesvalidate-pins.pyandrepo_shape.pytoo.
Effort: M: #443's idiom at three more sites, and a stdout reconfigure. Platform: all.
Files: scripts/validate-repository-naming.py (not pinned); templates/assembly-root/scripts/validate-pins.py, templates/assembly-root/scripts/validate-manifest.py and scripts/repo_shape.py (pinned shape copies, re-pinned). L4-147 would add scaffold-project.py; the rider may stay open without it, since no answer there is wrong.
Lane openRepoShape-4 claims it at filing; see its CLAIMED comment.
Why this is filed. One line is one a person can meet: L4-141 makes the naming gate WAIT on a stdin that is open and not a terminal, when a hand-edited manifest holds legs: openXdox. The rest is exotic input: a name that is not UTF-8, a link holding ESC. Line numbers are at c6122e8 (#639) unless a line says another commit; main has moved since.
Lines
- L4-141 (P2: it waits): a project manifest's
legs:that is not a list still ends the naming gate andvalidate-pins.pyin aTypeError, or is read as characters. It is validate-repository-naming.py (_pins_from_project:302,_chain_from_leg:318), validate-manifest.py (_declared_pins:85) and validate-pins.py (PinSources:390, called at:698) iterate the project's ownneutral_product_pins:and a leg'snaming.referent_chain:without asking they are lists, so5(ortrue,1.5) in either field ends the naming gate inTypeError: 'int' object is not iterable, exit 1,neutral_product_pins: 5ends the other two validators the same way before validate-manifest.py reaches themanifest-neutral-product-pinsfinding it has for it, andmake validatestops in a traceback at its first command; a scalaropenXdoxinneutral_product_pins:is read as the charactersX,d,e,n,o,p,xby the naming gate, which classifies and exits 0 #443's idiom in lines validate-repository-naming.py, validate-manifest.py and validate-pins.py: a project's ownneutral_product_pins:or a leg'snaming.referent_chain:that is not a list isvalidate-manifest.py's finding for that field in every gate that reads it, so5no longer endsmake validatein aTypeErrorand a scalaropenXdoxis no longer read as its characters (#443) #631 did not touch:data.get("legs") or []atscripts/validate-repository-naming.py:718and:762andmanifest.get("legs") or []attemplates/assembly-root/scripts/validate-pins.py:1378(at5b29ee1;:736,:780and:1378onc6122e8).legs: 5: the naming gate andvalidate-pins.pyend inTypeError: 'int' object is not iterable, exit 1, wherevalidate-manifest.pysays itsmanifest-legsfinding (:919).legs: openXdox: the naming gate iterates the characters, makes no target, and falls back to reading names from stdin. With stdin at/dev/nullit refusesREFUSED naming-no-target, exit 2. With a non-tty stdin left open it WAITS: R443's probe blocked until R443 killed it.validate-pins.pyexits 2 with a submodule remediation that has nothing to do with the cause. R443's search found no issue for it (only validate-repository-naming.py (_pins_from_project:302,_chain_from_leg:318), validate-manifest.py (_declared_pins:85) and validate-pins.py (PinSources:390, called at:698) iterate the project's ownneutral_product_pins:and a leg'snaming.referent_chain:without asking they are lists, so5(ortrue,1.5) in either field ends the naming gate inTypeError: 'int' object is not iterable, exit 1,neutral_product_pins: 5ends the other two validators the same way before validate-manifest.py reaches themanifest-neutral-product-pinsfinding it has for it, andmake validatestops in a traceback at its first command; a scalaropenXdoxinneutral_product_pins:is read as the charactersX,d,e,n,o,p,xby the naming gate, which classifies and exits 0 #443, adopt-project.py:Plan.__init__,allowed_empty_legs,namesandopen_sourceiterate or call.geton a hand-edited plan'spins:,allow_empty_legs:,paths:,legs:andsource:without asking their type, so a scalar endscheckandexecutein a TypeError or AttributeError traceback, exit 1, andpins: openGlasspassescheck, adopts, and writes eight one-letter neutral-product pins that the project's own validate-pins.py refuses #386 and adopt-project.py:checkandexecuteiterate a plan'sfollow_ups:without asking for a list, so a hand-edited plan withfollow_ups: 5(ortrue) endscheckin aTypeError: ... is not iterabletraceback, andexecutein the same one after both leg repositories are created and pushed #293 match) (R443 O1). (source) - L4-142 (P3): a chain link that is not a name is accepted silently. A one-link string chain holding ESC,
referent_chain: "\x1b[31mRED", exits 0 in the naming gate and invalidate-manifest.py, on main and on the head: a string is one link under_recorded_chain's rule (scripts/validate-repository-naming.py:639), so the ESC is a link name. This is validate-repository-naming.py (_pins_from_project:302,_chain_from_leg:318), validate-manifest.py (_declared_pins:85) and validate-pins.py (PinSources:390, called at:698) iterate the project's ownneutral_product_pins:and a leg'snaming.referent_chain:without asking they are lists, so5(ortrue,1.5) in either field ends the naming gate inTypeError: 'int' object is not iterable, exit 1,neutral_product_pins: 5ends the other two validators the same way before validate-manifest.py reaches themanifest-neutral-product-pinsfinding it has for it, andmake validatestops in a traceback at its first command; a scalaropenXdoxinneutral_product_pins:is read as the charactersX,d,e,n,o,p,xby the naming gate, which classifies and exits 0 #443's own "entries that are not names" class ([5],[[a]]), which validate-repository-naming.py, validate-manifest.py and validate-pins.py: a project's ownneutral_product_pins:or a leg'snaming.referent_chain:that is not a list isvalidate-manifest.py's finding for that field in every gate that reads it, so5no longer endsmake validatein aTypeErrorand a scalaropenXdoxis no longer read as its characters (#443) #631 leaves unexamined (R443 O2). (source) - L4-02 (P3):
scripts/validate-repository-naming.pyends in aUnicodeEncodeError(--explainand the plain listing, under a strict UTF-8 stdout such as the doctor gives its children), or aUnicodeDecodeError(a name read from stdin), on a directory name that is not UTF-8.--quietanswers the same nameFINDING naming-unclassified. The doctor's half is shape-doctor.py's twonamingrows read only the naming script's exit code, so a crash of the STANDARD's ownvalidate-repository-naming.pyis a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 getsnaming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410. (Was scripts/validate-repository-naming.py, a pinned copy in every project, ends in aUnicodeEncodeErrortraceback, exit 1, when it prints a name that is not UTF-8 (--explainand the plain listing, under a strict UTF-8 stdout such as the one the doctor gives its children) and in aUnicodeDecodeErrorwhen it reads one from stdin, though--quietanswers the same name withFINDING naming-unclassified, so a directory made on a Latin-1 machine gets a crash where the script's own exit codes give 1 to a name that matches no family and 2 to a question that could not be asked #464.) Where, onc6122e8: the script never reconfigures stdout (noreconfigureorbackslashreplacein it),shape-doctor.py:1606setsPYTHONIOENCODING=utf-8(strict) for its children, and stdin is read as text (:800). L4-60, ticked as this line's duplicate, carries the measurement:--explainraisesUnicodeEncodeError('utf-8' codec can't encode character '\udcff', from_classify_targets), exit 1, to a strict stdout (PYTHONIOENCODING=utf-8, which the doctor sets, or a UTF-8 locale other than C). WithPYTHONIOENCODINGunset underC.UTF-8, or with--quiet, it printsFINDING naming-unclassified: Atl\udcffasand no traceback. It should reconfigure stdout tobackslashreplace, asvalidate-pins.py's_emit_reportdoes (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 nameda=bis never consulted and the pin's digest silently goes togh apiand askip(pins ok, exit 0), or, when the text before the=is a declared pin's name, to aFileNotFoundErrortraceback #295). A doctor-side alternative isPYTHONIOENCODING=utf-8:backslashreplacefor the child. This is shape-doctor.py's twonamingrows read only the naming script's exit code, so a crash of the STANDARD's ownvalidate-repository-naming.pyis a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 getsnaming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410's decision 3 (W410, R557, measured at 5ed3e5d). (source; L4-60: source)
Rides along (backlog; tick it here if the same change covers it)
- L4-145: test gap: the chain half of the naming gate's "said first" order is unpinned (mutant M6b survives).
_leg_targetsays the chain sentence before its other findings (scripts/validate-repository-naming.py:676at5b29ee1;:672onc6122e8). Saying it LAST there leaves all 71 tests oftests/test_non_list_project_fields.pypassing, because no case holds a chain that is not a list beside another finding on the same leg. The pins half is held:test_the_pins_sentence_is_said_before_the_chain_line_it_explains(:259) kills the same change in_targets_from_project(:761). A pinning test needs a leg that has a second finding beside the non-list chain, such as the declared-role finding_leg_targetadds at:680-684, and asserts the chain sentence's index in stderr is below that finding's (W443's M6b). (source) - L4-146: the
SHAPE_PIN_SOURCE_*guard is asked per chain, so two chains of one project can read one variable for two pin files.pin_source_env_sharedis asked over ONE chain's links, atscripts/repo_shape.py:1458(_chain_link_findings) and:2930(link_pins_from_trees, at1babe42);:1583and:3055onc6122e8. In a project whose two legs' chains begin atopenßandopenss,SHAPE_PIN_SOURCE_OPENSSis read for both:FINDING codexQuill: the declared chain openss → openQuill is broken (openss declares openInk, not openQuill), exit 1. With nothing set, each leg's WARNING offers that same variable.validate-pins.pyasks over the project's declared pins (:1331), so it reads the variable for neither. Identical on main and on the head: repo_shape.py and validate-repository-naming.py: a chain link, a declared pin and a--link-sourcekey are one product bypin_product_key, the pin file's own fold, so a tree declaringopenssno longer verifies a chain that saysopenß, a chain naming both is two links and not a cycle, and--link-source openss=no longer answers foropenß(#444) #638 neither causes nor worsens it, but after it this is the one remaining way the naming gate reads one source for two pin files. It is 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's area: askpin_source_env_sharedover every chain a run reads, or over the project'sneutral_product_pins:plus every chain's links (R444 O2). (source) - L4-147: cosmetic: a Kelvin sign in
--pinor--referent-chainis recorded as typed. U+212A KELVIN SIGN lowers tok, so--pin openKdox --referent-chain openKdox,openInk, typed with U+212A, plans on main and on the head, and the manifest records the non-ASCII spelling. It is one pin file (contracts/openkdox-pin.yaml), so no answer is wrong. The scaffold could record the--pin's own spelling for the first link instead (R444 O3). Where:scaffold-project.pyrecords the chain as typed (referent_chain_links); this is the one rider outside the files above. (source)
What is expected
- A
legs:that is not a list is a finding in the naming gate and invalidate-pins.py, asvalidate-manifest.py'smanifest-legsis: noTypeError, no characters, and above all no wait on stdin. A test runslegs: openXdoxwith a stdin that is a pipe left open, under a timeout. - A chain link or a declared pin that is not a name is a finding in the naming gate and in
validate-manifest.py. validate-repository-naming.pyreconfigures stdout tobackslashreplace(asvalidate-pins.py's_emit_reportdoes) and reads stdin so that a name that is not UTF-8 reachesFINDING naming-unclassified. The doctor-side alternative,PYTHONIOENCODING=utf-8:backslashreplace, is NOT enough alone, because the script runs on its own too.- L4-145 adds its pinning test. L4-146 and L4-147 are taken only if the same change covers them; otherwise the pull request says so.
Not a duplicate: #443 and #444 are other faults in the same files (#631 and #638 landed them), and #410 is the doctor's half of L4-02. No issue numbered #650 to #700 cites any of these lines (read at 13:17Z); lane 2's #661 prints --link-source values raw, which is not these lines.
Each line above is a line of #459's checklist comments (issuecomment-6214788046, issuecomment-6214800985, issuecomment-6132014915 and issuecomment-6175509630). Tick them there too when this closes.
Lane: openreposhape-4 (openRepoShape-4)
Activity
brettheap commentedon Oct 16, 2026
CLAIMED — lane openRepoShape-4, session 11c6c0ef-495a-4a05-94cf-811b7b0a1012@Eagle, 2026-10-16T14:01:34Z, for #714
Logged in
lanes/log/openRepoShape-4.mdat516359a062aa1a4c33457f482e295162d7118a90.The three reads (lane-collision-protocol Rule 1):
validate-repository-naming.py: under `--project`, a manifest that nam…
validate-repository-naming.py and validate-pins.py: a `legs:` entry t…
Merge main (9f235da, #827 for #712 landed) into fix/714-legs-not-a-li…
tests/test_legs_not_a_list.py: a `legs:` list holding two entries tha…
validate-repository-naming.py, validate-manifest.py and validate-pins…
Merge main c15a49e into fix/832-legs-readers: #826 (#714) has landed,…
tests/test_legs_readers_not_a_list.py: with #826 merged, the doctor e…
shape-doctor.py, bootstrap.py, bump-leg.py and update-shape.py: a `le…
validate-repository-naming.py: every name the gate tables or refuses …