tickmarkr 2.1.4 → 2.1.6

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.
@@ -26,12 +26,18 @@
26
26
  # last beat aged into a permanent stale — gradual growth never reached the auto-clear path at all.
27
27
  # 4. EVERY TERMINAL EXIT STANDS THE TIER DOWN, so a watcher that finished reads DISARMED, not dead.
28
28
  # Only a killed watcher reads STALE, which is exactly what STALE means.
29
+ # 5. A BLIND READ ALARMS (OBS-739). Ageing the tier is visible only to someone already reading the beat
30
+ # table; the seat that armed this watcher must be TOLD its instrument went blind. Silence and health
31
+ # must never look alike.
29
32
  #
30
- # usage: watch-context.sh <orchestrator|overseer> <agent|pane> <warn-pct> <act-pct> [handoff-file] [poll-s] [cap-s]
33
+ # usage: watch-context.sh <role-slug> <agent|pane> <warn-pct> <act-pct> [handoff-file] [poll-s] [cap-s]
34
+ # <role-slug> is ANY seat role — orchestrator, overseer, surgeon, consult — and names the tier
35
+ # `<role>-context`. It is deliberately NOT a closed set: see OBS-730 at the guard below.
31
36
  # TKR_AUTO_CLEAR=1 at act-pct WITH a fresh handoff, send /clear and re-brief instead of waking.
32
37
  # TKR_REBRIEF=<path> the file the re-briefed seat is told to read (defaults to the handoff).
33
38
  # TKR_HANDOFF_MAX_AGE_S how fresh "fresh" is (default 900).
34
39
  # TKR_CLEAR_SETTLE_S seconds to let a cleared seat settle before the re-brief (default 6).
40
+ # TKR_BLIND_ALARM_S seconds unreadable before CONTEXT_BLIND alarms (default 120).
35
41
 
36
42
  set -u
37
43
  ROLE="${1:?supervising seat role required: orchestrator|overseer}"
@@ -45,10 +51,16 @@ MAXAGE="${TKR_HANDOFF_MAX_AGE_S:-900}"
45
51
  REBRIEF="${TKR_REBRIEF:-$HANDOFF}"
46
52
  SETTLE="${TKR_CLEAR_SETTLE_S:-6}"
47
53
 
54
+ # OBS-730: this rejected every role that was not orchestrator|overseer with exit 64 — while the skill
55
+ # mandates a context watcher on EVERY spawned seat. The rule and its own instrument disagreed, so the
56
+ # rule was unsatisfiable for surgeons, consults and every auxiliary seat, and the gap read as coverage.
57
+ # Any role slug names a tier; validate the SHAPE (a tier name reaches a shell) and never the membership.
48
58
  case "$ROLE" in
49
- orchestrator|overseer) TIER="${ROLE}-context" ;;
50
- *) echo "watch-context.sh: unknown seat role '$ROLE' — expected orchestrator or overseer" >&2; exit 64 ;;
59
+ *[!a-zA-Z0-9_-]*|'')
60
+ echo "watch-context.sh: role '$ROLE' must be a non-empty slug of [a-zA-Z0-9_-]" >&2; exit 64 ;;
51
61
  esac
62
+ TIER="${ROLE}-context"
63
+
52
64
 
53
65
  # The supervision beat interval (SUPERVISION_BEAT_MS = 10s). The loop ticks at the beat cadence or the
54
66
  # caller's poll, whichever is SHORTER: a beat may only follow a successful read (rule 2), so the read
@@ -57,9 +69,33 @@ BEAT_EVERY=5
57
69
  TICK=$(( POLL < BEAT_EVERY ? POLL : BEAT_EVERY ))
58
70
  [ "$TICK" -ge 1 ] 2>/dev/null || TICK=1 # a zero or junk poll would spin, not watch
59
71
  SEAT="$TARGET"
72
+ # ⚠ THE OTHER HALF OF OBS-730 LIVES IN THE PRODUCT, NOT HERE. `tickmarkr beat` enforces its own CLOSED
73
+ # tier set (`src/run/supervision.ts:45`), so widening only this script would let an auxiliary seat's
74
+ # watcher run while every beat failed SILENTLY — armed-looking, tier nonexistent. That is strictly worse
75
+ # than the exit 64 it replaced, and it is the precise failure this whole file exists to prevent.
76
+ # So PROBE ONCE and be loud about the answer. Watching still has real value without a tier — the warn,
77
+ # act and blind lines all still fire — but it must never be mistaken for registered supervision.
60
78
 
