tickmarkr 2.5.7 → 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.
- package/dist/adapters/qwen.js +30 -3
- package/dist/cli/commands/approve.js +23 -4
- package/dist/cli/commands/beat.d.ts +2 -0
- package/dist/cli/commands/beat.js +28 -29
- package/dist/cli/commands/compile.js +18 -0
- package/dist/cli/commands/fleet.js +61 -53
- package/dist/cli/commands/plan.d.ts +5 -0
- package/dist/cli/commands/plan.js +28 -23
- package/dist/cli/commands/resume.js +1 -1
- package/dist/cli/commands/run.js +1 -1
- package/dist/cli/commands/verify.d.ts +4 -1
- package/dist/cli/commands/verify.js +16 -5
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +6 -4
- package/dist/compile/native.js +68 -7
- package/dist/compile/retired-literals.d.ts +22 -0
- package/dist/compile/retired-literals.js +271 -0
- package/dist/config/config.d.ts +41 -3
- package/dist/config/config.js +48 -17
- package/dist/config/fleet-overlay.js +47 -62
- package/dist/drivers/index.d.ts +4 -2
- package/dist/drivers/index.js +54 -6
- package/dist/gates/baseline.d.ts +65 -0
- package/dist/gates/baseline.js +163 -9
- package/dist/gates/review.d.ts +6 -4
- package/dist/gates/review.js +62 -21
- package/dist/gates/run-gates.d.ts +6 -1
- package/dist/gates/run-gates.js +25 -13
- package/dist/gates/test-manifest.d.ts +20 -1
- package/dist/gates/test-manifest.js +50 -22
- package/dist/gates/test-reporter.js +22 -1
- package/dist/graph/schema.d.ts +28 -0
- package/dist/graph/schema.js +13 -1
- package/dist/run/daemon.d.ts +19 -0
- package/dist/run/daemon.js +363 -118
- package/dist/run/journal.d.ts +54 -3
- package/dist/run/journal.js +142 -11
- package/dist/run/merge.d.ts +15 -2
- package/dist/run/merge.js +74 -11
- package/dist/run/protocol.d.ts +82 -0
- package/dist/run/protocol.js +35 -0
- package/dist/run/receipt-resolver.d.ts +18 -0
- package/dist/run/receipt-resolver.js +132 -0
- package/dist/run/repair-disposition.d.ts +41 -0
- package/dist/run/repair-disposition.js +77 -0
- package/dist/run/supervision.d.ts +14 -1
- package/dist/run/supervision.js +122 -24
- package/dist/tui/cockpit/evidence-view.d.ts +10 -1
- package/dist/tui/cockpit/evidence-view.js +37 -5
- package/dist/tui/cockpit/home-view.js +45 -30
- package/dist/tui/cockpit/live-store.d.ts +18 -0
- package/dist/tui/ink/fleet-app.d.ts +12 -22
- package/dist/tui/ink/fleet-app.js +520 -131
- package/package.json +1 -1
- package/schema/rungraph.schema.json +54 -0
- package/skills/tickmarkr-overseer/SKILL.md +168 -36
- package/skills/tickmarkr-overseer/scripts/watch-context.sh +14 -2
package/package.json
CHANGED
|
@@ -149,6 +149,10 @@
|
|
|
149
149
|
"text": {
|
|
150
150
|
"type": "string",
|
|
151
151
|
"minLength": 1
|
|
152
|
+
},
|
|
153
|
+
"landing": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"minLength": 1
|
|
152
156
|
}
|
|
153
157
|
},
|
|
154
158
|
"required": [
|
|
@@ -201,6 +205,56 @@
|
|
|
201
205
|
]
|
|
202
206
|
}
|
|
203
207
|
},
|
|
208
|
+
"pins": {
|
|
209
|
+
"type": "array",
|
|
210
|
+
"items": {
|
|
211
|
+
"oneOf": [
|
|
212
|
+
{
|
|
213
|
+
"type": "object",
|
|
214
|
+
"properties": {
|
|
215
|
+
"kind": {
|
|
216
|
+
"type": "string",
|
|
217
|
+
"const": "literal"
|
|
218
|
+
},
|
|
219
|
+
"text": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"minLength": 1
|
|
222
|
+
},
|
|
223
|
+
"glob": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"minLength": 1
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"required": [
|
|
229
|
+
"kind",
|
|
230
|
+
"text",
|
|
231
|
+
"glob"
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"type": "object",
|
|
236
|
+
"properties": {
|
|
237
|
+
"kind": {
|
|
238
|
+
"type": "string",
|
|
239
|
+
"const": "fixture"
|
|
240
|
+
},
|
|
241
|
+
"paths": {
|
|
242
|
+
"minItems": 1,
|
|
243
|
+
"type": "array",
|
|
244
|
+
"items": {
|
|
245
|
+
"type": "string",
|
|
246
|
+
"minLength": 1
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"required": [
|
|
251
|
+
"kind",
|
|
252
|
+
"paths"
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
},
|
|
204
258
|
"gates": {
|
|
205
259
|
"default": [
|
|
206
260
|
"build",
|
|
@@ -288,6 +288,58 @@ journal tail to decide what happens next, or sweeping orphans — you have taken
|
|
|
288
288
|
3. **Gate a mid-run fix at the base, not at a summary (law 47 / OBS-909).** A fix landed on main while a
|
|
289
289
|
run is live is proved with `tickmarkr verify --base <main>`, never with a suite summary copied from a
|
|
290
290
|
different tree. A release proof runs every CI-ordered step — including lint — before its suite.
|
|
291
|
+
4. **Rescue a broken harness from OUTSIDE the run it broke — the daemon is NEVER asked to repair itself
|
|
292
|
+
as a task (v2.5.7 ledger D-59, D-64; OBS-1078).** When the harness is the defect — resume cannot be
|
|
293
|
+
relied on, a gate cannot be relied on, the binary every gate runs on is the thing that is wrong — no
|
|
294
|
+
task inside the run can fix it: the gates that would judge the repair are executed BY the suspect
|
|
295
|
+
binary, and the worktree the repair would run in is created by the same code. Dispatching
|
|
296
|
+
*"fix the daemon"* as a task of the run it broke buys a green gate from the defect and a repair nobody
|
|
297
|
+
can trust. The rescue route has three legs, each with its own record, and there is no fourth:
|
|
298
|
+
- **AN INDEPENDENTLY GATED EXTERNAL FIX LEG.** Its own checkout and branch off the run's base, its own
|
|
299
|
+
brief, its own `files[]`, its own battery — `tickmarkr verify --base <ref> --criteria <file>
|
|
300
|
+
--author <the seat that wrote it>` — plus the cross-vendor review its criterion names, all recorded
|
|
301
|
+
BEFORE the run consumes any of it. The leg's battery is scheduled at a boundary item 2 permits (the
|
|
302
|
+
run parked or ended, or the operator's recorded exception), and its reds are declared to the
|
|
303
|
+
contamination watcher in advance so they are classified instead of assumed.
|
|
304
|
+
- **AN EXPLICITLY RECORDED REBUILT BINARY AND RESTART — the provenance is written down in the same act,
|
|
305
|
+
or the rebuild did not happen.** Record: the fix leg's commit sha; the build command; the install
|
|
306
|
+
form (never `npm i -g .` on the repository directory — npm SYMLINKS a directory install and every
|
|
307
|
+
later build hot-swaps the machine-wide binary with no version change to notice it by); the
|
|
308
|
+
global-versus-repo **inode** comparison that proves a real install, which `tickmarkr version` cannot
|
|
309
|
+
do because it cannot go red when nothing is bumped; the version read-back; and the restart itself —
|
|
310
|
+
`tickmarkr resume <runId>`, the ORCHESTRATOR's command, never yours. An unrecorded rebuild leaves the
|
|
311
|
+
next seat arguing about which binary produced which journal rows, with nothing to read.
|
|
312
|
+
- **A SEPARATELY GATED UNION.** The rebuilt harness and the run's existing work are then gated
|
|
313
|
+
TOGETHER, on their own, at the one commit that carries both. A green fix leg beside a green run is
|
|
314
|
+
no proof of their union — see *a green leg beside a green parent*, under the release criterion
|
|
315
|
+
below — because the union's own conflict resolutions, ordering and collected-count changes are
|
|
316
|
+
exactly what neither green ever saw.
|
|
317
|
+
**NO BRANCH SURGERY: the run keeps its ORIGINAL base and its recorded baseline.** No rebase of the
|
|
318
|
+
integration branch, no re-cut `baseRef`, no re-baselining to make the rescue leg's diff read small.
|
|
319
|
+
Resume REPLAYS the journal's `baseRef`, so a rewritten one is a different run wearing the same id —
|
|
320
|
+
and a baseline re-recorded to suit the rescue launders every red the original baseline was there to
|
|
321
|
+
compare against.
|
|
322
|
+
5. **A rebuilt binary is NOT a rebuilt tree: a claimed fix arrival needs ANCESTRY EVIDENCE naming the
|
|
323
|
+
actual task subject (v2.5.7 ledger D-80; OBS-1078).** Rebuilding and reinstalling `dist` changes the
|
|
324
|
+
daemon the orchestrator launches and nothing else. It puts no test, fixture, schema or skill byte into
|
|
325
|
+
any task worktree or into the integration branch: those trees were checked out from the integration tip
|
|
326
|
+
BEFORE the fix leg landed, and git does not retro-fill a checkout that already exists. So *"the fix has
|
|
327
|
+
reached the task checkouts"* is never accepted on a version read-back, a rebuild log, or the fixer's
|
|
328
|
+
say-so — require ancestry evidence naming the ACTUAL subjects, and read it before the claim, not after:
|
|
329
|
+
- **the TASK subject** — that task worktree's own branch and HEAD sha (`git -C <task-worktree> rev-parse
|
|
330
|
+
--abbrev-ref HEAD` and `git -C <task-worktree> rev-parse HEAD`), with the fix proven an ancestor of
|
|
331
|
+
THAT commit: `git -C <task-worktree> merge-base --is-ancestor <fix-sha> HEAD`, whose exit code is the
|
|
332
|
+
evidence, recorded beside all three shas;
|
|
333
|
+
- **the INTEGRATION subject** — the integration-branch commit that worktree was created from, named by
|
|
334
|
+
sha from the journal's `task-dispatch` row or `tickmarkr status`; never "the run", and never the
|
|
335
|
+
branch name alone, which moves while you are reading it;
|
|
336
|
+
- **and, where the fix is a FILE the tree must hold** — a new test, a fixture, a schema — its presence
|
|
337
|
+
at that commit: `git -C <task-worktree> cat-file -e <task-head>:<path>`. A fix that lives only in the
|
|
338
|
+
daemon's `dist` cannot make a worker's missing oracle appear, and a worker red on that missing file is
|
|
339
|
+
still a plan defect, not a retry.
|
|
340
|
+
A fix present in the daemon and absent from the task trees has been INSTALLED, not ARRIVED: say which
|
|
341
|
+
of the two you mean, and name the commit each half was read against. Evidence rule 13's baseRef trap is
|
|
342
|
+
the same defect wearing a diff instead of a claim.
|
|
291
343
|
|
|
292
344
|
### What the ORCHESTRATOR does, and what you require of it
|
|
293
345
|
|
|
@@ -383,12 +435,67 @@ its own — **not because any instrument detected it.**
|
|
|
383
435
|
> covers — and a re-scope of any named subject voids it AUTOMATICALLY, with no ruling required.**
|
|
384
436
|
> A void condition that needs a ruling to fire is not a void condition; it is a second thing to forget.
|
|
385
437
|
|
|
438
|
+
**⚡ ONE QUALIFICATION, PROSPECTIVE ONLY: A FUTURE SEAL MAY DECLARE THAT AN AUDITED FILES-ONLY AMENDMENT
|
|
439
|
+
DOES NOT VOID IT — AND THAT DECLARATION HAS TO BE IN THE SEAL, WRITTEN AT ISSUE (v2.5.7 ledger D-80, D-83;
|
|
440
|
+
OBS-1078).** The automatic-void rule above is QUALIFIED for seals issued after this clause, never deleted:
|
|
441
|
+
it still fires on its own terms for every seal that does not carry the declaration. A requirements seal may
|
|
442
|
+
declare, prospectively, that exactly one narrow class of change does not void it — an **AUDITED FILES-ONLY
|
|
443
|
+
AMENDMENT**: a recorded amendment to a task's `files[]` and nothing else, which leaves all four of these
|
|
444
|
+
UNTOUCHED.
|
|
445
|
+
|
|
446
|
+
1. **the ACCEPTANCE TEXT** — every criterion's own words, byte for byte, a `test:` leaf title included;
|
|
447
|
+
2. **the TASK IDENTITY** — which task is which: its id, its shape, its deps, the suites it owns;
|
|
448
|
+
3. **the RUN IDENTITY** — the run, its base and its `baseRef`, the integration branch and its tip commit;
|
|
449
|
+
4. **the SAFETY REQUIREMENTS** — the gates, bounds, refusals and contamination rules the run is held to.
|
|
450
|
+
|
|
451
|
+
**Replay it both ways, because the clause is a procedure and not a mood:**
|
|
452
|
+
- *A seal carrying that declaration, then an audited amendment adding one owned path to a task's `files[]`,
|
|
453
|
+
with all four invariants read and found unchanged* → **THE SEAL IS KEPT.** No successor seal and no
|
|
454
|
+
re-grade of what it already sealed: the declaration was the seal's own pre-commitment about this exact
|
|
455
|
+
change, made before the change existed — the only moment such a commitment can honestly be made. Read the
|
|
456
|
+
four invariants BEFORE ruling, and record the read beside the amendment.
|
|
457
|
+
- *The same seal, then an amendment that changes the RUN IDENTITY — a new run id, a re-cut `baseRef`, a
|
|
458
|
+
different integration tip* → **THE SEAL IS VOID, on its own declaration's terms.** Run identity is one of
|
|
459
|
+
the four, so *"files-only"* does not describe that amendment; moving any one of the four puts the change
|
|
460
|
+
outside the declared class, and **any other change of any kind needs a SUCCESSOR SEAL** — a new document,
|
|
461
|
+
sealed before the work it grades, naming the subject as it now stands.
|
|
462
|
+
|
|
463
|
+
**Three limits, and each one has been argued around:**
|
|
464
|
+
- **The declaration is never retroactive and never inferred.** A seal issued BEFORE this change — one whose
|
|
465
|
+
own text does not carry the declaration — **stays governed by the existing void conditions above,
|
|
466
|
+
unmodified: any re-scope, narrowing, widening, split, merge or re-ownership of a named subject voids it
|
|
467
|
+
AUTOMATICALLY, with no ruling required.** So *"the acceptance text never changed, therefore the seal
|
|
468
|
+
still holds"* is **REFUSED** for such a seal once its SUBJECT has moved: unchanged acceptance text alone
|
|
469
|
+
preserves nothing over a changed subject, because a seal grades the subject it named, not the sentences
|
|
470
|
+
it happened to be written in. Retrofitting the declaration onto an already-sealed document is itself an
|
|
471
|
+
amendment to a sealed subject — write the successor seal instead.
|
|
472
|
+
- **A files-only clause confers no amendment authority.** It says nothing about whether the amendment is
|
|
473
|
+
LAWFUL: compile's unit bounds still refuse an over-bound `files[]`, and no seal can widen what compile
|
|
474
|
+
will certify.
|
|
475
|
+
- **AUDITED means recorded, or it did not happen.** The approval row carrying the amended files, the
|
|
476
|
+
before-and-after `files[]`, and the four-invariant read are what let the next seat replay your ruling
|
|
477
|
+
instead of trusting it. An unrecorded *"files-only"* amendment is indistinguishable from a re-scope, and
|
|
478
|
+
is voided as one.
|
|
479
|
+
|
|
386
480
|
**⚡ IDENTIFY THE SUBJECT BY WHAT THE CLAIM IS ABOUT. Half of the failure above was a category error, and
|
|
387
481
|
it is the cheap half to fix:** a graph hash identifies a **PLAN**, and a plan is recompiled, re-cut and
|
|
388
482
|
re-owned as a matter of course. **A criterion about a SHIPPED TREE names the COMMIT** — or the tag, or the
|
|
389
483
|
export tree hash — **never a graph hash, never a run id, never a task list.** Ask what a reader would have
|
|
390
484
|
to hold in their hand to check the clause: if it is bytes, name the bytes.
|
|
391
485
|
|
|
486
|
+
**⚡ A GREEN LEG BESIDE A GREEN PARENT IS NO PROOF OF THEIR UNION: THE RELEASE PROOF BINDS TO THE MERGED
|
|
487
|
+
CANDIDATE'S COMMIT, AND IS VOID THE MOMENT THAT SUBJECT MOVES (v2.5.7 ledger D-80, D-83; OBS-1078).** The
|
|
488
|
+
subject of a release proof is never *"the run"* and never *"the fix leg"* — it is the **MERGED candidate**:
|
|
489
|
+
the one commit that carries the run's work AND every leg merged into it. Name that commit's sha in the
|
|
490
|
+
proof, in the same act as the grade, and the proof is bound to that sha alone. **When the subject moves — a
|
|
491
|
+
later merge, a new integration tip, an amended or re-cut commit, a fresh export, a re-tag — the proof is
|
|
492
|
+
VOID, and the new subject is graded from scratch**, count oracle included, since the oracle is derived from
|
|
493
|
+
the merged tree and not from either parent. Two greens assembled into one verdict are two claims about two
|
|
494
|
+
trees that never contained each other: the union's own conflict resolutions, its ordering and its
|
|
495
|
+
collected-test total are exactly what neither green observed — which is why `RELEASING.md` step 4 matches
|
|
496
|
+
the run's head SHA to the mirror's `HEAD` before a single job log is graded. This is the void-condition duty
|
|
497
|
+
pointed at bytes: **a proof that names no commit cannot notice its subject leaving.**
|
|
498
|
+
|
|
392
499
|
**THE RECIPROCAL DUTY, and it is yours because you write both documents:** when you issue a ruling that
|
|
393
500
|
re-scopes, narrows, splits or re-owns anything, **the ruling must name every sealed document its subject
|
|
394
501
|
appears in** — and say, in the ruling, whether each one is now void. You are the only seat that can do
|
|
@@ -507,6 +614,11 @@ run the Herdr pane commands or Herdr context watcher below.
|
|
|
507
614
|
verify every new pid twice, preserve the partner-owned watchers the stand-down lists, then confirm."
|
|
508
615
|
```
|
|
509
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
|
+
|
|
510
622
|
⚠ **Steps 3 and 4 are two sends, never one.** A pointer batched with the clear lands *during* it and is
|
|
511
623
|
lost with the context it was meant to survive. Verify the clear landed by reading the prompt line
|
|
512
624
|
and re-reading the banner percentage below its pre-clear value before sending the pointer — the same
|
|
@@ -890,49 +1002,63 @@ tier's state from a beat file the tier itself writes, so a seat that never beats
|
|
|
890
1002
|
run: `orchestrator ARMED / overseer ABSENT / watch ABSENT` for the whole milestone, with a live overseer
|
|
891
1003
|
watching it. Two thirds of that line were constants, not measurements.
|
|
892
1004
|
|
|
893
|
-
The beat is one shipped command and
|
|
894
|
-
`
|
|
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:
|
|
895
1009
|
|
|
896
1010
|
```bash
|
|
897
|
-
cd <repo> &&
|
|
898
|
-
tickmarkr beat overseer --seat <overseer-agent-or-pane> --stand-down #
|
|
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
|
|
899
1013
|
```
|
|
900
1014
|
|
|
901
|
-
The
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
|
907
1028
|
tier ages to `STALE` (never `ABSENT`) within six beats, which is the state that says *armed, then lost*.
|
|
908
1029
|
Stand down explicitly when you hand off, or a deliberate exit reads as a death. Same rule as rule 29
|
|
909
1030
|
below, now with a conventional path the other tier already reads: `tickmarkr status` shows it.
|
|
910
1031
|
|
|
911
|
-
⚠
|
|
1032
|
+
⚠ **`--loop` NAMES A SEAT BUT STILL BINDS ITS LIFETIME TO A PROCESS — and that distinction is
|
|
912
1033
|
load-bearing.** The command refuses an anonymous beat, and `status` renders the declared seat beside
|
|
913
1034
|
the tier state; a legacy tier+pid+instant record cannot be attributed and reads `UNREADABLE`, never
|
|
914
|
-
`ARMED`. Naming the seat does not
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
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
|
+
|
|
921
1045
|
- **Split the liveness reads.** A tier's liveness is read from beat freshness in the repository status
|
|
922
|
-
path;
|
|
923
|
-
|
|
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
|
|
924
1048
|
recorded earlier can be stale, reused, or detached from the beat now holding the tier green.
|
|
925
|
-
- **At every adopt, clear, or re-brief, sweep for pre-existing
|
|
926
|
-
(`pgrep -f "tickmarkr beat <tier>"`, **read twice and intersected** — this exact probe
|
|
927
|
-
shell as pid 14680 on 2026-08-31)
|
|
928
|
-
|
|
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.
|
|
929
1056
|
- **`ARMED (<seat>)` is an attributable claim, not proof that the named seat is still alive.** Before
|
|
930
|
-
trusting it, ask whose session owns the beater;
|
|
931
|
-
(rule 11's outliving-its-trigger failure, in beat form).
|
|
932
|
-
- Stand-down
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
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.
|
|
936
1062
|
|
|
937
1063
|
Arm the bundled watcher as its OWN Bash call with `run_in_background` — chaining it after other commands
|
|
938
1064
|
with `&` orphans it from the wake chain. It prints one wake reason and exits; re-arm after every wake.
|
|
@@ -1081,7 +1207,7 @@ orchestrator turn boundary.
|
|
|
1081
1207
|
|
|
1082
1208
|
### On Orca (`TERM_PROGRAM=Orca` and non-empty `ORCA_TERMINAL_HANDLE`) — supervision instruments
|
|
1083
1209
|
|
|
1084
|
-
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
|
|
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).
|
|
1085
1211
|
|
|
1086
1212
|
## Specialist pipeline rules
|
|
1087
1213
|
|
|
@@ -1246,10 +1372,11 @@ Create specialist seats using `orca terminal create --worktree path:<repo> --com
|
|
|
1246
1372
|
— the entry survived, but nothing had checked. `grep -c '^## OBS-<id>'` for each id the diff
|
|
1247
1373
|
introduced. A citation pointing at nothing is the defect the ledger itself files (OBS-604), shipped
|
|
1248
1374
|
into `src/`.
|
|
1249
|
-
- **
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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`.
|
|
1253
1380
|
- **RECORD YOUR WATCHERS AS DYING WITH THIS SESSION — never as "armed".** A written stand-down or
|
|
1254
1381
|
handoff may NOT carry the bare wording *"watcher armed"* for anything this seat owns: that form
|
|
1255
1382
|
states an act and lets the successor read a fact, and it survived into a handoff exactly once before
|
|
@@ -1304,7 +1431,12 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
|
|
|
1304
1431
|
|
|
1305
1432
|
1. **CITE-IS-NOT-READ.** A queue, handoff, finding, or brief that cites an observation does not prove its
|
|
1306
1433
|
author opened it. Before acting on a cited premise, open the primary record, quote the operative bytes,
|
|
1307
|
-
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.
|
|
1308
1440
|
2. **EXECUTING-FORM PROBE.** Process ownership starts from the watcher pid file. Where legacy discovery is
|
|
1309
1441
|
unavoidable, match the executing form — interpreter plus exact script path and arguments — not a journal
|
|
1310
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
|
-
|
|
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; }
|