tickmarkr 2.6.7 → 2.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/adapters/catalog-remote.js +5 -2
  2. package/dist/adapters/codex.d.ts +1 -0
  3. package/dist/adapters/codex.js +18 -10
  4. package/dist/adapters/model-lints.js +21 -11
  5. package/dist/adapters/model-windows.js +0 -1
  6. package/dist/adapters/registry.js +7 -3
  7. package/dist/adapters/types.d.ts +2 -0
  8. package/dist/cli/commands/beat.d.ts +3 -3
  9. package/dist/cli/commands/beat.js +39 -19
  10. package/dist/cli/commands/doctor.js +3 -0
  11. package/dist/cli/commands/fleet.js +2 -0
  12. package/dist/cli/commands/plan.js +16 -18
  13. package/dist/cli/commands/verify.js +4 -2
  14. package/dist/cli/help.js +5 -2
  15. package/dist/config/config.js +6 -5
  16. package/dist/drivers/herdr.js +2 -2
  17. package/dist/drivers/orca.d.ts +12 -4
  18. package/dist/drivers/orca.js +200 -39
  19. package/dist/gates/evidence.js +3 -3
  20. package/dist/gates/review.d.ts +1 -1
  21. package/dist/gates/review.js +62 -13
  22. package/dist/gates/run-gates.d.ts +3 -0
  23. package/dist/gates/run-gates.js +115 -20
  24. package/dist/gates/test-manifest.d.ts +36 -2
  25. package/dist/gates/test-manifest.js +55 -10
  26. package/dist/run/beat-lifecycle.d.ts +109 -24
  27. package/dist/run/beat-lifecycle.js +1173 -216
  28. package/dist/run/daemon.d.ts +4 -0
  29. package/dist/run/daemon.js +111 -33
  30. package/dist/run/git-trust.d.ts +78 -3
  31. package/dist/run/git-trust.js +175 -19
  32. package/dist/run/git.d.ts +24 -3
  33. package/dist/run/git.js +140 -21
  34. package/dist/run/merge.js +2 -2
  35. package/dist/run/verification-job.d.ts +2 -0
  36. package/dist/run/verification-job.js +32 -10
  37. package/dist/tui/ink/fleet-app.d.ts +7 -2
  38. package/dist/tui/ink/fleet-app.js +81 -23
  39. package/package.json +4 -1
  40. package/skills/tickmarkr-auto/SKILL.md +58 -14
  41. package/skills/tickmarkr-loop/SKILL.md +65 -14
  42. package/skills/tickmarkr-overseer/SKILL.md +97 -28
  43. package/skills/tickmarkr-overseer/scripts/watch-journal.sh +9 -7
@@ -26,6 +26,30 @@ again on the new token; never re-issue the old one (D-470: an unbound approve wa
26
26
  gate). A failed task keeps its recheck with its bound failure token: status prints
27
27
  `failed — T3 — failure <line>@<ts>`, and `tickmarkr approve <runId> T3 --recheck --park <line>@<ts>`
28
28
  re-gates its landed commits with no worker.
29
+
30
+ **A waive is funded by EXECUTED evidence, never by a static read.** Every out-of-band (OOB) read
31
+ behind a ruling records its mode: `OOB: static` — the diff and source were read and nothing ran — or
32
+ `OOB: executed` — the finding's claim was run against the task's head commit (a test, a reproduction
33
+ or a command) with its exit code and log path on file. Only an executed read backs a waive. A static
34
+ CLEAN never does, however confident: a static read that missed an executed material defect reads
35
+ exactly like one that found nothing, and a static read relabelled `executed` is a false record.
36
+ Replay it before ruling. The reviewer's material finding says a killed taker strands the claim; a
37
+ static OOB read of the diff reports CLEAN; the finding's reproduction, executed at the task head,
38
+ exits 1 with the strand on disk — that static CLEAN would have waived a real defect, so the ruling
39
+ is uphold or recheck. Waive only on a supported executed read: the same reproduction at the task head
40
+ exits 0, its log is on file, and the record names both. The waive keeps its park and gate binding and
41
+ its debt: CURRENT `tickmarkr status <runId>` reads `outstanding 1 (T2 review)` until
42
+ `tickmarkr verify --record <runId>` discharges it. Record example:
43
+
44
+ ```text
45
+ D-NNN WAIVE T2 review — park <line>@<ts> --gate review
46
+ OOB: executed — `<reproduction>` at <task-head-sha>: exit 0, log <path>
47
+ finding: <the reviewer's material claim> — not reproduced by that execution
48
+ debt: outstanding 1 (T2 review) until `tickmarkr verify --record <runId>`
49
+ ```
50
+
51
+ A record whose OOB line reads `static`, or names no command, exit code and log, funds no waive: the
52
+ ruling is uphold or recheck.
29
53
  Manual UI retains the receipt; the daemon-owned board gracefully stands down only its own
30
54
  presence before closing its owned pane. Non-TTY supervision keeps `status`, `report` and
31
55
  default-watch line output (`--watch --plain` is also available on a TTY). Keep the canonical
@@ -121,13 +145,13 @@ through brief lineage. **An executor choice nobody made is still an executor cho
121
145
  tab OVERSEER; create ONE tab ORCHESTRATOR.
122
146
  **FIVE-TAB CANON (standing operator layout — corrected three times on 2026-07-27, layout approved
123
147
  2026-07-29, re-earned 2026-08-17):**
124
- - `OVERSEER` — you. Do not add a live run surface here: launch the daemon from the ORCH tab
125
- using the host-owned split form below; it self-places the shipped board there.
126
- - `ORCH` — the orchestrator, its host-owned daemon split, and the daemon-placed, run-id-pinned shipped board BESIDE it: the board
148
+ - `OVERSEER` — you. Do not add a live run surface here: launch the daemon detached at the ORCH's
149
+ recorded address (the detached launch below); it self-places the shipped board in the ORCH tab.
150
+ - `ORCH` — the orchestrator and the daemon-placed, run-id-pinned shipped board BESIDE it (the daemon itself runs detached, with no pane): the board
127
151
  takes the RIGHT half of the tab and the orchestrator's own narration keeps the LEFT half. (It was a
128
152
  full-width board above a narration rail until 2026-08-25; the operator changed it, because a task
129
153
  table is a few rows and it was spending height it did not need while squeezing the narration.) **Look for that `role: "watch"` pane; never hand-place or hand-roll a live
130
- run surface. Only the orchestrator, daemon and board belong here: a work seat NEVER splits into the ORCH tab.** Operator verbatim:
154
+ run surface. Only the orchestrator and board belong here: a work seat NEVER splits into the ORCH tab.** Operator verbatim:
131
155
  *"in orch tab should be the orch and the watcher only."* Re-earned 2026-08-17: a planning seat split
132
156
  beside the orchestrator, and the operator caught it, again. The daemon owns this vertical stack and
133
157
  places it the same way at every terminal width; neither the worker-pane halving floor, nor a
@@ -147,7 +171,7 @@ through brief lineage. **An executor choice nobody made is still an executor cho
147
171
  fraction (`ORCH · v1.19 4/5`, updated on every task-done); tickmarkr opens ONE TAB PER TASK, labelled
148
172
  with the task id and holding that task's worker plus its judge/review/consult panes (tickmarkr
149
173
  updates it). Never long context strings or ✓-chains.
150
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Do not load the `herdr` skill and do not map a Herdr workspace. Work from the current Orca terminal context. Keep the overseer in the launching terminal, name it in the same act — `orca terminal rename --terminal "$ORCA_TERMINAL_HANDLE" --title "OVERSEER · <version>"` (tab title; see the seat-name law under Seat-spawn recipes) — and inspect terminals with `orca terminal list --json`. Launch the daemon in a host-owned split of the orchestrator terminal using the form below; its self-placed watch board stays in the ORCH tab.
174
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Do not load the `herdr` skill and do not map a Herdr workspace. Work from the current Orca terminal context. Keep the overseer in the launching terminal, name it in the same act — `orca terminal rename --terminal "$ORCA_TERMINAL_HANDLE" --title "OVERSEER · <version>"` (tab title; see the seat-name law under Seat-spawn recipes) — and inspect terminals with `orca terminal list --json`. Launch the daemon detached at the orchestrator terminal's recorded handle using the detached launch below — no pane of its own; its self-placed watch board stays in the ORCH tab.
151
175
  2. **Orchestrator**: First consume a successful `tickmarkr fleet --pick consult` in the seat repository, per the Fleet selection contract below; then launch the returned adapter/model with your agent host.
152
176
  - **On herdr (`HERDR_ENV=1`)**: Spawning on current herdr is two-step — the one-shot `agent start --cwd` form was removed in the herdr CLI redesign and now fails with `unknown option` (OBS-138): first create the pane with `herdr tab create --workspace <ws> --cwd <repo> --label "ORCH · <version>"` and parse `result.root_pane.pane_id` from its JSON, then start the agent in it. For Claude Code, use `herdr agent start orchestrator --kind claude --pane <root-pane-id> -- --permission-mode bypassPermissions` (append `--model <picked-model>` after the `--` from the Fleet receipt). For Codex, use `herdr agent start orchestrator --kind codex --pane <root-pane-id> -- --dangerously-bypass-approvals-and-sandbox` (add `--model <picked-model>` from the Fleet receipt). The unsandboxed flag is REQUIRED: codex's `workspace-write` sandbox keeps `.git` refs read-only, so a sandboxed orchestrator's `tickmarkr run` dies at integration-branch creation — do not downgrade it. Workers you never spawn — tickmarkr spawns its own visible worker panes. Auxiliary agents you do spawn (consultants, reviewers, scouts) follow the same forms: never launch a claude session in plan mode or default permission mode for autonomous work — both stall on per-command approval prompts nobody is watching; claude is always `--permission-mode bypassPermissions --settings '{"promptSuggestionEnabled":false}'`. **For a codex consultant, use `-a never --sandbox workspace-write` — NOT `--sandbox read-only`.** ⚠ **`--sandbox read-only` CONTRADICTS this skill's own completion protocol and will hang the seat.** Every seat you spawn is told to deliver an ARTIFACT ending in a terminal MARKER, because that is the only completion signal the artifact watcher can key on (`done` is turn end). A read-only sandbox cannot write that artifact, so codex blocks on `Would you like to make the following edits?` for its OWN report — and the report exists ONLY in the pending edit, so abandoning the prompt destroys the work rather than merely delaying it. Measured 2026-08-28: a consultant spawned `--sandbox read-only` finished a 14,604-byte verdict, sat blocked on the write, and the operator saw the prompt before the supervising tier did. `read-only` is correct ONLY for a seat that writes nothing at all — which, under the artifact+marker rule, is no seat this skill tells you to spawn. When the prompt does appear, answer **"Yes, and don't ask again for these files"** rather than plain yes: plain yes re-blocks on the next write of the same file. **That `--settings` pair is not cosmetic and it is not optional:** claude-code's AUTOSUGGEST renders context-plausible ghost text into an idle seat's prompt line that is BYTE-IDENTICAL to a typed draft in text-format reads (OBS-482), so a supervising tier cannot tell a seat's own unsent work from a rendering artifact without `agent read --format ansi`. Turning the suggester off at spawn removes the ambiguity at its source instead of paying for the discrimination at every read. Verified against the shipped binary: `claude --settings '{"promptSuggestionEnabled":false}' -p …` exits 0 with a real response, and the key appears in the binary's own settings schema. **For kimi, pass `-y`** (`herdr agent start <name> --kind kimi --pane <id> -- -y`) — the adapter already launches its own workers that way (`src/adapters/kimi.ts:204`), and a kimi seat spawned without it sits on an approval prompt having done nothing. **Herdr cannot see that state**: it reports a kimi pane as `agent_status: working` with `screen_detection_skipped: true` while the prompt is up, so the BLOCKED-STATE watcher below is blind on this vendor and the spawn flag is the ONLY control. Every vendor you spawn needs its auto-approve form named here; a vendor absent from this list is a seat that will hang.
153
177
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Seats spawn with `orca terminal create` on a path worktree selector with a command:
@@ -197,20 +221,20 @@ through brief lineage. **An executor choice nobody made is still an executor cho
197
221
  The orchestrator's run log, handoff, and plan live inside its sandbox root and are artifacts of that orchestrator.
198
222
  The overseer reads them from there, using the path the orchestrator reports; do not substitute the
199
223
  overseer's repository or a machine-global planning directory. The orchestrator brief must say: if the seat's
200
- sandbox denies writes under `.git`, report the denial to the overseer and stop; the overseer launches the daemon itself; use the host-owned split of the ORCH pane, never the overseer tab;
224
+ sandbox denies writes under `.git`, report the denial to the overseer and stop; the overseer launches the daemon itself; use the detached launch at the recorded ORCH address, never a pane and never the overseer tab;
201
225
  and the daemon host is never sandboxed.
202
226
 
