tickmarkr 2.5.8 → 2.5.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 (39) hide show
  1. package/dist/adapters/qwen.js +30 -3
  2. package/dist/cli/commands/approve.js +15 -3
  3. package/dist/cli/commands/beat.d.ts +2 -0
  4. package/dist/cli/commands/beat.js +28 -29
  5. package/dist/cli/commands/compile.js +11 -0
  6. package/dist/cli/commands/fleet.js +61 -53
  7. package/dist/cli/commands/verify.d.ts +4 -1
  8. package/dist/cli/commands/verify.js +16 -5
  9. package/dist/cli/help.d.ts +2 -0
  10. package/dist/cli/help.js +6 -4
  11. package/dist/config/config.d.ts +41 -3
  12. package/dist/config/config.js +48 -17
  13. package/dist/config/fleet-overlay.js +47 -62
  14. package/dist/gates/baseline.d.ts +65 -0
  15. package/dist/gates/baseline.js +163 -9
  16. package/dist/gates/review.d.ts +6 -4
  17. package/dist/gates/review.js +62 -21
  18. package/dist/gates/run-gates.d.ts +4 -1
  19. package/dist/gates/run-gates.js +21 -11
  20. package/dist/gates/test-manifest.d.ts +11 -1
  21. package/dist/gates/test-manifest.js +41 -21
  22. package/dist/run/daemon.js +84 -41
  23. package/dist/run/journal.d.ts +17 -2
  24. package/dist/run/journal.js +99 -11
  25. package/dist/run/merge.d.ts +13 -2
  26. package/dist/run/merge.js +74 -12
  27. package/dist/run/protocol.d.ts +82 -0
  28. package/dist/run/protocol.js +35 -0
  29. package/dist/run/receipt-resolver.d.ts +18 -0
  30. package/dist/run/receipt-resolver.js +132 -0
  31. package/dist/run/supervision.d.ts +14 -1
  32. package/dist/run/supervision.js +122 -24
  33. package/dist/tui/cockpit/evidence-view.d.ts +10 -1
  34. package/dist/tui/cockpit/evidence-view.js +37 -5
  35. package/dist/tui/ink/fleet-app.d.ts +12 -22
  36. package/dist/tui/ink/fleet-app.js +520 -131
  37. package/package.json +1 -1
  38. package/skills/tickmarkr-overseer/SKILL.md +61 -36
  39. package/skills/tickmarkr-overseer/scripts/watch-context.sh +14 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tickmarkr",
3
- "version": "2.5.8",
3
+ "version": "2.5.9",
4
4
  "description": "Spec in, verified work out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -614,6 +614,11 @@ run the Herdr pane commands or Herdr context watcher below.
614
614
  verify every new pid twice, preserve the partner-owned watchers the stand-down lists, then confirm."
615
615
  ```
616
616
 
617
+ **Log every open per CITE-IS-NOT-READ:** the moment the returning seat opens `<handoff>` or
618
+ `<brief>`, append `opened <path>` to the append-only log `.tickmarkr/overseer/opened-files.log`
619
+ (create it if absent; never truncate or rewrite an existing line). On both hosts this log — not the
620
+ transcript — is the record a later handoff is measured against (OBS-1102).
621
+
617
622
  ⚠ **Steps 3 and 4 are two sends, never one.** A pointer batched with the clear lands *during* it and is
618
623
  lost with the context it was meant to survive. Verify the clear landed by reading the prompt line
619
624
  and re-reading the banner percentage below its pre-clear value before sending the pointer — the same
@@ -997,49 +1002,63 @@ tier's state from a beat file the tier itself writes, so a seat that never beats
997
1002
  run: `orchestrator ARMED / overseer ABSENT / watch ABSENT` for the whole milestone, with a live overseer
998
1003
  watching it. Two thirds of that line were constants, not measurements.
999
1004
 
1000
- The beat is one shipped command and the loop is yours, run from the repo root as its own
1001
- `run_in_background` Bash call:
1005
+ The beat is one shipped command and it arms through two explicit verbs, `--new-arm` and `--loop` —
1006
+ never a bare invocation. `--loop` already implies `--new-arm`: it creates a durable arm and beats in
1007
+ this process every 10 seconds, exiting on its own within one interval after a stand-down. Run it from
1008
+ the repo root as its own `run_in_background` Bash call:
1002
1009
 
1003
1010
  ```bash
