instar 1.3.343 → 1.3.345

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 (147) hide show
  1. package/dist/commands/init.d.ts +4 -4
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/reflect.d.ts.map +1 -1
  5. package/dist/commands/reflect.js +7 -0
  6. package/dist/commands/reflect.js.map +1 -1
  7. package/dist/commands/server.d.ts.map +1 -1
  8. package/dist/commands/server.js +126 -8
  9. package/dist/commands/server.js.map +1 -1
  10. package/dist/commands/setup.d.ts +1 -1
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/core/Config.d.ts +12 -3
  14. package/dist/core/Config.d.ts.map +1 -1
  15. package/dist/core/Config.js +40 -6
  16. package/dist/core/Config.js.map +1 -1
  17. package/dist/core/FrameworkSessionStore.d.ts +1 -1
  18. package/dist/core/FrameworkSessionStore.d.ts.map +1 -1
  19. package/dist/core/PiCliIntelligenceProvider.d.ts +52 -0
  20. package/dist/core/PiCliIntelligenceProvider.d.ts.map +1 -0
  21. package/dist/core/PiCliIntelligenceProvider.js +71 -0
  22. package/dist/core/PiCliIntelligenceProvider.js.map +1 -0
  23. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  24. package/dist/core/PostUpdateMigrator.js +57 -0
  25. package/dist/core/PostUpdateMigrator.js.map +1 -1
  26. package/dist/core/PreCompactionFlush.d.ts +1 -1
  27. package/dist/core/PreCompactionFlush.d.ts.map +1 -1
  28. package/dist/core/ResumeValidator.d.ts +1 -1
  29. package/dist/core/ResumeValidator.d.ts.map +1 -1
  30. package/dist/core/SessionManager.d.ts +130 -0
  31. package/dist/core/SessionManager.d.ts.map +1 -1
  32. package/dist/core/SessionManager.js +545 -10
  33. package/dist/core/SessionManager.js.map +1 -1
  34. package/dist/core/TopicFrameworksStore.d.ts.map +1 -1
  35. package/dist/core/TopicFrameworksStore.js +3 -0
  36. package/dist/core/TopicFrameworksStore.js.map +1 -1
  37. package/dist/core/frameworkInjectionProcesses.d.ts.map +1 -1
  38. package/dist/core/frameworkInjectionProcesses.js +4 -0
  39. package/dist/core/frameworkInjectionProcesses.js.map +1 -1
  40. package/dist/core/frameworkSessionLaunch.d.ts +8 -0
  41. package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
  42. package/dist/core/frameworkSessionLaunch.js +73 -0
  43. package/dist/core/frameworkSessionLaunch.js.map +1 -1
  44. package/dist/core/intelligenceProviderFactory.d.ts +15 -1
  45. package/dist/core/intelligenceProviderFactory.d.ts.map +1 -1
  46. package/dist/core/intelligenceProviderFactory.js +39 -1
  47. package/dist/core/intelligenceProviderFactory.js.map +1 -1
  48. package/dist/core/types.d.ts +88 -8
  49. package/dist/core/types.d.ts.map +1 -1
  50. package/dist/core/types.js.map +1 -1
  51. package/dist/messaging/SpawnRequestManager.d.ts +14 -0
  52. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
  53. package/dist/messaging/SpawnRequestManager.js +17 -0
  54. package/dist/messaging/SpawnRequestManager.js.map +1 -1
  55. package/dist/messaging/shared/telegramRelayPrompt.d.ts +1 -1
  56. package/dist/messaging/shared/telegramRelayPrompt.d.ts.map +1 -1
  57. package/dist/monitoring/ApprenticeshipCycleStore.d.ts +59 -1
  58. package/dist/monitoring/ApprenticeshipCycleStore.d.ts.map +1 -1
  59. package/dist/monitoring/ApprenticeshipCycleStore.js +77 -2
  60. package/dist/monitoring/ApprenticeshipCycleStore.js.map +1 -1
  61. package/dist/monitoring/ReapLog.d.ts +6 -0
  62. package/dist/monitoring/ReapLog.d.ts.map +1 -1
  63. package/dist/monitoring/ReapLog.js +1 -0
  64. package/dist/monitoring/ReapLog.js.map +1 -1
  65. package/dist/monitoring/SessionReaper.d.ts +1 -1
  66. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  67. package/dist/monitoring/SessionReaper.js.map +1 -1
  68. package/dist/monitoring/frameworkActivitySignals.d.ts.map +1 -1
  69. package/dist/monitoring/frameworkActivitySignals.js +21 -0
  70. package/dist/monitoring/frameworkActivitySignals.js.map +1 -1
  71. package/dist/monitoring/frameworkProcessSignals.d.ts.map +1 -1
  72. package/dist/monitoring/frameworkProcessSignals.js +22 -0
  73. package/dist/monitoring/frameworkProcessSignals.js.map +1 -1
  74. package/dist/providers/adapters/pi-cli/capabilities.d.ts +31 -0
  75. package/dist/providers/adapters/pi-cli/capabilities.d.ts.map +1 -0
  76. package/dist/providers/adapters/pi-cli/capabilities.js +40 -0
  77. package/dist/providers/adapters/pi-cli/capabilities.js.map +1 -0
  78. package/dist/providers/adapters/pi-cli/config.d.ts +65 -0
  79. package/dist/providers/adapters/pi-cli/config.d.ts.map +1 -0
  80. package/dist/providers/adapters/pi-cli/config.js +43 -0
  81. package/dist/providers/adapters/pi-cli/config.js.map +1 -0
  82. package/dist/providers/adapters/pi-cli/control/piHardKill.d.ts +19 -0
  83. package/dist/providers/adapters/pi-cli/control/piHardKill.d.ts.map +1 -0
  84. package/dist/providers/adapters/pi-cli/control/piHardKill.js +67 -0
  85. package/dist/providers/adapters/pi-cli/control/piHardKill.js.map +1 -0
  86. package/dist/providers/adapters/pi-cli/errors.d.ts +43 -0
  87. package/dist/providers/adapters/pi-cli/errors.d.ts.map +1 -0
  88. package/dist/providers/adapters/pi-cli/errors.js +71 -0
  89. package/dist/providers/adapters/pi-cli/errors.js.map +1 -0
  90. package/dist/providers/adapters/pi-cli/index.d.ts +39 -0
  91. package/dist/providers/adapters/pi-cli/index.d.ts.map +1 -0
  92. package/dist/providers/adapters/pi-cli/index.js +69 -0
  93. package/dist/providers/adapters/pi-cli/index.js.map +1 -0
  94. package/dist/providers/adapters/pi-cli/observability/sessionId.d.ts +27 -0
  95. package/dist/providers/adapters/pi-cli/observability/sessionId.d.ts.map +1 -0
  96. package/dist/providers/adapters/pi-cli/observability/sessionId.js +53 -0
  97. package/dist/providers/adapters/pi-cli/observability/sessionId.js.map +1 -0
  98. package/dist/providers/adapters/pi-cli/policy.d.ts +44 -0
  99. package/dist/providers/adapters/pi-cli/policy.d.ts.map +1 -0
  100. package/dist/providers/adapters/pi-cli/policy.js +74 -0
  101. package/dist/providers/adapters/pi-cli/policy.js.map +1 -0
  102. package/dist/providers/adapters/pi-cli/transport/agenticSessionRpc.d.ts +34 -0
  103. package/dist/providers/adapters/pi-cli/transport/agenticSessionRpc.d.ts.map +1 -0
  104. package/dist/providers/adapters/pi-cli/transport/agenticSessionRpc.js +194 -0
  105. package/dist/providers/adapters/pi-cli/transport/agenticSessionRpc.js.map +1 -0
  106. package/dist/providers/adapters/pi-cli/transport/oneShotCompletion.d.ts +33 -0
  107. package/dist/providers/adapters/pi-cli/transport/oneShotCompletion.d.ts.map +1 -0
  108. package/dist/providers/adapters/pi-cli/transport/oneShotCompletion.js +166 -0
  109. package/dist/providers/adapters/pi-cli/transport/oneShotCompletion.js.map +1 -0
  110. package/dist/providers/adapters/pi-cli/transport/piSpawn.d.ts +87 -0
  111. package/dist/providers/adapters/pi-cli/transport/piSpawn.d.ts.map +1 -0
  112. package/dist/providers/adapters/pi-cli/transport/piSpawn.js +242 -0
  113. package/dist/providers/adapters/pi-cli/transport/piSpawn.js.map +1 -0
  114. package/dist/providers/adapters/pi-cli/transport/rpcClient.d.ts +103 -0
  115. package/dist/providers/adapters/pi-cli/transport/rpcClient.d.ts.map +1 -0
  116. package/dist/providers/adapters/pi-cli/transport/rpcClient.js +238 -0
  117. package/dist/providers/adapters/pi-cli/transport/rpcClient.js.map +1 -0
  118. package/dist/providers/bootRegistration.d.ts +38 -0
  119. package/dist/providers/bootRegistration.d.ts.map +1 -1
  120. package/dist/providers/bootRegistration.js +52 -0
  121. package/dist/providers/bootRegistration.js.map +1 -1
  122. package/dist/providers/parity/rules/skillParityRule.d.ts.map +1 -1
  123. package/dist/providers/parity/rules/skillParityRule.js +6 -0
  124. package/dist/providers/parity/rules/skillParityRule.js.map +1 -1
  125. package/dist/scaffold/templates.d.ts.map +1 -1
  126. package/dist/scaffold/templates.js +3 -2
  127. package/dist/scaffold/templates.js.map +1 -1
  128. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  129. package/dist/scheduler/JobScheduler.js +22 -0
  130. package/dist/scheduler/JobScheduler.js.map +1 -1
  131. package/dist/server/routes.d.ts.map +1 -1
  132. package/dist/server/routes.js +10 -3
  133. package/dist/server/routes.js.map +1 -1
  134. package/dist/threadline/PipeSessionSpawner.d.ts +23 -0
  135. package/dist/threadline/PipeSessionSpawner.d.ts.map +1 -1
  136. package/dist/threadline/PipeSessionSpawner.js +27 -0
  137. package/dist/threadline/PipeSessionSpawner.js.map +1 -1
  138. package/package.json +2 -2
  139. package/scripts/lint-no-direct-destructive.js +3 -0
  140. package/scripts/lint-no-unfunneled-headless-launch.js +128 -0
  141. package/src/data/builtin-manifest.json +65 -65
  142. package/src/scaffold/templates.ts +3 -2
  143. package/upgrades/1.3.344.md +46 -0
  144. package/upgrades/1.3.345.md +154 -0
  145. package/upgrades/side-effects/apprenticeship-layer-balance.md +50 -0
  146. package/upgrades/side-effects/june15-headless-spawn-reroute.md +105 -0
  147. package/upgrades/side-effects/pi-harness-integration.md +82 -0
