tickmarkr 2.1.1 → 2.1.2
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.
|
@@ -105,7 +105,7 @@ Outside multi-agent environments, run the loop directly.
|
|
|
105
105
|
|
|
106
106
|
### Version preflight
|
|
107
107
|
|
|
108
|
-
Before \`tickmarkr compile\` or \`tickmarkr run\`: run \`tickmarkr version\`, read \`package.json\` version, and if the
|
|
108
|
+
Before \`tickmarkr compile\` or \`tickmarkr run\`: run \`tickmarkr version\`, read \`package.json\` version, and stop if the versions differ anywhere (major, minor, or patch); the binary and repository must agree on the entire version, so binary \`2.1.0\` versus repository \`2.1.1\` is a stop. Tell the operator to update or link the correct binary. Never proceed on hope — stale binaries silently skip daemon gates. Also verify no run is live in this repository before starting one: lead with this repository's \`.tickmarkr/graph.lock\`, read its holder pid, and treat it as live until \`kill -0 <pid>\` proves that holder dead. Never require a machine-wide process pattern to be empty: a lawful run in another repository — or the probing shell's own argv — can match. If you use a process probe as secondary evidence, exclude the probing process, resolve every candidate's own cwd (for example with \`lsof -a -p <pid> -d cwd\`), and count only candidates whose cwd is this repository root.
|
|
109
109
|
|
|
110
110
|
### Tip-verify-before-green
|
|
111
111
|
|
|
@@ -104,7 +104,15 @@ export async function verify(argv, cwd = process.cwd()) {
|
|
|
104
104
|
}
|
|
105
105
|
const wantAcceptance = acceptance.length > 0 && !values["no-acceptance"];
|
|
106
106
|
const wantReview = !values["no-review"];
|
|
107
|
-
|
|
107
|
+
// A gate that enforced nothing must not print a green row. `files` has exactly three sources —
|
|
108
|
+
// explicit --files, a compiled task's own files[], or nothing — and only the third leaves the
|
|
109
|
+
// allowlist empty, where scopeGate passes as "no file scope declared — unrestricted"
|
|
110
|
+
// (scope.ts:41). An honest `details` string is no defence: the ROW is what gets quoted, and
|
|
111
|
+
// quoting it launders a check that never ran. So scope filters on availability exactly as
|
|
112
|
+
// acceptance and review do below — the report omits the gate rather than crediting one that
|
|
113
|
+
// gated no allowlist. (Narrowing the empty allowlist to the changed set is the same green row by
|
|
114
|
+
// another mechanism, and hides the same fact.)
|
|
115
|
+
const gates = GATE_NAMES.filter((g) => (g !== "acceptance" || wantAcceptance) && (g !== "review" || wantReview) && (g !== "scope" || files.length > 0));
|
|
108
116
|
const task = {
|
|
109
117
|
id: "VERIFY", title: "standalone verification", goal, shape: "implement", complexity: 5,
|
|
110
118
|
deps: [], files, context: [], acceptance: acceptance.length ? acceptance : ["(deterministic verification only)"],
|
package/package.json
CHANGED
|
@@ -45,10 +45,27 @@ Before `tickmarkr compile` or `tickmarkr run`, compare the installed binary agai
|
|
|
45
45
|
|
|
46
46
|
1. Run `tickmarkr version` (one line, machine-parseable).
|
|
47
47
|
2. Read the `version` field from the repository's `package.json`.
|
|
48
|
-
3. If the binary
|
|
48
|
+
3. If the binary and repository do not **agree on the entire version** (including the patch; e.g. binary `2.1.0` vs repo `2.1.1`), **stop immediately** and tell the operator to update the global install (`npm i -g tickmarkr@latest`) or link the repo binary. Do not compile, plan, or run on hope.
|
|
49
49
|
|
|
50
50
|
A stale binary silently skips daemon gates shipped in newer releases — the v1.38 run exposed this when a global `1.36.0` binary missed the daemon tip-verify gate entirely (OBS-38). Preflight failure is always stop-and-report; never proceed-and-hope.
|
|
51
51
|
|
|
52
|
+
### No run may be live in THIS repository
|
|
53
|
+
|
|
54
|
+
The version check above is only half the preflight. Before `compile` or `run`, confirm no run is already
|
|
55
|
+
live **in this repository**:
|
|
56
|
+
|
|
57
|
+
1. Lead with this repository's own `.tickmarkr/graph.lock`. Read its recorded holder pid.
|
|
58
|
+
2. Treat the lock as held by a LIVE run until `kill -0 <pid>` proves that holder dead.
|
|
59
|
+
3. **Never require a machine-wide process pattern to be empty.** A lawful run in another repository — or
|
|
60
|
+
the probing shell's own argv — matches such a pattern, so an empty result is not evidence of safety
|
|
61
|
+
and a non-empty one is not evidence of danger.
|
|
62
|
+
4. If you use a process probe as secondary evidence, exclude the probing process itself, resolve every
|
|
63
|
+
candidate's own working directory (for example `lsof -a -p <pid> -d cwd`), and count only candidates
|
|
64
|
+
whose cwd is **this repository root**.
|
|
65
|
+
|
|
66
|
+
The invariant this protects is per-repository — *never run two tickmarkr runs in the same repository
|
|
67
|
+
concurrently* — so a machine-wide check answers a question nobody asked and blocks work that is lawful.
|
|
68
|
+
|
|
52
69
|
## Verified handoffs (agent-to-agent messaging)
|
|
53
70
|
|
|
54
71
|
When relaying missions between agents in a multi-agent terminal, **never use bare send-text** (`herdr agent send` / pane send-text) — it writes text without pressing Enter, so handoffs sit unsubmitted (OBS-39).
|
|
@@ -40,10 +40,27 @@ Before `tickmarkr compile` or `tickmarkr run`, compare the installed binary agai
|
|
|
40
40
|
|
|
41
41
|
1. Run `tickmarkr version` (one line, machine-parseable).
|
|
42
42
|
2. Read the `version` field from the repository's `package.json`.
|
|
43
|
-
3. If the binary
|
|
43
|
+
3. If the binary and repository do not **agree on the entire version** (including the patch; e.g. binary `2.1.0` vs repo `2.1.1`), **stop immediately** and tell the operator to update the global install (`npm i -g tickmarkr@latest`) or link the repo binary. Do not compile, plan, or run on hope.
|
|
44
44
|
|
|
45
45
|
A stale binary silently skips daemon gates shipped in newer releases — the v1.38 run exposed this when a global `1.36.0` binary missed the daemon tip-verify gate entirely (OBS-38). Preflight failure is always stop-and-report; never proceed-and-hope.
|
|
46
46
|
|
|
47
|
+
### No run may be live in THIS repository
|
|
48
|
+
|
|
49
|
+
The version check above is only half the preflight. Before `compile` or `run`, confirm no run is already
|
|
50
|
+
live **in this repository**:
|
|
51
|
+
|
|
52
|
+
1. Lead with this repository's own `.tickmarkr/graph.lock`. Read its recorded holder pid.
|
|
53
|
+
2. Treat the lock as held by a LIVE run until `kill -0 <pid>` proves that holder dead.
|
|
54
|
+
3. **Never require a machine-wide process pattern to be empty.** A lawful run in another repository — or
|
|
55
|
+
the probing shell's own argv — matches such a pattern, so an empty result is not evidence of safety
|
|
56
|
+
and a non-empty one is not evidence of danger.
|
|
57
|
+
4. If you use a process probe as secondary evidence, exclude the probing process itself, resolve every
|
|
58
|
+
candidate's own working directory (for example `lsof -a -p <pid> -d cwd`), and count only candidates
|
|
59
|
+
whose cwd is **this repository root**.
|
|
60
|
+
|
|
61
|
+
The invariant this protects is per-repository — *never run two tickmarkr runs in the same repository
|
|
62
|
+
concurrently* — so a machine-wide check answers a question nobody asked and blocks work that is lawful.
|
|
63
|
+
|
|
47
64
|
## Verified handoffs (agent-to-agent messaging)
|
|
48
65
|
|
|
49
66
|
When relaying missions between agents in a multi-agent terminal, **never use bare send-text** (`herdr agent send` / pane send-text) — it writes text without pressing Enter, so handoffs sit unsubmitted (OBS-39).
|
|
@@ -35,6 +35,16 @@ through brief lineage. **An executor choice nobody made is still an executor cho
|
|
|
35
35
|
status, and either ADOPT the
|
|
36
36
|
existing orchestrator (updated brief, re-armed watchers) or, if the old hierarchy is dead, archive the
|
|
37
37
|
stale brief and build fresh.
|
|
38
|
+
⚠ **VERIFY EVERY INHERITED WATCHER FROM THE PROCESS TABLE BEFORE YOU TRUST IT — re-arming your own
|
|
39
|
+
watchers is NOT enough, and a seat told only to re-arm its own is told the wrong thing.** An inherited
|
|
40
|
+
*"watcher armed"* line is a claim, not a watcher: it is a report by a seat that no longer exists, which
|
|
41
|
+
is strictly WEAKER than the live seat's report rule 11 already forbids trusting — and it reads as
|
|
42
|
+
settled fact. So at every adopt, walk the predecessor's watchers by class — **journal watchers,
|
|
43
|
+
artifact watchers, dialog watchers and beat loops, which is the closed set a session owns** — probe
|
|
44
|
+
each from the process table yourself (`pgrep -f <token>`, discriminated per rule 11), and re-arm every
|
|
45
|
+
one the table does not show. Earned 2026-08-25 (OBS-622): a handoff recorded *"artifact watcher armed"*
|
|
46
|
+
over two live consult verdicts; at adopt the only `watch-artifacts.sh` on the machine belonged to a
|
|
47
|
+
different repository, and nothing had been watching either file.
|
|
38
48
|
**An adopted seat ANNOUNCES itself, in the same act as re-arming:** tell the adopted orchestrator the
|
|
39
49
|
fresh seat is live (verified send: probe token + read-back). Through the gap its view of your tier read
|
|
40
50
|
STALE, and a tier that believes it is unsupervised escalates into a file nobody is reading. Earned
|
|
@@ -550,6 +560,15 @@ that hang is byte-identical to a seat still working. The script prints each unfi
|
|
|
550
560
|
last line on every timeout heartbeat — read it there, and when in doubt `tail -1` the artifact, never
|
|
551
561
|
the transcript's claim about it.
|
|
552
562
|
|
|
563
|
+
**An ABSENT artifact ALONE cannot discriminate a working producer from a dead watcher.** A producer still
|
|
564
|
+
working and a watcher that died with its seat write byte-identical evidence — nothing — so a missing file
|
|
565
|
+
is one signal carrying at least three meanings (still working, watcher dead, producer dead), and it is
|
|
566
|
+
**never** evidence that the watcher is still waiting. Reading it that way infers an instrument's liveness
|
|
567
|
+
from the silence it was built to sit through. Settle it with two probes that do not share a failure:
|
|
568
|
+
the watcher from the process table, the producer from its pane or seat status. Measured 2026-08-25
|
|
569
|
+
(OBS-622): both consultants were live and had written nothing, so the artifact side could not see that
|
|
570
|
+
nothing was watching them.
|
|
571
|
+
|
|
553
572
|
**Arm it in the same call as the spawn, not the next one.** A watcher armed "after I finish this step"
|
|
554
573
|
leaves a gap exactly as wide as however long you stay busy, and you will be busy — you just spawned work.
|
|
555
574
|
**Measured 2026-08-06 (OBS-369): two consult verdicts, 30KB and 12.8KB, sat COMPLETE with their markers
|
|
@@ -677,6 +696,50 @@ orchestrator turn boundary.
|
|
|
677
696
|
fix helps ONE operator and leaves every other user with the defect. If an overlay is the interim, it
|
|
678
697
|
says so in writing and names its removal condition.
|
|
679
698
|
|
|
699
|
+
8. **A SHIPPED VERSION IS NOT DONE UNTIL THE STATE IT LEAVES BEHIND IS CLEAN.** Publishing is the loud
|
|
700
|
+
half; the quiet half is that the NEXT seat inherits either the truth or a confident lie. Run this
|
|
701
|
+
before you stand down from any release — **operator instruction, 2026-08-25: *"overseer should always
|
|
702
|
+
leave clean state after a version is shipped"***. Every line below is a defect that actually happened
|
|
703
|
+
on the release that produced this rule.
|
|
704
|
+
|
|
705
|
+
- **REWRITE THE MEMORY INDEX FIRST, and read it back.** Minutes after `2.1.1` hit npm, the index line
|
|
706
|
+
a fresh session loads still read *"⛔ 2.1.1 CANNOT ship from run …2011"* — true when written, and by
|
|
707
|
+
then the exact opposite of the truth. **The index is what everyone loads and the body is what nobody
|
|
708
|
+
opens** (Evidence rule 25), so a stale index is not a cosmetic lag; it is the most-read wrong
|
|
709
|
+
sentence in the project. State what shipped, what did NOT, and the first three things the next seat
|
|
710
|
+
should do.
|
|
711
|
+
- **VERIFY EVERY ID THE CODE NOW CITES ACTUALLY EXISTS.** A release lands source comments citing
|
|
712
|
+
ledger ids. One of that release's entries was written by a heredoc in a command that then timed out
|
|
713
|
+
— the entry survived, but nothing had checked. `grep -c '^## OBS-<id>'` for each id the diff
|
|
714
|
+
introduced. A citation pointing at nothing is the defect the ledger itself files (OBS-604), shipped
|
|
715
|
+
into `src/`.
|
|
716
|
+
- **KILL THE BEAT LOOP *AND* RUN `--stand-down`.** Either alone is worse than neither: the loop
|
|
717
|
+
without the stand-down re-arms a tier you retired within 10s, and the stand-down without the loop
|
|
718
|
+
is undone by the next tick. Verify `status` reads `DISARMED` — which means *handed off*, distinct
|
|
719
|
+
from `STALE` (armed then died) and `ABSENT` (never armed).
|
|
720
|
+
- **RECORD YOUR WATCHERS AS DYING WITH THIS SESSION — never as "armed".** A written stand-down or
|
|
721
|
+
handoff may NOT carry the bare wording *"watcher armed"* for anything this seat owns: that form
|
|
722
|
+
states an act and lets the successor read a fact, and it survived into a handoff exactly once before
|
|
723
|
+
costing two unwatched consult verdicts (OBS-622). The admissible form names the lifetime and the
|
|
724
|
+
work it leaves the successor — *"watchers armed by this session (journal, artifact, dialog, beat);
|
|
725
|
+
they die with it — re-arm on adopt"* — and, per rule 11, says which tier's watchers were NOT armed.
|
|
726
|
+
A detached watcher is the one exception and must be labelled as such, with its heartbeat file, since
|
|
727
|
+
it outlives the seat instead.
|
|
728
|
+
- **SWEEP THE PANES THE RUN LEFT.** A daemon killed by a signal flushes its journal and releases its
|
|
729
|
+
lock but **does not clean up its worker panes or its board**. Two orphaned worker panes and a dead
|
|
730
|
+
board pane sat in the operator's tab bar until he screenshotted them. Verify each is inert first
|
|
731
|
+
(no agent, nothing running in its worktree) and confirm the WORK is on its branch — then close.
|
|
732
|
+
Emptied tabs disappear on their own.
|
|
733
|
+
- **CORRECT EVERY TAB LABEL.** `ORCH · 2.1.1 T1 regate` was still on screen hours after that regate
|
|
734
|
+
ended. Tab labels are how the operator reads fleet state; a stale one is a false status report.
|
|
735
|
+
- **LEAVE THE TREE CLEAN AND SAY WHAT IS UNMERGED.** Name the branches that hold real but ungated
|
|
736
|
+
work, so the next seat neither discards nor trusts them. *"Zero merges, T1/T3 branches ungated, T5
|
|
737
|
+
never dispatched"* is a handoff; *"the run ended"* is not.
|
|
738
|
+
|
|
739
|
+
⚠ **The half of this that is NOT operator discipline must be QUEUED, not absorbed:** a daemon that
|
|
740
|
+
orphans its panes on SIGTERM is a PRODUCT defect and belongs in `src/**`. Sweeping by hand every time
|
|
741
|
+
is the local remedy, and per rule 7 it says so in writing and names its removal condition.
|
|
742
|
+
|
|
680
743
|
---
|
|
681
744
|
|
|
682
745
|
## Briefing a seat to audit a security-shaped check — phrasing matters
|
|
@@ -787,6 +850,14 @@ twice.** They are mission-independent on purpose: nothing here names a task, a l
|
|
|
787
850
|
owns it** — "watchers alive" is the one claim a seat cannot verify about itself. Measured 2026-08-06:
|
|
788
851
|
an orchestrator sat `idle` through three merges and two dispatches with no journal watcher in the
|
|
789
852
|
process table, while its own last report read *"daemon, board, sweeper, watcher all alive"* (OBS-366).
|
|
853
|
+
**STATE THE LIFETIME, because an unstated one is read as the mission's: a session-scoped watcher DIES
|
|
854
|
+
WITH THE SEAT THAT ARMED IT.** Every watcher a seat arms — journal, artifact, dialog, beat loop — is
|
|
855
|
+
session-scoped unless it was deliberately detached (`ppid 1`, the heartbeat form below), so `/clear`,
|
|
856
|
+
a crash, an adopt or a stand-down ends it, and **a handoff is the one moment the arming seat stops
|
|
857
|
+
existing** — which is exactly when its watchers are most likely to be believed. The inverse failure is
|
|
858
|
+
the same root read the other way: a DETACHED loop outlives its seat and holds a tier `ARMED` with
|
|
859
|
+
nobody home (OBS-583). Neither direction may be assumed; the lifetime is a property of how the watcher
|
|
860
|
+
was launched, and it belongs in writing next to every claim that one is armed.
|
|
790
861
|
**And the process-table probe has a standard idiom that DEFEATS it, so the rule above needs one more
|
|
791
862
|
line to be usable.** Never probe for a watcher with `ps … | grep <token> | grep -v grep`: a poll-grep
|
|
792
863
|
watcher carries the word `grep` in its own argv, so the filter whose job is removing the *probing* grep
|