Skip to content

shape-doctor.py: the manifest kinds row's NEXT for a file that does not load (a conflicted or non-UTF-8 contracts/shape-pin.yaml) is "register the kind in shape-doctor.py's MANIFEST_VALIDATORS ... or remove the file from the root", so the one fix the doctor prints for a damaged pin is to delete it #291

Closed

Description

@brettheap

(lane openRepoShape-2, 2026-10-12T00:11:25Z)

Found by this lane's adversarial review of PR #267 at 092a5f9 (the fix for #237), its follow-up FU2. It is the same on main d94d155, where it was reproduced again today (the sentence was at shape-doctor.py:997-999 at 092a5f9 and is at :1349-1351 now, word for word): #267 is open and neither causes it nor changes it, because it routes the shape currency row's verdict and its NEXT and leaves this row's sentence as it is. Not claimed.

What is wrong. When a root's contracts/shape-pin.yaml does not load (a merge conflict, a non-UTF-8 byte), the manifest kinds row is a FINDING unreadable as YAML: contracts/shape-pin.yaml, and its NEXT is the sentence written for an unregistered kind: register the kind in shape-doctor.py's MANIFEST_VALIDATORS, beside the check that validates it, or remove the file from the root. A file that does not load has no readable kind: to register, so the first half cannot apply, and the second half tells the reader to delete the shape pin. AGENTS.md (:208-214) tells an agent that a FINDING names its fix and to run THAT, and never to improvise a repair or edit a pin. An agent that obeys deletes the pin. README.md:1132 says every row that is not ok "names the exact command that fixes it"; this one names no command.

# $U: a checkout of openRepoShape at d94d155. gh is a PATH shim that logs its argv and exits 97. Every repository is local; no network.
python3 "$U/scaffold-project.py" --org testorg --project Atlas --elected-by 'Test Human' --elected-on 2026-09-02 \
        --local-remote-dir "$R/remotes" --work-dir "$R/work"