1004
- cd <repo> && while :; do tickmarkr beat overseer --seat <overseer-agent-or-pane>; sleep 10; done
1005
- tickmarkr beat overseer --seat <overseer-agent-or-pane> --stand-down # after stopping that loop
1011
+ cd <repo> && tickmarkr beat overseer --seat <overseer-agent-or-pane> --loop
1012
+ tickmarkr beat overseer --seat <overseer-agent-or-pane> --stand-down # deliberately hand off; --loop exits
1006
1013
  ```
1007
1014
 
1008
- The pre-2.1.3 forms `while :; do tickmarkr beat overseer; sleep 10; done` and
1009
- `tickmarkr beat overseer --stand-down` are preserved here only as migration warnings: both are now
1010
- rejected because neither declares which seat the tier speaks for. Do not copy or run them.
1011
-
1012
- One beat per invocation, deliberately: the loop is what proves the seat is alive, so a command that
1013
- kept beating on its own would keep reporting a dead seat as healthy. Stop the loop — or die — and the
1015
+ **The legacy wrapper loop, `while :; do tickmarkr beat overseer --seat <pane>; sleep 10; done`, is the
1016
+ UNSAFE form and must not be used.** It is a bare one-shot call repeated by a shell loop the product
1017
+ cannot see, and it is the shape that re-armed a recorded stand-down (OBS-583, OBS-1088): a bare beat
1018
+ reuses whatever durable arm is already on disk instead of acknowledging the marker a stand-down just
1019
+ wrote, so a stray shell loop left running by a predecessor seat kept a stood-down tier reading ARMED.
1020
+ `--new-arm` and `--loop` are the only verbs that acknowledge a stand-down; a bare beat, wrapped in
1021
+ shell or not, never does. The pre-2.1.3 forms `while :; do tickmarkr beat overseer; sleep 10; done`
1022
+ and `tickmarkr beat overseer --stand-down` are preserved here only as older migration warnings: both
1023
+ are now rejected outright because neither declares which seat the tier speaks for. Do not copy or
1024
+ run either legacy form.
1025
+
1026
+ One beat per LIVE PROCESS, deliberately: `--loop`'s recorded pid is that process's own, so the tier's
1027
+ liveness is exactly as verifiable as the process table — stop the loop, or let it die, and the
1014
1028
  tier ages to `STALE` (never `ABSENT`) within six beats, which is the state that says *armed, then lost*.
1015
1029
  Stand down explicitly when you hand off, or a deliberate exit reads as a death. Same rule as rule 29
1016
1030
  below, now with a conventional path the other tier already reads: `tickmarkr status` shows it.
1017
1031
 
1018
- ⚠ **THE LOOP ABOVE NAMES A SEAT BUT STILL BINDS ITS LIFETIME TO A PROCESS — and that distinction is
1032
+ ⚠ **`--loop` NAMES A SEAT BUT STILL BINDS ITS LIFETIME TO A PROCESS — and that distinction is
1019
1033
  load-bearing.** The command refuses an anonymous beat, and `status` renders the declared seat beside
1020
1034
  the tier state; a legacy tier+pid+instant record cannot be attributed and reads `UNREADABLE`, never
1021
- `ARMED`. Naming the seat does not make the shell loop stop when that seat leaves.
1022
- The beat keeps running while its *session* lives, so a loop started by a seat that has since been
1023
- cleared, re-briefed, or replaced keeps beating that tier's file forever. Measured 2026-08-24
1024
- (OBS-583): a **2d20h** orphan loop from a predecessor seat held `orchestrator ARMED` through a
1025
- **three-hour window in which no orchestrator was alive**, and it would have silently re-armed a
1026
- recorded stand-down within 10 seconds. On the same sweep the overseer tier had **three** beat loops,
1027
- one owned by an unrelated session. So:
1035
+ `ARMED`. Naming the seat does not prove that the named seat is still alive: its `--loop` process can
1036
+ outlive the seat's clear, re-brief, or replacement while that process remains alive. The current loop
1037
+ does not re-arm after stand-down: it observes the recorded marker and exits within one interval.
1038
+
1039
+ Measured 2026-08-24 (OBS-583), the now-unsafe legacy wrapper loop left a **2d20h** orphan from a
1040
+ predecessor seat holding `orchestrator ARMED` through a **three-hour window in which no orchestrator
1041
+ was alive**; that wrapper could also re-arm a recorded stand-down. On the same sweep the overseer tier
1042
+ had three beat writers, one owned by an unrelated session. The shipped `--loop` supersedes that wrapper,
1043
+ but its process ownership still needs a live check. So:
1044
+
1028
1045
  - **Split the liveness reads.** A tier's liveness is read from beat freshness in the repository status
1029
- path; a loop's liveness is read from the live process payload that is emitting that beat (`tickmarkr
1030
- beat <tier> --seat <seat>` in this repo). Neither liveness claim is read from a recorded pid: a pid
1046
+ path; the loop's liveness is read from the live process payload (`tickmarkr beat <tier> --seat <seat>
1047
+ --loop` in this repo). Neither liveness claim is read from a recorded pid: a pid
1031
1048
  recorded earlier can be stale, reused, or detached from the beat now holding the tier green.
1032
- - **At every adopt, clear, or re-brief, sweep for pre-existing loops on YOUR tier before arming one**
1033
- (`pgrep -f "tickmarkr beat <tier>"`, **read twice and intersected** — this exact probe returned its own
1034
- shell as pid 14680 on 2026-08-31), trace each survivor to its parent session, and kill the **loop only**
1035
- — never the parent — then verify the parent survived.
1049
+ - **At every adopt, clear, or re-brief, sweep for pre-existing beat writers on YOUR tier before
1050
+ arming one** (`pgrep -f "tickmarkr beat <tier>"`, **read twice and intersected** — this exact probe
1051
+ returned its own shell as pid 14680 on 2026-08-31). **The PRIMARY target is the legacy
1052
+ `while … tickmarkr beat <tier>` wrapper**, which is why the pattern carries no `--loop` qualifier:
1053
+ a `--loop` exits by itself once your new arm replaces its own, but the wrapper's bare tick beats
1054
+ whatever arm is on disk, so it survives your re-arm and never exits on its own. Trace each survivor
1055
+ to its parent session. Stop an unowned writer only — never its parent — then verify the parent survived.
1036
1056
  - **`ARMED (<seat>)` is an attributable claim, not proof that the named seat is still alive.** Before
1037
- trusting it, ask whose session owns the beater; an orphan loop can keep naming a departed seat
1038
- (rule 11's outliving-its-trigger failure, in beat form).
1039
- - Stand-down must kill the loop **and** run `--stand-down`; the second without the first is undone
1040
- by the next tick.
1041
- The remaining product fix (a sentinel-terminated beat, armed and stood down in one act) is queued;
1042
- until it ships, this sweep is the guard.
1057
+ trusting it, ask whose session owns the beater; a still-running `--loop` can keep naming a departed
1058
+ seat (rule 11's outliving-its-trigger failure, in beat form).
1059
+ - **Stand down with `--stand-down` and verify the `--loop` exits within one interval.** Do not use the
1060
+ legacy bare shell wrapper: unlike `--loop`, it cannot observe the marker and was the form that
1061
+ re-armed a stand-down.
1043
1062
 
1044
1063
  Arm the bundled watcher as its OWN Bash call with `run_in_background` — chaining it after other commands
1045
1064
  with `&` orphans it from the wake chain. It prints one wake reason and exits; re-arm after every wake.
@@ -1188,7 +1207,7 @@ orchestrator turn boundary.
1188
1207
 
1189
1208
  ### On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`) — supervision instruments
