instar 1.2.61 → 1.2.62

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,100 @@
1
+ # Side-effects review — topic-intent auto-capture loop (rung 0)
2
+
3
+ **Scope**: Wire the topic-intent capture loop so the per-topic store actually
4
+ fills from live conversation (closing the "shipped but asleep" gap — the store,
5
+ read routes, and session-start briefing all shipped, but nothing ever invoked
6
+ `ingest()` on a real turn). Adds the adapter-agnostic capture "clerk", broader
7
+ context (rolling summary + established refs) feeding the extractor, cost
8
+ controls, prompt-injection-hardened extraction, the live wiring on the inbound
9
+ message path, and whole-loop observability. Spec:
10
+ `docs/specs/topic-intent-capture-loop.md` (converged iter 3, approved by justin).
11
+
12
+ **Files touched**:
13
+ - `src/core/TopicIntent.ts` — add `CaptureCounters` to `TelemetryCounters`
14
+ (defaulted on read for back-compat) + `defaultCaptureCounters()` +
15
+ `bumpCaptureCounters()` (atomic under the existing per-topic lock). Switch the
16
+ two `withTopicLock` lock-dir removals to `SafeFsExecutor.safeRmdirSync`.
17
+ - `src/core/TopicIntentExtractor.ts` — `createLlmExtractFn` gains an optional
18
+ `onDegrade(reason, topicId)` observability hook; still returns `[]` on every
19
+ degrade path (degrade-safety unchanged).
20
+ - `src/core/TopicIntentCapture.ts` — NEW. The capture step: `isSubstantiveTurn`
21
+ pre-filter (deterministic, fail-open) + canary; `createQueuedIntelligence`
22
+ (queue-backed, subscription-transport); `captureTurn` + `createCaptureLoop`
23
+ (rate-state-owning closure).
24
+ - `src/server/topicIntentRoutes.ts` — NEW `GET /topic-intent/:id/capture-metrics`
25
+ (the whole-loop funnel); `briefing_served` metering on the briefing route;
26
+ `arccheck_fired`/`arccheck_signalled` metering on the arccheck route.
27
+ - `src/server/CapabilityIndex.ts` — add `topic-intent` to `INTERNAL_PREFIXES`
28
+ (operator-only; not a discoverable agent endpoint).
29
+ - `src/config/ConfigDefaults.ts` — `topicIntent.capture.enabled: true` in
30
+ `SHARED_DEFAULTS` (auto-applies on init AND migration → migration parity).
31
+ - `src/core/types.ts` — add optional `topicIntent` to `InstarConfig`.
32
+ - `src/commands/server.ts` — construct the queue-backed extractor + capture loop
33
+ and chain it onto `telegram.onMessageLogged` (preserving prior callbacks),
34
+ gated on `sharedIntelligence && config.topicIntent.capture.enabled`.
35
+ - Tests: `tests/unit/TopicIntentCapture.test.ts`,
36
+ `tests/integration/topic-intent-capture-routes.test.ts`,
37
+ `tests/e2e/topic-intent-capture-lifecycle.test.ts`.
38
+ - `docs/specs/06-state-detector-registry.md` — NEW registry; pre-filter entry.
39
+
40
+ **Under-block**: The pre-filter is fail-open — when unsure it passes the turn to
41
+ the LLM, so it cannot silently swallow a substantive turn on an ambiguous input.
42
+ Its only confident skips are empty/whitespace, whole-message bare acks, and
43
+ agent sentinel/heartbeat lines (agent turns only). Risk of under-block (a real
44
+ turn skipped) is bounded to sentinel-format drift, which the canary guards.
45
+
46
+ **Over-block**: The only "block"-shaped behavior is the pre-filter skip and the
47
+ QuotaTracker shed. Over-skipping costs a missed cheap extraction, never a
48
+ delivery failure or a user-visible block. The canary asserts known substantive
49
+ turns (including ack-prefixed ones) are NOT skipped.
50
+
51
+ **Level-of-abstraction fit**: The capture step is adapter-agnostic — it takes a
52
+ generic `CaptureTurnEntry`, not a Telegram type; Telegram is merely the first
53
+ wiring (other adapters tracked as `cwa-multi-adapter-capture`). The store stays
54
+ the single authority for persistence/projection; the extractor owns extraction;
55
+ the capture helper only orchestrates. Transport is delegated to the injected
56
+ `sharedIntelligence` provider (subscription/REPL-pool) through the shared
57
+ `LlmQueue` — capture never reaches for a raw API client.
58
+
59
+ **Signal vs authority**: Capture only RECORDS (append-only evidence); it has no
60
+ blocking authority. ArcCheck SIGNALS; neither blocks a send. The pre-filter is a
61
+ brittle low-context detector emitting a skip signal, never a gate. This matches
62
+ `[[feedback_signal_vs_authority]]`.
63
+
64
+ **Interactions**:
65
+ - `telegram.onMessageLogged` is a single-assignment property already chained by
66
+ PresenceProxy, human-as-detector, and the keep-watching detector. The capture
67
+ wiring preserves the prior callback (`const before = ...; cb = (e) => { before?.(e); capture(e); }`),
68
+ verified by the e2e chain test (prior callback still fires).
69
+ - Capture is fire-and-forget (`void captureLoop(...)`) so extraction latency
70
+ can never reach the delivery path (acceptance #4).
71
+ - Extraction is admitted on the LlmQueue **background** lane, so it yields to
72
+ interactive (PresenceProxy/PromiseBeacon) work and shares the daily cap. On
73
+ cap breach the queue throws → `createLlmExtractFn` catches → degrades to a
74
+ `degraded_cap_or_error` tick.
75
+ - `bumpCaptureCounters`, `bumpTurn`, and `appendEvidence` each take the per-topic
76
+ lock separately (multiple short acquisitions per turn). Correct under
77
+ concurrency (the concurrency test still passes); accepted minor lock churn for
78
+ v1 since capture runs off the delivery path.
79
+ - The briefing route now has a metering side-effect on a GET (writes
80
+ `briefing_served`). Intentional per spec §10; best-effort and never blocks the
81
+ fetch.
82
+
83
+ **External surfaces**:
84
+ - New endpoint: `GET /topic-intent/:topicId/capture-metrics` (operator-only).
85
+ - New config field: `topicIntent.capture.enabled` (default true; kill-switch).
86
+ - New additive `TopicIntentFile` fields (`telemetry.capture.*`), defaulted on
87
+ read — old files load unchanged.
88
+ - New exported symbols in `TopicIntentCapture.ts`; no breaking change to
89
+ existing exports.
90
+
91
+ **Cost (the one genuinely new ongoing cost)**: This is the product's first
92
+ always-on per-turn LLM path. Bounded by: the deterministic pre-filter (most
93
+ turns never reach the model), a per-topic rate ceiling (30/60s), the LlmQueue
94
+ daily cap (best-effort, per-process), and QuotaTracker load-shedding. ON by
95
+ default is ratified.
96
+
97
+ **Rollback cost**: Low. Config kill-switch `topicIntent.capture.enabled: false`
98
+ makes capture inert immediately (store + routes remain, as today). Full revert:
99
+ drop the server.ts wiring block + the new file; the store/routes/briefing return
100
+ to the inert pre-capture state. Additive store fields are harmless if left.