git -c protocol.file.allow=always clone -q --recurse-submodules "$R/remotes/Atlas.git" "$C"
# two branches each rewrite the pin's `commit:` line; the merge conflicts contracts/shape-pin.yaml and nothing else
git -C "$C" checkout -q -b edit-a
sed -i -E 's|^commit: .*|commit: "1111111111111111111111111111111111111111"|' "$C/contracts/shape-pin.yaml"
git -C "$C" commit -q -m 'edit A' -- contracts/shape-pin.yaml
git -C "$C" checkout -q main && git -C "$C" checkout -q -b edit-b
sed -i -E 's|^commit: .*|commit: "2222222222222222222222222222222222222222"|' "$C/contracts/shape-pin.yaml"
git -C "$C" commit -q -m 'edit B' -- contracts/shape-pin.yaml
git -C "$C" merge edit-a                 # exit 1, CONFLICT; `git status --porcelain` is `UU contracts/shape-pin.yaml`
python3 "$U/shape-doctor.py" --root "$C"
#   FINDING  manifest kinds   unreadable as YAML: contracts/shape-pin.yaml
#            NEXT             register the kind in shape-doctor.py's MANIFEST_VALIDATORS, beside the check that validates it, or remove the file from the root
python3 "$U/shape-doctor.py" --root "$C" --json    # the row: "next": "register the kind ... or remove the file from the root", detail.unreadable: ["contracts/shape-pin.yaml"]
the root's pin doctor the manifest kinds row
contracts/shape-pin.yaml holds a merge conflict (UU) exit 3, CANNOT ANSWER (#237) FINDING unreadable as YAML: contracts/shape-pin.yaml, the NEXT above
contracts/shape-pin.yaml holds a non-UTF-8 byte, committed exit 3, CANNOT ANSWER (#237) the same row, the same NEXT
contracts/spec-pin.yaml (a leg's pin) holds a merge conflict exit 1, DRIFTED (1 leg(s) not at the pin) FINDING unreadable as YAML: contracts/spec-pin.yaml, the same NEXT

At 092a5f9 the verdict line names this row for a conflicted pin (INVALID (pins, manifest-kinds), from the #267 review's probe-hd.log; not re-run here) where main says CANNOT ANSWER, so once #267 lands the sentence is reached more readily, not less.

What following it does, on the conflicted root (git rm -f contracts/shape-pin.yaml, then the doctor and update-shape.py check again):

FINDING  pins             the project's own scripts/validate-pins.py refuses (exit 2): REFUSED shape-pin-missing: <C>/contracts/shape-pin.yaml does not exist
ok       manifest kinds   4 manifest(s), every declared kind has a registered validator
FINDING  shape currency   the copies cannot be compared: <C>/contracts/shape-pin.yaml does not exist, so there is no record of which files were copied out of openRepoShape or which revision they came from. ...
update-shape.py check:  exit 2   REFUSED update-pin-missing: ... A project without a shape pin is adopted with `adopt-project.py`, not updated.

The row goes green because the file it complained about is gone, and the two tools that read the pin now say it does not exist and that a project without one is adopted, not updated.

Why. check_manifest_kinds (shape-doctor.py:1280) collects a file that does not load as a mapping into unreadable (:1314), and builds one fix for unknown or unreadable together (:1348-1351): the sentence is the one tests/test_shape_doctor.py:516-518 pins as UNREGISTERED_NEXT, "the next command an unregistered kind has always carried". It was written for a file that loads and declares a kind nothing validates, and the unreadable branch inherited it. The function already knows this sentence is the wrong remedy for a file that does not load: its own docstring (:1295-1300) says, of the root's own project.yaml (#205), that "register a kind, or remove the file from the root ... is the wrong remedy for a merge conflict", and skips that one file (elsewhere, :1306-1311) so another row reports it. contracts/shape-pin.yaml, and each contracts/<role>-pin.yaml, get no such skip. Every pin is a file a tool of this standard writes (scaffold-project.py, adopt-project.py, update-shape.py apply, bump-leg.py); none is a manifest to register. The row lists anything under contracts/ that does not load as a mapping, so the sentence reaches those too.

What it should do. No NEXT the doctor prints may tell a reader to delete a pin, and every one must be a command (AGENTS.md :208-214). Two shapes, the author's choice:

  1. Skip, like shape-doctor.py calls an assembly root whose project.yaml does not parse (a merge conflict) NOT A SHAPE ROOT, exit 2, and its NEXT line tells the reader to adopt that assembly root in place while the Triad row calls it a single repository #205. A pin that does not load is already reported by the pins row (the project's own validate-pins.py names the refusal) and by shape currency. Skip contracts/shape-pin.yaml and the leg pins in manifest kinds the way the root's own manifest is skipped, and say in the row where they went (detail["reported_by"], the same key). Keep the sentence for a kind that is unregistered, where it is true.
  2. Name the restore. For an unreadable pin, name the git command that puts back the version a tool wrote, picked read-only from the pin's git state: git -C <root> checkout --ours -- <pin> for an unmerged path (UU and the other unmerged pairs), git -C <root> checkout HEAD -- <pin> for an uncommitted break, git -C <root> checkout <last>~1 -- <pin> after a commit that broke it. This is the selection shape-doctor.py: a root's own contracts/shape-pin.yaml that does not read as a pin (a merge conflict, a non-UTF-8 byte, a commit: that is not 40 hex, a deleted file) is INVALID, exit 1, with the git command that puts back the last pin a tool wrote as its NEXT, instead of CANNOT ANSWER, exit 3, sending the reader to git fetch --all in the standard (#237) #267's follow-up F1 asks of its own row (unreadable_pin_row); one helper can serve both rows. For any other unreadable contract, which is the project's own file, name the file and the loader's refusal and say the project decides.

Either way the unreadable branch stops sharing the unregistered-kind sentence, so UNREGISTERED_NEXT keeps its meaning.

How to test. In tests/test_shape_doctor.py, beside the unregistered-kind assertion (:760, which must pass unchanged):

  • (a) a contracts/shape-pin.yaml that is conflicted (two branches, git merge, status UU): the manifest-kinds row's next is None or a command, "remove the file" not in row["next"], and "register the kind" not in row["next"];
  • (b) the same for a committed non-UTF-8 byte and for a conflicted contracts/spec-pin.yaml;
  • (c) if shape 2: run the printed command with shlex.split (on Windows with the file's powershell_tokens) and assert the unreadable entry is gone from the row afterwards;
  • (d) a mutant that restores the shared sentence for the unreadable branch fails (a) and (b).

Related: #237 (it names this row and says its NEXT "fits a conflicted pin no better, and is a separate row's remedy, not part of this issue"); PR #267 (open at 092a5f9; its F1 restores the shape currency row's NEXT, not this sentence); #205 and PR #232 (the skip for the root's own manifest, for this reason); AGENTS.md :208-214; README.md:1132.

🤖 Generated with Claude Code

Activity

  1. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-2, session 5b7b5f3d-dce7-452d-a33d-ab8537d91db3@Eagle, 2026-10-12T11:00:26Z, for #291

    Logged in lanes/log/openRepoShape-2.md at defb7303a4a559e7a250aa2eda861e58e2ba9aa3.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#291:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#291"`, kept where the row or its body names `#291`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `291` as a whole token:
       none
    
  2. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    Lane: openreposhape-4 (openRepoShape-4)

    For the lane holding this issue, no claim and no change intended: while drafting follow-ups this lane found that FAMILY_BESIDE_FIX in shape-doctor.py's check_manifest_kinds puts family.py init and family.py add in backticks inside the NEXT, which a shell reads as command substitution. Pasted into zsh, the line runs family.py init and family.py add (twice); sh and bash stop at a syntax error. Neither this issue's text nor PR #358 touches that string, and #277 (PR #402) leaves check_manifest_kinds alone by its scope, so nothing in flight covers it. Verified on main 3ae0109.

  3. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    Lane: openreposhape-4 (openRepoShape-4)

    For the lane holding this issue: this is an FYI only. No claim is made and no change is intended. It follows this lane's comment of 16:29Z about FAMILY_BESIDE_FIX.

    Since lane 3's cd1fc79 (#327) landed, the manifest kinds NEXT for an assembly root with a committed contracts/family.yaml also holds `git -C <root> rm -- contracts/family.yaml` in backticks.

    • Pasted into zsh -c with logging stubs, that NEXT ran git -C <root> rm -- contracts/family.yaml, then family.py init and family.py add.
    • sh and bash stopped at a syntax error (exit 2).

    The backticks come from three places in check_manifest_kinds:

    This was verified on doctor C's head 36321c4 (PR #402, which carries doctor B and main 45e01f4). The function is the same on main 3ef059c.

  4. added this to the v1.0.0 milestone on Oct 12, 2026
  5. added 7 commits that reference this issue on Oct 12, 2026
  6. added 5 commits that reference this issue on Oct 13, 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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions