instar 1.3.345 → 1.3.347

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 (28) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +8 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  5. package/dist/core/PostUpdateMigrator.js +21 -0
  6. package/dist/core/PostUpdateMigrator.js.map +1 -1
  7. package/dist/monitoring/PresenceProxy.d.ts +8 -0
  8. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  9. package/dist/monitoring/PresenceProxy.js +28 -9
  10. package/dist/monitoring/PresenceProxy.js.map +1 -1
  11. package/dist/monitoring/RateLimitSentinel.d.ts +5 -2
  12. package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
  13. package/dist/monitoring/RateLimitSentinel.js.map +1 -1
  14. package/dist/monitoring/StuckSignatureClassifier.d.ts +52 -0
  15. package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -0
  16. package/dist/monitoring/StuckSignatureClassifier.js +133 -0
  17. package/dist/monitoring/StuckSignatureClassifier.js.map +1 -0
  18. package/dist/monitoring/sentinelWiring.d.ts +13 -4
  19. package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
  20. package/dist/monitoring/sentinelWiring.js +10 -8
  21. package/dist/monitoring/sentinelWiring.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/data/builtin-manifest.json +19 -19
  24. package/upgrades/1.3.347.md +76 -0
  25. package/upgrades/honest-turn-receipts.eli16.md +47 -0
  26. package/upgrades/rate-limit-resume-nudge-internal-channel.eli16.md +35 -0
  27. package/upgrades/side-effects/honest-turn-receipts.md +90 -0
  28. package/upgrades/side-effects/rate-limit-resume-nudge-internal-channel.md +102 -0
@@ -0,0 +1,90 @@
1
+ # Side-Effects Review — Honest Turn-Receipts
2
+
3
+ **Version / slug:** `honest-turn-receipts`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `Echo (instar-dev agent, session-robustness topic — Justin: "finish out item number four" + the "conversation too long is noise" observation)`
6
+ **Second-pass reviewer:** `self-adversarial pass over false-positive surface (the one way an honest classifier can become a new liar) + double-messaging`
7
+
8
+ ## Summary of the change
9
+
10
+ A pure tail-gated `StuckSignatureClassifier` recognizes a live-but-failing
11
+ session (rate-limited / policy-wedge / context-wedge / context-too-long) from
12
+ its LIVE tmux tail and returns an honest user-facing message. PresenceProxy's
13
+ `fireTier3` runs it after the existing quota check and before the process-tree
14
+ "working" assessment, surfacing the real reason instead of "working", deferring
15
+ to an owning recovery sentinel, and preserving the context-exhaustion recovery
16
+ path. The un-tail-gated context-exhaustion block it replaces was the source of
17
+ the "conversation too long" noise. Files: `StuckSignatureClassifier.ts` (new),
18
+ `PresenceProxy.ts`, `server.ts` wiring, `PostUpdateMigrator.ts` section, tests.
19
+
20
+ ## Decision-point inventory
21
+
22
+ - Stuck classification — **add (detector)** — signal only; tail-gated.
23
+ - fireTier3 placement (after quota, before process-tree) — **modify** — the
24
+ exact seam where a live process forced the "working" lie.
25
+ - context-exhaustion block — **replace** — same recovery path, now tail-gated
26
+ (fixes the noise) and folded into the unified honest block.
27
+ - `isStuckRecoveryActive` deference — **add (suppression)** — net reduction in
28
+ messaging authority (yields to an owning sentinel).
29
+
30
+ ## 1. Over-block
31
+
32
+ Nothing is blocked — no authority added. The honest message can only fire at
33
+ tier-3 (5 minutes of an unanswered user message), so a session that replied
34
+ promptly never reaches it. The deference callback REMOVES a double-message case
35
+ (standby + sentinel both speaking).
36
+
37
+ ## 2. Under-block (false-positive surface — the real risk)
38
+
39
+ The danger of an honest classifier is becoming a NEW liar (saying "stuck" about
40
+ a healthy session). Defenses, each tested:
41
+ - **Tail-gating:** the signature must be the live tail, not scrollback — kills
42
+ the stale "conversation too long" mention (the reported noise) and a session
43
+ that quoted an error then kept working.
44
+ - **Prose-vs-block rate-limit patterns:** "you've hit your limit" / "limit ·
45
+ resets" match; "when you hit your usage limit, the session pauses" does not.
46
+ - **AUP repetition gate** (inherited from classifyWedgeTail): a single benign
47
+ policy rejection is not a wedge.
48
+ - **Normal-compaction suppression:** the compaction lifecycle banner is not
49
+ treated as context-too-long.
50
+ Residual under-block: a stuck session whose signature the provider rewords
51
+ escapes — inherent to signature matching; the tier-3 LLM path still runs as the
52
+ fallback for the unmatched case (unchanged).
53
+
54
+ ## 3. Level-of-abstraction fit
55
+
56
+ The classifier is a pure module beside the sentinels whose detectors it reuses
57
+ (`classifyWedgeTail`). The wiring lives in `fireTier3` (which owns the
58
+ assessment) and reuses the existing `sendProxyMessage` surface and the composed
59
+ `wedgeRecoveryActive` checker (same one the SessionReaper veto uses). No new
60
+ message path, no new state store.
61
+
62
+ ## 4. Signal vs authority compliance
63
+
64
+ **Required reference:** `docs/signal-vs-authority.md`
65
+
66
+ - [x] Pure signal. The classifier decides nothing; it answers a question.
67
+ Recovery is unchanged (the sentinels'). The only behavioral delta is WHICH
68
+ honest string the standby sends, plus a new SUPPRESSION (deference). No new
69
+ decision-maker, and a net reduction in messaging authority.
70
+
71
+ ## 5. Interactions
72
+
73
+ - **Quota-exhaustion block (runs first):** owns the bare usage-limit form; the
74
+ honest classifier is the backstop for the forms it misses + the wedges +
75
+ tail-gated context-too-long. No double-handling (the first to match returns).
76
+ - **RateLimitSentinel:** already suppresses ALL tiers via
77
+ `hasActiveRateLimitRecovery` (unchanged); the honest rate-limit branch is the
78
+ backstop when no sentinel owns it (e.g. the cross-machine EXO case).
79
+ - **ContextWedgeSentinel:** when it is mid-recovery, `isStuckRecoveryActive`
80
+ makes the standby defer; otherwise the honest message is the only voice.
81
+ - **Existing tests:** all 119 presence-proxy tests pass unmodified; the
82
+ context-exhaustion test's source-grep section was updated to the new wiring.
83
+
84
+ ## 6. External surfaces / 7. Rollback
85
+
86
+ No API, no route, no config key (default-on behavioral change; the new config
87
+ field is an optional injected callback wired in server.ts). One idempotent
88
+ CLAUDE.md section (marker 'Honest standby (turn-receipts)'). Rollback = revert;
89
+ the standby returns to classifying live-but-failing sessions as "working" and
90
+ the context-too-long noise returns.
@@ -0,0 +1,102 @@
1
+ # Side-Effects Review — Rate-limit resume nudge routed through internal channel (not the user-message path)
2
+
3
+ **Version / slug:** `rate-limit-resume-nudge-internal-channel`
4
+ **Date:** `2026-06-05`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `independent reviewer subagent`
7
+
8
+ ## Summary of the change
9
+
10
+ The RateLimitSentinel's post-backoff resume nudge ("the throttle should have cleared — please continue where you left off") was injected into the throttled session via `injectTopicNudge`, which prefixes the text with `[telegram:${topicId}]` — the exact wire format of a real inbound user message. The agent therefore could not distinguish the infrastructure's own poke from a message from the user: it answered the nudge conversationally ("no throttle on my end, still rolling") and, because the `[telegram:N]` prefix triggers the mandatory "relay your reply to the user" rule, posted that denial into the topic between the sentinel's own throttle notices — appearing to contradict itself. The change routes the resume nudge through the internal recovery channel (`injectInternalNudge` → `injectInternalMessage`) for ALL sessions, topic-bound or not. The internal path converges on the same low-level `rawInject` (identical un-stick behavior) but carries no user-message prefix, so the agent never mistakes it for the user. The now-dead `injectTopicNudge` method is removed from `RateLimitRecoverySurface` and its `server.ts` wiring. Files: `src/monitoring/sentinelWiring.ts`, `src/commands/server.ts`, plus the two rate-limit recovery test files.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `buildRateLimitRecoveryDeps.resumeFn` (sentinelWiring.ts) — **modify** — was: topic-bound → `[telegram:N]`-prefixed inject; non-topic-bound → internal inject. Now: ALL sessions → internal inject; topic recorded in audit detail only.
15
+ - `RateLimitRecoverySurface.injectTopicNudge` (interface + server.ts wiring) — **remove** — the prefixed-inject path is deleted so the resume nudge cannot use it again.
16
+ - The user-facing notice path (`notifyFn` → `deliverNotice` → Telegram) — **pass-through** — unchanged; the "throttled / back online" heads-ups still post exactly as before.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ No block/allow surface — over-block not applicable. This change selects an injection channel; it neither rejects nor admits inputs.
23
+
24
+ ---
25
+
26
+ ## 2. Under-block
27
+
28
+ No block/allow surface — under-block not applicable. The recovery still fires on every detected throttle; nothing is newly suppressed. The user-facing throttle notices are unchanged.
29
+
30
+ ---
31
+
32
+ ## 3. Level-of-abstraction fit
33
+
34
+ Correct layer. The resume nudge is infrastructure→session communication; the right primitive for that is `injectInternalMessage` (`source: 'sentinel-recovery'`), which exists precisely for "trusted internal nudge that bypasses the topic-prefix requirement" and logs the trusted bypass to the security log. The previous design borrowed the user-message primitive (`injectMessage` with a topic prefix) for InputGuard-provenance convenience, which was the wrong layer — it made an infra signal indistinguishable from user speech. The fix moves the resume to the primitive built for it. No higher-level gate is bypassed; the InputGuard provenance check the old path ran is intentionally not needed for a known-internal nudge (and `injectInternalMessage` records the bypass).
35
+
36
+ ---
37
+
38
+ ## 4. Signal vs authority compliance
39
+
40
+ **Required reference:** docs/signal-vs-authority.md
41
+
42
+ - [x] No — this change has no block/allow surface.
43
+
44
+ The change chooses a delivery channel for a recovery nudge. It holds no blocking authority and adds no brittle gate. It removes an accidental coupling (infra wearing the user's identity), which strictly reduces surface.
45
+
46
+ ---
47
+
48
+ ## 5. Interactions
49
+
50
+ - **Shadowing:** None. The resume nudge and the user-facing notice are separate paths (`resumeFn` vs `notifyFn`); this change touches only `resumeFn`. The notice path is untouched.
51
+ - **Double-fire:** Reduced. Previously the agent burned a full conversational-reply turn answering each nudge (and relayed it). That spurious turn is eliminated — fewer LLM turns hitting the already-throttled account during a fleet-wide throttle.
52
+ - **Races:** No new shared state. `rawInject` resets the same idle-prompt timer it always did; the verify()/JSONL-growth recovery detection is unchanged. The internal path was already the live path for non-topic-bound sessions, so its reliability is proven in production.
53
+ - **Feedback loops:** This CLOSES one. The old behavior was a feedback loop — sentinel poke → agent reply → relayed to topic → contradicts sentinel notice. Removing the user-message framing breaks the loop at its source.
54
+
55
+ ---
56
+
57
+ ## 6. External surfaces
58
+
59
+ - **Other agents / users (install base):** behavior change ships to every instar agent on update. It strictly improves coherence (removes self-contradiction during throttle recovery); no capability is added or removed.
60
+ - **Telegram:** fewer messages posted to topics during a throttle (the spurious "no throttle on my end" replies stop). The intended "throttled / back online" notices are unchanged.
61
+ - **Persistent state:** the audit line in `logs/sentinel-events.jsonl` / recovery records now reads "resume nudge injected via internal recovery channel (topic N)" instead of "via topic". Read-only observability; no consumer parses the prior exact string for control flow.
62
+ - **Timing/runtime:** none introduced. Same inject mechanism, same timers.
63
+
64
+ ---
65
+
66
+ ## 7. Rollback cost
67
+
68
+ Pure code change — revert the two source edits and ship as the next patch. No persistent state, no schema, no migration, no agent-state repair. The only user-visible effect during a rollback window would be the return of the original incoherence, not a hard failure. Recovery itself never depended on the prefix (both channels converge on `rawInject`), so reverting cannot break un-sticking.
69
+
70
+ ---
71
+
72
+ ## Conclusion
73
+
74
+ The review found no block/allow surface and no new authority — the change removes an accidental identity coupling (infrastructure injecting a nudge that looked like a user message) and replaces it with the purpose-built internal channel. It reduces external surface (fewer spurious Telegram messages, fewer wasted LLM turns under throttle) and closes a self-reinforcing feedback loop. The one related site found in the sibling sweep — the compaction-resume re-inject in `server.ts`, which also uses a `[telegram:N]` prefix — is left unchanged because it only fires when there is a genuinely unanswered user message, where a relayed reply is wanted; it is tracked for audit under the new Truthful Provenance standard at JKHeadley/instar#894 rather than altered here. Clear to ship. Regression coverage pins the contract (the resume nudge can never again carry a `[telegram:` prefix).
75
+
76
+ ---
77
+
78
+ ## Second-pass review (if required)
79
+
80
+ **Reviewer:** independent reviewer subagent
81
+ **Independent read of the artifact: CONCUR**
82
+
83
+ The reviewer independently read the diff and the SessionManager injection internals and concurred the fix is correct, minimal, and the artifact is honest:
84
+
85
+ - **Resume parity is real (the load-bearing claim).** `injectInternalNudge` → `injectInternalMessage` (SessionManager.ts:3060) and the old `injectTopicNudge` → `injectMessage` both converge on the identical `rawInject` (3080): same bracketed-paste, same framework-aware Enter (codex double-Enter), same `verifyInjection`/`recordStrandedDraftMarker`. The fix does not weaken un-sticking. The redundant `isSessionAlive` guard inside `injectInternalMessage` is harmless (resumeFn already guards it).
86
+ - **The relay-trigger cannot fire on the new path.** The mandatory "relay your reply" behavior keys strictly on the anchored `^\[telegram:(\d+)` prefix (InputGuard.ts:114; UserPromptSubmit hook regex). The bare nudge text carries no such prefix; the new test asserts `not.toContain('[telegram:')`, pinning the contract.
87
+ - **Audit/recordRecovery preserved.** Collapses to one `recordRecovery(..., ['internal-injection'])`; topic retained in the detail string, verified by the updated unit test.
88
+ - **No dead/broken code.** `injectTopicNudge` fully removed from interface, server.ts wiring, and both test harnesses (grep returns zero hits); typecheck clean.
89
+ - **The "compaction-resume is defensible to leave" judgment is correct, with a concrete reason.** That sibling inject (server.ts:6053-6055) is gated on `lastReal?.fromUser` — it fires only when the most recent real message is a genuinely unanswered user message, where a relayed reply is wanted. The RateLimitSentinel resume has no such gate, which is exactly why a user prefix there manufactured a phantom user turn. Structurally different cases.
90
+ - **No edge-case regression** for InputGuard provenance (the internal path auditably bypasses it via the logged `internal-recovery-injection` event), multi-line handling (nudge is single-line), or codex-vs-claude submit (shared `rawInject`).
91
+
92
+ **Reviewer nits, both resolved:**
93
+ 1. Stale comments that still described the nudge as "topic-tagged so InputGuard accepts it" (RateLimitSentinel.ts:85 + sentinelWiring.ts:329) — **fixed** in this change so no future reader re-introduces the prefix.
94
+ 2. The compaction-resume "leave unchanged" decision must be tracked durably, not left as prose — **filed as JKHeadley/instar#894** (audit it under the new Truthful Provenance standard).
95
+
96
+ ---
97
+
98
+ ## Evidence pointers
99
+
100
+ - Root cause confirmed live in `logs/sentinel-events.jsonl` (2026-06-06 ~04:31Z): a fleet-wide throttle drove ~7 sessions through this recovery, each logging "resume nudge injected via topic".
101
+ - Smoking-gun line: `src/commands/server.ts` `injectTopicNudge: (name, topicId, text) => sessionManager.injectMessage(name, \`[telegram:${topicId}] ${text}\`)` (pre-fix).
102
+ - Tests: `tests/unit/rate-limit-recovery-reachability.test.ts` (incl. new anti-impersonation regression) + `tests/integration/rate-limit-recovery-sentinel-lifecycle.test.ts`; 59 tests green across the rate-limit/sentinel suites.