Skip to content

TASK-120 — a KR's current is an assertion, and the payload says so - #24

Merged
ranjiao merged 16 commits into
feat/work-modesfrom
coding/task-120-kr-progress-provenance
Aug 20, 2026
Merged

TASK-120 — a KR's current is an assertion, and the payload says so#24
ranjiao merged 16 commits into
feat/work-modesfrom
coding/task-120-kr-progress-provenance

Conversation

@ranjiao

@ranjiao ranjiao commented Aug 20, 2026

Copy link
Copy Markdown
Owner

What was wrong

phase/<NNN>-linkage.md carries target and current per KR, both hand-written, and nothing derived, checked or aged them. The row's title says the edges are never folded into progress; the measurement says worse — the numbers were wrong in both directions on the same day:

KR target current read as actually
P-O1.1 1.0 0.0 0% all four linked tasks closed
P-O2.2 0.0 0.0 met TASK-094 measured 13 row splits and 87 header resolutions still live

Six of the eight phase KRs drive a count to zero, and the register template writes current: 0, so a drive-to-zero KR reads as achieved on the day it is written.

What this adds

Three additive blocks per KR, computed once in bin/lib § kr_progress_provenance and emitted by both perry-goals list --json and perry-state --json § linkage — one derivation, two payloads, so they cannot come to disagree.

  • current_provenanceasserted / unasserted, when, at what granularity (asserted_scope: "register" — the date belongs to the register, not the KR), and measured: false. That last is true of every current Perry publishes, because no tool re-runs a KR's metric. It is emitted rather than implied so a consumer showing "measured" is contradicted by the payload rather than by a docstring.
  • current_staleness — whether a linked task changed state after the number was asserted, naming which and how it moved. evaluated is carried apart from stale so nothing moved cannot be read as there was nothing to ask.
  • linked_task_completion{total, done, dropped, open, unknown}. A count of tasks, beside current and never inside it.
  • conformance.krs_with_stale_current, and a perry-state warning naming the KRs.

The trap, and where the line is drawn

Nothing here computes current. "TASK-091 is closed" does not establish that the count its KR asks about is zero — only re-running the count does — and a ratio presented as current would be the fabricated measurement perry/OKR.md § Operating Principles forbids in its first line.

The linked-task tally is therefore emitted beside current, in a different unit, under its own name. On P-O1.1 the payload now carries current: 0.0, target: 1.0 and 4 of 4 tasks done simultaneously — it states the contradiction and resolves none of it. There is deliberately no conformance entry for "current disagrees with its tasks": that judgement needs the metric re-run.

Contract

perry-goals/list2.1. Four keys added, none removed or retyped, every one documented in schema/goals-list-contract.md in the same change.

  • tests/test_contract_key_parity.py, perry-goals/list: before 0 documented-not-emitted / 5 emitted-not-documented; after 0 / 5 — the same five keys, which are TASK-131's. None of the new fields hides inside that number. Baseline re-recorded only because the contract-id key renamed.
  • tests/test_contract_invariance.py green; its baseline is deliberately not re-recorded (see Notes).

Verification

  • python3 tests/parallel -j 4: 66 modules · 1951 tests, 1 red module — test_diagnose, the same 2 failures as the baseline taken in this worktree before any edit (65 · 1930, same module, same 2). That is TASK-126's, not this row's.
  • bash tests/run once: same single red module.
  • python3 bin/perry-lint: 0 errors (3 pre-existing NS-01 warnings).
  • git diff --check clean. git diff -- perry/ is empty apart from the dispatched spec.

ranjiao and others added 16 commits August 20, 2026 20:04
…gnature

C1 (TASK-127) is dispatched. C3 (TASK-128) is a human gate and is in
review with the exact edit written out. C4 (TASK-129) is blocked on it.
TASK-126 is the reason test_diagnose is still red after the merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ao 2026-08-20)

DESIGN-007 step 2. The third ownership change to the hand-off contract
and the second to carry its own signature.

- SKILL.md § The hand-off contract: one row, work's cell only. goals and
  decide are byte-identical across the edit. 20410 → 20457 of a 20480 cap.
- schema/state-schema.json: role-card owner user → work, and the note that
  argued for `user` rewritten to record what the change cost rather than
  being deleted.
- tests/test_ownership.py: the mapping the check demanded once the contract
  listed the path. It refused first, which is what it is for.
- reference/hand-off-contract.md: the account, including the 23 bytes of
  router headroom left and the stale "Two changes" paragraph.

Unblocks TASK-129 (Agent becomes a store).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TASK-133. One track added to .perry/config.md § Tracks: intake, mode
queue. One file changed, zero state files rewritten (BOARD.md,
tasks.jsonl and OKR.md byte-identical by md5).

The result: a queue-track row carried a kr: edge to a project-mode KR
with no friction at all. attribution.linked 4 → 5, no error, no warning.
There is no mechanical gate anywhere — 'No objectives cascade' is prose
in modes/queue.md and nothing else.

Three defects found on the way, none of them the one under test:
TASK-135 no existing row can be moved to a track, TASK-136 a track SLA
is never measured against anything, TASK-137 a new queue row is born in
the second stage.

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

TASK-138, and it is why CI has been red on every PR.

GNU coreutils `stat -f` is `--file-system`: it exits 0 and prints four
lines about the filesystem. So `stat -f %m || stat -c %Y || echo 0`
never reaches either fallback on Linux, mtime() returned a paragraph,
and `$((now - last))` died with "syntax error in expression". Green on
every mac, red on every CI run.

Three scripts carried it. perry-update-check runs on EVERY Perry
invocation, so on Linux the 7-day throttle has been reading a filesystem
block as a timestamp all along, silently.

