instar 1.3.437 → 1.3.439

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,56 @@
1
+ # Side-Effects Review — Autonomous setup-race hardening (per-topic state write)
2
+
3
+ **Version / slug:** `autonomous-setup-race-hardening`
4
+ **Date:** 2026-06-08
5
+ **Author:** Instar Agent (echo)
6
+ **Second-pass reviewer:** not required (see Phase 5 note below)
7
+
8
+ ## Summary of the change
9
+
10
+ The autonomous stop-hook already resolves a **per-topic** state file (`.instar/autonomous/<topicId>.local.md`) and migrates the legacy single file into it. The only gap was that the autonomous **skill's** setup step still instructed writing the legacy single file `.instar/autonomous-state.local.md` — so two autonomous sessions booting near-simultaneously could both write that shared file and clobber each other in the window before the hook migrates. This change makes the skill write the per-topic file **directly** at setup. Files: `.claude/skills/autonomous/SKILL.md` (the setup write step + cancel/hook-config references), `src/core/PostUpdateMigrator.ts` (bumps the existing autonomous-SKILL.md upgrade marker so existing agents re-deploy the corrected skill), `tests/unit/PostUpdateMigrator-autonomousHookPath.test.ts` (+3 tests). The stop-hook's reading/decision logic is **untouched**.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `autonomous-stop-hook.sh` state resolution (per-topic read + legacy migrate) — **pass-through** — unchanged; it already prefers the per-topic file and keeps the legacy fallback for in-flight older jobs.
15
+ - `PostUpdateMigrator` autonomous-SKILL.md upgrade — **modify (marker bump only)** — re-deploys the corrected bundled SKILL.md to existing agents; same idempotent `upgrade()` mechanism, no new migration machinery.
16
+ - Skill setup state-file write — **modify** — writes the per-topic path instead of the legacy single path. This is instruction content, not runtime decision logic.
17
+
18
+ ---
19
+
20
+ ## 1. Over-block
21
+
22
+ **What legitimate inputs does this change reject that it shouldn't?** No block/allow surface — over-block not applicable. The change relocates where a state file is written; it gates nothing.
23
+
24
+ ## 2. Under-block
25
+
26
+ **What failure modes does this still miss?** None introduced. The one residual it does NOT address: a session with no resolvable `report_topic` still falls back to the legacy single file (documented in the skill) — that path is rare and the hook's legacy migrate still handles it. This is intentional back-compat, not a missed failure.
27
+
28
+ ## 3. Level-of-abstraction fit
29
+
30
+ Correct layer. The race was created by the **skill** writing the shared path; the fix is in the skill (write per-topic directly). The hook already owned per-topic resolution at the right layer and is left untouched — we feed it the file it already prefers rather than adding a parallel mechanism.
31
+
32
+ ## 4. Signal vs authority compliance
33
+
34
+ Compliant. No blocking authority is added or changed. The decision-making authority (the stop-hook, which blocks/allows session exit) is untouched. This change only changes which file the skill writes and re-deploys that instruction — it adds no brittle check with blocking power. (`docs/signal-vs-authority.md`.)
35
+
36
+ ## 5. Interactions
37
+
38
+ - Does NOT shadow or get shadowed by the hook (the hook reads the same per-topic file the skill now writes — they agree).
39
+ - The migration marker bump rides the existing `upgrade('.claude/skills/autonomous/SKILL.md', …)` call; it does not double-fire (one marker carries cumulative SKILL.md fixes because `upgrade()` re-deploys the whole bundled file). Idempotent: a second run finds the new marker and no-ops.
40
+ - `setup-autonomous.sh` already wrote the per-topic path when `REPORT_TOPIC` is set — unchanged, no race with the skill's in-context write.
41
+
42
+ ## 6. External surfaces
43
+
44
+ No user-facing surface. Internal robustness only. Existing agents receive the corrected skill on their next update via the migration; new agents get it via `init`. No new config, route, or API. No dependence on timing beyond the boot window it closes.
45
+
46
+ ## 7. Rollback cost
47
+
48
+ Low. Back-out is a revert of 3 files + a marker re-bump (or simply shipping a follow-up that restores the prior marker). No data migration, no agent-state repair: the hook's legacy fallback + migrate path means even a mixed fleet (some agents on the old skill writing legacy, some on the new writing per-topic) keeps working — the hook migrates legacy regardless. Worst case is the narrow boot-race returns until re-fixed; nothing is corrupted.
49
+
50
+ ## Phase 1 — Principle check
51
+
52
+ The change involves **no new decision point**. It relocates a state-file write and re-deploys that instruction; the gate/authority that makes the session-continuation decision (the stop-hook) is unchanged. Signal-vs-authority therefore applies only as "no new authority added."
53
+
54
+ ## Phase 5 — Second-pass note
55
+
56
+ The Phase-5 trigger list includes session-lifecycle changes. This change is session-lifecycle **adjacent** (it affects where the autonomous loop's state file lives) but changes **no decision logic** — the stop-hook that drives spawn/continue/exit is byte-for-byte unchanged (verified against the diff). Declared **not-required**: there is no new or modified block/allow decision for a reviewer to audit. The substantive review above stands on the verified diff.
@@ -0,0 +1,25 @@
1
+ # Side-effects review — subscription-pool session pinning (makes auto-swap functional)
2
+
3
+ ## What changed
4
+ - `src/core/SessionManager.ts`: new optional `spawnAccountResolver` dep + `setSpawnAccountResolver()` setter (mirrors `setTopicBindingChecker`). In BOTH initial-spawn lanes (headless `spawnSession` + the rerouted-interactive spawn), when the resolver returns an account AND the launching framework is claude-code, set `spec.envOverrides.CLAUDE_CONFIG_DIR = account.configHome` and tag the session record `subscriptionAccountId: account.id`.
5
+ - `src/core/types.ts`: new `subscriptionPool.pinSessionsToPool?: boolean` (default off).
6
+ - `src/commands/server.ts`: when `pinSessionsToPool` is enabled, wire `setSpawnAccountResolver` to `selectAccount(pool.list(), {nowMs})` (the scheduler's reset-date/headroom score).
7
+
8
+ ## Why (the bug it fixes)
9
+ Auto-swap (`server.ts` `rate-limit:escalated` handler) does `if (!session.subscriptionAccountId) return;` — it only moves sessions tagged with an account. But NO spawn ever wrote that field (0/60 live), because sessions launch on the default config, not via the pool. So auto-swap was a structural no-op and a session that hit its account's quota wall just died (live incident 2026-06-08). This change writes the tag (and launches on a real pool account), which is the missing prerequisite that makes auto-swap function.
10
+
11
+ ## Blast radius — CRITICAL PATH, gated to a strict no-op by default
12
+ - This touches session SPAWNING (the most critical path). It is gated three ways so the default is byte-identical: (1) `pinSessionsToPool` defaults off → server never calls the setter → `spawnAccountResolver` stays unset; (2) with the resolver unset, both lanes take the `pinnedAccount = null` branch → no `CLAUDE_CONFIG_DIR` added, no `subscriptionAccountId` set — exactly today's behavior; (3) the claude-code guard means a codex/gemini spawn is never given a Claude config home.
13
+ - When ENABLED: claude-code spawns launch under the scheduler-picked account's config home. The config homes come from the pool (real enrolled accounts), so the spawn authenticates correctly. If `selectAccount` finds no eligible account, the resolver returns null → falls back to the default config (no breakage).
14
+ - The account-swap restart method (`SessionManager` ~line 3057) ALREADY set `subscriptionAccountId` + configHome for swaps — untouched; this only adds the same tagging to INITIAL spawns.
15
+
16
+ ## Framework generality
17
+ - Pinning is claude-code-specific by design (CLAUDE_CONFIG_DIR is a Claude env var; the headless lane guards on `headlessFramework === 'claude-code'`, the interactive reroute lane is always claude-code). Codex/Gemini spawns are unaffected — they never receive a config-home override.
18
+
19
+ ## Migration parity
20
+ - No agent-installed files change. New config field (`pinSessionsToPool`) is optional, default off → existing agents unaffected until explicitly enabled. No `PostUpdateMigrator` entry needed.
21
+
22
+ ## Tests
23
+ - Unit (`session-manager-behavioral.test.ts`): spawnSession pins (CLAUDE_CONFIG_DIR flag + subscriptionAccountId, persisted) when the resolver is set; does NOT pin when unset or when the resolver returns null. Inspects the real tmux `new-session` argv via the execFileSync mock.
24
+ - Integration (`subscription-pin-sessions.test.ts`): the full production chain — real SubscriptionPool + the real `selectAccount` resolver wired exactly as server.ts → spawnSession pins to the optimal account (the higher-headroom/sooner-reset one wins); empty pool → no pin.
25
+ - No HTTP route, so the standard's Tier-3 "route alive (200 not 503)" form does not apply; the integration test exercises the production wiring shape (the server.ts resolver closure) instead.