61
- beat() { tickmarkr beat "$TIER" --seat "$SEAT" >/dev/null 2>&1; }
62
- stand_down() { tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&1; }
79
+ # ⚠ THE OTHER HALF OF OBS-730 LIVES IN THE PRODUCT, NOT HERE. `tickmarkr beat` enforces its own CLOSED
80
+ # tier set (`src/run/supervision.ts:45`), so widening only this script would let an auxiliary seat's
81
+ # watcher run while every beat failed SILENTLY — armed-looking, tier nonexistent, which is strictly
82
+ # WORSE than the exit 64 it replaced and is the precise failure this file exists to prevent.
83
+ # The refusal is reported by the FIRST REAL BEAT rather than by a startup probe: a probe that beats
84
+ # would emit one before any successful read and break rule 2 outright, and a probe that parses the
85
+ # usage banner binds this script to another command's help text. Beating is what we do anyway, so it
86
+ # perturbs nothing — and because a beat only ever follows a successful read, rule 2 still holds.
87
+ beat_refused=0
88
+ beat() {
89
+ tickmarkr beat "$TIER" --seat "$SEAT" >/dev/null 2>&1 && return 0
90
+ if [ "$beat_refused" -eq 0 ]; then
91
+ beat_refused=1
92
+ echo "TIER_UNREGISTERED ${TIER} — the product refused this tier (its set: src/run/supervision.ts:45)"
93
+ echo " watching CONTINUES and every warn/act/blind line below is real"
94
+ echo " but \`tickmarkr status\` will NOT show this seat as covered — never read that absence as safe"
95
+ fi
96
+ return 0
97
+ }
98
+ stand_down() { tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&1; return 0; }
63
99
  # EVERY terminal exit — act, unsafe-act, cap — leaves through here, so none of them can forget to
64
100
  # record the hand-off. A killed watcher never runs it, which is the one case that must read STALE.
65
101
  trap stand_down EXIT
@@ -108,13 +144,35 @@ act_on() {
108
144
 
109
145
  warned=0
110
146
  elapsed=0
147
+ blind=0 # seconds in the current unreadable spell (OBS-739)
148
+ blind_alarmed=0
149
+ BLIND_ALARM_S="${TKR_BLIND_ALARM_S:-120}"
111
150
  while [ "$elapsed" -lt "$CAP" ]; do
112
151
  P=$(context_pct)
113
152
  if [ -z "$P" ]; then
114
153
  # Rule 2: no reading, no beat. The tier ages to STALE and a supervisor comes looking, which is the
115
154
  # truth about a watcher that cannot see the seat it was armed on.
155
+ #
156
+ # Rule 5 (OBS-739): ALARM ON THE BLIND READ. Ageing a tier is not enough — it is only visible to
157
+ # someone already reading the beat table, and the measured failure was a watcher ALIVE AND BLIND for
158
+ # hours because the run's own status text pushed the percentage off the statusline. An instrument
159
+ # that cannot report its own absence is worse than none: the seat believes it has coverage and stops
160
+ # looking. So say so on stdout, where the supervising seat is actually woken. Once per blind spell,
161
+ # not every tick — a repeating alarm trains the reader to ignore it.
162
+ blind=$((blind + TICK))
163
+ if [ "$blind" -ge "$BLIND_ALARM_S" ] && [ "$blind_alarmed" -eq 0 ]; then
164
+ blind_alarmed=1
165
+ echo "CONTEXT_BLIND $TARGET — no percentage readable for ${blind}s; tier ${TIER} is ALIVE AND BLIND"
166
+ echo " this watcher is NOT providing coverage: read the seat by hand and re-arm on a clear statusline"
167
+ echo " do NOT substitute the token total — '∑ Nk tok' is cumulative SPEND, not context fill"
168
+ fi
116
169
  sleep "$TICK"; elapsed=$((elapsed + TICK)); continue
117
170
  fi
171
+ # a successful read closes the blind spell and re-arms the alarm for the next one
172
+ if [ "$blind_alarmed" -eq 1 ]; then
173
+ echo "CONTEXT_BLIND_CLEARED $TARGET — percentage readable again at ${P}% after ${blind}s blind"
174
+ fi
175
+ blind=0; blind_alarmed=0
118
176
  beat
119
177
 
120
178
  if [ "$P" -ge "$ACT" ] 2>/dev/null; then