Skip to content

shape-doctor.py check_shape_currency takes every refusal update-shape.py prepare raises over the ROOT's own contracts/shape-pin.yaml (a merge conflict, a commit: that is not 40 hex, a deleted file) for this checkout failing to ask, so the verdict is CANNOT ANSWER, exit 3, outranking INVALID, and its NEXT git -C <standard> fetch --all blames the standard for a fault in the repository #237

Closed

Description

@brettheap

Lane: openreposhape-4 (openRepoShape-4)

Found by this lane's adversarial review of the #205 branch (50aa967, its SHOULD S1), re-verified on main a5b09c3 today, and again on 54aa69a (the one commit since, #173, changes adopt-project.py and its tests only, so every file named below is byte-identical at both). It is pre-existing on main and not a regression of that branch. Not claimed.

What is wrong. shape-doctor.py answers CANNOT ANSWER, exit 3, for a root whose own contracts/shape-pin.yaml does not load, and its NEXT sends the reader to git -C <standard> fetch --all. AGENTS.md (Checking a repository's compliance, item 1) says CANNOT ANSWER is "THIS CHECKOUT failing to ask, never a statement about their repository", and the doctor's own exit-code text (shape-doctor.py:186-190) limits it to "a pin naming a commit it does not carry". A pin file with a conflict marker in it is a statement about the repository. The verdict ladder tests cannot_answer before it reaches invalid_verdict (:3650-3652), so the one row that is wrong outranks the two that are right (pins, which runs the project's own validate-pins.py and names the real refusal; manifest kinds, which names shape-pin.yaml unreadable), and a reader who follows AGENTS.md reads exit 3 as "this checkout is at fault, not the repository" and runs a fetch that succeeds, changes nothing and leaves the next run identical.

# $U: a clean checkout at a5b09c3. gh is a PATH shim that logs its argv and exits 97 (the doctor's `machine` row asks `gh --version` and `gh auth status` and nothing else).
# Every repository is a bare repository under $R/remotes; 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 clone -q --recurse-submodules $R/remotes/Atlas.git $C
python3 $U/shape-doctor.py --root $C
#   exit 0   COMPLIANT
# 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 main
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
#   exit 3
#   stderr   REFUSED shape-doctor-cannot-answer: the copies cannot be compared: $C/contracts/shape-pin.yaml: line 30: merge keys are not supported
#              Remediation: git -C $U fetch --all   # this checkout of the standard does not carry the commit the pin names; fetch it, or run the doctor from a clone that has it
#   rows     ok       manifest         the project's own scripts/validate-manifest.py passes
#            FINDING  pins             the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unparsable: $C/contracts/shape-pin.yaml: line 30: merge keys are not
#                     NEXT             python3 $C/scripts/validate-pins.py --root $C   # read its output in full; it names what it refused
#            FINDING  manifest kinds   unreadable as YAML: contracts/shape-pin.yaml
#            FINDING  shape currency   the copies cannot be compared: $C/contracts/shape-pin.yaml: line 30: merge keys are not supported
#                     NEXT             git -C $U fetch --all   # this checkout of the standard does not carry the commit the pin names; ...
#   verdict  CANNOT ANSWER (shape currency: the copies cannot be compared: $C/contracts/shape-pin.yaml: line 30: merge keys are not supported)   (exit 3)
python3 $U/shape-doctor.py --root $C --json     # the shape-currency row's detail: {"error": "yaml-unparsable", "environment": true}
python3 $U/update-shape.py check --root $C --upstream $U
#   exit 2   REFUSED yaml-unparsable: $C/contracts/shape-pin.yaml: line 30: merge keys are not supported

It is not only a conflict. Each way the ROOT's pin can fail read_pin gives the same verdict, exit 3 and NEXT on a5b09c3 (one fixture per row, each edit made to a clean Atlas; the code is the row's detail.error):

the root's contracts/shape-pin.yaml refusal doctor
holds a merge conflict yaml-unparsable CANNOT ANSWER, exit 3
commit: "zzzz" update-pin-commit CANNOT ANSWER, exit 3
digest_definition: other-v9 update-pin-digest-definition CANNOT ANSWER, exit 3
files: [] update-pin-no-files CANNOT ANSWER, exit 3
deleted update-pin-missing CANNOT ANSWER, exit 3
a list, not a mapping update-pin-unreadable CANNOT ANSWER, exit 3
a non-UTF-8 byte yaml-unreadable CANNOT ANSWER, exit 3
commit: names 40 hex this checkout does not hold (control) update-revision-unresolvable CANNOT ANSWER, exit 3, and RIGHT: this is the case the code is written for

On a5b09c3 a root whose project.yaml is conflicted too reads NOT A SHAPE ROOT, exit 2 (that is #205, fixed by PR #232). The review of #232's head (50aa967) reports that tree there as CANNOT ANSWER, exit 3, which is this verdict: after #232, the realistic two-shape-update merge, which conflicts both files, reaches exactly this issue. That run was the reviewer's and is not repeated here.

Why. check_shape_currency calls us.prepare(args) (shape-doctor.py:1097-1098) and catches Refusal (:1099) as one thing: a Row whose detail carries "environment": True (:1112) and whose NEXT is the fetch --all (:1108-1111). Its comment (:1100-1105) argues the case it was written for, a pin naming a commit THIS checkout does not carry. But prepare (update-shape.py:1278-1300) raises two kinds of refusal. open_upstream and Upstream.resolve raise about the STANDARD (update-revision-unresolvable, update-upstream-*), and read_pin (:364-405) and load_yaml raise about the ROOT (yaml-unparsable, yaml-unreadable, update-pin-missing, -unreadable, -commit, -digest-definition, -row, -no-files). Nothing in the except arm tells them apart. cannot_answer (:3502-3507) then returns the first row carrying environment, verdict_for returns exit 3 for it before any other rung (:3650-3652), and the stderr block (:3713-3717) prints the row's NEXT under REFUSED shape-doctor-cannot-answer.

What it should do. environment belongs to the refusals about the standard, and only to them. A refusal that names the ROOT's pin is a FINDING on shape currency with no environment, so the ladder reaches invalid_verdict (:3589-3607) and the verdict names the gates that are red. On a throwaway clone of a5b09c3, the one condition exc.code != "update-revision-unresolvable" (a plain FINDING otherwise) turned the conflict tree into INVALID (pins, manifest-kinds), exit 1, turned the commit: "zzzz" tree into INVALID (pins), exit 1, and left the unresolvable-commit control at CANNOT ANSWER, exit 3. That is a probe and not the fix; two choices are the author's. One is how to tell the two kinds apart: by code, or by splitting the root-side read out of prepare so the doctor can call the two halves separately (which keeps the knowledge in one place and survives a new refusal). The other is the NEXT for the root-side case, which should name the file that has to be fixed and the project's own scripts/validate-pins.py, as the pins row already does, and never fetch --all. The manifest kinds row's own NEXT for the same file (register the kind in shape-doctor.py's MANIFEST_VALIDATORS ... or remove the file from the root) fits a conflicted pin no better, and is a separate row's remedy, not part of this issue.

How to test. In tests/test_shape_doctor.py, beside test_a_pin_this_standard_cannot_resolve_is_exit_three (:2179), which must still pass unchanged: (a) a contracts/shape-pin.yaml holding a conflict marker, committed over a clean project fixture: exit 1, payload["verdict"] starts INVALID, the shape-currency row's detail has no environment, stderr holds neither shape-doctor-cannot-answer nor fetch --all, and the pins row still names validate-pins.py; (b) the same assertions parametrised over the other root-side breakages in the table (commit: not 40 hex, digest_definition, files: [], deleted, not a mapping, a non-UTF-8 byte), each with the row's detail.error asserted so the case under test is the case reached; (c) the control, the pin naming a commit the standard lacks, still exit 3 with fetch --all; (d) restoring the blanket environment: True makes (a) and (b) fail.

Related: #205 (PR #232; its description says a follow-up issue will carry this), #96

Activity

  1. brettheap commented on Oct 11, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-2, session 224cb7ce-54c8-4501-bfa8-7a7e398984a0@Eagle, 2026-10-11T22:15:41Z, for #237

    Logged in lanes/log/openRepoShape-2.md at 537011ad4c97b6faae80daf13151886183554f85.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#237:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#237"`, kept where the row or its body names `#237`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `237` as a whole token:
       none
    
  2. added this to the v1.0.0 milestone on Oct 12, 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