instar 1.3.654 → 1.3.656

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 (42) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +40 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/LiveTestHarness.d.ts +30 -1
  5. package/dist/core/LiveTestHarness.d.ts.map +1 -1
  6. package/dist/core/LiveTestHarness.js +33 -0
  7. package/dist/core/LiveTestHarness.js.map +1 -1
  8. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  9. package/dist/core/devGatedFeatures.js +6 -0
  10. package/dist/core/devGatedFeatures.js.map +1 -1
  11. package/dist/core/rateLimitFalsePositiveMatrix.d.ts +28 -0
  12. package/dist/core/rateLimitFalsePositiveMatrix.d.ts.map +1 -0
  13. package/dist/core/rateLimitFalsePositiveMatrix.js +74 -0
  14. package/dist/core/rateLimitFalsePositiveMatrix.js.map +1 -0
  15. package/dist/core/types.d.ts +13 -0
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/monitoring/CompactionSentinel.d.ts +9 -0
  19. package/dist/monitoring/CompactionSentinel.d.ts.map +1 -1
  20. package/dist/monitoring/CompactionSentinel.js +8 -0
  21. package/dist/monitoring/CompactionSentinel.js.map +1 -1
  22. package/dist/monitoring/PresenceProxy.d.ts +14 -0
  23. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  24. package/dist/monitoring/PresenceProxy.js +103 -11
  25. package/dist/monitoring/PresenceProxy.js.map +1 -1
  26. package/dist/monitoring/QuotaCollector.d.ts.map +1 -1
  27. package/dist/monitoring/QuotaCollector.js +21 -8
  28. package/dist/monitoring/QuotaCollector.js.map +1 -1
  29. package/dist/monitoring/RateLimitSentinel.d.ts +25 -0
  30. package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
  31. package/dist/monitoring/RateLimitSentinel.js +44 -0
  32. package/dist/monitoring/RateLimitSentinel.js.map +1 -1
  33. package/dist/monitoring/ResumeQueue.d.ts +17 -2
  34. package/dist/monitoring/ResumeQueue.d.ts.map +1 -1
  35. package/dist/monitoring/ResumeQueue.js +19 -2
  36. package/dist/monitoring/ResumeQueue.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/data/builtin-manifest.json +2 -2
  39. package/upgrades/1.3.655.md +29 -0
  40. package/upgrades/1.3.656.md +68 -0
  41. package/upgrades/side-effects/false-ratelimit-recovery-completed-sessions.md +113 -0
  42. package/upgrades/side-effects/honest-session-state-surfaces.md +185 -0