@@ -0,0 +1,154 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ The apprenticeship program can now SEE when its deepest layer is starving.
9
+ `GET /apprenticeship/instances/:id/role-coverage` gains an observe-only
10
+ `keystoneBalance` block:
11
+
12
+ ```
13
+ keystoneBalance: {
14
+ keystoneAxis: 'mentor-mentee-differential',
15
+ keystoneCycleCount, lastKeystoneAt,
16
+ oversightCycleCount, oversightSinceKeystone,
17
+ starved: boolean, starvationThreshold, reason
18
+ }
19
+ ```
20
+
21
+ `starved:true` means the deepest layer (the real mentor→mentee drive) is
22
+ under-firing relative to ongoing activity — the silent "mentor-heavy /
23
+ mentee-light" drift. It generalizes the old narrow `driftWarning` to catch the
24
+ common "keystone fired once long ago, then drifted into pure review" case, not
25
+ just "never fired." Tune the threshold per call with
26
+ `?oversightStarvationThreshold=N` (default 3).
27
+
28
+ Why: a 2026-06-06 holistic check found the program lopsided — the mentor layer
29
+ had run 13 cycles while the mentee layer ran 3, and nothing surfaced it. By
30
+ "Observation Needs Structure," a duty to notice is a wish unless the structure
31
+ makes it visible.
32
+
33
+ PR 2 of the June-15 interactive-only readiness arc (PR 1 = #873). The
34
+ provider substrate could route INTERNAL intelligence calls onto the
35
+ subscription lane, but Instar's other headless surface — full agentic
36
+ `claude -p` one-shots for scheduled jobs, mentor loops, dispatch actions,
37
+ `POST /sessions/spawn`, and Threadline A2A cold replies — still billed the
38
+ Agent SDK pot with no reroute. This PR adds the reroute at the single funnel
39
+ those spawns share (`SessionManager.spawnSession`'s headless branch,
40
+ claude-code only): under `intelligence.subscriptionPath.mode` `force` (or
41
+ `auto` when the shared credit decision says subscription), the spawn
42
+ launches an INTERACTIVE claude session instead — same tmux session, same
43
+ watchdogs/reaper semantics, same `--allowedTools`/`--strict-mcp-config`
44
+ flags (spliced for parity), wide-pane geometry, with the prompt delivered
45
+ via the existing ready-wait + guarded-inject machinery. Because an
46
+ interactive REPL never exits, each rerouted task carries a deterministic
47
+ completion sentinel (`INSTAR_JOB_COMPLETE_<id>`) the monitor reaps as
48
+ SUCCESS (job history records success, not timeout), plus a hard
49
+ `maxLifetimeMinutes` backstop (default 45). Safety gates from the 5-reviewer
50
+ convergence: a rerouted-session concurrency cap (`maxRerouted`, default 3) +
51
+ memory-pressure pre-spawn gate (auto → degradation-reported headless
52
+ fallback; force → loud refusal); subscription-quota backpressure on the A2A
53
+ (`SpawnRequestManager`) and pipe (`PipeSessionSpawner`) surfaces so peer
54
+ traffic cannot rate-limit the operator's own account; force-mode pipe
55
+ refusal that falls through to the rerouted A2A path; a paste-escape
56
+ sanitizer in the tmux inject path; per-slug double-run guard + boot-time
57
+ reconciliation so a restart can't double-execute a mid-flight job; an F6
58
+ recurrence cap so a silently-dead reroute escalates instead of hiding behind
59
+ its own fallback; a persisted `Session.launchLane` field surfaced in
60
+ `GET /sessions` and the reap-log (the soak's machine-checkable criterion);
61
+ a new CI lint (`lint-no-unfunneled-headless-launch`) that fails any future
62
+ direct `buildHeadlessLaunch` callsite outside the funnel; three
63
+ factory-bypass intelligence fallbacks in server.ts routed through the
64
+ factory (carrying breaker + router); and the CLAUDE.md awareness block
65
+ corrected (+ a new migration that reaches already-deployed agents).
66
+ Everything ships dark: mode `off` (the fleet default) is pinned
67
+ byte-for-byte to today's argv by test at every touched callsite.
68
+
69
+ ## What to Tell Your User
70
+
71
+ Your agent can now answer "is my apprenticeship loop actually exercising its
72
+ deepest layer, or has it drifted into just reviewing?" — and flag when the
73
+ mentee side is starving, so the imbalance can't silently persist.
74
+
75
+ After June 15, the one-shot commands I use for background jobs and
76
+ agent-to-agent replies start drawing from a prepaid credit pot — and when it
77
+ empties, they'd just fail. I already have a switch (from the last release)
78
+ that can move my internal thinking onto normal interactive sessions; this
79
+ release teaches that SAME switch to also cover my scheduled jobs and
80
+ agent-to-agent replies. It also adds the guardrails that make flipping it
81
+ safe: caps so rerouted work can't overload the machine or eat your
82
+ subscription's rate window (other agents messaging me can never crowd out
83
+ YOUR conversations), a marker system so finished jobs are recorded as
84
+ successes, restart protection so a job can't accidentally run twice, and a
85
+ visible label on every session showing which billing lane it used — so when
86
+ we run the full-day proof, "everything ran on the subscription lane" is
87
+ something you can check, not something you have to take my word for.
88
+ Nothing changes at this release — the switch is still OFF everywhere.
89
+
90
+ ## Summary of New Capabilities
91
+
92
+ - `keystoneBalance` on the role-coverage response — observe-only deepest-layer
93
+ health, with a plain-English `reason`. Tunable via
94
+ `?oversightStarvationThreshold=N`. Never gates.
95
+
96
+ - `intelligence.subscriptionPath.mode` now governs headless job/A2A/dispatch
97
+ spawns too (claude-code only): `force` → interactive sessions with a
98
+ completion sentinel + lifetime backstop; `auto` → credit-driven, with
99
+ loud, capped fallback.
100
+ - New config knobs: `subscriptionPath.maxRerouted` (concurrent rerouted
101
+ sessions, default 3), `subscriptionPath.maxReroutedLifetimeMinutes`
102
+ (default 45).
103
+ - `Session.launchLane` (`headless` | `rerouted-interactive`) in
104
+ `GET /sessions` and the reap-log — machine-checkable billing-lane audit.
105
+ - Quota backpressure on A2A + pipe spawn admission when the reroute is
106
+ active (wired to the same QuotaTracker gate jobs already use).
107
+ - Per-slug job double-run guard + boot reconciliation of rerouted job
108
+ sessions.
109
+ - CI funnel lint: direct `buildHeadlessLaunch` use outside the allowlist
110
+ fails the build.
111
+
112
+ ## Evidence
113
+
114
+ Computed from existing cycle rows (no new storage, no migration). Three test
115
+ tiers green: unit (both sides of every boundary incl. shortcut-still-un-driven
116
+ and exactly-at-threshold), integration (route surfaces it + honors the tuning
117
+ query), e2e (alive through the real AgentServer). Migration-parity + agent-
118
+ awareness covered (template line + idempotent PostUpdateMigrator backfill, 4
119
+ tests). `tsc --noEmit` clean.
120
+
121
+ 5-reviewer adversarial convergence (correctness/wiring, security/cost,
122
+ ops/scale, standards/lessons, spec-vs-reality — ALL initially BLOCK; every
123
+ finding folded into the spec, `docs/specs/june15-headless-spawn-reroute.md`
124
+ + `.eli16.md`). 56+ new tests across all three tiers: 43 unit
125
+ (`headless-spawn-reroute` 23 incl. argv pins both sides / completionMode
126
+ both sides / cap + lifetime + sanitizer + reconciliation;
127
+ `subscription-quota-gates` 8; `lint-no-unfunneled-headless-launch` 4;
128
+ `PostUpdateMigrator-subscriptionPathScope` 5; `job-scheduler-double-run-
129
+ guard` 3), 1 integration (`sessions-launch-lane` — launchLane through the
130
+ real route pipeline), 4 e2e (`june15-headless-spawn-reroute` — production-
131
+ mirroring construction: force spawn has no `-p` + wide pane, sentinel
132
+ completes as success through the real monitor, launchLane via real HTTP,
133
+ default-off invariance). Funnel lint clean on the tree + self-tested both
134
+ ways. Silent-fallback ratchet held at 458.
135
+
136
+ ## Scope (honest)
137
+
138
+ Observe-only this slice. It surfaces the imbalance; it does NOT yet
139
+ auto-correct. The natural phase-2 (a cadence rule that drives the mentee layer
140
+ at least once per K mentor cycles) is deliberately deferred so the signal
141
+ proves out first — the same ship-the-observation-before-the-law order used for
142
+ #856/#864 ahead of the #861 constitution article.
143
+
144
+ ## Incidental fix surfaced by the merge (Zero-Failure Standard)
145
+
146
+ Merging current `main` in surfaced a pre-existing route-completeness ratchet
147
+ break introduced by #884 (P1 Coherence Journal): the new `/coherence/journal`
148
+ route's catch handled `InvalidCursorError` then `throw err` for everything
149
+ else — leaving the routes.ts catch/instanceof count at 225/224 and producing an
150
+ unhandled Express 500 (HTML stack leak) on any non-cursor error. Fixed in this
151
+ PR by returning a proper JSON 500 (`err instanceof Error ? err.message :
152
+ String(err)`) instead of re-throwing — strictly better behavior, and it
153
+ rebalances the ratchet (225/225). Owned per the Zero-Failure Standard ("there
154
+ is no such thing as a pre-existing failure").
@@ -0,0 +1,50 @@
1
+ # Side-Effects Review - apprenticeship layer-balance signal
2
+
3
+ **Version / slug:** `apprenticeship-layer-balance`
4
+ **Date:** `2026-06-06`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier 1)`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds an observe-only `keystoneBalance` block to `ApprenticeshipCycleStore.roleCoverage()` (surfaced via `GET /apprenticeship/instances/:id/role-coverage`). It makes the deepest-layer (mentor→mentee keystone) health a queryable fact: starved when the keystone never fired while oversight did, or enough oversight accrued since the last drive (default 3, tunable via `?oversightStarvationThreshold`). Generalizes the existing narrow `driftWarning`.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `computeKeystoneBalance` (new, private) - pure function over the already-tallied axes + oversight timestamps; no I/O.
15
+ - `roleCoverage(instanceId, opts?)` - modify - gains an optional opts param (back-compat: existing single-arg callers unchanged) and returns the new `keystoneBalance` field.
16
+ - role-coverage route - modify - parses optional `?oversightStarvationThreshold` and passes it through.
17
+
18
+ ## 1. Behavior change / gating
19
+
20
+ NONE. This is observe-only. It adds a computed read-only field; it never blocks a cycle record, never gates a transition, never alters the loop. The existing `driftWarning` is untouched (kept for back-compat). No new failure modes.
21
+
22
+ ## 2. Over/under-signal
23
+
24
+ - Over-signal (false "starved"): a long-idle instance whose keystone fired once then sat through 3+ reviews reads starved even if that's intended. Acceptable — it's a surfaced signal a human/loop reads, not an action; and "intended long review phase" is exactly the thing worth surfacing.
25
+ - Under-signal: an instance with keystone fired recently + <threshold oversight reads healthy even if the mentee is actually stuck. Acceptable for this slice — the signal is about cadence/balance, not per-drive quality (that's operatorSeatUx + the transcript auditor's job).
26
+
27
+ ## 3. Blast radius
28
+
29
+ `roleCoverage()` callers: the one route + tests. The added param is optional, so every existing call compiles and behaves identically (just gains the extra field). No persistent-state change (computed from existing rows), so NO migration and NO data backfill. The field appears automatically for all instances, including legacy/grandfathered ones (their keystone history is read as-is).
30
+
31
+ ## 4. Failure modes
32
+
33
+ `computeKeystoneBalance` is pure and total — no throw paths, no I/O. A malformed threshold from the query string falls back to the default (parsed-int guard). ISO string timestamp comparison is safe (createdAt is always normalized ISO).
34
+
35
+ ## 5. Migration parity
36
+
37
+ No agent-installed files change. The signal is a new read-only API field, so Agent Awareness gets a one-line mention in the CLAUDE.md template + a PostUpdateMigrator backfill so deployed agents learn the state question ("is my mentee layer starving?"). No config, hooks, or skills touched.
38
+
39
+ ## 6. Scope honesty (what this is NOT)
40
+
41
+ Observe-only by design. It does NOT auto-correct the imbalance — the natural phase-2 (a cadence rule that makes the autonomous loop drive the mentee layer at least once per K mentor cycles) is deliberately deferred so the signal proves out first (Graduated-Feature-Rollout discipline; ship the observation structure before the enforcement law — the same order as #856/#864 before the #861 constitution article).
42
+
43
+ ## 7. Incidental fix carried by this PR (`src/server/routes.ts`)
44
+
45
+ Merging current `main` surfaced a pre-existing route-completeness ratchet break introduced by #884 (P1 Coherence Journal): the new `/coherence/journal` route caught `InvalidCursorError` then `throw err` for everything else. That left routes.ts at 225 `catch (err)` / 224 `err instanceof Error` (ratchet red) and leaked an unhandled Express HTML 500 on any non-cursor error.
46
+
47
+ - **Decision point:** the journal route's catch block — `modify`. Behavior change: a non-cursor error now returns a clean JSON `{ error }` 500 instead of propagating to Express's default HTML error handler. Strictly better (no stack-trace leak), no new failure mode.
48
+ - **Causal autopsy:** balance was even (224/224) at `6c2af5a6a`, went to 225/224 at `7afa768f5` (#884). Confirmed via per-commit catch/instanceof bisect.
49
+ - **Blast radius:** one route's error path. No state, no migration, no gating. Rebalances the ratchet to 225/225 (route-completeness green).
50
+ - **Why carried here vs separate PR:** the merge made this PR's CI red and #893 can't go green without it; owned per the Zero-Failure Standard ("there is no such thing as a pre-existing failure"). Documented loudly rather than silently bundled.
@@ -0,0 +1,105 @@
1
+ # Side-Effects Review — june15-headless-spawn-reroute (PR 2)
2
+
3
+ Spec: `docs/specs/june15-headless-spawn-reroute.md` (converged, 5-reviewer
4
+ panel, approved by Justin in topic 9984 2026-06-05: "you have my approval if
5
+ needed. please continue!").
6
+
7
+ ## Behavior changes when `mode` is `off`/unset (the fleet default)
8
+
9
+ NONE intended, and pinned: V1/V5 argv pin tests assert the headless launch
10
+ argv is byte-for-byte unchanged at every touched callsite for claude-code,
11
+ codex-cli, and gemini-cli. Admission behavior of SpawnRequestManager and
12
+ PipeSessionSpawner is unchanged (the quota/mode seams are absent unless the
13
+ server wires them, and the server only wires them when mode is auto/force).
14
+ Two deliberate always-on deltas, both additive:
15
+
16
+ 1. **`Session.launchLane` is now always stamped** (`'headless'` on the
17
+ default lane) and serialized via the existing `{ ...s }` spreads in
18
+ `GET /sessions` + the reap-log. Consumers that enumerate fields see one
19
+ more key. No existing field changes meaning.
20
+ 2. **`rawInject` strips bracketed-paste boundary sequences**
21
+ (`\x1b[200~` / `\x1b[201~`) from injected text (finding S2). Legitimate
22
+ prompts never contain raw paste-boundary bytes; anything that did was a
23
+ forged-submit vector, not a feature.
24
+ 3. **Three server.ts intelligence FALLBACK sites** (shared, relationships,
25
+ topic-summarizer) now construct via `buildIntelligenceProvider` instead
26
+ of `new ClaudeCliIntelligenceProvider` directly. When the factory returns
27
+ null (no claude binary), the fallback is now an honest absence instead of
28
+ a provider with an undefined binary path that fails at call time — a
29
+ strict improvement on an already-degraded path.
30
+
31
+ ## New failure modes introduced (and their bounds)
32
+
33
+ - **Rerouted session never prints the sentinel** → reaped at
34
+ `maxLifetimeMinutes` (default 45) as timeout + DegradationReporter event.
35
+ Bound: the lifetime cap; tested.
36
+ - **Reroute silently dead under `auto`** (cap stuck / pressure stuck /
37
+ continuity gap) → every spawn falls back headless; the F6 recurrence cap
38
+ raises ONE escalated degradation event per 30-min window instead of
39
+ letting the self-heal hide it.
40
+ - **Restart with a rerouted job mid-flight** → boot reconciliation kills the
41
+ surviving REPL (job reruns cleanly); per-slug guard prevents concurrent
42
+ double-execution. Tested both.
43
+ - **Force-mode refusal paths** (cap/memory/pipe) are loud: throw or
44
+ `{spawned:false}` + degradation event — never a silent drop. The pipe
45
+ refusal shape (`{spawned:false}` from inside `spawn()`) is pinned by test
46
+ because only that shape falls through to the rerouted A2A path.
47
+
48
+ ## Quota / cost surface
49
+
50
+ - Rerouted spawns bill the subscription 5h window. Bounds: `maxRerouted`
51
+ (default 3) + memory-pressure pre-spawn gate + QuotaTracker backpressure
52
+ on A2A/pipe admission (the same gate scheduled jobs already use). The
53
+ worst case (window already ≥95%) denies non-critical spawns BEFORE they
54
+ land on the window.
55
+ - The `auto` decision reuses PR 1's TTL-cached credit reader via
56
+ `setSdkCreditReader` — one credit source for both routing layers, no
57
+ drift.
58
+
59
+ ## Framework generality (launch/inject surface)
60
+
61
+ The reroute is **claude-code-only by design** — it exists to dodge
62
+ Anthropic's June-15 SDK-pot billing; codex-cli and gemini-cli headless
63
+ spawns are untouched on every mode (V5 pin test). The launch builders are
64
+ not modified; the reroute composes `buildInteractiveLaunch` +
65
+ `claudeHeadlessExtraFlags`, both existing per-framework abstractions. Works
66
+ for codex-cli/gemini-cli: yes — by explicit non-interference, pinned by
67
+ test. The funnel lint allowlist names the two legitimate
68
+ `buildHeadlessLaunch` callsites; a future framework adding a third must
69
+ justify it at review.
70
+
71
+ ## Migration / fleet rollout
72
+
73
+ - New config keys are optional with safe defaults; `migrateConfig` additions
74
+ are not needed (absence = off).
75
+ - CLAUDE.md template block corrected + a NEW idempotent migration
76
+ (`headless job / agent-to-agent / dispatch spawns` sniff) patches the
77
+ already-deployed PR-1 wording on update — both PR-1 wordings ("routes
78
+ them" / "can route them") handled; hand-edited sections left untouched.
79
+ Tested (5 cases incl. idempotency).
80
+
81
+ ## Evidence pointers
82
+
83
+ - Unit: `tests/unit/headless-spawn-reroute.test.ts` (23),
84
+ `tests/unit/subscription-quota-gates.test.ts` (8),
85
+ `tests/unit/lint-no-unfunneled-headless-launch.test.ts` (4),
86
+ `tests/unit/PostUpdateMigrator-subscriptionPathScope.test.ts` (5),
87
+ `tests/unit/job-scheduler-double-run-guard.test.ts` (3)
88
+ - Integration: `tests/integration/sessions-launch-lane.test.ts` (1)
89
+ - E2E: `tests/e2e/june15-headless-spawn-reroute.test.ts` (4 —
90
+ production-mirroring construction, the spawn-path "200 not 503")
91
+ - Spec + ELI16: `docs/specs/june15-headless-spawn-reroute.md` / `.eli16.md`
92
+ - Lint: `scripts/lint-no-unfunneled-headless-launch.js` (chained in
93
+ `pnpm lint`)
94
+
95
+ ## CI-green follow-up #2 (Zero-Failure Standard)
96
+
97
+ A main-drift imbalance surfaced by this PR's CI (route-completeness:
98
+ 225 `catch (err)` vs 224 `err instanceof Error` in routes.ts — landed on
99
+ main via a [skip ci] merge that never ran this shard). Per the Zero-Failure
100
+ Standard ("no pre-existing failures — if you see it you own it"), fixed by
101
+ converting one `/providers/...` route's `(err as Error).message` cast to the
102
+ safe `err instanceof Error ? err.message : String(err)` guard — balances the
103
+ count AND is strictly safer (an `as Error` cast lies if err isn't an Error).
104
+ Also: the codex-model-swap structure-pin window widened past the reroute
105
+ branch (test-only — invariant unchanged).
@@ -0,0 +1,82 @@
1
+ # Side-Effects Review — pi-harness-integration (Phases A-D)
2
+
3
+ ## Blast radius
4
+
5
+ Additive at every layer; ships DARK behind `enabledFrameworks` containing
6
+ `'pi-cli'` (default absent) AND a detectable `pi` binary. With the default
7
+ config, NOTHING registers and NO dispatch path changes outcome.
8
+
9
+ Shared-file behavior changes (the only non-additive surfaces):
10
+
11
+ 1. **SessionManager.isMarkerStuckAtPrompt** — adds a new detection branch
12
+ (marker sandwiched between two `─` rule lines, pi's input box). Requires
13
+ BOTH the injected marker AND a rule within 2 lines above AND below —
14
+ normal transcript output and the other frameworks' panes cannot satisfy
15
+ it (their input areas use prompt chars, not rule sandwiches). Existing
16
+ prompt-char branches untouched; pinned by tests against real captured
17
+ pane bytes for all four frameworks.
18
+ 2. **SessionManager binary resolution** — pi-cli now resolves to bare `'pi'`
19
+ instead of falling back to `claudePath` (which holds a CLAUDE binary).
20
+ Without this, a pi topic on a claude install would silently launch
21
+ Claude — the exact additive-only violation this project forbids. Other
22
+ frameworks' resolution is byte-identical.
23
+ 3. **framework-arg-rendering-matrix test** — now DERIVED from
24
+ SUPPORTED_FRAMEWORKS instead of a hand-list (which had silently dropped
25
+ gemini-cli). Test-only.
26
+
27
+ ## Framework generality
28
+
29
+ This change EXTENDS the framework abstraction rather than special-casing:
30
+ 'pi-cli' rides the same Record<IntelligenceFramework, …> dispatch maps as
31
+ claude-code / codex-cli / gemini-cli (launch builders, injection processes,
32
+ activity + process signals, parity renderer), and the never-exhaustiveness
33
+ checks force every future dispatch site to handle all four. The
34
+ framework-agnosticism test suite passes with pi included; the arg-rendering
35
+ matrix now derives from the registry so a fifth framework cannot silently
36
+ miss coverage. Warm-session injection works for pi via the
37
+ FRAMEWORK_INJECTION_PROCESS_NAMES entry ('pi'), same as every framework.
38
+
39
+ ## Security / spend surfaces
40
+
41
+ - **Subscription guard (structural)**: Anthropic/Claude-routed pi model
42
+ patterns are DENIED at every call-construction path (one-shot transport,
43
+ RPC session start, intelligence provider construction), including
44
+ aggregator pass-throughs (`openrouter/anthropic/...`) and the
45
+ pattern-less case (pi's ambient default could be an Anthropic login).
46
+ Override is file-config only (`piCli.allowAnthropicProviders`), never env
47
+ or per-call; allowed calls are audit-logged with a cost warning.
48
+ - **Child env hardening**: pi child processes get an explicit allowlist env
49
+ with UNCONDITIONAL deletion of all billing-capable vars (ANTHROPIC/
50
+ OPENAI/GEMINI/GOOGLE/XAI/GROQ/OPENROUTER keys + CLAUDE_CODE_OAUTH_TOKEN),
51
+ mirroring the gemini Rule-1a analog. pi authenticates via its own
52
+ ~/.pi/agent/auth.json (subscription OAuth) or models.json custom keys.
53
+ - **No permission system in pi**: instar's gate layer wraps pi sessions
54
+ exactly like other frameworks; containerization noted as future hardening
55
+ in the spec (§ kickoff Risks).
56
+
57
+ ## Rollback
58
+
59
+ Revert the commit. No migrations mutate state destructively (the CLAUDE.md
60
+ migration is content-sniffed append-only; no config defaults are written).
61
+ Agents that opted in simply lose the pi framework value on downgrade —
62
+ their claude-code/codex-cli/gemini-cli behavior is untouched.
63
+
64
+ ## Deliberate non-goals (tracked in spec §9, not silent drops)
65
+
66
+ Replacing any Claude Code path; pi-ai adoption for non-pi internal calls;
67
+ OAuth flow automation; cross-provider mid-session handoff; event-stream
68
+ dashboard renderer (optional Phase E, decided after the core lands).
69
+
70
+ ## CI-fix follow-up (post-first-commit)
71
+
72
+ Greening CI surfaced expected churn, no behavior change:
73
+ - Three existing tests hardcoded the 3-framework SUPPORTED list → added
74
+ 'pi-cli' to the expectations.
75
+ - The `no-silent-fallbacks` ratchet counted the intelligenceProviderFactory
76
+ pi-cli degrade-to-null catch → marked `@silent-fallback-ok` (it warns
77
+ loudly and the router emits its own DegradationReporter; a second report
78
+ here would double-count the same degrade).
79
+ - The release-fragment lint required `## Summary of New Capabilities` → added
80
+ it and switched to the canonical `<!-- bump -->` comment.
81
+ None of these touch runtime behavior; they align the new framework with the
82
+ existing structural gates.