203
227
  **Sandbox-denial daemon launch (OVERSEER):** use the ORCH address recorded at seat creation,
204
228
  re-resolved by title if stale; the launching overseer's own terminal is not the target.
205
229
 
206
230
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: record the ORCH's
207
- `result.terminal.handle` from its seat-create receipt. For the fallback, run
208
- `orca terminal split --terminal <ORCH handle> --direction vertical --command "tickmarkr run"`.
231
+ `result.terminal.handle` from its seat-create receipt. For the fallback, run the detached
232
+ `ORCA_TERMINAL_HANDLE="<ORCH handle>"` line of the detached launch below with that recorded handle.
209
233
  Here `$ORCA_TERMINAL_HANDLE` names the OVERSEER's own terminal; use it only when the ORCH
210
234
  launches its own daemon, never when the OVERSEER launches on the ORCH's behalf.
211
235
  - **On herdr (`HERDR_ENV=1`)**: record the ORCH's `result.root_pane.pane_id` from its tab-create
212
- receipt. For the fallback, use `herdr pane split` targeting that recorded ORCH pane id, then
213
- `herdr pane run <new> "tickmarkr run"`; never target the OVERSEER's pane.
236
+ receipt. For the fallback, run the detached `HERDR_PANE_ID="<ORCH pane id>"` line of the detached
237
+ launch below with that recorded ORCH pane id; never target the OVERSEER's pane.
214
238
 
215
239
  The same recorded ORCH address is required for a fallback `tickmarkr resume <runId>`.
216
240
 
@@ -308,23 +332,38 @@ For Leg-2 (both hosts), a Codex reviewer under `workspace-write` must be briefed
308
332
  `<repo>/.tickmarkr/overseer/verdicts/<task>.md`, and its verdict must be written there before it is read.
309
333
  ## Host-owned daemon and detached beats
310
334
 
311
- Launch the daemon as a SPLIT of the orchestrator's own pane in the ORCH tab, owned by the
312
- host PTY. Never launch it as an agent harness background task or in a separate tab. The daemon
313
- self-places its board from that split, keeping both daemon and board in the ORCH tab.
335
+ Launch the daemon DETACHED, with no pane of its own: one `tickmarkr run` or `tickmarkr resume <runId>`
336
+ process in its own session, its stdout and stderr appended to `<state-dir>/daemon.log`, and the
337
+ ORCH's recorded address as its board anchor, so the daemon self-places its one board beside the
338
+ ORCH in the ORCH tab. Never launch it in a visible split, pane or tab, as an agent harness
339
+ background task, or tied to an agent session: the launching shell may end, and the daemon keeps
340
+ running and logging. The detach wrapper is `node`, which tickmarkr already requires — no private
341
+ script, no extra visible daemon pane and no CLI log flag:
314
342
 
315
- - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: only when the ORCH
316
- launches its own daemon, use
317
- `orca terminal split --terminal "$ORCA_TERMINAL_HANDLE" --direction vertical --command "tickmarkr run"`.
318
- When the OVERSEER launches the sandbox-denial fallback, substitute the ORCH handle recorded
319
- at seat creation: `orca terminal split --terminal <ORCH handle> --direction vertical --command "tickmarkr run"`.
320
- - **On herdr (`HERDR_ENV=1`)**: use `herdr pane split` on the orchestrator's own pane, then
321
- `herdr pane run <new> "tickmarkr run"` in the returned split. When the OVERSEER launches the
322
- fallback, target the ORCH pane id recorded at seat creation, never the OVERSEER's pane.
343
+ ```bash
344
+ # On Orca: <ORCH handle> is the ORCH terminal handle
345
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
346
+ cd <repo> && ORCA_TERMINAL_HANDLE="<ORCH handle>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
347
+ # On herdr: <ORCH pane id> is the ORCH pane id
348
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr run >> <state-dir>/daemon.log 2>&1 < /dev/null
349
+ cd <repo> && HERDR_PANE_ID="<ORCH pane id>" node -e "require('child_process').spawn(process.argv[1], process.argv.slice(2), { detached: true, stdio: 'inherit' }).unref()" tickmarkr resume <runId> >> <state-dir>/daemon.log 2>&1 < /dev/null
350
+ ```
323
351
 
