Skip to content

TASK-121 — a check that reads live project state as its expected value is now found mechanically - #28

Open
ranjiao wants to merge 6 commits into
feat/work-modesfrom
coding/task-121-live-state-guard
Open

TASK-121 — a check that reads live project state as its expected value is now found mechanically#28
ranjiao wants to merge 6 commits into
feat/work-modesfrom
coding/task-121-live-state-guard

Conversation

@ranjiao

@ranjiao ranjiao commented Aug 20, 2026

Copy link
Copy Markdown
Owner

TASK-113 found five of these by hand in one afternoon and the pass was thrown away. Three more arrived within the week — two the moment .perry/config.md declared its first track, two when PR #14 changed which paths the schema says Perry owns. There was no mechanism; there was a memory of having looked. This is the mechanism.

What I decided the class is

A check is in this class when a value it read out of the project it lives in is asserted equal to a literal that enumerates or counts what that project happens to hold today. Both halves must hold:

  1. it reaches live state — a path schema/state-schema.json declares Perry writes, resolved through the State root: line in .perry/config.md, or the parsed payload of a bin/ tool run with cwd=/--root pointing inside this repository. The path list is read out of the schema and never named in the guard: instance 8's literals were about which paths Perry owns, so a guard keyed on BOARD.md or the task store would have missed it. test_the_same_claim_lands_where_the_state_root_points builds two projects from one schema and shows the same claim landing at BOARD.md and at docs/BOARD.md.
  2. its expectation is closed — a non-trivial constant, or a non-empty list/set/tuple/dict display, including one reached through a module or class constant or through set(...). [], {}, 0, 1, "" are not closed: "nothing is wrong" is a property quantified over whatever the project holds, and it is the shape every one of TASK-113's repairs converged on.

Half one keeps test_prioritize — which asserts exact rendered tables against boards it wrote itself — off the report. Half two keeps sum(...) == len(records) and problems == [] off it.

What it deliberately does not catch

The module docstring names seven, including two of the eight known instances:

  • containment — instance 8's assertIn("perry/tasks.jsonl (unclaimed)", …). Growth cannot falsify a containment assertion, and test_md_store kept exactly that shape through its repair; catching instance 8 needs a path-literal signal that cannot be told from the hundreds of path strings the fixtures legitimately write.
  • a live value used as INPUT — instance 2's TASK-038 borrowed off the live board. The defect is in the fixture, not in an expected value.
  • also: assertTrue/assertFalse on a live value; a tool's exit code and human text; code/contracts/templates (schema/, SKILL.md, bin/ — a test SHOULD go red when the schema is edited); the repository root handed to a helper (tried, reverted — it put 21 fixture-only assertions on the report); anything outside tests/.

Verification

1. It finds instances it was not shown. Three are checked in verbatim as whole modules under tests/fixtures/live-state/, each the module as its repair found it:

fixture commit repaired by flagged
md_store.before.py d90612a (f3c4461^) f3c4461 report["kinds"] == {"setting": len(records)}
track_attribution.before.py d90612a f3c4461 [t["track"] …] == ["main"]
v5_signoff.before.py e116f8a (cbbc41a^) cbbc41a set(events) == set(self.SIGNED)

Whole modules, not excerpts — trimming is hand-writing an approximation by another name, and the analysis reads module bindings, class attributes and sibling methods. test_the_fixtures_are_what_git_holds byte-compares against git show where the history is reachable; test_the_fixtures_have_not_been_edited pins SHA-256 for CI, which checks out at depth 1 and cannot.

2. It does not flag the fixes. tests/test_track_attribution.py and tests/test_v5_signoff.py as they stand today are clean end to end — and the repaired TestAProjectWithNoRegisterIsUnmoved still asserts ["main"], against a project it built itself. test_md_store's repaired assertion is gone from the report.

3. False-positive floor: 6, stated, over 65 modules and 1930 tests. Recorded with a verdict apiece in tests/fixtures/live-state-expectations.json — a new hit is a red, not a line in a report nobody reads.

Three real instances (each owes its own row; this row fixes none of them):

  • test_md_store § test_okrassertGreater(len(krs), 20) over perry/OKR.md. c9018ae's shape exactly.
  • test_task_writer § test_every_hand_written_row_in_perrys_own_board_round_tripsassertGreater(len(rows), 5) over Perry's own BOARD.md.
  • test_prioritize § test_an_id_shaped_word_in_prose_is_warned_aboutctx built from load_task_records(perry/). Its neighbour is the more fragile half and is not flagged, because [] is not closed: fn("see ADR-006 and USER-014", ctx) == [] needs both ids to still resolve on this board.

Three false positives, named rather than silenced:

  • test_contract_invariance:208semantics is a changelog bin/perry-task carries in its own source; half one cannot tell a tool-built payload subtree from a project-built one.
  • test_md_store:300"2099-01-01" is the value the test wrote into the store four lines earlier.
  • test_md_store:314state_root is the record the test selected and mutated.

4. Reverting the guard reddens its own test. Five mutations, each a red: dict displays no longer closed (instance 6), no constant folding through a class attribute (instance 1), the state root not resolved (instances 6 and 7), and each half removed in turn.

5. python3 tests/parallel -j 4: 66 modules · 1951 tests · 1 module redtest_diagnose, user_load.dangling non-empty. That is TASK-126 on unmerged PR #22 and it is inherited: my own baseline on 5dd745f before touching anything was 65 modules · 1930 tests · the same 1 red. python3 bin/perry-lint 0 errors (3 pre-existing NS-01 warnings), git diff --check clean.

git diff -- perry/ is empty.

Note on the file count

feat/work-modes is ahead of origin, so every PR cut from it carries those commits. My own code diff is 6 new files, 0 modifiedtests/live_state_expectations.py, tests/test_live_state_expectations.py, tests/fixtures/live-state-expectations.json, and three history fixtures.

🤖 Generated with Claude Code

ranjiao and others added 6 commits August 20, 2026 23:38
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TASK-122 corrected the PMO: its prompt said test_diagnose's red had been
fixed on a sibling branch. It has not — PR #22 is unmerged, so every
worktree cut from feat/work-modes inherits it. The agent measured its own
baseline, found the red, and reported rather than absorbing it.

And the list is now three: ZZZ-404 came from the TASK-126 spec and result
records, both written by the PMO. Writing about the self-reference defect
produced another instance of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nically

TASK-121. TASK-113 found five of these by hand in one afternoon and the
pass was thrown away; three more arrived within the week — two the moment
`.perry/config.md` declared its first track, two when PR #14 changed which
paths the schema says Perry owns. This is the mechanism.

**The class, and it is the whole row.** A check is in it when a value it
read out of the project it lives in is asserted equal to a literal that
enumerates or counts what that project happens to hold today. Both halves
have to hold, and each one alone is a useless direction:

1. it reaches live state — a path `schema/state-schema.json` declares
   Perry writes, resolved through the `State root:` line, or the parsed
   payload of a `bin/` tool run at `cwd=`/`--root` inside this repo. The
   path list is READ OUT OF THE SCHEMA, never named here: instance 8's
   literals were about which paths Perry owns, so a guard keyed on
   BOARD.md would have missed it;
2. its expectation is closed — a non-trivial constant, or a non-empty
   list/set/tuple/dict DISPLAY, including through a class constant or
   `set(...)`. `[]`, `{}`, `0`, `""` are not closed: "nothing is wrong"
   is a property quantified over whatever the project holds, and it is
   the shape every one of TASK-113's repairs converged on.

Half one is what keeps `test_prioritize` — which asserts exact rendered
tables against boards it wrote itself — off the report. Half two is what
keeps `sum(...) == len(records)` off it.

What it deliberately does NOT catch is in the module docstring and is
seven items long, including two of the eight known instances: containment
(instance 8's `assertIn("perry/tasks.jsonl (unclaimed)", …)`) and a live
value used as INPUT (instance 2's `TASK-038` fixture). A guard whose
boundary is undocumented gets widened until it flags everything.

**Verified against history, not against my own handwriting.** Three
instances are checked in verbatim as whole modules under
`tests/fixtures/live-state/` — `d90612a:test_md_store.py`,
`d90612a:test_track_attribution.py`, `e116f8a:test_v5_signoff.py`, each
the module as its repair found it. The guard flags the repaired assertion
in all three and is silent on all three after repair. The fixtures are
byte-compared against `git show` where the history is reachable and
SHA-256 pinned where it is not (CI checks out at depth 1).

**The floor is six, stated, and not zero.** Three are real instances and
owe rows of their own — this row ships the mechanism and fixes none of
them; three are false positives named and explained in
`tests/fixtures/live-state-expectations.json`. Reaching zero would have
meant silencing a verdict or making a repair this row is not allowed to
make.

Five mutations proved the guard's own test can fail: dict displays not
closed, no folding through a class attribute, the state root not
resolved, and each half removed in turn.

No file under `perry/` is edited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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