Skip to content

TASK-113 — a check must not read live project state as its expected value - #18

Merged
ranjiao merged 12 commits into
feat/work-modesfrom
coding/task-113-checks-read-live-state
Aug 20, 2026
Merged

TASK-113 — a check must not read live project state as its expected value#18
ranjiao merged 12 commits into
feat/work-modesfrom
coding/task-113-checks-read-live-state

Conversation

@ranjiao

@ranjiao ranjiao commented Aug 20, 2026

Copy link
Copy Markdown
Owner

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.TestHistoryIsNotRewritten named 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.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 a vanished fixture and a broken refusal can never be confused again.

3. LOAD-02 reported REL-00 dangling. 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-diagnose now 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 a test_… identifier) — 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.

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-diagnose says so where the rule is written.

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.

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 ordinary DESIGN-900-probe.md … turns the test red" — and LOAD-02 charged the project for DESIGN-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_foo loses nothing — TASK-042 is defined and was never a candidate. What survives both filters is the demonstration id. A test pins that direction too.

Each check still fires

  • V5: test_the_check_fails_on_a_record_whose_dispositions_were_rewritten mutates a copy of the journal in memory and asserts the checker reddens — heading renamed, provenance rewritten, signer blanked, item deleted.
  • Flag refusal: disabling the value→flag match in bin/perry-task (the original defect) turns test_two_flags_do_not_produce_the_same_message red.
  • LOAD-02: a genuinely dangling id still fires; relaxing split_dangling into 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

ranjiao and others added 12 commits August 20, 2026 16:14
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>
@ranjiao
ranjiao merged commit cee1ef1 into feat/work-modes Aug 20, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant