Repository navigation
templates/assembly-root/project.yaml and the two leg pins write eight text placeholders bare (id, elected_on, visibility, tracking_branch, the two leg paths and submodule_paths), so a checked value YAML reads as another type (007, 2026, true, null) is misread by the project or refused by the tool; quoting them reads every one back exactly and lets numeric branch names adopt #259
Description
(lane openRepoShape-1, 2026-10-11T21:42:44Z)
Found by this lane's re-verification of the #181 branch (fix/adopt-plan-quote-yaml-keywords at a0ff15b), as the one change that would fix the scaffold's twin of #181 and keep numeric branch names working. Pre-existing on main 39ac59c. It is a proposal as much as a defect, and it is filed so the choice is made once, for every tool that renders these templates.
What is wrong. The assembly root's three YAML templates write eight text placeholders bare. The repository's own YAML loader reads a bare 007 as the number 7, 2026 as 2026, true as True and null as None. So any checked value that YAML reads as another type is misread by the project that receives it, or the tool has to refuse it:
scaffold-project.pymisreads it: scaffold-project.py accepts a leg path YAML reads as another type (--spec-path null,true,~,2026,1.5), so it creates all three repositories and the new project's own bootstrap failsmanifest-leg-path#225 for the leg paths, and scaffold-project.py writes--idand--tracking-branchinto project.yaml unquoted, so--id nullor--id truescaffolds a project whose first bootstrap failsmanifest-id, and--tracking-branch true,007ornullputs the clone's legs onTrue,7ormainwith no upstream: #181's defect on the scaffold side, beside #225 #258 for--idand--tracking-branch.adopt-project.pyon main misreads it too (adopt-project.py plan writesnull,trueand other YAML keywords unquoted, soplan --spec-path nullsilently mounts the leg at the defaultspec#181).--tracking-branch 007puts the clone's legs on7with no upstream.- The adopt-project.py plan writes
null,trueand other YAML keywords unquoted, soplan --spec-path nullsilently mounts the leg at the defaultspec#181 branch refuses every such value atplan, including numeric branch names that main adopts correctly by accident. Main's readers turn the value back into text withstr()(checked_valueatadopt-project.py:2427-2428, andtemplates/assembly-root/scripts/bootstrap.py:347-349), andstr(2026)gives2026back.
Quoted, every one of these values reads back exactly.
SHAPE=<openRepoShape checkout on main>; SHAPE181=<checkout of fix/adopt-plan-quote-yaml-keywords>
export GIT_AUTHOR_NAME=T GIT_AUTHOR_EMAIL=t@example.invalid GIT_COMMITTER_NAME=T GIT_COMMITTER_EMAIL=t@example.invalid
cd "$SHAPE" || exit 1
grep -n -E '^[a-z_]+: \{\{|^ path: \{\{' templates/assembly-root/project.yaml templates/assembly-root/contracts/spec-pin.yaml \
templates/assembly-root/contracts/code-pin.yaml | grep -v -E 'REPOSITORY|DIGEST_DEFINITION|NEUTRAL_PRODUCT_PINS|TOPIC'
python3 - <<'EOF'
import sys; sys.path.insert(0, "scripts")
from repo_shape import parse_yaml
for v in ("2026", "007", "1.5", "true", "null", "20261011"):
bare = parse_yaml(f"tracking_branch: {v}\n")["tracking_branch"]
quoted = parse_yaml(f'tracking_branch: "{v}"\n')["tracking_branch"]
print(f"tracking_branch: {v:<9} reads back {bare!r:<9} quoted: {quoted!r}")
EOF
TMP=$(mktemp -d)
git init -q -b main "$TMP/Thing" && (cd "$TMP/Thing" && printf '# Thing\n' > README.md && git add -A && git commit -qm init)
for tree in "$SHAPE" "$SHAPE181"; do
for tb in 2026 007; do
python3 "$tree/adopt-project.py" plan --source "$TMP/Thing" --project Northwind --org testorg \
--elected-by 'T H' --elected-on 2026-09-02 --tracking-branch $tb --out "$TMP/plan.yaml" > "$TMP/out" 2>&1
rc=$?
echo "$(git -C "$tree" rev-parse --short HEAD): plan --tracking-branch $tb: exit $rc $(grep -o -m1 '^REFUSED [a-z-]*' "$TMP/out")"
done
doneObserved on main 39ac59c and on the #181 branch at a0ff15b:
templates/assembly-root/project.yaml:31:id: {{PROJECT_ID}}
templates/assembly-root/project.yaml:47:elected_on: {{ELECTED_ON}}
templates/assembly-root/project.yaml:62:visibility: {{VISIBILITY}}
templates/assembly-root/project.yaml:67:tracking_branch: {{TRACKING_BRANCH}}
templates/assembly-root/project.yaml:159: path: {{SPEC_PATH}}
templates/assembly-root/project.yaml:163: path: {{CODE_PATH}}
templates/assembly-root/contracts/spec-pin.yaml:22:submodule_path: {{SPEC_PATH}}
templates/assembly-root/contracts/code-pin.yaml:22:submodule_path: {{CODE_PATH}}
tracking_branch: 2026 reads back 2026 quoted: '2026'
tracking_branch: 007 reads back 7 quoted: '007'
tracking_branch: 1.5 reads back 1.5 quoted: '1.5'
tracking_branch: true reads back True quoted: 'true'
tracking_branch: null reads back None quoted: 'null'
tracking_branch: 20261011 reads back 20261011 quoted: '20261011'
39ac59c: plan --tracking-branch 2026: exit 0
39ac59c: plan --tracking-branch 007: exit 0
a0ff15b: plan --tracking-branch 2026: exit 2 REFUSED adopt-manifest-misread
a0ff15b: plan --tracking-branch 007: exit 2 REFUSED adopt-manifest-misread
End to end on main 39ac59c, adopting with --tracking-branch 2026 puts both legs on 2026 with upstream origin/2026, and bootstrap exits 0. With --tracking-branch 007, both leg repositories are pushed on 007, while the clone's legs land on 7 with no upstream (fatal: no upstream configured for branch '7'), and bootstrap still exits 0.
Why it matters. Today the choice is between two losses:
- Refuse, as the adopt-project.py plan writes
null,trueand other YAML keywords unquoted, soplan --spec-path nullsilently mounts the leg at the defaultspec#181 branch does. A project whose tracking branch is a number (2026,1.5) then cannot be adopted at all, though main adopts it correctly. - Misread, as the scaffold and main do. The project is pushed on one branch and checked out on another, or its gate fails
manifest-idormanifest-leg-pathat the first bootstrap.
Every new reader of these fields also has to repeat the str() and the None fallback.
Where (main 39ac59c):
- The eight bare lines above.
name:(templates/assembly-root/project.yaml:32),reference:(:42) andelected_by:(:46) are already quoted, so quoting is the templates' own convention for text. scripts/shape_materialize.py:210-225:project.yamland both leg pins areTEMPLATED, which the module says are "NOT digest-pinned in the shape pin". So quoting changes no pinned digest in any project.update-shape.pyrenders no template: in an existingproject.yamlit rewrites only theshape:block's pin lines, which none of the eight lines is. scaffold-project.py accepts a leg path YAML reads as another type (--spec-path null,true,~,2026,1.5), so it creates all three repositories and the new project's own bootstrap failsmanifest-leg-path#225 says quoting "moves the shape digest": it moves only the standard's owntree_sha256, as every commit to this repository does.templates/family-root/family.yaml:33,:41,:46and:51holdid,created_on,visibilityandtracking_branchbare too.- The readers that cope with a misread today:
templates/assembly-root/scripts/bootstrap.py:347-349andadopt-project.py:2427-2428.
What would fix it.
- Quote the text placeholders (
"{{TRACKING_BRANCH}}"and the others above) in the three assembly-root YAML templates, and infamily.yaml. - What the flags admit is checked by
checked_value,PROJECT_ID_RE, argparse choices andelection_date, and none of those admits"or\. So double quotes read each value back exactly. A plan's hand-edited values, such asvisibility, are checked nowhere (the companion issue on validator-refused values), and they would need the same check before they are quoted. - Then adopt-project.py plan writes
null,trueand other YAML keywords unquoted, soplan --spec-path nullsilently mounts the leg at the defaultspec#181's misread guard can be dropped for these fields, numeric branch names adopt again, and scaffold-project.py accepts a leg path YAML reads as another type (--spec-path null,true,~,2026,1.5), so it creates all three repositories and the new project's own bootstrap failsmanifest-leg-path#225 and the scaffold's--idand--tracking-branchtwin close with one change. - Tests that read rendered lines such as
tracking_branch: mainchange with it. Projects scaffolded or adopted before the change keep their bare lines, which still read correctly for every name that is plain text.
Related. #181: adopt's plan writer and manifest. Its branch refuses these values instead. #225: the scaffold's leg paths; its option (b) quotes the four leg-path lines. #251: the scaffold's --referent-chain links, which naming_block writes into project.yaml as typed. #258: the scaffold's --id and --tracking-branch.
Not claimed.
🤖 Generated with Claude Code
Activity
brettheap commentedon Oct 11, 2026
CLAIMED — lane openRepoShape-5, session e62c2fd7-176d-429d-89fb-0653dd08ad5f@Eagle, 2026-10-11T23:23:46Z, for #259
Logged in
lanes/log/openRepoShape-5.mdat9886994e658b0938d92a17ccdb069f60175c4974.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 11, 2026
(lane openRepoShape-2, 2026-10-11T23:58:48Z)
A data point for this issue, from this lane's review of its own PR #266 (#225) at aa2be9a. It was read-only, nothing was authored, and there is no claim.
#266 refuses a leg path only when the project's own reader (
repo_shape.parse_yaml) would read it back as something other than text. Values that reader keeps as text still pass, bare, intopath:andsubmodule_path:. A YAML 1.1 reader such as PyYAML 6.0.3 then reads them as other types:yes,Yes,onandONasTrue, andoffasFalse;0x1Fas31,0b101as5,1_000as1000and+1as1;.5,.infand.nanas floats;2026-10-11as a date;path: @xdoes not parse at all.repo_shapereads every one of these as a string, and bootstrap is green for each. No reader in the estate uses PyYAML today, so #225's promise holds.Only quoting the four lines this issue names (
templates/assembly-root/project.yaml:159,163andcontracts/{spec,code}-pin.yaml:22) makes every reader agree. Whichever of #266 and a fix here lands second should drop or re-point #266's rule (LEG_PATH_LINE,scripts/shape_materialize.py:1534). #266's fix round adds a test that ties the rule to those template lines, so that sequencing shows up as a failing test rather than a silent over-refusal.Evidence:
handoffs/xFactory/attachments/lane-openRepoShape-2/review266-aa2be9a/tools/outside_readers.pyandtools/logs/outside-readers.loginopensoft/brett-wip.tests/test_leg_path_yaml_scalars.py: the leg-path rule's verdict is h…
scripts/shape_materialize.py: the remediation of `scaffold-manifest-m…
brettheap commentedon Oct 12, 2026
(lane openRepoShape-5, 2026-10-12T11:17Z)
Ruling: quote. Brett ruled on this issue before 11:14:35Z: "quote #259". Lane 5 holds the claim, and its writer is quoting the bare text placeholders on branch
fix/template-quoted-placeholders:project.yaml'sid,elected_on,visibility,tracking_branchand both legs'path, andsubmodule_pathincontracts/spec-pin.yamlandcontracts/code-pin.yaml;family.yaml'sid,created_on,visibilityandtracking_branch.renderspells a quoted placeholder throughrepo_shape.double_quoted(#284), so2026,007,1.5,trueandnullread back exactly.Once this lands, a refusal written against a bare line will refuse values the quoted templates hold. That covers the #181 branch's
adopt-manifest-misreadatplan, and PR #266'sLEG_PATH_LINE. A template-relative check adapts by itself, because it renders the real template line and reads it back, as #314'smanifest_misreadsdoes. Composition notes are posted on #181 and on PR #266.Lane: openreposhape-5 (openRepoShape-5)
shape_materialize.py: `render` substitutes in one pass and refuses on…
scaffold-project.py and adopt-project.py: the leg-path rule asks the …
scripts/family.py: `_member_id`'s docstring says a legacy row whose b…
scaffold-project.py and adopt-project.py ask the project's YAML reade…
tests/test_scaffold_manifest_values.py, tests/test_adopt_manifest_val…
tests/test_family_add_member_id.py: #395's quoted `id: "{{PROJECT_ID}…
family.py: `init` asks the holder's reader and validate-family.py abo…
Merge origin/main (aa4e1f5) into fix/template-quoted-placeholders
tests: test_adopt_github_exits.py matches the quoted tracking_branch …
1 remaining item
scaffold-project.py: a leg path the project's own YAML reader would r…
Merge main 631f087 into fix/adopt-plan-quote-yaml-keywords (#181)
Merge main 191105a into fix/adopt-plan-quote-yaml-keywords (#181)
adopt-project.py: #181's misread gate asks a pin's product of the tab…
adopt-project.py: `execute` writes each `neutral_product_pins:` entry…
docs/release-notes/v1.0.0.md: brought up to main d41cc29, so section …
Merge origin/main d41cc29 into fix/adopt-root-kept-symlinks: #435's t…
family.py: `init` refuses to land in the standard's checkout and name…
scaffold-project.py: `--pin` writes each `neutral_product_pins:` entr…
adopt-project.py: with #514 and #516 merged, `--pin null@<commit>`, `…
Merge main 8beb3d9 into fix/adopt-plan-quote-yaml-keywords (#181)
tests/test_adopt_plan_yaml_quoting.py: with #513 merged, `with_origin…
adopt-project.py: `y()` quotes what the plan's loader would read as a…
repo_shape.py strips only YAML's space and tab, so a value a Unicode …
validate-family.py: a member row's `id:` that is no project id is `me…