TASK-113 — a check must not read live project state as its expected value - #18
Merged
ranjiao merged 12 commits intoAug 20, 2026
Merged
Conversation
PR #6's conflicts were a wrong base, not a code conflict: it targeted main, which trails this branch by ~280 commits. The day's first dispatch prompt did not name a base branch. Verified zero code conflict before touching anything. TASK-092 dispatched — the chain head that gates nine rows including both P0s. It became dispatchable the moment the blank-marker fix was committed, because that fix is in the exact module the row extends. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
USER-003 spent two days reported as a person waiting, for a decision the user had already made the day it was minted. Nothing reconciles a USER- row against an ADR that answers it. TASK-070 split rather than cleared: TASK-110 measures and proposes, TASK-070 keeps the execute half. Re-measurement moved it P2 -> P1 — Perry's own state is 31.3% of tracked bytes now, and evidence/ grew 5.6x in two days. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three findings, increasing in severity: PR #12's CI red is the branch base, not the change; a test reads two files outside the repository and is therefore green here and red on CI forever; and main carries a stale second implementation of the NS-01 change that PR #13 reverts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three checks went red on ordinary progress, all one defect class: a test that reads the project living around it. One of them is self-referential — writing that REL-00 was gone, inside a signed V5 record, is what brought it back. TASK-114 is delegated rather than dispatched: aiMark is another repository and the work is a version catch-up, deliberately not the architecture change its KR eventually wants. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… value Three tests went red on an ordinary afternoon and none was a regression. All three asserted something taken from the project's own state at the moment they were written, so ordinary progress made them false. The same class was fixed once already that day (c9018ae, `rows_from_store > 20`). 1. `test_v5_signoff.TestHistoryIsNotRewritten` named exactly three V5 closes. Three more were signed the day it shipped. It now asserts the property the count stood in for: every signature already in the journal parses, keeps its disposition headings and provenance labels, and agrees with the counts in its own event. Quantified over whatever the log holds. 2. `test_one_line_break_rule.TestTheRefusalNamesTheFlag` borrowed TASK-038 off the live board. That row closed and left, so the tool answered "TASK-038 is not a row on the board" and the flag assertion stopped running — a red that misnamed its own cause. The class now builds its own project and its own row under `--root`, and asserts the row is writable before asserting the refusal, so the two can never be confused again. 3. `LOAD-02` reported `REL-00` dangling. Its only sources were a signed V5 record saying the dangling report was empty, and two lines of the spec reporting that finding. Writing that a code was gone brought it back: the check was reading its own output as input, and the signed record is append-only, so nobody was permitted to fix the line it named. `bin/perry-diagnose` now separates a REFERENCE from a REPORT by three structural marks — inside a signed record, inside a blockquote, or on a line naming one of this checker's own finding codes — and exempted ids are reported beside the count as `user_load.dangling_in_reports`, never into it. Bare prose saying a code is gone still counts; that boundary is deliberate and carries its own test. 4. Found by the sweep, same class: `test_diagnose`'s reconciliation test asserted `open_decisions == asks.open` on this repository. `open_decisions` sums two registers and `asks.open` reads one, so the equality held only while no design doc had the open `Chosen` row the template requires. `open_decisions_by_register` now carries the queue half, and the test reconciles that against the queue. No file under `perry/` is edited. LOAD-03's fix (137ffb3) does not transfer literally — LOAD-02 already counts one entry per id — and `bin/perry-diagnose` says so where the rule is written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A fixture's own V5 signature header carries ids nobody defined either, and they are exempted for the same reason the id under test is. Asserting the whole list back was one more assertion about incidental state — the defect this row exists to remove. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t by The base branch grew a second instance while this row was open. A V4 review under `perry/evidence/` demonstrates a check going red — "adding one ordinary `DESIGN-900-probe.md` … turns the test red" — and LOAD-02 charged the project for `DESIGN-900`. Same defect as `REL-00`, one document over: prose reporting a check result, counted as a demand that the code resolve. The structural mark was too narrow. It recognised this checker's own finding codes and nothing else, so a line naming a TEST rather than a finding walked past it. It now recognises either. Broad-sounding, and safe for a reason worth stating: the rule is applied only to ids that are ALREADY undefined everywhere, and only when EVERY mention is a report. `TASK-042 is covered by test_foo` loses nothing, because TASK-042 is defined and was never a candidate. What survives both filters is the demonstration id, which is exactly what must not be charged. A test pins that direction too. Also drops `dangling == []` from this row's own new test — an assertion over whatever the repository happens to hold today, which is the defect this row exists to remove. `test_perry_itself_passes_its_own_id_checks` owns that gate; this test owns the id the fix was written for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e line The V4 review names the check on one line and the id it demonstrated with three lines later. Markdown wrapped one sentence; a per-line mark exempted half of it and charged the project for the other half, so DESIGN-900 stayed dangling. A run of non-blank lines is the smallest unit of prose carrying one statement, and it is still a structural unit rather than a reading of the English. A test pins that the paragraph ends at its blank line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What went wrong
Three tests went red on an ordinary afternoon and none was a regression. Each asserted something read out of the project's own state at the moment it was written, so ordinary progress made it false. The same class had been fixed hours earlier in
c9018ae(rows_from_store > 20→> 0, because closing a task removes a projected row).What changed
1.
test_v5_signoff.TestHistoryIsNotRewrittennamed exactly three V5 closes; three more were signed the day it shipped. It now asserts the property the count stood in for, quantified over whatever the log and journal hold: every signature parses, keeps its disposition headings and provenance labels, and agrees with the counts in its own event. No task id and no total appear in the class.2.
test_one_line_break_rule.TestTheRefusalNamesTheFlagborrowedTASK-038off the live board. That row closed and left, so the tool answeredTASK-038 is not a row on the boardand the flag assertion stopped running — a red that misnamed its own cause. The class now builds its own project and its own row under--root, and asserts the row is writable before asserting the refusal, so a vanished fixture and a broken refusal can never be confused again.3.
LOAD-02reportedREL-00dangling. Its only sources were a signed V5 record recording that the dangling report was empty, plus two spec lines reporting that finding. Writing that a code was gone brought it back: the check was reading its own output as input, and the signed record is append-only, so nobody was permitted to fix the line it named.bin/perry-diagnosenow separates a REFERENCE from a REPORT by three structural marks — inside a signed record, inside a blockquote, or in a paragraph naming a check (one of this checker's own finding codes, or atest_…identifier) — and exempted ids are reported beside the count asuser_load.dangling_in_reports, never into it. Bare prose saying a code is gone still counts; that boundary is deliberate and carries its own test.137ffb3's fix (count records, not mentions) does not transfer literally: LOAD-02 already counts one entry per id, so deduplicating buys nothing. What transfers is the move underneath it — ask what the number is for, and count only what the answer admits.bin/perry-diagnosesays so where the rule is written.4. Found by the sweep, same class.
test_diagnose's reconciliation test assertedopen_decisions == asks.openon this repository.open_decisionssums two registers andasks.openreads one, so the equality held only while no design doc had the openChosenrow the template requires.open_decisions_by_registernow carries the queue half and the test reconciles that.5. A second instance of (3) arrived on the base branch mid-row. A V4 review under
perry/evidence/demonstrates a check going red — "adding one ordinaryDESIGN-900-probe.md… turns the test red" — and LOAD-02 charged the project forDESIGN-900. Same defect one document over. The mark now recognises a paragraph naming a test, not only a finding code; and it is scoped to the paragraph because markdown had wrapped that one sentence across four lines.Broad-sounding, safe for a stated reason: the rule is applied only to ids already undefined everywhere, and only when every mention is a report.
TASK-042 is covered by test_fooloses nothing —TASK-042is defined and was never a candidate. What survives both filters is the demonstration id. A test pins that direction too.Each check still fires
test_the_check_fails_on_a_record_whose_dispositions_were_rewrittenmutates a copy of the journal in memory and asserts the checker reddens — heading renamed, provenance rewritten, signer blanked, item deleted.bin/perry-task(the original defect) turnstest_two_flags_do_not_produce_the_same_messagered.split_danglinginto always-exempt reddens 4 of the 11 tests in the new class.Demonstrated, not reasoned about
Merged locally against the four PMO commits the base gained while this row was open — real further closes, a new signature, new evidence — and the full suite is green there: 63 modules, 1864 tests, 0 red.
Not touched
git diff -- perry/is empty. No signed record is edited.🤖 Generated with Claude Code