1190
1209
 
1191
- At seat spawn, arm file/journal watchers for artifact completion, run events, missing progress and context evidence. Record each watcher owner and bounded expiry; renew on every wake and stop on stand-down. Read `orca terminal read --terminal <handle> --screen --json` on each wake to detect blocked or pending input. For a human gate, write a checkpoint evidence file and announce it through verified terminal send. Use Orca notifications only when the installed host advertises a notification capability; the current CLI has no `notification` command, so the file and terminal receipt remain the delivery path. A notification or accepted input alone never proves delivery or completion. A seat-liveness watcher on the ORCHESTRATOR handle is mandatory, not optional: poll `orca terminal read --terminal <handle> --screen --json` on a bounded interval, treat `terminal_handle_stale` or a missing terminal as seat death, and re-resolve by title before re-arming (OBS-1050 — the orchestrator seat exited silently and the daemon ran unsupervised to a PARTIAL run-end).
1210
+ At seat spawn, arm file/journal watchers for artifact completion, run events, missing progress and context evidence. Record each watcher owner and bounded expiry; renew on every wake and stop on stand-down. Read `orca terminal read --terminal <handle> --screen --json` on each wake to detect blocked or pending input. For a human gate, write a checkpoint evidence file and announce it through verified terminal send; the moment a successor opens that file (or any other cited file) back, log the open per CITE-IS-NOT-READ (`opened <path>` appended to `.tickmarkr/overseer/opened-files.log`). Use Orca notifications only when the installed host advertises a notification capability; the current CLI has no `notification` command, so the file and terminal receipt remain the delivery path. A notification or accepted input alone never proves delivery or completion. A seat-liveness watcher on the ORCHESTRATOR handle is mandatory, not optional: poll `orca terminal read --terminal <handle> --screen --json` on a bounded interval and key liveness on `result.terminal.status === "running"` — never on the envelope's own `ok`. `ok: true` proves only that the READ command executed; a closed seat's read still returns that same `ok: true` with `result.terminal.status: "exited"`, and a matcher keyed on `ok` reads that exited seat as alive and never fires (OBS-1087). Treat `result.terminal.status !== "running"`, `terminal_handle_stale`, or a missing terminal as seat death, and re-resolve by title before re-arming (OBS-1050 — the orchestrator seat exited silently and the daemon ran unsupervised to a PARTIAL run-end).
1192
1211
 
1193
1212
  ## Specialist pipeline rules
1194
1213
 
@@ -1353,10 +1372,11 @@ Create specialist seats using `orca terminal create --worktree path:<repo> --com
1353
1372
  — the entry survived, but nothing had checked. `grep -c '^## OBS-<id>'` for each id the diff
1354
1373
  introduced. A citation pointing at nothing is the defect the ledger itself files (OBS-604), shipped
1355
1374
  into `src/`.
1356
- - **KILL THE BEAT LOOP *AND* RUN `--stand-down`.** Either alone is worse than neither: the loop
1357
- without the stand-down re-arms a tier you retired within 10s, and the stand-down without the loop
1358
- is undone by the next tick. Verify `status` reads `DISARMED` — which means *handed off*, distinct
1359
- from `STALE` (armed then died) and `ABSENT` (never armed).
1375
+ - **STAND DOWN THE BEAT THROUGH `--stand-down`, THEN VERIFY THE `--loop` EXITS.** The shipped loop
1376
+ observes the recorded marker and exits within one interval; it does not re-arm after the stand-down.
1377
+ Verify `status` reads `DISARMED` — which means *handed off*, distinct from `STALE` (armed then died)
1378
+ and `ABSENT` (never armed). A bare legacy shell wrapper is unsafe because it cannot observe that
1379
+ marker; do not substitute one for `--loop`.
1360
1380
  - **RECORD YOUR WATCHERS AS DYING WITH THIS SESSION — never as "armed".** A written stand-down or
1361
1381
  handoff may NOT carry the bare wording *"watcher armed"* for anything this seat owns: that form
1362
1382
  states an act and lets the successor read a fact, and it survived into a handoff exactly once before
@@ -1411,7 +1431,12 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
1411
1431
 
1412
1432
  1. **CITE-IS-NOT-READ.** A queue, handoff, finding, or brief that cites an observation does not prove its
1413
1433
  author opened it. Before acting on a cited premise, open the primary record, quote the operative bytes,
1414
- and state the qualifier or falsifier the citation would otherwise hide.
1434
+ and state the qualifier or falsifier the citation would otherwise hide. **On BOTH hosts, log the open:**
1435
+ append one line, `opened <path>`, to the append-only log `.tickmarkr/overseer/opened-files.log` (create
1436
+ it if absent; never truncate or rewrite an existing line) for every cited file this rule makes you open.
1437
+ Nothing else records which cited files a successor actually opened after a clear (OBS-1102) — this log
1438
+ is the record a later handoff is measured against: a claimed read with no matching line here did not
1439
+ happen.
1415
1440
  2. **EXECUTING-FORM PROBE.** Process ownership starts from the watcher pid file. Where legacy discovery is
1416
1441
  unavoidable, match the executing form — interpreter plus exact script path and arguments — not a journal
1417
1442
  path or name substring, resolve the candidate's cwd/parent, read the arm log for startup failure, then
@@ -95,9 +95,21 @@ SEAT="$TARGET"
95
95
  # would emit one before any successful read and break rule 2 outright, and a probe that parses the
96
96
  # usage banner binds this script to another command's help text. Beating is what we do anyway, so it
97
97
  # perturbs nothing — and because a beat only ever follows a successful read, rule 2 still holds.
98
+ # OBS-583/OBS-1088: a bare `tickmarkr beat` reuses whatever durable arm is already on disk, so a fresh
99
+ # script instance beating a tier a PRIOR instance stood down would either silently re-arm the stand-down
100
+ # (pre-fence) or, now that a stand-down fences the old arm, sit refused forever. The skill's beat recipe
101
+ # names that bare-call shape the unsafe legacy form for the same reason; this loop arms through the same
102
+ # explicit verb: `--new-arm` on the first beat only, so this instance's arm always acknowledges whatever
103
+ # marker is current before settling into ordinary per-tick beats.
98
104
  beat_refused=0
105
+ armed=0
99
106
  beat() {
100
- tickmarkr beat "$TIER" --seat "$SEAT" >/dev/null 2>&1 && return 0
107
+ new_arm=""
108
+ [ "$armed" -eq 0 ] && new_arm="--new-arm"
109
+ if tickmarkr beat "$TIER" --seat "$SEAT" $new_arm >/dev/null 2>&1; then
110
+ armed=1
111
+ return 0
112
+ fi
101
113
  if [ "$beat_refused" -eq 0 ]; then
102
114
  beat_refused=1
103
115
  echo "TIER_UNREGISTERED ${TIER} — the product refused this tier (its set: src/run/supervision.ts:45)"
@@ -106,7 +118,7 @@ beat() {
106
118
  fi
107
119
  return 0
108
120
  }
109
- stand_down() { tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&1; return 0; }
121
+ stand_down() { armed=0; tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&1; return 0; }
110
122
  # EVERY terminal exit — act, unsafe-act, cap — leaves through here, so none of them can forget to
111
123
  # record the hand-off. A killed watcher never runs it, which is the one case that must read STALE.
112
124
  cleanup() { stand_down; clear_pid; }