The fix tries the GNU form first — BSD stat rejects `-c` outright, which
`-f` does not — and accepts an answer only if it is all digits, because
an exit code is not evidence that the output means what was asked for.

Reproduced on this mac with a fake GNU stat on PATH, so the defect is
catchable without a Linux box; reverting the fix reddens the new tests.
A grep guard fails if the chain reappears anywhere in bin/.

Also fixes two checks that read this repository's live state as their
expected value, both reddened by declaring the first track (TASK-133):
test_md_store asserted every config record is a `setting`, and
test_track_attribution asserted Perry itself has no track register. Both
now derive the expectation from the file, and the second proves its
no-op property on a project that has no register rather than on this one.

Adds DESIGN-008 (draft): Mode is three axes wearing one name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#1 went against the document's own draft. It had bolded three axes;
what beat it is that the third one needs no axis to exist — Default
rung is already an overridable column, and this repository overrode it
on the intake track today without any axis machinery. Two axes, and
the rung stays the plain field it is.

#2 was the one genuinely contested slot and resolved to 'do not declare
it at all': each spine value implies exactly one unit, so a declarable
field could only ever hold a wrong value, and it would surface only when
two presets are mixed — which is the case this design creates.

#3, #4 and #6 are one decision in three places, all forced by goal 2:
the preset name survives, a blank leg inherits from the preset, nothing
migrates. #5 follows #3.

Title, section 5.1's example, section 5.2's slot table, one risk row and
section 8's second open question all moved as a consequence.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pre-flight clean: 10/10 required sections non-empty, 0 open decisions,
5 implementation steps. The advisory input-quality pass added section
5.4 (blast radius, nine surfaces) and a detection signal on every risk
— the same two gaps DESIGN-003 closed at its own lock. One point was
overridden rather than fixed and recorded as overridden: decision #5
is arguably agent-decidable.

DESIGN-003 section 9 now points here, with the measurement that made
this a revision rather than an opinion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 1 only. Steps 2-5 are functions of its output and stay closed until
it lands, so their acceptance criteria can be falsifiable rather than
'per step 1's table'.

Dispatch pre-flight refuses on `design/` and this one is a genuine hit,
not a false positive: the row's deliverable is an edit to a locked
design document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Local is ahead 10 / behind 13 with all four PRs merged on the remote —
that is step 1 of the handoff, because the same divergence cost half of
today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The user asked whether TASK-037's blocked status was right. It was not,
and it was not alone: both rows had every dependency closed, TASK-037 on
TASK-092 and TASK-045 on the TASK-044 -> TASK-047 chain.

bin/perry-task:4728 reads the stored status before blocked_by, so a
stale 'blocked' can never be contradicted by the computed state — both
rows carried blocked_by [] and startable False at once. Opened TASK-141.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The signal was already firing. next_action_cites_closed named exactly
TASK-037 and TASK-045, and earlier the same day the PMO read that check
as prose hygiene and rewrote the cells to silence it. A check that
reports a pattern without its meaning gets suppressed by whoever reads it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR #14 (TASK-100) put both store files into claims[] at e3f8621. PR #15
(TASK-110) shipped test_state_cost asserting the pre-claim world:
`perry/tasks.jsonl (unclaimed)` and `.perry/events.jsonl` rolling up
under the `.perry/` row. Each was green on its own base; the merge was
red on two tests neither PR could have seen.

The behaviour never changed — only which row states it. The history
assertion now reads the file's own row and its number is unchanged. The
unclaimed-file test writes a file nothing will ever claim rather than
borrowing one that happened to be unclaimed that week; it needed a
commit too, because the tool reads git.

Both proved non-vacuous by mutation: pointing the row back at `.perry/`
reddens one, dropping the commit reddens the other.

Closes TASK-100, TASK-111, TASK-127, TASK-133 at V3 — all four verified
locally now that the merged code is here. TASK-094 stays open: its PR
merged but verification item 1 is unmet.

Opens TASK-143.

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


Both pre-flights refuse and both releases were given by the user on
2026-08-20. Recorded in evidence rather than left in chat: TASK-126's hit
reads as a false positive (the fragment matched a filename, not the
execute stage) and TASK-140's does not (the row really does rewrite a
locked design document).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`phase/<NNN>-linkage.md` carries `target` and `current` per KR, both
hand-written, and nothing derived, checked or aged them. The two readings
a consumer could take from Perry's own register were wrong in OPPOSITE
directions on the same day: `P-O1.1` read 0-of-1 with all four of its
linked tasks closed, and `P-O2.2` read MET while the task under it had
measured 13 row splits and 87 header resolutions still live. Six of the
eight phase KRs drive a count to zero, so a `current` left at the
template's `0` reads as achieved on the day it is written.

Three additive blocks per KR, computed once in `bin/lib §
kr_progress_provenance` and emitted by both `perry-goals list --json` and
`perry-state --json § linkage`:

- `current_provenance` — asserted or unasserted, when, at what
  granularity, and `measured: false`, which is true of every `current`
  Perry publishes because no tool re-runs a KR's metric.
- `current_staleness` — whether a linked task moved after the number was
  asserted, naming which. `evaluated` is carried separately so "nothing
  moved" cannot be confused with "there was nothing to ask".
- `linked_task_completion` — a count of TASKS, beside `current` and never
  inside it. "TASK-091 is closed" does not establish that a count is
  zero; only re-running the count does, and rolling the edges into
  `current` would publish a fabricated measurement.

The payload carries the P-O1.1 contradiction and resolves none of it.

`perry-goals/list` moves to `2.1` — four keys added, none removed or
retyped, all four documented in the same change. Parity on that contract
is unchanged at 0 documented-not-emitted / 5 emitted-not-documented; the
five are TASK-131's and none of the new fields hides inside them.

`perry/` is unchanged apart from the dispatched spec.

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