Skip to content

update-shape.py: apply keeps each of the manifest's shape: pin lines at the indentation it already has and reads the rewrite back, so a project.yaml indented four spaces takes the update instead of being rolled back under the validators' lockstep remedy (#208) - #224

Merged
brettheap merged 2 commits into
mainfrom
fix/update-shape-manifest-indent
Oct 11, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-3 (openRepoShape-3)
Claim: #208 (comment)

What

update-shape.py apply wrote the manifest's two pin lines at a fixed indent, so a project.yaml indented four spaces came back as YAML its own reader refuses.

  • rewrite_manifest writes each of commit: and tree_sha256: as the line's own leading whitespace, the key, and the new quoted value. It already FOUND them by stripped text at any indentation; it now WRITES them the same way. Only the two values move, in any indentation the reader accepts.
  • What it returns is READ BACK with parse_yaml: shape.commit and shape.digests.tree_sha256 must equal the target, or apply refuses as update-manifest-unrecognised before the manifest is written. The copies and the pin apply had already written are rolled back, so the tree ends exactly as it was. The remedy says the fault is this tool's line rewrite. The one layout a line rewrite cannot carry, a value on the line below its key, reads and validates, so it used to reach the validators and relay their lockstep remedy; it is now refused here by name.
  • The read-back blames this tool only when the fault is its own. A manifest that did not read BEFORE the rewrite was red before apply ran; it is rewritten as before and left to the validators, which name the line.
  • family.yaml goes through the same function, and has a test of its own.

Why

The issue's reproduction, measured on this machine: an Atlas scaffolded from a local clone of main a5b09c3, cloned --recurse-submodules, one upstream commit 6afa1cf that changes one copied template, and project.yaml's shape: block re-indented (the commit and the digests' value at the indents shown). Main's update-shape.py and this branch's ran against byte-identical copies of each project, --upstream the same clone, --at 6afa1cf. Validators were exit 0 and exit 0 before every run.

shape: block main a5b09c3 apply this branch (bb79295) apply
four spaces, tree_sha256 at six (the issue) exit 2: REFUSED yaml-unparsable: .../project.yaml: line 77: unexpected indentation twice, each with the lockstep remedy, then REFUSED update-validators-red; tree rolled back (git status --short empty) exit 0: pins ok, manifest ok: Atlas (atlas), 3 legs; git diff project.yaml is the two value lines, still at four and six
two spaces (the scaffold's own) exit 0 exit 0; the project tree after apply (diff -r), check's output and apply's output are byte-identical to main's (diff, scratch path normalised)
commit: folded onto the line below its key exit 2: REFUSED yaml-unparsable ... line 78: unexpected indentation twice, the lockstep remedy, update-validators-red; tree unchanged exit 2: REFUSED update-manifest-unrecognised: project.yaml's shape: block reads as it stands, but this tool's rewrite of it does not read back to the target ... and Remediation: the fault is this tool's line rewrite, not the project's manifest.; tree unchanged (git status --short empty)
contracts/shape-pin.yaml with digests:'s child at four spaces exit 2: REFUSED update-pin-unrecognised: ... has 0 tree line(s) where exactly one was expected the same, unchanged (see Left out)

Tests

Targeted set on head bb7929519040295528b22b81d3d4dcac888cb49d, which is one commit on a5b09c3:

  • tests/test_update_shape.py tests/test_update_shape_add.py tests/test_manifest_validator.py tests/test_shape_doctor.py tests/test_repo_hygiene.py tests/test_cli_reference.py: 377 passed in 347.50s. The earlier WIP head 2ccad27 (on 20e13f0) ran 374 of these; the three added are the family.yaml cases.
  • tests/test_family.py tests/test_commit_trailers.py -k 'update_shape or trailer' (the family apply paths and the trailer composition both go through rewrite_manifest's caller): 133 passed, 68 deselected.

docs/cli.md is untouched: no --help text changed. README.md and AGENTS.md are untouched. No new test is Windows-skipped: the new tests write the manifest as bytes (commit_manifest), so the line endings under test are the same on every platform. The one WINDOWS_SKIP in the file is an older test of another subject.

New tests in tests/test_update_shape.py:

  • rewrite_manifest over eight indent pairs (2,4) (4,6) (3,6) (1,2) (8,12) (4,5) (2,6) (3,4): the output is the input with the two quoted values substituted and nothing else, and it parses back to the target. For (2,4) that is byte for byte what main wrote.
  • End to end apply over (4,6) (3,4) (2,6): exit 0, both validators green, the diff is the two values.
  • The folded layout: refused as update-manifest-unrecognised, not update-validators-red, without the lockstep text, the tree byte for byte as it was.
  • A manifest that did not read before the rewrite is rewritten as before and left to its validators.
  • family.yaml over (2,4) (4,6) (3,6), with a member row's own commit: above the block left alone.

Composes with

Trial merges (git merge-tree --write-tree <sibling> HEAD, base a5b09c3) against every branch that touches update-shape.py or tests/test_update_shape.py:

sibling sha touches merge
fix/update-shape-pr-create-failure (#204) 8705b27a3 (local = origin) update-shape.py CLEAN
fix/update-shape-pr-base-branch (#209) 30240df3d (local = origin) update-shape.py, tests/test_update_shape.py CLEAN
fix/decode-git-output-utf8 (#217) b6f7cfdcf (local = origin) update-shape.py CLEAN
origin/fix/update-shape-add-rollback (#202, lane 4) c022b64df update-shape.py CLEAN
fix/plan-report-stdout-encoding 300630329 update-shape.py CLEAN

No sibling's hunks lie in rewrite_manifest or _shape_block_bounds. For each of the first four I also built the merged tree in a scratch worktree and ran the 16 new tests on it (the eight-indent unit test, the three end-to-end applies, the folded layout, the unreadable-before case, and the three family cases): 16 passed on each, the local fix/... ref for #204, #209 and #217 and origin's for #202. That checks that a Refusal raised mid-apply still rolls back under #202's rewritten Rollback. Neither side's assertions break the other's. Those merged-tree runs were made on the earlier tips of #204 (ffc5284a5) and #217 (328c09971); the shas in the table are the tips at 2026-10-11T19:30Z, where merge-tree was re-run and is CLEAN for each. #209's and #202's tips are unchanged.

Left out

  • rewrite_shape_pin in update-shape.py matches tree_sha256: with exactly two spaces, and scripts/bump-leg.py's rewrite_pin the same way (read, not run), so a contracts/shape-pin.yaml whose digests: child is indented four is refused as hand-edited (update-pin-unrecognised, measured, rolled back). The issue names both as the same root cause with a louder outcome; that file is scaffold-written and says not to hand-edit it, so a follow-up issue can decide whether those two rewrites should keep each line's indentation as this one does.
  • rewrite_manifest still replaces the whole line, so a trailing comment on the commit: or tree_sha256: line is dropped, identically on main and here (measured).

Closes #208

🤖 Generated with Claude Code

Summary by Sourcery

Preserve manifest pin indentation during shape updates and verify the rewritten pins read back correctly before allowing the update to proceed.

Bug Fixes:

  • Preserve the existing indentation of manifest shape.commit and shape.digests.tree_sha256 lines when applying updates, preventing valid manifests from becoming unreadable.
  • Validate rewritten manifests by reading them back and refuse tool-induced invalid layouts with a tool-specific rollback error.

Enhancements:

  • Extend manifest rewrite coverage to family manifests while avoiding unrelated commit: fields.
  • Distinguish pre-existing manifest errors from rewrite-induced errors so validators continue to report issues that existed before the update.

Tests:

  • Add unit and end-to-end coverage for multiple accepted indentation layouts, family manifests, folded pin values, rollback behavior, and pre-existing invalid manifests.

…nes at the indentation it already has and reads the rewrite back, so a `project.yaml` indented four spaces takes the update instead of being rolled back under the validators' lockstep remedy (#208)

`rewrite_manifest` FINDS `commit:` and `tree_sha256:` inside `shape:` by
their stripped text, so it recognises the block at any indentation the
reader accepts, and it then WROTE them back at a fixed two and four spaces.
A `project.yaml` whose `shape:` block is indented four spaces (`tree_sha256:`
at six) came back with `commit:` at two among siblings at four, which
`load_yaml` refuses. Both validators went red, `apply` rolled back, and the
refusal blamed the project: each `yaml-unparsable` line carried the lockstep
remedy ("advance the gitlink ...") and `update-validators-red` said a tool
that left a project red would have moved the cost to somebody else's pull
request. `check` kept offering the same `apply`.

- Each of the two lines is written as its own leading whitespace + key + the
  new quoted value. Only the value moves; the diff to the manifest is those
  two values, whatever indentation the block was written in.
- What `rewrite_manifest` returns has been READ BACK with `parse_yaml`: it
  must give `shape.commit` and `shape.digests.tree_sha256` equal to the
  target, or it is refused as `update-manifest-unrecognised` before a byte is
  written, with a remedy that says the fault is this tool's line rewrite. The
  one layout a line rewrite cannot carry, a value on the line below its key,
  reads and validates, and is now refused here by name instead of by the
  validators with a remedy about the pin.
- The read-back blames this tool only when the fault is its own: a manifest
  that did not read before the rewrite was red before `apply` ran, and is
  rewritten as before and left to the validators, which name that line.
- `family.yaml` goes through the same function; its block is covered too.

Unchanged: a manifest indented the way the scaffold writes it. Measured on
main a5b09c3 and on this branch against one upstream commit, two byte-for-
byte copies of one scaffolded project: the two project trees after `apply`,
`check`'s output and `apply`'s output are identical (`diff -r`, `diff`).

Left out: `rewrite_shape_pin` matches `  tree_sha256:` with exactly two
spaces, so a `contracts/shape-pin.yaml` whose `digests:` child is indented
four is refused as `update-pin-unrecognised`, loudly and rolled back, on main
and here alike (measured); `scripts/bump-leg.py`'s `rewrite_pin` matches the
same way (read, not run). That file is written by the scaffold and says not
to hand-edit it, and its refusal names the file, so it is not changed here.

Lane: openreposhape-3 (openRepoShape-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-11T18:58Z) @codex review

@sourcery-ai

sourcery-ai Bot commented Oct 11, 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 1 day and 4 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@sourcery-ai

sourcery-ai Bot commented Oct 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes update-shape.py apply so shape pin rewrites retain each line’s existing indentation, preventing valid re-indented manifests from becoming invalid; it also reads the rewritten manifest back and refuses only rewrites that the tool itself made unreadable, with comprehensive project and family manifest tests.

Flow diagram for safe manifest pin rewriting

flowchart TD
    A["apply: rewrite_manifest"] --> B["Find shape pin lines by stripped key"]
    B --> C["Preserve each line's existing indentation"]
    C --> D["Read rewritten manifest with parse_yaml"]
    D --> E{"Pins equal target?"}
    E -->|Yes| F["Return rewritten manifest"]
    E -->|No| G{"Manifest read before rewrite?"}
    G -->|Yes| H["Refuse update-manifest-unrecognised"]
    G -->|No| I["Leave failure to validators"]
Loading

File-Level Changes

Change Details Files
Preserve existing indentation when rewriting the manifest’s shape pins and validate the rewritten manifest before writing it.
  • Capture each pin line’s leading whitespace and reuse it when replacing only the quoted value.
  • Parse the rewritten text and verify both target pin values are read back correctly.
  • Refuse layouts that the rewrite breaks with an update-manifest-unrecognised error, while leaving manifests already unreadable before rewriting to existing validator handling.
  • Keep the rewrite scoped to the selected shape block and distinguish project/family manifests.
update-shape.py
Add coverage for indentation-preserving rewrites, read-back refusal behavior, and family manifests.
  • Test eight accepted indentation combinations and assert that only the two pin values change and the result parses to the target.
  • Test end-to-end apply for re-indented project manifests, including validator success and rollback on unsupported folded values.
  • Verify pre-existing unreadable manifests remain subject to validator diagnostics.
  • Test family manifest rewriting without changing a member’s unrelated commit: pin.
tests/test_update_shape.py

Assessment against linked issues

Issue Objective Addressed Explanation
#208 Make update-shape.py apply preserve the existing indentation of the shape.commit and shape.digests.tree_sha256 lines when rewriting manifests, so accepted four-space-indented manifests remain valid and can be updated successfully. ✅
#208 Validate the rewritten manifest before writing it and refuse with a tool-specific update-manifest-unrecognised error when the rewrite cannot be read back to the target commit and tree, while leaving the project unchanged. ✅
#208 Apply the same indentation-preservation fix to the related rewrite_shape_pin and bump-leg.py pin rewrites, which also reject or mishandle valid manifests with nonstandard indentation. ❌ The PR explicitly leaves rewrite_shape_pin in update-shape.py and scripts/bump-leg.py unchanged; both still require exactly two spaces before tree_sha256:. Thus the related four-space-indented pin case remains refused as hand-edited rather than being handled consistently.

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-2, 2026-10-11T19:29:51Z)

This lane reviewed PR #224 at bb79295 read-only, in its own detached worktrees. It authored nothing and holds no claim.

Verdict: MERGEABLE WITH NITS (0 P1, 0 P2, 1 P3, 2 nits). No sequencing constraint.

#208 is fixed. The test: a scaffolded Atlas with its shape: block re-indented to 4/6 (validators green), then one upstream fix.

  • Main exits 2: REFUSED yaml-unparsable: .../project.yaml: line 77: unexpected indentation twice, the lockstep remedy, then update-validators-red.
  • Head exits 0 (pins ok, manifest ok). The diff is the two values, still at 4 and 6.
  • At the scaffold's own 2/4, head's tree and output are identical to main's.
  • A real family holder at 4/6 fails on main and is family ok on head.
  • A folded commit: is now refused as update-manifest-unrecognised, with "the fault is this tool's line rewrite", and the tree is left clean.

P3-1. Closes #208 leaves the half the issue names.

  • rewrite_shape_pin (update-shape.py:836) and bump-leg.py's rewrite_pin (scripts/bump-leg.py:213) still match exactly tree_sha256:.
  • With shape-pin.yaml's digests: child at four spaces, both validators pass and the doctor says COMPLIANT, SHAPE BEHIND. apply still refuses update-pin-unrecognised as hand-edited, on main and head.
  • The body defers this to a follow-up issue, and none is open. Please file it before merge.

Nits:

  1. The body says the read-back refuses "before a byte is written". In fact the copies (:1981) and the pin (:1844) are written first and then rolled back by the ledger. The end state is clean (verified).
  2. The "Composes with" table names superseded shas (da2035f, ffc5284, 328c099, 4fbc8aa).

Notes:

  • A commit: line indented with a tab makes the manifest red before apply. Main's apply silently repaired it (rc 0). Head leaves it to the validators, which name the line (rc 2, rolled back). That matches the PR's stated design.
  • Pre-existing on main and head, no open issue: shape: # comment is refused as "has no shape: block ... add it" (update-shape.py:974), which is false.

Tests:

Merges (merge-tree bb79295, all clean, no conflicts): #218 30240df, #227 6ed4e70 and 8705b27, #233 c022b64, #230 b6f7cfd, main 54aa69a. No sibling hunk touches rewrite_manifest or _shape_block_bounds.

Report and drivers: opensoft/brett-wip handoffs/xFactory/attachments/lane-openRepoShape-2/review224-bb79295/ (final-report.md, tools/).

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-11T19:30Z) independent review (codex is out of quota): ready; PR body corrected ("rolled back", not "before a byte is written"), no code change.

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap merged commit e7d1720 into main Oct 11, 2026
5 checks passed
@brettheap
brettheap deleted the fix/update-shape-manifest-indent branch October 11, 2026 21:08
brettheap added a commit that referenced this pull request Oct 11, 2026
…ng-paths

main moved to 4835fb4: #224 (e7d1720, update-shape.py), #244 (404760f,
repo_shape.py `_split_flow`) and #247 (4835fb4, the plan writer escapes
every character that is not printable). The textual merge is clean. The
next commit takes #247's one assertion that this branch's spelling
changes.

Lane: openreposhape-3 (openRepoShape-3)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 11, 2026
…, #213 and #199 land beside #190, and the `repo_shape` import list carries #199's `double_quoted` with #190's `redacted`, `remote_is_a_path`, `remote_key` and `remote_local_path`

Main moved from 54aa69a to 4835fb4: e7d1720 (#208 via #224), 404760f
(#213 via #244) and 4835fb4 (#199 via #247). One conflict hunk, in
adopt-project.py, and it takes BOTH sides:

1. The `repo_shape` import list. #199 adds `double_quoted`, which `y`
   now spells every quoted plan scalar with; #190 adds `redacted`,
   `remote_is_a_path`, `remote_key` and `remote_local_path`, which its
   relative-url check calls (`_Resolution.same_as` and `__str__`,
   `_default_remote`, `_relative_url_findings`). Main's line is kept as
   main wrote it, and #190's four names follow `parse_yaml` in the
   list's order. Either side's list alone leaves a name the merged code
   calls undefined.

Merged with no conflict:

- adopt-project.py, #199's other hunks: `_PLAIN_RE` ending at `\Z`,
  `y` through `double_quoted`, `_spelled`'s docstring, `_shown` and the
  four report lines that print through it, and `write_paths_listing`
  in the `shape_materialize` import and in `_extract_leg`. None touches
  a #190 line. #190's findings and NOTE already print every path and
  url through `_spelled`, which is what `_shown` falls back to for a
  name that is not printable.
- scripts/repo_shape.py: #199's escape table (`DOUBLE_QUOTED_ESCAPES`
  with `\r`, `CODE_POINT_ESCAPES`, `double_quoted`, `_escaped`,
  `_escape_at`, the one-pass `_unescape`), with its module-docstring
  line and its `_BLOCK_SENTINEL` comment, and #213's `_split_key_flow`
  and `_split_flow`, are all in the YAML reader at the top of the
  file. #190's `REMOTE_PORT_RE`, `REMOTE_HOST_ALIASES` and
  `remote_key` are in the remote helpers some 1,400 lines below, and
  call nothing main changed. Each side's added and removed lines are
  the same before and after the merge, byte for byte.
- update-shape.py (#208), scripts/shape_materialize.py (#199) and
  main's three test files are main's blobs; #190's three test files
  are this branch's.

#190's added and removed lines against 4835fb4 are its lines against
54aa69a, but for the import hunk.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 11, 2026
…) into fix/adopt-record-submodule-placement

Brings in e7d1720 (#208), 404760f (#213) and 4835fb4 (#199) on top of
54aa69a. No rebase.

#199 landed first, so this branch is the one that lands second, and it
takes #199's side of the double-quoted escape section, as cd2a622's message
and lane openRepoShape-2's review of e167d8b both said it would.

Conflicts, and how each was resolved:

- scripts/repo_shape.py, two hunks (textual): the escape section and
  `_unescape`'s docstring. Both sides added `CODE_POINT_ESCAPES`,
  `_HEX_DIGITS` and an `_escape_at` whose body is the same line for line,
  and both reworded the docstring; #199 also added `r` to
  `DOUBLE_QUOTED_ESCAPES` and the writer, `double_quoted` with `_escaped`
  and `_ESCAPE_OF`. The auto-merge had also left this branch's own copies
  of `CODE_POINT_ESCAPES`, `_HEX_DIGITS` and `_escape_at` beside main's.
  Resolved as main's file, byte for byte, plus this branch's one change to
  it that is not about escapes: 379763c's `path_spelling_fault` section and
  its line in the module docstring, applied with `git apply`. `git diff
  origin/main -- scripts/repo_shape.py` is that 51-line addition alone.

Merged without a conflict, and checked:

- adopt-project.py: main's `y` now quotes with `double_quoted`, and this
  branch's `submodule_record` still quotes with `_quoted`, which reads
  main's table now, so it writes a carriage return as `\r`, where it wrote
  `\x0d`. The two writers give the same text for every character to U+2FFF,
  every surrogate and seven more (14,343 compared, 0 different). The next
  commit removes `_quoted`.
- scripts/shape_materialize.py: main's `write_paths_listing` and this
  branch's `path_spelling_fault` import and `LEG_SEGMENT_REASONS`.

Every merged module compiles. On this merge, tests/test_windows_paths.py,
test_adopt_plan_escapes.py, test_yaml_flow_quotes.py,
test_manifest_validator.py and the record's two name tests in
test_adopt_submodules.py: 218 passed.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 12, 2026
…e-notice

It merged with no conflict. Main brings six commits since 54aa69a:

- e7d1720, update-shape.py (#208 via #224);
- 404760f, scripts/repo_shape.py's double-quoted flow items (#213 via
  #244), with tests/test_yaml_flow_quotes.py;
- 4835fb4, adopt-project.py's plan writer escapes (#199 via #247), with
  tests/test_adopt_plan_escapes.py;
- b538015, scaffold-project.py's leg paths against the shape's own
  paths (#197 via #234);
- 150c41d, update-shape.py's `apply --add` (#202 via #233);
- 39ac59c, scaffold-project.py's leg mount `--force` (#229 via #235).

Two of them touch adopt-project.py, and no hunk of either meets this
branch's:

- 4835fb4 changes the plan writer (`y`, `_PLAIN_RE`), adds `_shown` to
  the plan and check reports, and writes the `--paths-from-file` listing
  in `_extract_leg` through `write_paths_listing`. This branch changes
  other lines of `_extract_leg`: it reads the tree digest before the
  push and pushes through `_push_the_leg`. The signature is the same on
  both sides, so main's new direct `_extract_leg` test calls it as
  written.
- b538015 moves `_overlap`, `_first_and_more`, `MOVE_THE_LEG` and
  `CASE_ASIDE` into scripts/shape_materialize.py, and imports the first
  three back under the same names. This branch adds no use of any of
  them. A `symtable` check finds no name that the merged adopt-project.py,
  scaffold-project.py or scripts/shape_materialize.py reads and nothing
  binds.

`git diff -U0` of this merge against 39ac59c equals the branch's against
54aa69a, apart from the hunk headers. On the merged tree,
tests/test_adopt_plan_escapes.py, tests/test_yaml_flow_quotes.py,
tests/test_scaffold_leg_path_collisions.py and
tests/test_adopt_leg_path_collisions.py give 275 passed.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 12, 2026
…indentation, spacing and trailing comment and reads its result back before writing it, so a `digests:` child indented four spaces takes the update instead of `update-pin-unrecognised` / `bump-leg-pin-unrecognised`, and a `# comment` on `commit:` or `tree_sha256:` survives every pin rewrite, `rewrite_manifest` included (#323) (#380)

`rewrite_shape_pin` (update-shape.py) and `rewrite_pin` (scripts/bump-leg.py)
FOUND `tree_sha256:` by exactly two leading spaces and WROTE both lines back
as `commit: "<sha>"` and `  tree_sha256: "<digest>"`; `rewrite_manifest` kept
each line's indentation since #224 and still wrote its two lines bare. So a
pin whose `digests:` child is indented four spaces, which the reader and both
validators accept, was refused as hand-edited ("0 `tree` line(s)"), and a
trailing comment on either line was dropped by all three rewrites. #224
measured the first for `apply` and left the pin rewrites alone; this is the
same fix on the same pattern.

- `shape_materialize.with_scalar(line, key, value)` replaces ONLY the value:
  the indentation, the key, the spacing between the colon and the value and a
  trailing `# comment` (found by the reader's own `_strip_comment`, so a `#`
  inside a quoted value is not one) are the line's own. The new value is
  double-quoted, as the scaffold writes it. `is_nested_key` finds
  `tree_sha256:` as any INDENTED line that begins with it; `commit:` is still
  found at column 0, and each line must still be found exactly once.
- `rewrite_shape_pin`, `rewrite_pin` and `rewrite_manifest` all write their
  two lines through it. The `files:` block of the shape pin is still written
  whole, from the recomputed rows.
- What each returns has been READ BACK with `parse_yaml`: `commit` and
  `digests.tree_sha256` must equal the target, or it is refused as
  `update-pin-unrecognised` / `bump-leg-pin-unrecognised`, as this tool's
  fault, before the pin is written. The one layout a line rewrite cannot carry,
  a value on the line below its key, reads and validates, and used to reach
  the validators and the lockstep remedy about the gitlink. Raised only when
  the pin read before the rewrite: a pin that did not was red before the
  command ran and is left to the validators, as in #208. `apply` rolls the
  copies it had made back and `bump-leg.py` puts the leg's detached checkout
  back; the tree ends exactly as it was.
- The reading and the question ("is this the tool's fault?") are one
  implementation, `shape_materialize.pin_values_in` / `rewrite_fault`, that
  #208's manifest check now calls too, with its messages unchanged. They live
  in `shape_materialize.py` and not in `repo_shape.py`, which is a shape copy
  every project holds and digest-pins.
- `update-shape.py` still imports `parse_yaml` and `YamlError`, which nothing
  in it reads any more now that #208's reading is `pin_values_in`. The `--pr`
  re-run branch (#333) reads HEAD's pin with them in `_committed_pin`; its
  merge with this one is textually clean and, without the two names, fails
  with a `NameError` in that function, so they stay as main has them.
- A pin laid out its own way and already at the commit is now `nothing to do`
  under `bump-leg.py` (exit 1): its rewrite is itself. A commented pin used to
  differ from its own rewrite, so a bump that had landed was made again as a
  commit that only deleted a comment.
- The digest is still RECOMPUTED from the leg's own objects, never edited to
  fit (AGENTS.md, "Advancing a leg", rule 2). The new refusals tell the
  operator to leave both values alone and re-run the command, which
  recomputes it.

Unchanged: a scaffold-shaped pin or manifest. Measured on a real scaffold, the
sha256 of what `rewrite_shape_pin` (shape-pin.yaml), `rewrite_pin` (spec-pin,
code-pin) and `rewrite_manifest` (project.yaml) return is identical on main
c7c02fa and on this branch, and `tests/test_pin_rewrite_layout.py` holds each
of the four pin kinds to the scaffold's own rendering of the target values.
The refusals for a missing or doubled `commit:` or `tree_sha256:` line and the
`files:` replacement are as they were.

Left out: `files:` and the manifest's `shape:` followed by a trailing comment
are still refused (`update-pin-unrecognised`, `update-manifest-no-shape`) by
name rather than rewritten; `bump-leg.py --dry-run` does not rewrite the pin
unless nothing moves, so it exits 0 for a pin the real run refuses, as it did
for the four-space pin on main.

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

Labels

None yet

Projects

None yet

1 participant