TASK-135 — an existing row can change track - #29
Open
ranjiao wants to merge 9 commits into
Open
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>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TASK-141 found two live copies of the startable rule and fixed both. That corrects TASK-127's record, which called the board-era path dead — _cmd_list_from_board is called at bin/perry-task:1525. Its own REL-00 diagnosis was wrong and is corrected in its record: the id is a literal the PMO wrote into a V5 sign-off, not a truncated match of REL-002. Verified both ways against the harvester — a file containing only REL-002 harvests ['REL-002'], and one containing REL-00 harvests ['REL-00']. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`--track` was accepted by exactly two commands: `add`, at creation, and `route`, which turns an intake row into a task. So a project that declared a second track started it empty and could not move any existing work onto it — measured on this repository, which declared `intake` (mode `queue`) and left six rows that genuinely arrived stranded on `main`. A track with no rows is not a mode running on a live track. `perry-task track <ID> --track <name>` is the third entrance, and it stamps what `route` stamps. Onto a queue track it sets `Arrived` and the track's first post-intake `Stage` through the same `entry_stage` the other two entrances call, because `today − Arrived` is the number every SLA check measures and a second entrance that omitted it would make `Arrived` mean one thing per door. An existing `Arrived` is CARRIED rather than restamped: a queue → queue move that reset the clock would erase an in-flight breach, which is the same exemption arriving the other way. Off a track whose destination reads neither, `Stage` / `Stage since` / `Arrived` are CLEARED and what they held is written into the journal line and the event. `Arrived` is not provenance, it is a queue's clock, and one left on a row no queue governs is a live-looking number the next SLA reader picks up — `cmd_route` already had to stop writing it onto non-queue rows because a non-empty `arrived` hides the row from `rows_with_no_computable_age`. Dropping the cell is not dropping the fact: that is `drop`'s precedent, one field down. A track no row of `.perry/config.md § Tracks` declares is refused by name with the declared ones listed, in `check_role`'s shape — `track_of` used to print a Python list repr and stop, which names the set and not the way forward. Nothing is created. - `commit`'s `changed` map carries all four cells with the track, so the store cannot keep yesterday's clock beside the new track and the board, which is rendered FROM the store, cannot half-revert on re-render. - `widen_row_section` asks the widening question for both section shapes. `cmd_stage` widens only under `## P0`/`P1`/`P2`, so on the board shape `--group` exists for a stamp is dropped silently at exit 0. - `tests/test_track_move.py`: 30 tests, one class per V3 item. 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.
The gap
--trackwas accepted by exactly two commands:add, at creation, androute, which turns an intake row into a task. There was noperry-task track <id>, andstatus --trackis refused.So a project that declared a second track started it empty and could not move any existing work onto it. This repository declared
intake(modequeue) on 2026-08-20 and it is still empty, while six rows that genuinely arrived rather than being decomposed belong on it and could not get there. A track with no rows is not a mode running on a live track — which is why the declaration did not meet KR-O1.1.Source:
perry/evidence/2026-08/TASK-133-track-experiment.md, specperry/evidence/2026-08/TASK-135-spec.md.What ships
perry-task track <ID> --track <name> [--stage S] [--arrived YYYY-MM-DD] [--reason "…"]Onto a queue track it stamps what
routestamps —Arrived, and the track's first post-intakeStagevia the sameentry_stagethataddandroutecall.today − Arrivedis the number every SLA check measures, so a second entrance that omitted it would makeArrivedmean one thing per entrance and silently exempt every moved row from the only clock governing it.An existing
Arrivedis carried, never restamped. A queue → queue move that reset the clock to today would erase an in-flight breach — the same exemption, arriving through the other door.--arrivedoverrides.Off a track that reads neither,
Stage/Stage since/Arrivedare cleared, and what they held is recorded in the journal line and the event (stage_from,arrived_from). This is deliverable 2's decision, and it isdrop's precedent applied one field down.Arrivedis not provenance, it is a queue's clock, and one left on a row no queue governs is a live-looking number the next SLA reader picks up. It is already load-bearing in the negative direction and measured:cmd_routehad to stop writingArrivedonto pipeline rows precisely because a non-emptyarrivedhides the row fromrows_with_no_computable_age— the finding that says "this row has no clock at all". Keeping it would suppress the one report that could notice the move. The journal is append-only, so the cell goes and the fact does not.An undeclared track is refused by name, in
check_role's shape:It used to print a Python list repr and stop, which names the set and not the way forward. Nothing is created — a tool that invented the track would turn a typo into a second track every count then reports as real.
The two seams that would have half-landed the write
commit'schangedmap carries("track", "stage", "stage_since", "arrived"). Listing onlytrackwould land the new track in the store beside yesterday's clock; the board is rendered from the store, so the move would half-revert on the next render. Asserted againstperry-tasks render --write.widen_row_sectionasks the widening question for both section shapes.Board.findreturnsP0/P1/P2or a project's own heading, and the two widenings live on different methods —cmd_stagewidens only in the first case, so on the board shape--groupexists for a stamp is dropped silently at exit 0. A track move writes four such cells.Verification — the spec's six items
TestSixRowsMove— six existing rows reach a declaredqueuetrack, the board rendersArrivedandStage, andperry-state --jsonreportsstage_counts: {"triaged": 6}where it reported{}before.TestTheRefusalIsByName— names the track, lists the declared ones, exit 1 with no traceback, creates no track and writes no event.TestBothDirectionsOfTheFieldQuestion— onto a queue track, then off it, asserting the store record both ways, plus the row that already had anArrivedbefore the move.TestAnEventPerMove— onetrackevent per move,field: "track", reaching bothtimeline[]andperry-task events.TestTheProjectionAgreesAfterAMove—perry-lint --jsonreports nostore-driftfinding after a move, and after a move that clears the clock, which is the direction a renderer silently skips.python3 tests/parallel -j 4,bash tests/run,python3 bin/perry-lint,git diff --check— all below.Tests
6893da6)python3 tests/parallel -j 4The one red is
test_diagnosewithuser_load.danglingnon-empty — TASK-126, fixed on unmerged PR #22, inherited by every worktree cut from this branch. Identical before and after; nothing else is red.bash tests/runagrees.perry-lint: 0 errors, 3 pre-existing NS-01 warnings, 0 rows drifted.git diff --checkclean.Out of scope, and honoured
git diff -- perry/is empty. The six live rows are not moved — this ships the tool; using it on this project's board is a separate act the user takes.Note on the file count
feat/work-modesis ahead of origin, so this PR carries those commits until the branch is pushed. This row's own code diff is 6 files, +713 / −18:bin/perry-task,tests/test_track_move.py(new, 30 tests),tests/test_prioritize.py,schema/task-list-contract.md,schema/events-list-contract.md,work/reference/subcommands.md.The contract-doc edits were not optional:
EVENT_FIELDgains a key, andtests/test_prioritize.pybinds that map's size to a number written in the contract's prose. That guard hardcodedof the fourteenin its own regex, so the day a fifteenth task event landed the doc could be corrected and the test would stop matching altogether and pass by finding nothing — it now reads both numbers out of the prose and checks the total againstTASK_EVENTS.🤖 Generated with Claude Code