instar 1.3.340 → 1.3.342

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.
@@ -0,0 +1,76 @@
1
+ # Side-Effects Review — GuardPostureTripwire
2
+
3
+ **Version / slug:** `guard-posture-tripwire`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `Echo (instar-dev agent, session-robustness topic per Justin's "work on more robust ways to handle these scenarios")`
6
+ **Second-pass reviewer:** `self-adversarial pass over alarm fatigue + boot-safety (the two ways a boot-time alarm can go wrong)`
7
+
8
+ ## Summary of the change
9
+
10
+ A boot-time detector compares the resolved guard posture (every
11
+ `monitoring.*` enabled flag + `scheduler.enabled`) against the previous
12
+ boot's persisted snapshot. enabled→disabled → loud log + one aggregated
13
+ `logs/guard-posture.jsonl` row + ONE aggregated HIGH Attention item;
14
+ disabled→enabled → log + breadcrumb only; first boot → baseline only.
15
+ Files: `GuardPostureTripwire.ts` (new), `server.ts` boot wiring,
16
+ `PostUpdateMigrator.ts` CLAUDE.md section, three test files, spec.
17
+
18
+ ## Decision-point inventory
19
+
20
+ - Posture extraction — **add (read-only)** — resolved config in, key/boolean map out; generic convention, no registry.
21
+ - Transition diff — **add (pure)** — intersection-only (shape changes are not flips).
22
+ - Attention emit — **add (signal)** — one aggregated HIGH item per boot-with-disables; dedupe by stable id; absent Telegram → breadcrumb only.
23
+ - Snapshot write order — **deliberate** — baseline advances BEFORE alarms so an emit failure cannot cause repeat alarms.
24
+
25
+ ## 1. Over-block
26
+
27
+ Nothing is blocked — the tripwire has no authority. Worst noise case: an
28
+ operator deliberately disabling a guard gets exactly ONE Attention item at the
29
+ next boot (transition-based; the following boots are silent). A batch flip of
30
+ N guards is ONE item, not N (Bounded Notification Surface).
31
+
32
+ ## 2. Under-block
33
+
34
+ (a) A flip that is reverted BETWEEN boots (off at 2pm, back on at 5pm, no
35
+ restart in between) never trips — acceptable: the guard never actually ran
36
+ disabled. (b) Mid-run flips alarm only at the next boot — that is when config
37
+ takes effect, so the alarm coincides with the guard actually dying; with
38
+ auto-update restarts every ~30 min the window is bounded. (c) Guards that
39
+ don't follow the `enabled` convention (none today) would be invisible —
40
+ the convention IS the contract, documented in the spec.
41
+
42
+ ## 3. Level-of-abstraction fit
43
+
44
+ Lives in `monitoring/` beside the sentinels it watches over; wired in
45
+ server.ts boot exactly like the worktree detector (same emitAttention
46
+ adapter, same placement constraint). The CLAUDE.md knowledge rides the
47
+ existing migrateClaudeMd path with a content-sniff marker.
48
+
49
+ ## 4. Signal vs authority compliance
50
+
51
+ **Required reference:** `docs/signal-vs-authority.md`
52
+
53
+ - [x] Pure signal. Never re-enables, never blocks a boot (every failure path
54
+ degrades into `result.error` + a log line), never edits config. The
55
+ Attention item is the operator's consent surface, not an action.
56
+
57
+ ## 5. Interactions
58
+
59
+ - **Attention flood guard:** a single aggregated item with sourceContext
60
+ `guard-posture-tripwire` — inside every budget; HIGH priority is justified
61
+ (a dark guard is exactly the "user should know" class) and HIGH items are
62
+ never coalesced away.
63
+ - **AttentionQueue dedupe:** stable id (`guard-posture-disabled:<date>:<list>`)
64
+ makes a same-day re-emit a no-op via the existing id-collision path.
65
+ - **No Telegram:** breadcrumb still lands (the worktree-detector fallback
66
+ pattern); boot line says "breadcrumb only".
67
+ - **Snapshot corruption:** degrades to first-boot semantics + self-repairs.
68
+
69
+ ## 6. External surfaces / 7. Rollback
70
+
71
+ New files only: `state/guard-posture.json` (snapshot), `logs/guard-posture.jsonl`
72
+ (append-only history), one CLAUDE.md section (idempotent migration). No API,
73
+ no schema, no config key (default-on by design — a tripwire you can silently
74
+ disable would be the joke version of this feature; disabling it means deleting
75
+ the wiring, which is a reviewed code change). Rollback = revert; the files
76
+ stay as inert history.
@@ -0,0 +1,58 @@
1
+ # Side-Effects Review - pending-inject durability
2
+
3
+ **Version / slug:** `pending-inject-durability`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier 1)`
7
+
8
+ ## Summary of the change
9
+
10
+ Makes in-flight initial-message injects durable across server restarts (finding 8d300555): a JSON record per pending inject under `<stateDir>/state/pending-injects/`, written at spawn, cleared after the inject runs, swept at boot (`SessionManager.recoverPendingInjects`). Alive sessions get re-delivery through the normal readiness path; dead/expired records are reported via DegradationReporter and retired.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `PendingInjectStore` (new) - file-based CRUD; record/clear never throw into the spawn/inject paths.
15
+ - `sweepPendingInjects` (new) - pure decision function: expired → report+clear; dead → report+clear; alive → redeliver (clear on success, keep on failure for next-boot retry).
16
+ - `SessionManager.spawnInteractiveSession` - modify - records before scheduling the ready-wait.
17
+ - `SessionManager.handleReadyAndInject` - modify - clears at both inject points; fallback "succeeded" log reworded to "launched (inject pending)".
18
+ - `SessionManager.recoverPendingInjects` (new public) - boot sweep wired in `server.ts` after `purgeDeadSessions`, fire-and-forget.
19
+ - `StateManager.baseDir` (new getter) - read-only path accessor.
20
+
21
+ ## 1. Duplicate delivery (the chosen failure mode)
22
+
23
+ At-least-once by design: a crash between inject and clear re-delivers on the next boot. A duplicated message into an agent session is recoverable noise (agents already tolerate replays); the alternative (clear-before-inject) recreates the silent drop. Documented in code and ELI16.
24
+
25
+ ## 2. Re-delivery into a moved-on conversation
26
+
27
+ A record older than 6h is expired (reported, never re-injected) — re-typing an hours-old message mid-conversation confuses more than it helps. Within 6h, the target session was spawned FOR that message and has at most been idle; re-delivery is the intended outcome.
28
+
29
+ ## 3. Boot-time load
30
+
31
+ The sweep runs in the background (void + catch), after the dead-session purge, and re-uses the existing per-session readiness waits. Typical population is 0–2 records; the 90s ready-wait per alive record cannot block boot.
32
+
33
+ ## 4. Blast radius
34
+
35
+ Spawn hot path gains one synchronous small-file write (try/caught, non-fatal on failure) and each inject one unlink. No routes, no config, no migrations (state dir is created on demand; absent dir = no pending injects). Existing behavioral/inject/lifecycle suites green (53 tests) plus 13 new.
36
+
37
+ ## 5. Failure modes
38
+
39
+ - Record write fails → warning, spawn proceeds (no new failure mode vs today).
40
+ - Clear fails (non-ENOENT) → warning; stale record re-examined and expired by the next boot sweep.
41
+ - Redeliver fails → record kept, reported, retried next boot (bounded by the 6h expiry — no unbounded loop, P19-compliant).
42
+ - Corrupt record file → skipped with a warning, never bricks the sweep.
43
+
44
+ ## 6. Security
45
+
46
+ Records contain the message text (which may be a bootstrap-file pointer or full message) — same sensitivity class and same directory tree as the existing `telegram-inbound/bootstrap-*.txt` files; no new exposure surface.
47
+
48
+ ## 7. What this does NOT fix (honest scope)
49
+
50
+ The bootstrap-*.txt files themselves are still unswept (consumed implicitly by the session reading them). A dead-session loss is REPORTED but not auto-respawned — the bridge's next-message respawn remains the recovery path; auto-respawn is a candidate follow-up once this slice proves itself.
51
+
52
+ ## 8. CI-found edge (mock construction)
53
+
54
+ E2e suites construct SessionManager with mock StateManager objects lacking `baseDir`; the wiring now falls back to `path.join(config.projectDir, '.instar')` instead of crashing construction. Caught by CI (shard 2/4), reproduced and fixed; the failing e2e (`secret-sync-alive`) verified green locally post-fix.
55
+
56
+ ## 9. no-silent-fallbacks ratchet (+1, justified)
57
+
58
+ The subsystem's defensive catches (record/clear/list + the boot-recovery guard) tripped the no-silent-fallbacks baseline 458→459. None is a silent swallow: record/clear/list warn with full context and carry in-brace `@silent-fallback-ok`; the boot-recovery guard's real failures route to DegradationReporter via `sweepPendingInjects.reportLoss`. Baseline bumped with a precise in-test comment — the PR adds zero unjustified silent fallbacks.