324
- For `resume`, use the same host-owned split form with `tickmarkr resume <runId>` in place of
325
- `tickmarkr run`. After either launch, read this repository's lock pid and walk its ppid chain
326
- in the process table to the host PTY; verify no agent session is an ancestor. If an agent is
327
- an ancestor, stop that launch and relaunch through the host split before continuing.
352
+ - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: run the
353
+ `ORCA_TERMINAL_HANDLE` lines. Only when the ORCH
354
+ launches its own daemon is `<ORCH handle>` its own `$ORCA_TERMINAL_HANDLE`. When the OVERSEER
355
+ launches the sandbox-denial fallback, substitute the ORCH handle recorded
356
+ at seat creation; the OVERSEER's own `$ORCA_TERMINAL_HANDLE` would put the board in its tab.
357
+ - **On herdr (`HERDR_ENV=1`)**: run the `HERDR_PANE_ID` lines; `<ORCH pane id>` is the ORCH's own
358
+ `$HERDR_PANE_ID`. When the OVERSEER launches the fallback, substitute the ORCH pane id recorded
359
+ at seat creation, never the OVERSEER's pane.
360
+
361
+ After either launch, read this repository's lock pid from `<state-dir>/graph.lock` and confirm it:
362
+ `kill -0 <pid>` succeeds, `ps -o pgid= -p <pid>` prints that same pid (its own session group;
363
+ ppid 1 is never proof on its own), walk its ppid chain and verify no agent session is an ancestor,
364
+ and read `<state-dir>/daemon.log`. No live holder means the launch failed: read the log, fix the
365
+ cause and run the same line again — never a second daemon beside a live holder, and never a
366
+ visible split to watch it; the daemon-placed board is the live surface.
328
367
 
329
368
  Detached beats are product-owned, one per (tier, seat), from the repository root, through the
330
369
  shipped lifecycle verbs — never a hand-rolled setsid plus nohup wrapper, pidfile or shell loop:
@@ -346,6 +385,34 @@ generation and identity still match, and reads back DISARMED. Never put beats in
346
385
  background task or a visible tab. Include a run-end `beat stop` item for every beat started, also
347
386
  on failure, park or handoff, and require its DISARMED read-back.
348
387
 
388
+ **Legacy beat migration.** `beat start` refuses a legacy (pre-lifecycle, unowned) arm nonzero and
389
+ prints its exit under the RECORDED seat; `beat stop` refuses it too, so a stop followed by a start
390
+ is a dead end, never a migration. When the refusal names `stop pid <N> first`, that pid is the old
391
+ `--loop` writer: stop exactly that pid yourself (`kill <N>`, then confirm `kill -0 <N>` fails) —
392
+ tickmarkr never signals a process it did not launch. Then run the printed step under the seat it
393
+ names, start again and read back ARMED:
394
+
395
+ ```bash
396
+ cd <repo> && tickmarkr beat <tier> --seat <recorded seat> --stand-down
397
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
398
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
399
+ ```
400
+
401
+ **Crash recovery.** A crashed owner, a writer killed inside its claimed tick or a killed recovery
402
+ taker leaves its claim, removal lock or stage directory behind. `beat status` only observes: it
403
+ exits nonzero, names the dead pids, prints the command that recovers them and changes no file —
404
+ status never recovers. Run the command it prints; the next ordinary `stop` or `start` recovers what
405
+ was left, with a notice naming each dead pid, and the start reads back ARMED:
406
+
407
+ ```bash
408
+ cd <repo> && tickmarkr beat status <tier> --seat <seat>
409
+ cd <repo> && tickmarkr beat stop <tier> --seat <seat>
410
+ cd <repo> && tickmarkr beat start <tier> --seat <seat>
411
+ ```
412
+
413
+ Never delete a claim, lock or stage file by hand. A live or unprovable holder refuses BUSY naming
414
+ it: wait for that holder to finish or exit, then run the same command.
415
+
349
416
  Keep only watchers that must WAKE the seat in the harness. Re-arm them on each wake and at
350
417
  their harness cap; daemon and beat lifetimes must not depend on that cap.
351
418
 
@@ -461,8 +528,9 @@ journal tail to decide what happens next, or sweeping orphans — you have taken
461
528
  ensure it is re-armed at most every twenty minutes. Never rely on a `Monitor`-only wake.
462
529
  - **On herdr (`HERDR_ENV=1`)**: Use the shipped journal instruments:
