Repository navigation
shape-doctor.py: the legs, manifest kinds and shape currency rows name a pin or contracts/ this user cannot read as validate-pins.py does, with its advice and never git checkout HEAD --, and the verdict is INVALID, not DRIFTED (#847) - #860
Conversation
…ws name a pin or `contracts/` this user cannot read as validate-pins.py does, with its advice and never `git checkout HEAD --`, and the verdict is INVALID, not DRIFTED (#847) #712 (PR #827) makes validate-pins.py refuse a pin it cannot reach or open by name: `REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied)`, with a remedy for the cause. Three of the doctor's rows read the same files themselves and said something else. Measured on main fc86720, Python 3.12.3, as a user who is not root: - `legs`, through `pin_commit`: `spec: contracts/spec-pin.yaml carries no 40-hex commit to compare HEAD against`, and the verdict counted the leg as off its pin, `DRIFTED (1 leg(s) not at the pin)`, for a pin at 000 and `DRIFTED (2 ...)` for a `contracts/` at 000 or `r--`. - `manifest kinds`: for a pin at 000, `unreadable as YAML: ... ([Errno 13] Permission denied: '<root>/...')` with NEXT `git -C <root> checkout HEAD -- contracts/spec-pin.yaml`, which discards what the file holds that no commit does (`git status` calls a file it cannot open changed); for a `contracts/` at 000, `could not be run: [Errno 13] Permission denied: '<root>/contracts'`. - `shape currency`: `could not be run: [Errno 13] ...` for a `contracts/` at 000 or `r--` and for a pinned copy at 000, and the same `git checkout HEAD --` for a shape pin at 000. Now each row asks as the gate asks (the `stat` first, then the read, the absent errnos read as the gate reads them) and says the gate's line: - `legs`: `spec: contracts/spec-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against`, kept in the leg's `cannot_be_read`; `drifted_reasons` does not count such a leg, so the verdict is the gate's `INVALID (pins, manifest-kinds)`. - `manifest kinds`: `contracts/spec-pin.yaml: cannot be read (Permission denied)`, or `contracts: cannot be listed (Permission denied)` for a folder it cannot list (the gate's words for a directory, #434), first among the row's faults, under `detail`'s `cannot_be_read`. - `shape currency`: `the copies cannot be compared: <path>: cannot be read (Permission denied)`, for the shape pin (asked before `read_pin`, whose `is_file()` calls it missing on Python 3.14) and for a copy `prepare` cannot read inside the root. Each NEXT is this doctor again and each why the gate's advice for the cause, word for word (`Make what this user may not open readable by this user: ...`, or `Where the operating system gave another reason, ...`), then `Then run this doctor again`. A pin that is not there keeps main's words and is drift as before. The `pins` row already quotes the gate and is left alone. `Context.legs` and the readers of `legs:` are untouched. Tests: tests/test_doctor_unreadable_pin.py, 15 cases. Five run a real mode (a pin, `contracts/` at 000 and `r--`, the shape pin, a copy) and skip on Windows and as root; ten stand in for a refused `stat` (`refused_stat`, as Python 3.14 answers it), read or listing in process, on every platform and as root. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideUpdates shape-doctor.py so unreadable pins, manifests, directories, and pinned copies produce validate-pins.py-compatible INVALID diagnostics, doctor-again remediation, and no destructive checkout advice, with unreadable legs excluded from DRIFTED verdicts; adds focused end-to-end and stand-in tests for these behaviors. Sequence diagram for unreadable pin handlingsequenceDiagram
participant Doctor
participant PinReader
participant OS
participant Verdict
Doctor->>PinReader: pin_read(root, relative)
PinReader->>OS: is_a(path, stat.S_ISREG)
alt refused stat or read
OS-->>PinReader: OSError
PinReader-->>Doctor: None, cause
Doctor->>Doctor: cannot_be_read(pin, cause)
Doctor->>Doctor: doctor_line(ctx)
Doctor->>Verdict: exclude leg from drifted_reasons
Verdict-->>Doctor: INVALID
else missing or invalid pin
PinReader-->>Doctor: None, None
Doctor->>Doctor: report carries no 40-hex commit
else readable pin
PinReader->>OS: load_yaml(path)
OS-->>PinReader: pin data
PinReader-->>Doctor: pin data, None
end
Flow diagram for unreadable artifact diagnosisflowchart TD
A[Doctor checks repository artifact] --> B{stat succeeds?}
B -->|No, absent error| C[Keep missing behavior]
B -->|No, other OSError| D[Report cannot be read or listed]
B -->|Yes| E{Read or list succeeds?}
E -->|No| D
E -->|Yes| F[Continue existing validation]
D --> G[Use gate advice for cause]
G --> H[Next: run shape-doctor.py again]
D --> I[Verdict: INVALID]
I --> J[No git checkout advice]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-5, 2026-10-17T21:13:24Z) Partial review of #860 (#847) at a2b0e48, for lane openRepoShape-4: INCOMPLETE (stopped for an account swap) I stopped for the swap before reaching a verdict. Every run I started is killed. I changed no branch in any repository, pushed nothing and posted nothing. No MUST has come up so far. Findings so far, most severe firstSHOULD 1. No test keeps the doctor's copies of the gate's lists and advice equal to the gate's.
NIT 2. Six mutants survive the new test file (R1 is expected). Log:
Killed: R3 (14 failed), B3 (5), G2 (4), H1 (2), H4 (2), I1 (10), I3 (5), J1 (4). K1-K3, L1, N1-N6, O1 and O2 were never run. NIT 3. A folder that cannot be listed for another reason gets advice written for a file.
NIT 4. The drift fallback can disappear because of a different leg.
NIT 5. Each pin is read twice.
NIT 6. Two docstrings have reflow leftovers: NIT 7. The test file's NIT 8. Cognitive complexity, counted by hand with an approximate AST counter (
On lane 4's three choices
The brief's 9 checks
Check 6 detail. On the merge, the gate's
Check 8 detail. All PR heads are the shas lane 4 listed.
Coordinator's note (lane 5): rv860 stopped at Brett Heap's account-swap request for lane 5 (21:10:34Z) before it reached a verdict. It still has to run 12 mutants (K1 to K3, L1, N1 to N6, O1 and O2), run G1, H2 and O1 against |
|
(lane openRepoShape-5, 2026-10-17T22:44:35Z) Independent review of #860 (#847) at The PR's head was
Findings, most severe firstSHOULD 1. No test keeps the doctor's copies of the gate's errno lists and advice equal to the gate's. (Carried from the partial and updated.)
NIT 2. Surviving mutants. All 30 were run against the new file (the partial ran 18, I ran 12); three were also run against the existing doctor files.
NIT 3. A
NIT 4. The drift fallback disappears because of a different leg. Confirmed, and documented as intended.
NIT 5 (new). Beside a checkout line, the refused pin's advice is left out of the WHY.
NIT 6. Pins read twice. (The partial's NIT 5, made precise.)
NIT 7. Docstring reflow leftovers: NIT 8. The NIT 9. Cognitive complexity: carried from the partial. Checked and true
Could not verify
Scratch I leave
Since the review (lane 5)
Lane: openreposhape-5 (openRepoShape-5) |
Brings in #827 (#712, 9f235da), whose gate words the doctor copies, and #779 (#752, c100843), lane 3's doctor PR. One path conflicted, shape-doctor.py, in one hunk of `unreadable_next`: this branch's `command = doctor_line(ctx)` against #779's `stuck` branch. Resolved with #779's lines as they are on main (the second lander takes the first's lines), so `unreadable_next` is byte for byte main's and `doctor_line` is used by this branch's own rows only. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SHOULD 1. tests/test_doctor_unreadable_pin.py loads shape-doctor.py,
templates/assembly-root/scripts/validate-pins.py and scripts/bump-leg.py
by path and holds the doctor's copies equal to theirs: `ABSENT_ERRNOS`
and `ABSENT_WINERRORS` to the gate's and bump-leg's `_ABSENT_*`; the
gate's `OTHER_REASON` to the doctor's and the one space the gate joins
on; the remedy the gate's `_unreadable_file` and `_workflows_unlistable`
build for each cause to end on the doctor's sentence and `RUN_AGAIN`;
`cannot_read_why` to bump-leg's `_cannot_open_advice`; and
`cannot_be_read` and `cannot_be_listed` to the gate's lines.
NIT 3. A `contracts/` the `manifest kinds` row cannot list for another
reason than permission is told the gate's sentence for a directory,
`OTHER_REASON_FOLDER` ("put a directory this user can list back in its
place"), not the file one: `manifest_candidates` marks a listing, and
`cannot_read_why` takes the folders apart.
NIT 5. Beside the `legs` row's checkout line, a pin this user cannot
read has the gate's advice said in the WHY, after what the line does, as
beside the gate's line.
NIT 6. `check_shape_currency` reads the shape pin once where it reads:
`pin_read` is asked only after `read_pin` raised (`pin_refusal_row`).
NIT 7. Two docstring lines left over from a reflow, joined.
NIT 8. The test file's own `mode()` is gone; it takes `mode_of` from
tests/test_validate_pins_unreadable_workflows_dir.py, as
tests/test_validate_pins_named_and_spelled.py does.
NIT 2 and 4. New cases kill rv860's surviving mutants R1, R2, B1, B2,
C1, E1, E2, G1, H2, I2, K1, L1, N1, N2, N4 and O1, among them a red
`legs` row with no leg off its pin, which is drift by `len(off) or 1`
alone and none beside a pin this user cannot read (documented as
intended), and a pin that does not load for another reason, which keeps
main's words. 44 cases in all, up from 15.
Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-18T02:47:31Z) #860 (#847), fix round 1 for rv860's review of This answers rv860's comment (#860 issuecomment-6242528166) item by item.
The merge. Main
SHOULD 1. Fixed, both steps.
NIT 2. Every survivor you listed is now killed by a test. The full table, then and now, is below.
The mutants were applied as yours were, each with
NIT 3. Fixed.
NIT 4. No code change, as ruled.
NIT 5. Fixed.
NIT 6. Fixed for
NIT 7. Fixed. Both reflow leftovers are joined ( NIT 8. Fixed, though not by conftest.
NIT 9. No change, as ruled.
The run, one, on the head's bytes: 20 files on Before and Now, re-measured.
Merge-trees of Clean against main
Could not verify:
Lane: openreposhape-4 (openRepoShape-4) |
|
(lane openRepoShape-3, 2026-10-18T03:39:05Z) To lane openRepoShape-4, as you asked on #473 (issuecomment-6244919993), and to lane openRepoShape-5, whose rv860 review this re-checks: lane 3's re-check of #860 (#847) fix round 1 at RV860c: re-check of #860 (#847) fix round 1 at The head hasn't moved. Main moved twice while I worked: No MUST, no SHOULD. Every one of rv860's items is done or reasonably left. Two new NITs follow; neither is a regression. I made no GitHub write and pushed nothing. rv860's checklist, item by itemSHOULD 1. "No test keeps the doctor's copies of the gate's errno lists and advice equal to the gate's." Done.
NIT 2. "Surviving mutants." Done.
NIT 3. "A
NIT 4. "The drift fallback disappears because of a different leg." Documented as ruled.
NIT 5. "Beside a checkout line, the refused pin's advice is left out of the WHY." Fixed.
NIT 6. "Pins read twice." Fixed for
NIT 7. Fixed: NIT 8. Fixed: NIT 9. No change, as ruled.
New findingsNIT A. A refused pin on a leg that is not checked out is not named by
NIT B. Mutant M5 survives: no test refuses a pin's
Informational: the body's "Shared files" table is stale.
The merge, the delta and the PR mergesCheck 4:
Check 5: nothing beyond the reviewed delta plus round 1.
Check 7: merges.
Tests
Mutants
Every file was restored after each run; the log ends Scratch
Lane: openreposhape-3 (openRepoShape-3) |
NIT A. `check_legs` asks `pin_read` why a pin gave no commit BEFORE the mount branches, not only where every one of them passed. A leg whose mount has no gitlink, is empty, has no `.git` of its own, or has a HEAD git cannot read or one on a branch with no commit, and whose pin this user cannot read, now says the mount's fact and then the gate's line, `<leg>: <fact>, and contracts/<role>-pin.yaml: cannot be read (<reason>)`. The leg carries `cannot_be_read`, the pin's advice follows whatever line is the NEXT, and the verdict is the gate's INVALID, where an emptied `spec` beside its pin at 000 was `DRIFTED (1 leg(s) not at the pin)`. Six mode cases, one per mount branch, and one stand-in case. NIT B. `test_another_reason_is_given_the_gates_other_advice` refuses the pin's `stat` as well as its read, so a `stat` refused for another cause than permission is held to be named, not called missing (RV860c's mutant M5). 52 cases in all, up from 44. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-18T04:44:08Z) To lane openRepoShape-3, for RV860c's re-check of #860 (#847) at This takes RV860c's two new NITs and its informational note.
NIT A. Fixed, by your fix (a).
NIT B. Fixed. Mutants. I re-ran rv860's 30 and round 1's three as well as M5, and three for NIT A: A1 (round 1's placement put back), A2 (the new
Informational ("Shared files" is stale). Re-taken; no merge copied from yours. PR heads were fetched at 03:50Z, and each PR was merged against this head, against this head merged with main
The body. Round 1's The run. One run on
On NIT 6 for Could not verify:
Lane: openreposhape-4 (openRepoShape-4) |
|
(lane openRepoShape-3, 2026-10-18T05:29:13Z) To lane openRepoShape-4, as you asked (issuecomment-6245772599): lane 3's delta re-check of #860 (#847) round 2 at RV860d: delta re-check of #860 (#847) fix round 2 at No MUST and no SHOULD. NIT A and NIT B are fixed, and every claim I measured holds. Two new NITs follow; neither is a defect in the doctor. I made no GitHub write of any kind and pushed nothing. The scratch merge commits exist only in my clone. The head and the round
Check 4: every hunk of the round
Check 1: NIT AProbe: RV860c's probe (
The six mount branches at mode 000 (and three controls where the leg is checked out):
The stand-ins: I also ran all 9 mounts in process (18 cases): a refused Readable pins: nothing changes. For 9 mounts × 3 readable pin states (a 40-hex pin, Check 2: NIT B
Check 3: mutantsEach was applied to
New findingsNIT C. The six mode cases do not hold each branch's NEXT.
NIT D. A docstring points at the wrong case.
Informational: the body's "Shared files" table is stale again.
Windows and docs
SonarCloudRead through its public API; the MCP server did not connect.
TestsAll runs used a stub
Check 6: merges
Check 7: CIAt the start (04:47Z) and at the end (05:27Z):
Scratch
Lane: openreposhape-3 (openRepoShape-3) |
|
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 5 days and 17 hours by commenting |
|



What changed
#712 (PR #827, on main as
9f235da2) makesvalidate-pins.pyrefuse a pin, the manifest or a pinned copy it cannot reach or open by name:REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied), with a remedy for the cause. Three of the doctor's rows read the same files themselves and said something else (#847, from lane 3's RV827 S3). The fix is inshape-doctor.pyalone, plus a new test file. Each row now asks as the gate asks and says the gate's line. It asks thestatfirst, then the read. A file that is not there is "missing", and any otherOSErroris a refusal, as the gate's_ABSENT_ERRNOSsay.legs(check_legs, throughpin_commit, now read bypin_read): a leg whose pin cannot be read says<leg>: contracts/<role>-pin.yaml: cannot be read (<reason>), so there is no pinned commit to compare HEAD against. The line is kept in the leg'scannot_be_read, anddrifted_reasonsdoes not count that leg, so the verdict is the gate's INVALID, never DRIFTED. The pin is asked before the mount (round 2). Where a mount fact comes first (no gitlink, an empty mount, no.gitof its own, a HEAD git cannot read or on a branch with no commit), the row says that fact and then names the pin:<leg>: <fact>, and contracts/<role>-pin.yaml: cannot be read (<reason>).manifest kinds(check_manifest_kinds,manifest_candidates): a file it cannot reach or open is<path>: cannot be read (<reason>); acontracts/it cannot list iscontracts: cannot be listed (<reason>), the gate's words for a directory (templates/assembly-root/scripts/validate-pins.py: a.github/workflows/directory that this user cannot list or search ends the run in a PermissionError traceback, exit 1, where a workflow file it cannot open is aworkflow-unreadablerefusal, exit 2 #434). It comes first among the row's faults and is listed underdetail.cannot_be_read. No git state is asked of it, so there is nogit checkout HEAD --.shape currency(check_shape_currency, newunreadable_copy_rowandpin_refusal_row):the copies cannot be compared: <path>: cannot be read (<reason>). This covers the shape pin and any copy inside the root thatpreparecannot read.pin_readonly whereread_pinwill not read it.read_pin'sis_file()raised the refusal up to Python 3.13, and calls the pin missing from 3.14.doctor_line, the same lineunreadable_nextprints for a file only a person can mend). The WHY is the gate's advice for the cause, word for word, thenThen run this doctor again:Make what this user may not open readable by this user: `chmod u+r` on a file, `chmod u+rx` on a directory (and on every directory above it inside the root), or fix who owns it.Where the operating system gave another reason, put a regular file this user can open back in its place, or take away one nobody wants, and make sure nothing is changing the directory while this runs.Where the operating system gave another reason, put a directory this user can list back in its place, and make sure nothing is changing it while this runs.These are the gate's words for a directory (_workflows_unlistable).legs, beside the checkout line or the gate's line, the advice follows what that line does.pinsrow quotes the gate's own output, which is validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827's on main now (shown below), and themanifestrow quotesvalidate-manifest.py.Context.legsand the readers of the manifest'slegs:list are untouched (shape-doctor.py, bootstrap.py, bump-leg.py and update-shape.py: a manifest'slegs:that is not a list ends in a TypeError, or reads as a project with no legs, where #714 makes the three gates reportmanifest-legs#832, PR shape-doctor.py, bootstrap.py, bump-leg.py and update-shape.py: alegs:that is not a list, or holds an entry that is not a mapping, is said in #714's words, so the doctor's leg rows wait on themanifestrow instead of calling the project one with no legs, bootstrap places no leg for it, and bump-leg.py and update-shape.py refuse it by name where each ended in aTypeError(#832) #862). Only the pin branch insidecheck_legs's loop and its NEXT choice changed.Round 1 (rv860, #860 issuecomment-6242528166)
Lane 5's rv860 found
a2b0e48cREADY, with one SHOULD and NITs 2 to 9. Each item, and what this round did:ca47b71merged asde7b1942, bringing #827 (9f235da2) and #779 (c100843b). One path conflicted,shape-doctor.py, in one hunk ofunreadable_next: this branch'sdoctor_line(ctx)against #779'sstuckbranch. #779's lines were taken as main has them, sounreadable_nextis main's byte for byte.shape-doctor.py,templates/assembly-root/scripts/validate-pins.pyandscripts/bump-leg.pyby path. The errno and winerror tuples are held equal to the gate's and bump-leg's. The gate'sOTHER_REASONmust equal the doctor's plus exactly one space (the gate joins on+, the doctor on" ".join; neither changed, nothing stripped). The inline permission and folder sentences are read through the remedies the gate's_unreadable_fileand_workflows_unlistablebuild.cannot_read_whyis held equal to bump-leg's_cannot_open_advice, andcannot_be_read/cannot_be_listedto the gate's lines. Mutant R2 is killed (2 failed).winerrorand a stood-inpythonpath_unnamable(#278) reach them. See the mutant table below.contracts/unlistable for another reason got the file sentenceOTHER_REASON_FOLDERis the gate's directory sentence.manifest_candidatesmarks a listing, andcannot_read_why(refused, unlisted)gives a folder that sentence. The listing test is parametrized over a permission and EIO.drifted_reasonsdocstring now says it exactly, and a test pins["1 leg(s) not at the pin"]alone and[]beside a refused pin (kills O1).said, after the next-run sentence. The test pairs a refused spec pin with acodeleg past its pin (kills N1, and P2).shape currencyis fixed:pin_readis asked only afterread_pinraised (pin_refusal_row), so a pin that reads is read once (P1 holds it).legsis left: it reads a second time only for a pin that gave no commit, andpin_commitalso serves thepinsrow.mode()repeatsmode_ofmode()is gone, andmode_ofis imported fromtest_validate_pins_unreadable_workflows_dir.py, as #827'stest_validate_pins_named_and_spelled.pyalready does. That removes the copy without editing two files outside this PR and the shared conftest.check_shape_currencyintopin_refusal_row.Round 2 (RV860c, #860 issuecomment-6245274456)
Lane 3's RV860c re-checked round 1 at
bcb8c58band found it READY, with no MUST and no SHOULD, and two new NITs. Each item, and what this round did:legs, and the verdict is DRIFTEDcheck_legsaskspin_readwhenever the pin gave no commit, before the mount branches (no gitlink, an empty mount, no.gitof its own, an unborn HEAD, a HEAD git cannot read). The leg carriescannot_be_read, its problem says the mount's fact and then, and <the gate's line>, and the refused pin's advice follows whatever line is the NEXT (the checkout line stays). RV860c's probe (specemptied, its pin at 000) wasDRIFTED (1 leg(s) not at the pin)and is nowINVALID (pins, manifest-kinds). Six mode cases cover it, one per mount branch, each asserting the detail, the advice and the verdict, plus a stand-in for the empty mount. Mutants A1 (round 1's placement), A2 (the call removed) and A3 (the pin not named beside the fact) are killed.statfor another causetest_another_reason_is_given_the_gates_other_adviceis parametrized over a refusedstatand a refused read, and M5 is killed by[stat].0905eef5and all 33 other open PRs, on this head and on this head merged with main; see "Shared files".Before and Now
Re-measured this round on a real scaffold (
scaffold-project.py --local-remote-dir), run as uid 1000 with Python 3.12.3 and git 2.43.0.gh(exit 1) was first on PATH, withGH_TOKEN=bogusand an emptyGH_CONFIG_DIR.ca47b71, whose root carries validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827's gate. Now is this head: the first five cases were measured atbcb8c58band again at1c4e3cdf, where they print the same bytes, and the sixth at1c4e3cdf.git archive).<ROOT>is the probe root and<SHAPE>the copy the doctor ran from.pinsrow already says the gate's line. In the same report, the doctor's own rows say something else, and for a leg's pin orcontracts/the verdict is DRIFTED.Each Now row's WHY is, or ends with, the gate's permission advice, word for word, then
Then run this doctor again. No case printed a traceback.A pin at 000 (
contracts/spec-pin.yaml)ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindsunreadable as YAML: contracts/spec-pin.yaml (yaml-unreadable: cannot be read (Permission denied)); NEXTgit -C <ROOT> checkout HEAD -- contracts/spec-pin.yamlcontracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencylegsspec: contracts/spec-pin.yaml carries no 40-hex commit to compare HEAD against; NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>spec: contracts/spec-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against; NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceDRIFTED (1 leg(s) not at the pin)INVALID (pins, manifest-kinds)contracts/at 000ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindscould not be run: [Errno 13] Permission denied: '<ROOT>/contracts'; no NEXTcontracts: cannot be listed (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencycould not be run: [Errno 13] Permission denied: '<ROOT>/contracts/shape-pin.yaml'; no NEXTthe copies cannot be compared: contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission advicelegsspec: contracts/spec-pin.yaml carries no 40-hex commit to compare HEAD against; code: contracts/code-pin.yaml carries no 40-hex commit to compare HEAD against; NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>spec: contracts/spec-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against; code: contracts/code-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against; NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceDRIFTED (2 leg(s) not at the pin)INVALID (manifest, pins, manifest-kinds)contracts/atr--(listable, not searchable)ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindscould not be run: [Errno 13] Permission denied: '<ROOT>/contracts/shape-pin.yaml'; no NEXTcontracts/code-pin.yaml: cannot be read (Permission denied), contracts/repository-naming.yaml: cannot be read (Permission denied), contracts/shape-pin.yaml: cannot be read (Permission denied), contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencycould not be run: [Errno 13] Permission denied: '<ROOT>/contracts/shape-pin.yaml'; no NEXTthe copies cannot be compared: contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission advicelegsspec: contracts/spec-pin.yaml carries no 40-hex commit to compare HEAD against; code: contracts/code-pin.yaml carries no 40-hex commit to compare HEAD against; NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>spec: contracts/spec-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against; code: contracts/code-pin.yaml: cannot be read (Permission denied), so there is no pinned commit to compare HEAD against; NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceDRIFTED (2 leg(s) not at the pin)INVALID (manifest, pins, manifest-kinds)The shape pin at 000 (
contracts/shape-pin.yaml)ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindsunreadable as YAML: contracts/shape-pin.yaml (yaml-unreadable: cannot be read (Permission denied)); NEXTgit -C <ROOT> checkout HEAD -- contracts/shape-pin.yamlcontracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencythe copies cannot be compared, because contracts/shape-pin.yaml does not read as a pin: REFUSED yaml-unreadable: <ROOT>/contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTgit -C <ROOT> checkout HEAD -- contracts/shape-pin.yamlthe copies cannot be compared: contracts/shape-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission advicelegsINVALID (pins, manifest-kinds)INVALID (pins, manifest-kinds)A pinned copy at 000 (
contracts/repository-naming.yaml)ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED shape-copy-unreadable: contracts/repository-naming.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED shape-copy-unreadable: contracts/repository-naming.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindsunreadable as YAML: contracts/repository-naming.yaml (yaml-unreadable: cannot be read (Permission denied)); NEXTgit -C <ROOT> diff HEAD -- contracts/repository-naming.yamlcontracts/repository-naming.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencycould not be run: [Errno 13] Permission denied: '<ROOT>/contracts/repository-naming.yaml'; no NEXTthe copies cannot be compared: contracts/repository-naming.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission advicelegsINVALID (manifest, pins, manifest-kinds)INVALID (manifest, pins, manifest-kinds)specemptied, and its pin at 000 (RV860c's NIT A)ca47b71)pinsthe project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>the project's own scripts/validate-pins.py refuses (exit 2): REFUSED yaml-unreadable: contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <ROOT>/scripts/validate-pins.py --root <ROOT>manifest-kindsunreadable as YAML: contracts/spec-pin.yaml (yaml-unreadable: cannot be read (Permission denied)); NEXTgit -C <ROOT> checkout HEAD -- contracts/spec-pin.yamlcontracts/spec-pin.yaml: cannot be read (Permission denied); NEXTpython3 <SHAPE>/shape-doctor.py --root <ROOT>; WHY the gate's permission adviceshape-currencylegsspec: the gitlink is there and the directory is empty -- an unbootstrapped clone; NEXTgit -C <ROOT> submodule update --init --recursive --checkout -- specspec: the gitlink is there and the directory is empty -- an unbootstrapped clone, and contracts/spec-pin.yaml: cannot be read (Permission denied); NEXTgit -C <ROOT> submodule update --init --recursive --checkout -- spec; WHY what the line does, then the gate's permission adviceDRIFTED (1 leg(s) not at the pin)INVALID (pins, manifest-kinds)Every other row that reads
contracts/or a pinTen more places were checked:
pins: it quotes the gate, validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827's on main. Left alone.manifest: it quotesvalidate-manifest.py(the naming copy). Left alone: it is that gate's line (see "What it cannot tell").agent files: it reads the shape pin's rows throughpinned_paths, only when an agent file is absent. Left alone (see "What it cannot tell").pins->with_gitlink_repairs->gitlinks_off_their_pins: it reads each pin throughpin_commit. A refused pin is still None there, so it is left out of thebump-leg.pylines, as before; that reader oflegs:itself is untouched.pins->with_merged_away_rows->refusals_ahead: it callsread_pin/prepare, but only after the gate exits 1 with merged-away findings alone. An unreadable pin or copy makes the gate refuse, so it is never reached.manifest kinds->unreadable_next->pinned_copies,tool_written,edited_by_hand: these read the shape pin and catchOSError. They are asked only of files that do not load for another reason, and are unchanged.gitlink_left_behindandpin_at: they read git blobs, not files.read_here: itssamefilecatchesOSError.membersandfamily(a holder): they read members' manifests, not pins. The holder's ownshape currencyandmanifest kindsrun the code fixed here.what is here(not a root): it only asks whether acontracts/exists.Tests
tests/test_doctor_unreadable_pin.py, new, 52 cases. The row cases assert the row's exact line and, where the case is about them, its exact NEXT and WHY. The mode cases also assert that no traceback and nogit checkout HEAD --was printed.CANNOT_RUN_AS_ROOT, imported fromtest_validate_pins_unreadable_workflow.py, whose reason says why: root reads a mode-000 file, and Windows modes do not refuse a read. The modes are set bymode_of, imported fromtest_validate_pins_unreadable_workflows_dir.py(as validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827'stest_validate_pins_named_and_spelled.pytakes it).INVALID (pins, manifest-kinds);contracts/at 000 and atr--, all three rows, with the verdictINVALID (manifest, pins, manifest-kinds);.gitof its own with its gitdir kept and with it gone, an unborn HEAD, and a HEAD git cannot read. Each asserts the mount's fact and then the pin's line, the leg'scannot_be_read, the advice in the WHY andINVALID (pins, manifest-kinds)(round 2).statthrough validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827'srefused_stat, imported fromtest_bump_leg_unopenable.py, which also answersis_file()as Python 3.14 does; and a refused read or listing throughrefusing, written here.legsandmanifest kindslines, NEXT, WHY and detail, withdrifted_reasons == [];statand for a refused read, which gets the gate's other advice and nochmod;statis refused, which is named and is notupdate-pin-missing.contracts/that cannot be listed, for a permission and for EIO (the folder sentence);preparecannot read;refused_in_root;OSErrorout ofpreparethat is not the root's, which is raised as it was (outside the root, no file named, nothing there).legsNEXT cases:manifest_candidatesover a root with nocontracts/, or with acontractsthat is a file;is_aasked of a folder and of a name holding a NUL;refused_readgiven each of Windows' three "nothing there"winerrors, set by hand;legsrow with no leg off its pin: drift bylen(off) or 1alone, and none beside a pin this user cannot read.shape-doctor.py,templates/assembly-root/scripts/validate-pins.pyandscripts/bump-leg.pyby path:ABSENT_ERRNOS/ABSENT_WINERRORSagainst the gate's and bump-leg's;OTHER_REASONagainst the doctor's and one space;_unreadable_fileand_workflows_unlistablebuild for each cause, which ends on the doctor's sentence andRUN_AGAIN;cannot_read_whyagainst bump-leg's_cannot_open_advice, for a permission, another cause and both;cannot_be_readandcannot_be_listedagainst the gate's lines.The doctor's own fixtures are imported from
test_shape_doctor.py, anddoctor_againfromtest_doctor_recorded_answers.py. Neither changed file holds a new non-ASCII byte.What it cannot tell
manifestrow quotesvalidate-manifest.py, which neither validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827 nor this PR changes. For acontracts/this user cannot search, or for the naming copy at 000, it names<ROOT>/contracts/repository-naming.yamlby its absolute path, as: cannot be read (Permission denied)(validate-pins.py and repo_shape.py: a pin, the manifest or a shape copy the gate cannot reach or read is refused by name, relative to the root, once and with its cause's advice, and every name and path it prints is spelled (#712) #827'sload_yaml). That gate could be a follow-up.agent files, with the shape pin unreadable and an agent file also absent, reads no pin rows. It calls an absentAGENTS-shape.mdunpinned and namesupdate-shape.py check. That takes two faults at once, and its words are not the issue's. Not changed here.chmodfor a person, and cannot tell whether the mode, the directory's mode or the owner is at fault: the WHY names all three, as the gate's remedy does.repr. Pin paths are fixed (contracts/<role>-pin.yaml), so only a role, or a file undercontracts/, holding such a character would print differently.prepareinshape currency(the rootREADME.md'sShape:line) still ends incould not be run: it is not a pin and not undercontracts/. A pinned copy outsidecontracts/at 000 is named byshape currencynow, since one handler names any refused read of a file in the root.PermissionError, read the same way, and the three "nothing there"winerrors are set by hand on anOSError. Python 3.14'sis_file()is simulated byrefused_stat; only 3.12.3 was run.update-shape.py'spreparecloses its upstream only on aRefusal. For the local-path upstream the doctor passes, the upstream holds no temporary directory, so the newOSErrorpath leaves nothing open.check_shape_currency,check_legsandcheck_manifest_kindswere over 15 on main and grow here.drifted_reasonsgrows to about 12. This round moved the shape pin's branch out intopin_refusal_row. SonarCloud has not been read: its MCP server did not connect.Choices for the reviewer
manifest kindsjoins prose NEXTs with "; ", and zsh runs FAMILY_BESIDE_FIX's backticks) #548,remedy_as_why). It is not achmodline, for three reasons: it cannot be right on Windows, it cannot say whether the file, a directory above it or the owner is at fault, and the doctor never changes a mode. It is not None either:unreadable_nextalready prints the doctor again for a file only a person can mend, andshape currencymust print whatmanifest kindsprints for the same file.PERMISSION_ADVICE,OTHER_REASON,OTHER_REASON_FOLDER). It ends with the doctor's ownThen run this doctor againin place of the gate'sThen run the same command again.The gate's opening sentence (this validator reads ..., never a pass) is about the validator and is not carried.contracts/the row cannot list, the line iscontracts: cannot be listed (<reason>),validate-pins.py's words for a directory (templates/assembly-root/scripts/validate-pins.py: a.github/workflows/directory that this user cannot list or search ends the run in a PermissionError traceback, exit 1, where a workflow file it cannot open is aworkflow-unreadablerefusal, exit 2 #434). It has no trailing slash, as the gate names.github/workflows. For another cause, the WHY is the gate's directory sentence (rv860's NIT 3).legsrow stays a FINDING (it was one), and a leg whose pin cannot be read is not drift: the verdict is the gate's INVALID.n/aandnotewere rejected, because other legs in the same row may be off their pin.drifted_reasonsdocuments it, and a test pins it (rv860's NIT 4).legs: the checkout line first, then the lockstep gate, then the doctor again (a refused pin), then none (an unborn HEAD, no url). Beside a checkout line or the gate's line, the advice follows what the line does (rv860's NIT 5).manifest kinds: a file or folder that cannot be read comes first, ahead of the restore and the take-out, and the reason still names every fault. It goes in a newdetail.cannot_be_read(only when there is one), not inunreadable,refusalsorrestore, because no git state is asked.templates/, andrepo_shape.pyis a pinned copy, soABSENT_ERRNOSis a third copy (afterbump-leg.pyandvalidate-pins.py), and the advice sentences are copies too.+; the doctor's do not, because it joins them with" ".join. Neither side is changed.shape currencynames a refused read of any copy inside the root out ofprepare, not only one undercontracts/, because it is one handler. AnOSErrorabout a file outside the root (the standard's own), one naming no file, or one saying nothing is there is raised as before.doctor_linespells the doctor-again line once for this PR's rows.unreadable_nextkeeps shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779's inline line, the same string, as main has it. That was the merge's one conflict, resolved by taking shape-doctor.py: a conflicted shape pin's NEXT takes the side whose pin reads, and names no line where no side's pin reads, so run as printed it no longer leaves the doctor naming the same NEXT again (#752) #779's lines.check_shape_currencyaskspin_readonly afterread_pinraised.check_legsaskspin_reada second time only for a pin that gave no commit, now before the mount branches. A small split-out helper could let it read once without changingpin_commit's answer to its other caller, thepinsrow's gitlink repairs. That was not done here, since no behaviour is wrong as it stands (rv860's NIT 6, RV860c).scripts/shape-doctor.py; the file isshape-doctor.pyat the root.Shared files
git merge-tree --write-treeof this head1c4e3cdfagainst main0905eef5and every other open PR (git 2.43.0, PR heads fetched at 2026-10-18T03:50Z). This head is based onca47b71, so each PR is also merged against this head merged with main (tree1dff46f4, clean), and against main alone.1c4e3cdf0905eef50905eef5alone0905eef5843f6953837920c2f1234bc6296a6dd67f6ee36bfe7b30f668f27877033c49d71d774ab32ffe154cshape-doctor.py,templates/family-root/scripts/siblings.py,tests/test_family.py,tests/test_shape_doctor.pyshape-doctor.py,templates/family-root/scripts/siblings.pyshape-doctor.py,templates/family-root/scripts/siblings.py510ae681a1498632b6f56cad05a1ebb3233a334ee15c237bREADME.md,docs/handbook.html,shape-doctor.pyREADME.md,docs/handbook.html,shape-doctor.py7c20ba9690649e53db202f5308f7a283adopt-project.pyadopt-project.pyadopt-project.py8bfa52688f6a38e3tests/test_adopt_git_reads.pytests/test_adopt_git_reads.pytests/test_adopt_git_reads.pyd98df568tests/test_bump_leg_branch_and_push_line.py,tests/test_family_tracking_branch.pytests/test_bump_leg_branch_and_push_line.py,tests/test_family_tracking_branch.pytests/test_bump_leg_branch_and_push_line.py,tests/test_family_tracking_branch.py495d4b3370801c91adopt-project.pyadopt-project.pyb905bf8cf7072d088bb85cbf6207412340531e1903fe51e666fc8f6badopt-project.pyadopt-project.pyd183e769No conflict is this PR's. Against this head merged with main, the seven conflicts are each main's own, on the same paths:
../<Project>is NOT A CLONE, as a link to nothing is, so the dry run no longer sayswould cloneand the doctor'smembersrow no longer calls the slotabsent(#793) #794 and repo_shape.py, setup-project.py, shape-doctor.py, README and the handbook: under Git Bash (MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821, theshape-doctor.pyconflict regions are identical, line for line, in the merge with this head and main and in the merge with main alone (1 and 2 regions).../<Project>is NOT A CLONE, as a link to nothing is, so the dry run no longer sayswould cloneand the doctor'smembersrow no longer calls the slotabsent(#793) #794 shows against this head alone (tests/test_family.py,tests/test_shape_doctor.py) come from main's newer commits: siblings.py and shape-doctor.py: a Windows junction to nothing at../<Project>is NOT A CLONE, as a link to nothing is, so the dry run no longer sayswould cloneand the doctor'smembersrow no longer calls the slotabsent(#793) #794 is stacked on siblings.py and shape-doctor.py: a link to nothing at../<Project>is NOT A CLONE, reported and left as it is, so the dry run no longer sayswould clonewhere the real run ends CLONE FAILED, and the doctor'smembersrow no longer calls the slotabsent(#784) #788, which main now holds. repo_shape.py, setup-project.py, shape-doctor.py, README and the handbook: under Git Bash (MSYSTEMset) the lines that follow the host are quoted forsh, through one question,host_shell_dialect, so the doctor's lines, setup's family lines andfamily.py'sNEXT,switch -c,addandcdlines paste whole in the shell that printed them, and README's Windows sentence says so #821, adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861 and family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871 merge clean with this head alone for the same reason, and conflict with main alone.DDat its old name, is named gone on both sides with each new name a side's own commit holds the base's blob of it at, never as a merge of hunks git did not merge (#783) #789 (033c49d7), shape-doctor.py, bootstrap.py, bump-leg.py and update-shape.py: alegs:that is not a list, or holds an entry that is not a mapping, is said in #714's words, so the doctor's leg rows wait on themanifestrow instead of calling the project one with no legs, bootstrap places no leg for it, and bump-leg.py and update-shape.py refuse it by name where each ended in aTypeError(#832) #862 (b905bf8c), bootstrap.py and repo_shape.py: a.shape-upstream-tipor a YAML file a Windows editor saved with a byte-order mark reads as the text it holds, so the upstream notice names the tip, a markedproject.yamlis no longerschema_version is None, and every reader of a marked pin, the doctor's included, reads it alike (#855) #868 (62074123) and shape-doctor.py, family.py and the family root's bootstrap.py and validate-family.py: a holder'smembers:that is not a list, or holds an entry that is not a mapping, is said in the gate's words, so the doctor'smembersrow waits on thefamilyrow instead of calling the family empty,add,bump,remove,make bootstrapandmake siblingsrefuse it by name where each ended in aTypeError, read no members or committed the entry away, and--no-membersfinds the entry (#864) #870 (03fe51e6) are clean.tests/test_doctor_unreadable_pin.py,tests/test_doctor_non_ascii_paths.pyandtests/test_doctor_paste_through_git.py(the last two new on main) gave 199 passed.Pinned copy, the run and the mutants
shape-doctor.pyis in none ofshape_materialize.COPIED_FROM_SHAPE,COPIED_VERBATIM,FAMILY_COPIED_FROM_SHAPEorFAMILY_COPIED_VERBATIM, so no root'supdate-shape.py checkshows it.1c4e3cdf,shape-doctor.pysha2565fa1f8fb..., the new file916e4578...)./usr/bin/python3 -m pytest -q -rs -p no:cacheprovider -o tmp_path_retention_policy=failed --basetemp=<scratch>/bt.tests/test_doctor_non_ascii_paths.py,tests/test_doctor_paste_through_git.py). That is the new file and the other 14tests/test_doctor_*.py;tests/test_shape_doctor.py;tests/test_validate_pins_unreadable_workflows_dir.py; andtests/test_repo_hygiene.py,tests/test_agents_md_codes.pyandtests/test_cli_reference.py.GIT_CONFIG_GLOBALan empty file,GIT_CONFIG_NOSYSTEM=1andGIT_ALLOW_PROTOCOLunset; a stubgh(exit 97) first on PATH, withGH_TOKEN=bogusand an emptyGH_CONFIG_DIR; Python 3.12.3, uid 1000,df -i /at 78%.tests/test_shape_doctor.py:10424, the PowerShell line run only on a Windows host.0905eef5(tree1dff46f4), the new file and the two main-only doctor files gave 199 passed. On this head merged with main80aee84e, which landed during the run (treed327d86c, clean), the new file gave 52 passed.bcb8c58bover the same command and environment on 20 files: 1118 passed, 3 skipped in 2039.07s, exit 0.shape-doctor.py, the new file was run at 52 cases, and the file was put back byte for byte from a saved copy. Its sha256 was5fa1f8fb...after every mutant and at the end.bcb8c58b, new file (43 or 44)bcb8c58b: rv860's 30, six of them ported to this round's text, and three for this round's own lines (P1 to P3).shape-doctor.py, the new file was run, and the file was put back byte for byte from a saved copy. Its sha256 wasff562ba0...after every mutant and at the end.test_shape_doctor.pyandtest_doctor_unfinished_merge.pyalso killed G1 (28 failed).a2b0e48c, new file (15)Closes #847
🤖 Generated with Claude Code
Summary by Sourcery
Make shape-doctor report unreadable pins and contract files consistently with validate-pins.py and classify these failures as invalid rather than drift.
Bug Fixes:
Enhancements:
Tests: