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
Open
TASK-121 — a check that reads live project state as its expected value is now found mechanically#28ranjiao wants to merge 6 commits into
ranjiao wants to merge 6 commits into
Conversation
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>
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.
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.mddeclared 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:
schema/state-schema.jsondeclares Perry writes, resolved through theState root:line in.perry/config.md, or the parsed payload of abin/tool run withcwd=/--rootpointing 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 onBOARD.mdor the task store would have missed it.test_the_same_claim_lands_where_the_state_root_pointsbuilds two projects from one schema and shows the same claim landing atBOARD.mdand atdocs/BOARD.md.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 keepssum(...) == len(records)andproblems == []off it.What it deliberately does not catch
The module docstring names seven, including two of the eight known instances:
assertIn("perry/tasks.jsonl (unclaimed)", …). Growth cannot falsify a containment assertion, andtest_md_storekept 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.TASK-038borrowed off the live board. The defect is in the fixture, not in an expected value.assertTrue/assertFalseon 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 outsidetests/.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:md_store.before.pyd90612a(f3c4461^)f3c4461report["kinds"] == {"setting": len(records)}track_attribution.before.pyd90612af3c4461[t["track"] …] == ["main"]v5_signoff.before.pye116f8a(cbbc41a^)cbbc41aset(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_holdsbyte-compares againstgit showwhere the history is reachable;test_the_fixtures_have_not_been_editedpins SHA-256 for CI, which checks out at depth 1 and cannot.2. It does not flag the fixes.
tests/test_track_attribution.pyandtests/test_v5_signoff.pyas they stand today are clean end to end — and the repairedTestAProjectWithNoRegisterIsUnmovedstill 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_okr—assertGreater(len(krs), 20)overperry/OKR.md. c9018ae's shape exactly.test_task_writer § test_every_hand_written_row_in_perrys_own_board_round_trips—assertGreater(len(rows), 5)over Perry's ownBOARD.md.test_prioritize § test_an_id_shaped_word_in_prose_is_warned_about—ctxbuilt fromload_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:208—semanticsis a changelogbin/perry-taskcarries 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:314—state_rootis 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 red —test_diagnose,user_load.danglingnon-empty. That is TASK-126 on unmerged PR #22 and it is inherited: my own baseline on5dd745fbefore touching anything was 65 modules · 1930 tests · the same 1 red.python3 bin/perry-lint0 errors (3 pre-existing NS-01 warnings),git diff --checkclean.git diff -- perry/is empty.Note on the file count
feat/work-modesis ahead of origin, so every PR cut from it carries those commits. My own code diff is 6 new files, 0 modified —tests/live_state_expectations.py,tests/test_live_state_expectations.py,tests/fixtures/live-state-expectations.json, and three history fixtures.🤖 Generated with Claude Code