463
530
  **All four are covered by one shipped instrument** — `scripts/watch-journal.sh <runs-dir> [poll] [cap]
464
- [events-csv]` — which arms on a line baseline, wakes once, and grades a `run-end` against every green
465
- clause. `scripts/watch-parks.sh` stays the park-specific wake for THIS seat (it counts parks and speaks
531
+ [events-csv]` — which arms on a line baseline, wakes once, and grades a `run-end`'s execution clauses —
532
+ `EXECUTION COMPLETE` or `NOT GREEN`, never green: green is CURRENT `tickmarkr status <runId>` at
533
+ `outstanding 0`. `scripts/watch-parks.sh` stays the park-specific wake for THIS seat (it counts parks and speaks
466
534
  about rulings); the two overlap on `task-human` deliberately, and arming both is coverage, not a bug.
467
535
  - **On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`)**: Arm a file/journal consumer for the same terminal events and inspect the seat with `orca terminal read --terminal <handle> --screen --json`; re-arm after every wake.
468
536
  - **Daemon liveness ≠ journal activity.** A dead daemon emits no events, so journal watchers sleep through
@@ -1194,7 +1262,8 @@ wrote, so a stray shell loop left running by a predecessor seat kept a stood-dow
1194
1262
  shell or not, never does. The pre-2.1.3 forms `while :; do tickmarkr beat overseer; sleep 10; done`
1195
1263
  and `tickmarkr beat overseer --stand-down` are preserved here only as older migration warnings: both
1196
1264
  are now rejected outright because neither declares which seat the tier speaks for. Do not copy or
1197
- run either legacy form.
1265
+ run either legacy form. To leave an arm a legacy writer left, run the **Legacy beat migration** steps in Host-owned
1266
+ daemon and detached beats: the printed `--stand-down` under the recorded seat, then `beat start`.
1198
1267
 
1199
1268
  One beat per LIVE PROCESS, deliberately: `--loop`'s recorded pid is that process's own, so the tier's
1200
1269
  liveness is exactly as verifiable as the process table — stop the loop, or let it die, and the
@@ -82,19 +82,21 @@ report() {
82
82
  done=$(bucket done "$line"); failed=$(bucket failed "$line")
83
83
  human=$(bucket human "$line"); blocked=$(bucket blocked "$line")
84
84
  pending=$(bucket pending "$line")
85
- # GREEN IS A CONJUNCTION, AND THE SHORT FORM OF IT IS WRONG. "run-end plus tip verify" passes a run
86
- # that ended `done=[T1,T3,T4] human=[T2]` — three delivered, one PARKED — and calling that green is
87
- # how a park becomes invisible. Grade every clause here so the reader never has to remember to.
85
+ # NO RUN-END RECORD CERTIFIES GREEN. "run-end plus tip verify" passes a run that ended
86
+ # `done=[T1,T3,T4] human=[T2]` — three delivered, one PARKED — and even empty buckets leave the debt
87
+ # clause open: a waived review is still owed (D-660), and a `verify --record` discharge that lands
88
+ # AFTER run-end moves only CURRENT status. This row is history, so the best it can say is
89
+ # EXECUTION COMPLETE; green is read from CURRENT `tickmarkr status <runId>` at `outstanding 0`.
88
90
  if [ "$tv" != "failed" ] && [ -z "$failed$human$blocked$pending" ]; then
89
- verdict="GREEN"
91
+ verdict="EXECUTION COMPLETE"
90
92
  else
91
93
  verdict="NOT GREEN"
92
94
  fi
93
95
  echo "RUN_END $run — $verdict (tipVerify=${tv:-unknown})"
94
96
  echo " done=[${done}] failed=[${failed}] human=[${human}] blocked=[${blocked}] pending=[${pending}]"
95
- [ "$verdict" = "GREEN" ] \
96
- && echo " all four buckets empty and tip verify is not failed — this run is green" \
97
- || echo " a non-empty bucket above is the reason; name it, never report this run as green"
97
+ [ "$verdict" = "EXECUTION COMPLETE" ] \
98
+ && echo " execution buckets empty and tip verify is not failed — not yet green: read CURRENT \`tickmarkr status $run\`; only \`outstanding 0\` is green, \`outstanding N (...)\` and \`outstanding unknown\` are not, and this run-end record's own count is historical" \
99
+ || echo " a non-empty bucket or a failed tip above is the reason; name it, never report this run as green"
98
100
  ;;
99
101
  task-human)
100
102
  echo "TASK_HUMAN $(field taskId "$line") — $run"