instar 1.3.798 → 1.3.800

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,123 @@
1
+ # Side-Effects Review — Single-alerts-topic attention routing (kill per-item alert topics)
2
+
3
+ **Version / slug:** `attention-single-topic-routing`
4
+ **Date:** `2026-07-10`
5
+ **Author:** `echo (instar-dev agent)`
6
+ **Second-pass reviewer:** `independent reviewer subagent (see below)`
7
+
8
+ ## Summary of the change
9
+
10
+ `TelegramAdapter.createAttentionItem` gains a routing mode, `attentionRouting.mode`, with the code DEFAULT `'single-topic'`: every attention item — all priorities, HIGH/URGENT included — is posted as one message into the durable "🔔 Attention" hub topic (created at boot by `ensureAgentAttentionTopic`, state key `agent-attention-topic`, resolved live via a new injected `getAttentionHubTopicId` accessor wired at both TelegramAdapter construction sites in `src/commands/server.ts`). No per-item forum topic is ever created in this mode. `'per-item'` restores the legacy behavior byte-for-byte (still shaped by `attentionTopicGuard` + `topicCreationBudget`). If no hub id resolves or the hub send fails, the adapter finds-or-creates the hub once (create-once + in-flight-promise, mirroring the agent-health lane) — never a per-item topic. Hub-routed items are marked `coalesced` and deliberately NOT registered in the per-item topic maps. Files: `src/messaging/TelegramAdapter.ts`, `src/commands/server.ts`, `src/scaffold/templates.ts`, `src/core/PostUpdateMigrator.ts` (CLAUDE.md awareness parity), docs (`STANDARDS-REGISTRY.md`, `attention-topic-flood-guard.md` addendum), and tests across all three tiers. Trigger: operator directive, topic 11960, 2026-07-09 — ~317 junk topics from slow-drip alert sources that never trip the burst guard.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `TelegramAdapter.createAttentionItem` routing branch — **modify** — chooses WHERE an alert surfaces (hub message vs per-item topic); never whether it surfaces.
15
+ - `AttentionTopicGuard` decide() at createAttentionItem — **pass-through** — unreached in single-topic mode; unchanged and load-bearing in legacy mode.
16
+ - `topicCreationGuard` ceiling inside createForumTopic — **pass-through** — unchanged; hub creation uses the exempt `origin:'system', bounded:true` class (fixed cardinality: one hub).
17
+ - Agent-health lane branch — **pass-through** — unchanged; still takes precedence for `lane:'agent-health'` items.
18
+ - `updateAttentionStatus` topic close/reopen — **pass-through** — hub items are absent from `attentionItemToTopic`, so resolving one never closes the shared hub (same mechanism the coalesce path already relies on).
19
+
20
+ ---
21
+
22
+ ## 1. Over-block
23
+
24
+ **What legitimate inputs does this change reject that it shouldn't?**
25
+
26
+ No input is rejected. Every attention item is still accepted, stored, and delivered. The nearest thing to an over-block is a VISIBILITY reduction: a HIGH/URGENT alert no longer gets its own topic (it is a hub message with a priority emoji). That is the deliberate, operator-directed behavior, not a side effect — and the per-item carve-out remains available via the legacy mode.
27
+
28
+ ---
29
+
30
+ ## 2. Under-block
31
+
32
+ **What failure modes does this still miss?**
33
+
34
+ - A deleted-hub + stale-registry edge: if the user deletes the hub topic in Telegram AND the adapter's topic registry still holds the hub name, `findOrCreateForumTopic` can return the dead id; the send fails, the cached id is dropped, and the item remains store-only until the registry entry is corrected (same semantics the agent-health lane already has). No item is lost; delivery degrades to the store + dashboard.
35
+ - The hub can accumulate many messages under a pathological flood (there is no per-hub message rate cap). That is Telegram messages in ONE topic — exactly the surface the directive asks for — and upstream emitters remain bounded by their own dedup/aggregation rules. No issue identified beyond this accepted shape.
36
+
37
+ ---
38
+
39
+ ## 3. Level-of-abstraction fit
40
+
41
+ The change sits at the exact chokepoint where per-item topics were born (`createAttentionItem`), which is the layer the Bounded-Notification-Surface standard names for this class of fix. It does not re-implement any primitive: hub resolution reuses the boot topic + StateManager key; self-heal reuses `findOrCreateForumTopic`; delivery reuses `sendToTopic`. The flood guard stays at its layer for the legacy mode rather than being deleted — the default simply stops reaching it for attention items.
42
+
43
+ ---
44
+
45
+ ## 4. Signal vs authority compliance
46
+
47
+ **Required reference:** docs/signal-vs-authority.md
48
+
49
+ - [x] No — this change has no block/allow surface.
50
+
51
+ The routing branch holds no blocking authority: it decides destination, not permission. Every failure path fails toward delivery (self-heal hub creation) or toward durable recording (attention store) plus a DegradationReporter signal — never toward silent suppression of content.
52
+
53
+ ---
54
+
55
+ ## 5. Interactions
56
+
57
+ - **Shadowing:** the single-topic branch runs AFTER the agent-health lane (lane wins, unchanged) and BEFORE the flood guard (guard unreached in default mode — intended; it still runs in legacy mode). Verified order in `createAttentionItem`.
58
+ - **Double-fire:** no other component posts attention items to the hub; `CrossPlatformAlerts.alertOnTelegram` already targets the same hub via `getAlertTopicId` for a different event class (adapter disconnects) — both are plain messages into one topic, no conflict.
59
+ - **Races:** concurrent first-items share ONE in-flight hub creation via `attentionHubPending` (the same promise-guard pattern as the agent-health lane and flood-notice topics). The `attentionItemToTopic` map is untouched for hub items, so no reverse-map corruption (the exact hazard the coalesce-path comment documents).
60
+ - **Feedback loops:** none — routing output does not feed any input of the attention system.
61
+
62
+ ---
63
+
64
+ ## 6. External surfaces
65
+
66
+ - **Telegram:** user-visible change — alerts appear as messages in the existing "🔔 Attention" topic instead of new topics. This is the requested behavior.
67
+ - **API consumers:** `AttentionItem.topicId` now points at the shared hub and `coalesced: true` is set (the shape the coalesce path already produced); `/attention` routes, dashboard, and pool reads are unchanged. `/ack`-family commands inside the hub topic are no-ops for hub items (as they already were for coalesced items) — management is via `/attention` PATCH / dashboard.
68
+ - **Slack:** verified — the Slack attention surface already routes to the single `slack-attention-channel`; no per-item surfaces exist there. No change.
69
+ - **Operator surface (Mobile-Complete):** no new operator-facing action; no dashboard change required. The dashboard attention tab continues to manage items.
70
+
71
+ ---
72
+
73
+ ## 6b. Operator-surface quality
74
+
75
+ No operator surface touched — not applicable (no dashboard/approval/form files staged).
76
+
77
+ ---
78
+
79
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
80
+
81
+ **Machine-local BY DESIGN, matching the existing attention-topic architecture:** each machine's TelegramAdapter holds its own attention store, and the hub topic id lives in each machine's StateManager under `agent-attention-topic` (the boot path already ensures it per machine; all machines share ONE Telegram chat, and `findOrCreateForumTopic` name-matching keeps them converging on the same hub topic rather than minting one per machine). The pool-wide question ("what needs my attention across machines?") is already answered by the proxied-on-read `GET /attention?scope=pool`, which is unchanged. User-facing notices: this change REDUCES notice surface (one topic); one-voice gating is unaffected because delivery still flows through the same adapter send funnel. No URLs generated; no durable state strands on topic transfer (attention items were never topic-transferable state).
82
+
83
+ ---
84
+
85
+ ## 8. Rollback cost
86
+
87
+ - **Hot-fix release:** pure code-default change — revert and ship a patch, or flip `messaging[].config.attentionRouting = { "mode": "per-item" }` per agent (live rollback lever, no code).
88
+ - **Data migration:** none. No persistent state format changes; hub-routed items reuse the existing `coalesced`/`topicId` fields.
89
+ - **Agent state repair:** none. The CLAUDE.md awareness migration is idempotent text replacement.
90
+ - **User visibility:** rollback restores per-item topics going forward; hub messages already posted stay in the hub (harmless history).
91
+
92
+ ---
93
+
94
+ ## Conclusion
95
+
96
+ The review confirmed the design rides existing, battle-tested patterns (boot hub + injected resolver, create-once promise guard, coalesced-item map exclusion) and holds no blocking authority. One design choice was re-verified against the hazard comment in the coalesce path: hub items must NOT enter the per-item topic maps, or resolving one item would close the shared hub — the implementation and a dedicated unit test pin this. Clear to ship; the legacy mode plus a one-key rollback bound the blast radius.
97
+
98
+ ---
99
+
100
+ ## Second-pass review (if required)
101
+
102
+ **Reviewer:** independent reviewer subagent (general-purpose, fresh context, artifact + diff only)
103
+ **Independent read of the artifact: concur**
104
+
105
+ Verbatim verdict core: "Concur with the review." The reviewer independently verified against the working-tree diff: (1) the no-drop guarantee — the store write is unconditional after `routeToAttentionHub`, whose injected-send, `ensureAttentionHubTopic`, and fallback-send paths are all caught, with store-only degradation carrying a DegradationReporter signal; (2) hub-close immunity including across restart — hub items never enter the per-item maps, and the pre-existing `!item.coalesced` guard in `loadAttentionItems` keeps that true after a reload; (3) bounded self-heal — `findOrCreateForumTopic` with the budget-exempt bounded/system class, same name + label as the boot path, shared in-flight promise; (4) branch order and legacy fidelity — the agent-health lane runs first, and the `'per-item'` path is byte-for-byte unchanged.
106
+
107
+ Three non-blocking observations were raised; ALL THREE were folded into this change rather than deferred: (1) fresh-install race where a self-healed hub could be duplicated by the boot path → `ensureAgentAttentionTopic` now uses `findOrCreateForumTopic` (reuse-by-name, intro message only on a genuinely new topic); (2) the injected accessor was the one call outside a try/catch → now wrapped, making the no-drop claim injector-independent; (3) the migrator's in-place patch left the old "Default-ON" bullet on previously-migrated agents → the patch now rewrites that stale bullet too (covered by a new migrator test).
108
+
109
+ ---
110
+
111
+ ## Evidence pointers
112
+
113
+ - `tests/unit/attention-single-topic-routing.test.ts` — all-priorities hub routing, zero topic creation, hub-close immunity, self-heal (null id + dead hub), legacy byte-for-byte, lane precedence, server.ts wiring (both construction sites).
114
+ - `tests/integration/notification-flood-burst-invariant.test.ts` — SHIPPED DEFAULT burst: 1,000 mixed-priority items → ≤1 topic; legacy-mode guard invariants preserved under `attentionRouting.mode:'per-item'`.
115
+ - `tests/e2e/attention-topic-flood-guard-lifecycle.test.ts` — stock production config (token+chatId only): slow-drip 40 alerts → exactly ONE hub topic; legacy opt-out still capped at budget+1.
116
+
117
+ ---
118
+
119
+ ## Class-Closure Declaration (display-only mirror)
120
+
121
+ No agent-authored-artifact defect — the change alters product-source routing behavior (a code default), not a defect in an LLM prompt, hook, config, skill, or standards text.
122
+
123
+ For the `unbounded-self-action` class (the added diff introduces `sendToTopic`/`findOrCreateForumTopic` emit callsites), the trace carries the explicit NEGATIVE declaration: `{ "defectClass": "unbounded-self-action", "closure": "n/a", "reason": "caller-driven delivery-path routing inside the existing createAttentionItem funnel — not a new self-triggered controller/loop; emission is one hub message per item (replacing one TOPIC per item, a strict reduction), and hub creation is bounded create-once behind an in-flight-promise guard" }`. No new self-triggered controller is added or modified; the controllers that CALL createAttentionItem are unchanged and separately registered.
@@ -0,0 +1,59 @@
1
+ # Side-Effects Review — Codex 0.144 internal-call linger (early-terminal-settle)
2
+
3
+ **Version / slug:** `codex-0144-internal-call-linger`
4
+ **Date:** `2026-07-09`
5
+ **Author:** Echo (autonomous)
6
+ **Tier:** 1 (small, low-risk, adapter-layer-only bug fix; no gating/routing/dev-gate surface)
7
+ **Second-pass reviewer:** self (fresh second pass); reviewer-concurred
8
+
9
+ ## Summary of the change
10
+
11
+ codex-cli 0.144.0 (host upgrade 2026-07-09, needed for GPT-5.6) regressed `codex exec --json` shutdown: the process emits its final `agent_message` + `turn.completed`, then LINGERS ~16-30s (scaling with host concurrency) before writing `--output-last-message` and exiting. `CodexCliIntelligenceProvider.evaluateExecJson` waited for process exit before accepting the result, so the 30s `DEFAULT_TIMEOUT_MS` killed ALREADY-COMPLETED calls (recording ~92% errors on the affected host, 18/32 codex error rows carrying real usage). Lingering processes also held host spawn-cap slots for the whole linger, compounding the backlog.
12
+
13
+ The fix adds an opt-in early-terminal-settle to the codex adapter transport, driven by the provider:
14
+ - `spawnCodexExecJson` gains `settleOnTerminalLine?(line) => boolean` + `terminalSettleGraceMs` (default 750ms) and an `ExecJsonResult.terminalCompletion` flag. On the first true predicate, the child gets the grace to exit on its own; if still running, the promise settles (`terminalCompletion: true`) after the final flush and the lingering child is reaped (SIGTERM → sigkillGrace → SIGKILL, both unref'd).
15
+ - `CodexCliIntelligenceProvider.evaluateExecJson` captures the agent's final answer from the structured `item.completed`→`agent_message`→`text` event, passes `settleOnTerminalLine = () => sawTerminalTurn && agentMessageText !== null`, and on `terminalCompletion` returns the captured text (finalizing usage as success) instead of reading the deferred file.
16
+
17
+ Files modified:
18
+ - `src/providers/adapters/openai-codex/transport/codexSpawn.ts` — new `settleOnTerminalLine` + `terminalSettleGraceMs` options, `terminalCompletion` result field, `armTerminalReap()` grace+reap, `settle({terminal})` variant, `emitLine` terminal-detection hook.
19
+ - `src/core/CodexCliIntelligenceProvider.ts` — `tryParseCodexResultEvent()` (typed, top-level parse of `turn.completed` / `agent_message.text`), agent-message capture in `onLine`, `settleOnTerminalLine` wiring, and the `terminalCompletion` early-return in `evaluateExecJson`.
20
+ - `tests/unit/codex-exec-json-spawn.test.ts` — transport early-settle tests (linger → fast settle + reap; prompt-exit → normal path, `terminalCompletion` false).
21
+ - `tests/unit/codex-cli-provider-execjson.test.ts` — provider tests (event-sourced result + fast settle; funnel records SUCCESS/noop with usage, not error; `turn.failed` still fails; `turn.completed` without agent_message falls through to the file path).
22
+
23
+ ## Decision-point inventory
24
+
25
+ - **Added (transport)**: `spawnCodexExecJson` terminal-line detection in `emitLine` — a *settlement-timing* decision (when to accept a completed result), NOT a message-flow or authority gate. Only fires when the caller supplies `settleOnTerminalLine`; every existing caller that omits it is byte-for-byte unchanged.
26
+ - **Added (provider)**: `evaluateExecJson` result-source selection — on `terminalCompletion`, the result comes from the structured `agent_message` event instead of the `--output-last-message` file. This is the one behavior reversal of note (see below); it is confined to the linger regime and to codex's own typed final-answer field.
27
+ - **Unchanged**: exit-code classification, file-authority read for prompt-exiting CLIs, the `intelligence.codexExecJson` kill-switch (plain mode), env allowlist (`buildCodexChildEnv`), usage accounting, out-dir lifecycle/sweep, timeout/SIGTERM/SIGKILL machinery for non-terminal paths.
28
+
29
+ ## Signal-vs-Authority note (the one deliberate reversal)
30
+
31
+ The pre-fix code read the result ONLY from `--output-last-message` ("events are observability signal, the file is authority"). On the early-terminal-settle path the result is instead read from the `item.completed`→`agent_message`→`text` event. This is safe and semantically identical:
32
+ - codex writes `--output-last-message` FROM that same last agent message — the bytes are identical.
33
+ - Extraction is a TYPED, top-level `JSON.parse` with explicit shape checks (`type === 'item.completed'` && `item.type === 'agent_message'` && `typeof text === 'string'`) — the same trust surface the usage parser already applies to `turn.completed.usage`. It is NOT substring/regex matching over loose stdout (model content embedded in a string field cannot match a top-level parse).
34
+ - The file-authority path REMAINS the behavior whenever the process exits within the grace (any non-lingering CLI) and whenever a turn produced no agent_message (falls through). The divergence is confined to the codex-0.144 linger regime.
35
+
36
+ ## Roll-up across the seven review dimensions
37
+
38
+ 1. **Over-block**: none. No gate tightened. A prompt-exiting CLI is unchanged; a `turn.completed`-without-message call still reads the file.
39
+ 2. **Under-block / masking real failures**: none. Early-settle fires ONLY on codex's own `turn.completed` success event AND a captured answer. `turn.failed` (genuine failure) never emits `turn.completed` → still throws via the existing error path (unit-tested). A call that never completes still hits the timeout and fails.
40
+ 3. **Idempotency / double-settle**: `armTerminalReap` guarded by `terminalArmed`; `settle()` guarded by `settled`; the child's later exit/close is a no-op once settled. Reap timers are unref'd (never hold the event loop).
41
+ 4. **Resource safety**: the fix REDUCES footprint — lingering processes are reaped ~16-30s earlier, freeing host spawn-cap slots sooner (the systemic backlog relief). SIGKILL backstop reaps a SIGTERM-ignoring child. Same grandchild-reap posture as the pre-existing timeout kill (no new leak class).
42
+ 5. **Usage accounting**: `settle()` runs the final flush BEFORE resolving, so `turn.completed`'s usage reaches the accumulator; `finalize({ success: true })` records it — a completed call now correctly books a SUCCESS row with usage instead of an error row.
43
+ 6. **Concurrency**: validated against real codex 0.144 at N=4 concurrent — all settled `terminalCompletion: true` with correct results.
44
+ 7. **Rollback**: `intelligence.codexExecJson: false` (or `INSTAR_CODEX_EXEC_JSON=0`) restores plain-output mode (no exec-json, no early-settle) byte-for-byte. Setting `terminalSettleGraceMs` very high effectively disables early-settle (behaves like the old wait-for-exit).
45
+
46
+ ## Tracked follow-ups (NOT in this PR)
47
+
48
+ - **#1410 non-gating swap-to-pi timeout (5s)**: pi-cli cold-start exceeds the global `swapAttemptTimeoutMs` default (5000, `commands/server.ts`), producing `nongating-swap-attempt-timeout: pi-cli` degradations with zero usage. DEFERRED: the 5s cap applies to BOTH gating and non-gating swaps, so a blanket bump would slow every gating gate's fail-closed (responsiveness) path; a properly-scoped non-gating-only (or per-framework `pi-cli`) timeout needs its own design + soak. This PR's primary fix removes the codex failures that TRIGGER those swap attempts, so their volume drops without touching the timeout.
49
+ - **Intelligence calls load `~/.codex/config.toml` MCP servers** (playwright via npx, threadline) per call. Disabling MCP (`-c mcp_servers={}`) did NOT remove the 0.144 linger (still 16-22s), so it is not the root cause; but skipping MCP load for pure text-classification calls remains a worthwhile separate hygiene optimization.
50
+
51
+ ## Class-Closure Declaration (display-only mirror)
52
+
53
+ - **`defectClass`** — `unbounded-self-action` (the change adds a `child.kill(SIGTERM)` → `SIGKILL` reap in `spawnCodexExecJson.armTerminalReap`, which the Self-Action Convergence gate keys on).
54
+ - **`closure`** — `n/a` (negative declaration) — this is NOT a self-triggered control loop, so no convergence guard/ratchet is required.
55
+ - **`reason`** — One-shot, per-call child-process reap: when a single `codex exec --json` call's turn completes but its OWN child lingers past the grace, that one child is reaped exactly once (SIGTERM + a single unref'd SIGKILL backstop) at that call's settlement. There is no loop, respawn, retry, or feedback controller — the reap count equals the call count, itself bounded by the host spawn-cap. It cannot storm under sustained pressure because it does not re-arm, re-spawn, or re-drive anything; it only cleans up the one child of the one call that is settling.
56
+
57
+ ## Migration parity
58
+
59
+ No agent-installed file changes (no `.claude/settings.json` hooks, no `.instar/config.json` defaults, no CLAUDE.md template capability, no hook scripts, no built-in skills). Pure `src/` adapter behavior — reaches every agent on `pnpm build` / update with no `PostUpdateMigrator` entry required.