@@ -0,0 +1,113 @@
1
+ # Side-Effects Review — Fix false rate-limit/error recovery on finished sessions + user-channel proof harness
2
+
3
+ **Version / slug:** `false-ratelimit-recovery-completed-sessions`
4
+ **Date:** `2026-06-24`
5
+ **Author:** Echo (autonomous, 8-hour run)
6
+ **Spec:** `docs/specs/false-ratelimit-recovery-completed-sessions.md` (review-convergence + approved)
7
+ **Second-pass reviewer:** REQUIRED (touches "sentinel"/"guard"/session-recovery) — verdict appended below.
8
+
9
+ ## Summary of the change
10
+
11
+ A finished/idle session sitting at a prompt with a stale throttle string in its
12
+ scrollback was mistaken for a live-but-throttled session, so `RateLimitSentinel` (and
13
+ its sibling `CompactionSentinel`) ran a futile recovery and spammed the user with
14
+ `RATE_LIMIT_RESUME_NUDGE` ("the temporary server throttle should have cleared…").
15
+ Fleet-wide (shared detector). Fix makes a terminal session structurally incapable of
16
+ being a recovery target, and adds a reusable test capability that catches spurious
17
+ background messages before deploy.
18
+
19
+ Files modified:
20
+ - `src/monitoring/RateLimitSentinel.ts` — new optional `isSessionRecoverable?` dep,
21
+ consulted in `report()` (no-op + no notice for a non-recoverable session),
22
+ `attemptResume()` and `verify()` (silent `abort()` if the session finished mid-flight
23
+ — new `rate-limit:aborted` event; `abort()` keeps the `recentReports` dedupe entry as
24
+ a flap cooldown).
25
+ - `src/monitoring/CompactionSentinel.ts` — the SAME `isSessionRecoverable?` guard in
26
+ `report()`.
27
+ - `src/commands/server.ts` — defines `isSessionRecoverable = (name) =>
28
+ sessionManager.listRunningSessions().some(...)`; passes it to BOTH sentinels; adds a
29
+ `sessionComplete` handler that clears both sentinels (the completion cleanup that had
30
+ zero callers).
31
+ - `src/monitoring/QuotaCollector.ts` — the OAuth-429 `DegradationReport` now fires only
32
+ when the 3-strike breaker trips (kills the `retry-after:0` log spam); the error is
33
+ still recorded in `errors[]`, breaker + quota accounting unchanged.
34
+ - `src/core/LiveTestHarness.ts` — the prevention layer: an optional
35
+ `ChannelDriver.collectMessages` + a scenario `absenceWindowMs` + `expect.noMessageMatching`
36
+ / `expect.replyMustNotContain`. An absence scenario collects every channel message over
37
+ the window and FAILs if any matches; an unsupported driver yields BLOCKED (never a
38
+ silent pass).
39
+
40
+ Files added:
41
+ - `src/core/rateLimitFalsePositiveMatrix.ts` — the rate-limit user-role scenario matrix
42
+ (happy-path reply + absence regression, optional Slack parity).
43
+ - `tests/unit/RateLimitSentinel-recoverable-guard.test.ts`,
44
+ `tests/unit/CompactionSentinel-recoverable-guard.test.ts`,
45
+ `tests/unit/rate-limit-false-positive-matrix.test.ts`,
46
+ `tests/unit/sentinel-recoverable-wiring.test.ts`,
47
+ `tests/integration/rate-limit-false-positive-prevention.test.ts`.
48
+ - Spec + ELI16 + convergence report.
49
+
50
+ ## Decision-point inventory
51
+
52
+ - **Added** `isSessionRecoverable` guard — a bounded NO-OP that SUPPRESSES a recovery
53
+ action; it never adds an authoritative block over a user. Signal-vs-authority: it
54
+ removes a spurious action, the safe direction.
55
+ - **Added** harness absence assertion → a PASS/FAIL SIGNAL the already-dark, dry-run-
56
+ default `LiveTestGate` consumes. BLOCKED (not silent-pass) when unverifiable. No new
57
+ blocking authority.
58
+ - **Modified** `QuotaCollector` 429 path — a LOG-LEVEL decision only (report vs not);
59
+ accounting + breaker untouched.
60
+
61
+ ## Side effects & blast radius
62
+
63
+ - **Behavior change (intended):** a finished/killed session no longer receives recovery
64
+ nudges; a recovery whose session ends mid-flight aborts silently (no escalation ping).
65
+ - **Preserved:** a genuinely-throttled RUNNING session still recovers — the notify
66
+ templates are byte-for-byte unchanged; dep-absent installs (bare/test) behave exactly
67
+ as before (regression-locked by tests).
68
+ - **Fail direction:** `listRunningSessions()` fails OPEN on a transient tmux error, so a
69
+ hiccup cannot drop a live session and suppress a real recovery; only genuine
70
+ termination removes it.
71
+ - **Machine-local:** each machine runs its own sentinels over its own running set; no
72
+ cross-machine state introduced. The QuotaCollector poll is already per-machine.
73
+ - **Residual (scoped to CMT-1785):** a still-RUNNING idle session with stale throttle
74
+ scrollback can still trigger one self-correcting "back online" message (not the
75
+ 6-nudge spam). The known fix (adopt the watchdog `evaluateThrottleSettle` settle-gate
76
+ on the idle path) is tracked, driven evidence-first by the new absence harness.
77
+
78
+ ## Migration parity
79
+
80
+ None required. All changes are server-internal wiring + a log-level change — no
81
+ `.claude/settings.json` hook, `.instar/config.json` default, CLAUDE.md template, or
82
+ built-in-skill change ships. Existing agents pick this up on the normal code-update path.
83
+
84
+ ## Rollback
85
+
86
+ Each fix is independently revertible: F1 is additive (dep-absent = old behavior); F2 is
87
+ a pure cleanup addition; F4 is log-level only; the harness extension is additive (the
88
+ absence path only runs when a scenario sets `absenceWindowMs`).
89
+
90
+ ## Tests
91
+
92
+ 16 unit (guards both-sides + verify/abort lifecycle + harness absence + matrix + wiring-
93
+ integrity) + 2 integration (harness → signed artifact → `LiveTestGate` ALLOW on clean /
94
+ VETO on a reintroduced regression). No regression across the existing sentinel (96),
95
+ quota (53), and harness/gate (30) suites; clean `tsc`.
96
+
97
+ ---
98
+
99
+ ## Second-pass review
100
+
101
+ **Concur with the review.** Independent audit of the diff verified: the
102
+ `isSessionRecoverable` guard uses running-set membership (the correct criterion) and
103
+ genuinely fails OPEN via `isSessionAlive`'s catch-returns-true path, so no genuine
104
+ recovery is suppressed by a transient tmux error — only actual termination removes a
105
+ session. The `verify()`/`attemptResume()` abort returns BEFORE any user-facing
106
+ `notify()` (no escalation/"still throttled" ping for a finished session); `abort()`
107
+ correctly keeps `recentReports` as the flap cooldown. The `sessionComplete` cleanup is
108
+ best-effort try/catch and cannot break completion. The QuotaCollector change preserves
109
+ the 429 signal in `errors[]` + the breaker, suppressing only transient per-poll log
110
+ spam (no other consumer keys on that feature). The notify templates are byte-for-byte
111
+ unchanged (a genuinely-throttled RUNNING session recovers as before). The harness
112
+ returns BLOCKED (never a silent pass) when absence is unverifiable. All 322 relevant
113
+ tests green. — independent second-pass reviewer, 2026-06-24.
@@ -0,0 +1,185 @@
1
+ # Side-Effects Review — Honest Session-State Surfaces (Tier1/Tier2 standby honesty + paused-queue notice correctness)
2
+
3
+ **Version / slug:** `honest-session-state-surfaces`
4
+ **Date:** `2026-06-24`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `general-purpose (independent Phase-5 diff review)`
7
+
8
+ ## Summary of the change
9
+
10
+ Two small honesty fixes to the standby/reap user-facing surfaces, both signal-only.
11
+ **Finding (b)** lifts the existing Tier-3 honest stuck-state classifier
12
+ (`classifyStuckSignature` — rate-limited / policy-wedge / context-wedge /
13
+ context-too-long) into PresenceProxy **Tier 1** and **Tier 2** so a live-but-failing
14
+ session is reported with its REAL reason at the 20s / 2-minute marks instead of
15
+ "actively working". It is gated behind a dev-dark flag
16
+ `monitoring.standbyHonestyTiers.enabled` (OMITTED from ConfigDefaults → resolved via
17
+ `resolveDevAgentGate`; live on a dev agent, dark on the fleet). **Finding (c)** fixes a
18
+ false "A restart is queued — I'll bring it back" claim when the ResumeQueue is paused:
19
+ it adds a NEW `ResumeQueue.hasClaimableQueuedEntryFor` (= `hasLiveQueuedEntryFor && !isPaused()`)
20
+ and re-points ONLY the ReapNotifier copy consumer at it, leaving the paused-blind
21
+ `hasLiveQueuedEntryFor` (ownership) for the PromiseBeacon I2 double-spawn guard. Files:
22
+ `src/monitoring/PresenceProxy.ts`, `src/commands/server.ts`, `src/core/types.ts`,
23
+ `src/core/devGatedFeatures.ts`, `src/monitoring/ResumeQueue.ts`, plus 4 test files.
24
+
25
+ ## Decision-point inventory
26
+
27
+ - `PresenceProxy Tier 1 / Tier 2 standby copy` — modify — flag-ON substitutes the honest stuck reason (or suppresses the message under one-voice) for "working" copy; flag-OFF byte-identical.
28
+ - `ReapNotifier "restart is queued" line (via ResumeQueue predicate)` — modify — now reads claimability (false while paused) instead of ownership; no flag, pure correctness.
29
+ - `PromiseBeacon I2 double-spawn coordination guard (server.ts:11980)` — pass-through — still reads the unchanged paused-blind `hasLiveQueuedEntryFor` (ownership). Deliberately UNCHANGED.
30
+
31
+ ---
32
+
33
+ ## 1. Over-block
34
+
35
+ The change has no block/allow surface — it changes message TEXT and suppresses a
36
+ specific false claim. (b) The honest pre-check can only substitute the standby message
37
+ string or suppress it under one-voice ownership; it never blocks a session, a send, or
38
+ a tier. (c) Suppressing the "restart is queued" copy while paused omits one notice LINE,
39
+ never a whole notice and never a queue entry. "No block/allow surface — over-block not applicable."
40
+
41
+ ---
42
+
43
+ ## 2. Under-block
44
+
45
+ Not applicable (no block surface). The honest classifier is inherited as-is (tail-gated,
46
+ same false-positive profile as the established Tier-3 usage — the early tiers do not
47
+ widen its input surface). The claimability predicate is a strict conjunction over the
48
+ already-shipped ownership predicate, so it cannot pass a case the ownership predicate
49
+ already rejects. "No block/allow surface — under-block not applicable."
50
+
51
+ ---
52
+
53
+ ## 3. Level-of-abstraction fit
54
+
55
+ Right layer in both. (b) reuses the existing classifier (a detector) and feeds the
56
+ existing standby-message emission path — it adds NO new detector and NO new parse
57
+ surface; recovery stays with the Tier-3 block and the sentinels (the early tiers only
58
+ REPORT or stay silent). (c) adds a sibling read accessor on the store that already owns
59
+ the queue state (`ResumeQueue`) rather than re-deriving pause state in the caller — the
60
+ authority over "is this claimable?" lives with the queue.
61
+
62
+ ---
63
+
64
+ ## 4. Signal vs authority compliance
65
+
66
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
67
+
68
+ - [x] No — this change produces a signal (a message string / a boolean read) consumed by existing smart gates and emission paths; it holds NO new block authority.
69
+
70
+ (b) only alters which message string is sent, or sends none when a recovery sentinel
71
+ owns the voice — it NEVER gates scheduling (`scheduleTier` runs in every branch),
72
+ initiates recovery, spends, or egresses. Every uncertainty (no snapshot, classifier
73
+ returns null/throws, recovery owner present) fails toward today's behavior. (c) is a
74
+ read-accessor correctness fix; the I2 guard's blocking decision still reads the
75
+ unchanged ownership predicate, so the only coordination authority in play is untouched.
76
+
77
+ ---
78
+
79
+ ## 5. Interactions
80
+
81
+ - **Shadowing:** (b) The honest pre-check is placed AFTER the existing quota
82
+ short-circuit (`detectQuotaExhaustion`) and AFTER the idle/finished checks in both
83
+ fireTier1 and fireTier2, so it never shadows the quota or "agent finished" paths
84
+ (quota panes — the rate-limit form — are still handled by the pre-existing quota
85
+ short-circuit, which the lift sits below; the lift's incremental value is the wedge
86
+ set quota does not catch). It is placed BEFORE the LLM block, so a stuck session never
87
+ reaches the LLM "working" summary.
88
+ - **Double-fire:** (c) The whole point — the I2 guard keeps deferring to the queue while
89
+ paused (reads ownership, still true), so a PromiseBeacon escalation cannot double-spawn
90
+ a revive for a paused-frozen topic. A naïve single-predicate paused-aware edit would
91
+ have re-opened exactly that double-spawn; the split predicate prevents it.
92
+ - **Races:** No new shared state. (b) reuses the existing per-topic PresenceState and the
93
+ same persist/schedule tail. (c) reads `ResumeQueue` state through its existing accessors.
94
+ - **Feedback loops:** None — message text and a boolean read have no feedback into the systems they describe.
95
+
96
+ ---
97
+
98
+ ## 6. External surfaces
99
+
100
+ - Other agents: none.
101
+ - Install base: (b) ships dark on the fleet (dev-gate) → no fleet behavior change until a
102
+ deliberate flip; (c) ships live but only removes a false claim line.
103
+ - External systems: the honest standby message and the reap notice both flow through the
104
+ EXISTING `sendMessage` / Telegram formatter+escape path — no new outbound surface, no raw send.
105
+ - Persistent state: none changed. ResumeQueue entries are untouched (paused entries
106
+ remain queued and revive normally on unpause).
107
+ - **Operator surface (Mobile-Complete Operator Actions):** No operator-facing actions —
108
+ both surfaces are passive notifications the agent emits; there is nothing for the
109
+ operator to do/tap. Not applicable.
110
+
111
+ ---
112
+
113
+ ## 6b. Operator-surface quality (Operator-Surface Quality standard)
114
+
115
+ No operator surface — not applicable. This change touches no dashboard renderer/markup
116
+ file, no approval page, and no grant/revoke/secret-drop form. It alters two passive
117
+ agent-emitted notification strings (a 🔭 standby line and a 🪦 reap-notice line); there
118
+ is no operator form, button, or page in the diff.
119
+
120
+ ---
121
+
122
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
123
+
124
+ **machine-local BY DESIGN** — both surfaces are per-machine-local honesty surfaces with a
125
+ concrete reason each:
126
+
127
+ - **(b)** `PresenceProxy` already runs under the WS3 one-voice speaker election (only the
128
+ topic's OWNER machine speaks 🔭). The honest classifier reads the LIVE LOCAL tmux pane of
129
+ a session running on THIS machine — there is no remote session to classify, so it is
130
+ correctly machine-local: the machine serving the topic reports honestly about the session
131
+ it is actually running. User-facing notice → one-voice gating is already inherited from
132
+ the existing PresenceProxy speaker election (the pre-check sits inside that gate; the
133
+ SUPPRESS branch also honors the recovery-owner one-voice rule).
134
+ - **(c)** `ResumeQueue` is a durable PER-MACHINE queue (it holds a host-local lock
135
+ precisely to forbid two machines sharing its state). Its `paused` flag and entries are
136
+ machine-local, and the reap notice is emitted by the machine that reaped the session, so
137
+ the paused-guard is correctly local — it suppresses the claim on the only machine that
138
+ could (or could not) honor it. No durable state strands on transfer (entries are
139
+ untouched); no URLs are generated.
140
+
141
+ No `multiMachine.*` config, no replicated-store work, no pool routes touched.
142
+
143
+ ---
144
+
145
+ ## 8. Rollback cost
146
+
147
+ - **Hot-fix release:** pure code change — revert and ship a patch. (b) is additionally
148
+ flag-gated, so disabling `monitoring.standbyHonestyTiers.enabled` (or running on the
149
+ fleet, where it is already dark) reverts Tier 1/2 to byte-identical-to-today wording
150
+ without a code change.
151
+ - **Data migration:** none — no persistent state added or changed.
152
+ - **Agent state repair:** none — no agent needs notification or reset.
153
+ - **User visibility:** none during rollback — reverting (b) restores "actively working";
154
+ reverting (c) restores the (incorrect) "restart is queued" claim, a cosmetic regression
155
+ only, with no behavioral loss.
156
+
157
+ ---
158
+
159
+ ## Conclusion
160
+
161
+ This review produced no design changes — the spec's convergence had already resolved the
162
+ two load-bearing constraints this review re-verifies: (b) scheduling is never gated (the
163
+ honest pre-check substitutes the message string or suppresses it, then falls through to
164
+ the existing scheduling tail in every branch, including SUPPRESS), and (c) the shared
165
+ `hasLiveQueuedEntryFor` stays paused-blind for the I2 double-spawn guard while a NEW
166
+ claimability predicate feeds only the user-facing copy. The 3-tier test suite pins both
167
+ sides of each boundary, including the I2-guard regression assertion (ownership survives a
168
+ pause) and the no-leak contract at the new Tier1/2 callsite. Clear to ship.
169
+
170
+ ---
171
+
172
+ ## Second-pass review (if required)
173
+
174
+ **Reviewer:** general-purpose (independent Phase-5 diff review)
175
+ **Independent read of the artifact: concur**
176
+
177
+ The independent reviewer read the real staged diff and the surrounding source (the full
178
+ `fireTier1`/`fireTier2` methods, `maybeStuckMessage`, `ResumeQueue.hasLiveQueuedEntryFor`
179
+ / `hasClaimableQueuedEntryFor` / `isPaused`, and server.ts lines 7219/7244/7501/11980),
180
+ checking specifically: (1) the next tier is scheduled in EVERY branch of the new
181
+ pre-check (string / SUPPRESS / null); (2) the SUPPRESS branch sends no "working"
182
+ fallback; (3) the pre-check sits after the quota/idle short-circuits; (4)
183
+ `hasLiveQueuedEntryFor` is left paused-blind for the I2 guard; (5) only the ReapNotifier
184
+ copy reads the claimable predicate; (6) the no-leak contract holds; (7) no control-flow
185
+ bug from the fireTier2 brace restructuring. Findings are recorded below.