instar 1.3.90 → 1.3.92

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 (152) hide show
  1. package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +17 -0
  2. package/dashboard/index.html +108 -0
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +214 -1
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  7. package/dist/config/ConfigDefaults.js +30 -0
  8. package/dist/config/ConfigDefaults.js.map +1 -1
  9. package/dist/config/LiveConfig.d.ts +3 -1
  10. package/dist/config/LiveConfig.d.ts.map +1 -1
  11. package/dist/config/LiveConfig.js +4 -1
  12. package/dist/config/LiveConfig.js.map +1 -1
  13. package/dist/config/stageWriteGuard.d.ts +23 -0
  14. package/dist/config/stageWriteGuard.d.ts.map +1 -0
  15. package/dist/config/stageWriteGuard.js +33 -0
  16. package/dist/config/stageWriteGuard.js.map +1 -0
  17. package/dist/core/DeliverMessageHandler.d.ts +33 -0
  18. package/dist/core/DeliverMessageHandler.d.ts.map +1 -0
  19. package/dist/core/DeliverMessageHandler.js +34 -0
  20. package/dist/core/DeliverMessageHandler.js.map +1 -0
  21. package/dist/core/LeaseCoordinator.d.ts +39 -2
  22. package/dist/core/LeaseCoordinator.d.ts.map +1 -1
  23. package/dist/core/LeaseCoordinator.js +45 -9
  24. package/dist/core/LeaseCoordinator.js.map +1 -1
  25. package/dist/core/MachineIdentity.d.ts +28 -1
  26. package/dist/core/MachineIdentity.d.ts.map +1 -1
  27. package/dist/core/MachineIdentity.js +81 -1
  28. package/dist/core/MachineIdentity.js.map +1 -1
  29. package/dist/core/MachineLoadBroker.d.ts +94 -0
  30. package/dist/core/MachineLoadBroker.d.ts.map +1 -0
  31. package/dist/core/MachineLoadBroker.js +87 -0
  32. package/dist/core/MachineLoadBroker.js.map +1 -0
  33. package/dist/core/MachinePoolRegistry.d.ts +94 -0
  34. package/dist/core/MachinePoolRegistry.d.ts.map +1 -0
  35. package/dist/core/MachinePoolRegistry.js +150 -0
  36. package/dist/core/MachinePoolRegistry.js.map +1 -0
  37. package/dist/core/MeshRpc.d.ts +165 -0
  38. package/dist/core/MeshRpc.d.ts.map +1 -0
  39. package/dist/core/MeshRpc.js +156 -0
  40. package/dist/core/MeshRpc.js.map +1 -0
  41. package/dist/core/MeshRpcClient.d.ts +59 -0
  42. package/dist/core/MeshRpcClient.d.ts.map +1 -0
  43. package/dist/core/MeshRpcClient.js +58 -0
  44. package/dist/core/MeshRpcClient.js.map +1 -0
  45. package/dist/core/MultiMachineCoordinator.d.ts +11 -0
  46. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  47. package/dist/core/MultiMachineCoordinator.js +13 -0
  48. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  49. package/dist/core/NicknameAssigner.d.ts +51 -0
  50. package/dist/core/NicknameAssigner.d.ts.map +1 -0
  51. package/dist/core/NicknameAssigner.js +91 -0
  52. package/dist/core/NicknameAssigner.js.map +1 -0
  53. package/dist/core/NicknameCommand.d.ts +28 -0
  54. package/dist/core/NicknameCommand.d.ts.map +1 -0
  55. package/dist/core/NicknameCommand.js +69 -0
  56. package/dist/core/NicknameCommand.js.map +1 -0
  57. package/dist/core/PlacementExecutor.d.ts +71 -0
  58. package/dist/core/PlacementExecutor.d.ts.map +1 -0
  59. package/dist/core/PlacementExecutor.js +156 -0
  60. package/dist/core/PlacementExecutor.js.map +1 -0
  61. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  62. package/dist/core/PostUpdateMigrator.js +21 -0
  63. package/dist/core/PostUpdateMigrator.js.map +1 -1
  64. package/dist/core/RebalancePlanner.d.ts +48 -0
  65. package/dist/core/RebalancePlanner.d.ts.map +1 -0
  66. package/dist/core/RebalancePlanner.js +71 -0
  67. package/dist/core/RebalancePlanner.js.map +1 -0
  68. package/dist/core/SessionManager.d.ts +20 -3
  69. package/dist/core/SessionManager.d.ts.map +1 -1
  70. package/dist/core/SessionManager.js +54 -6
  71. package/dist/core/SessionManager.js.map +1 -1
  72. package/dist/core/SessionOwnership.d.ts +103 -0
  73. package/dist/core/SessionOwnership.d.ts.map +1 -0
  74. package/dist/core/SessionOwnership.js +0 -0
  75. package/dist/core/SessionOwnership.js.map +1 -0
  76. package/dist/core/SessionOwnershipRegistry.d.ts +115 -0
  77. package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -0
  78. package/dist/core/SessionOwnershipRegistry.js +128 -0
  79. package/dist/core/SessionOwnershipRegistry.js.map +1 -0
  80. package/dist/core/SessionPoolE2EResultStore.d.ts +45 -0
  81. package/dist/core/SessionPoolE2EResultStore.d.ts.map +1 -0
  82. package/dist/core/SessionPoolE2EResultStore.js +66 -0
  83. package/dist/core/SessionPoolE2EResultStore.js.map +1 -0
  84. package/dist/core/SessionRouter.d.ts +95 -0
  85. package/dist/core/SessionRouter.d.ts.map +1 -0
  86. package/dist/core/SessionRouter.js +151 -0
  87. package/dist/core/SessionRouter.js.map +1 -0
  88. package/dist/core/StageAdvancer.d.ts +47 -0
  89. package/dist/core/StageAdvancer.d.ts.map +1 -0
  90. package/dist/core/StageAdvancer.js +70 -0
  91. package/dist/core/StageAdvancer.js.map +1 -0
  92. package/dist/core/TransferByNickname.d.ts +51 -0
  93. package/dist/core/TransferByNickname.d.ts.map +1 -0
  94. package/dist/core/TransferByNickname.js +50 -0
  95. package/dist/core/TransferByNickname.js.map +1 -0
  96. package/dist/core/TransferOrchestrator.d.ts +113 -0
  97. package/dist/core/TransferOrchestrator.d.ts.map +1 -0
  98. package/dist/core/TransferOrchestrator.js +79 -0
  99. package/dist/core/TransferOrchestrator.js.map +1 -0
  100. package/dist/core/types.d.ts +177 -1
  101. package/dist/core/types.d.ts.map +1 -1
  102. package/dist/core/types.js.map +1 -1
  103. package/dist/monitoring/RateLimitSentinel.d.ts +24 -2
  104. package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
  105. package/dist/monitoring/RateLimitSentinel.js +25 -14
  106. package/dist/monitoring/RateLimitSentinel.js.map +1 -1
  107. package/dist/scaffold/templates.d.ts.map +1 -1
  108. package/dist/scaffold/templates.js +5 -0
  109. package/dist/scaffold/templates.js.map +1 -1
  110. package/dist/server/AgentServer.d.ts +8 -0
  111. package/dist/server/AgentServer.d.ts.map +1 -1
  112. package/dist/server/AgentServer.js +4 -0
  113. package/dist/server/AgentServer.js.map +1 -1
  114. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  115. package/dist/server/CapabilityIndex.js +14 -0
  116. package/dist/server/CapabilityIndex.js.map +1 -1
  117. package/dist/server/routes.d.ts +12 -0
  118. package/dist/server/routes.d.ts.map +1 -1
  119. package/dist/server/routes.js +91 -0
  120. package/dist/server/routes.js.map +1 -1
  121. package/package.json +1 -1
  122. package/src/data/builtin-manifest.json +65 -65
  123. package/src/scaffold/templates.ts +5 -0
  124. package/upgrades/1.3.91.md +54 -0
  125. package/upgrades/1.3.92.md +63 -0
  126. package/upgrades/side-effects/session-error-nudge-rearm.md +17 -0
  127. package/upgrades/side-effects/session-pool-capability-classification.md +16 -0
  128. package/upgrades/side-effects/session-pool-review-fixes.md +23 -0
  129. package/upgrades/side-effects/session-pool-track-a-router-lease.md +29 -0
  130. package/upgrades/side-effects/session-pool-track-b-agent-awareness.md +29 -0
  131. package/upgrades/side-effects/session-pool-track-b-nicknames.md +35 -0
  132. package/upgrades/side-effects/session-pool-track-b-pool-api.md +36 -0
  133. package/upgrades/side-effects/session-pool-track-b-registry.md +31 -0
  134. package/upgrades/side-effects/session-pool-track-b-wiring.md +39 -0
  135. package/upgrades/side-effects/session-pool-track-c-dispatcher.md +24 -0
  136. package/upgrades/side-effects/session-pool-track-c-meshrpc.md +35 -0
  137. package/upgrades/side-effects/session-pool-track-c-route-wiring.md +31 -0
  138. package/upgrades/side-effects/session-pool-track-d-ownership-fsm.md +31 -0
  139. package/upgrades/side-effects/session-pool-track-d-registry.md +27 -0
  140. package/upgrades/side-effects/session-pool-track-d-wiring.md +30 -0
  141. package/upgrades/side-effects/session-pool-track-e-boot-wiring.md +20 -0
  142. package/upgrades/side-effects/session-pool-track-e-placement-core.md +17 -0
  143. package/upgrades/side-effects/session-pool-track-e-router.md +23 -0
  144. package/upgrades/side-effects/session-pool-track-f-config.md +12 -0
  145. package/upgrades/side-effects/session-pool-track-f-handoff-core.md +17 -0
  146. package/upgrades/side-effects/session-pool-track-g-load-broker.md +16 -0
  147. package/upgrades/side-effects/session-pool-track-h-gate-wiring.md +17 -0
  148. package/upgrades/side-effects/session-pool-track-h-mesh-client.md +15 -0
  149. package/upgrades/side-effects/session-pool-track-h-rebalance.md +13 -0
  150. package/upgrades/side-effects/session-pool-track-h-rollout-gate.md +17 -0
  151. package/upgrades/side-effects/session-pool-track-h-stage-guard.md +16 -0
  152. package/upgrades/side-effects/transient-api-error-recovery-generalization.md +18 -0
@@ -0,0 +1,23 @@
1
+ # Side effects — Track E part 2: SessionRouter dispatch engine + deliverMessage (§L4)
2
+
3
+ ## What this adds
4
+ The L4 inbound-message dispatch engine, shipped DARK (constructed/activated only when `multiMachine.sessionPool.enabled` — boot wiring + the live-ingress interception land in Track E part 3 / Track H's staged rollout). No runtime behavior change for any deployed agent.
5
+
6
+ - `src/core/SessionRouter.ts` — `route(InboundMessage): Promise<RouteOutcome>`. Resolves session ownership and dispatches per §L4:
7
+ - owned+alive+self → handle locally (no MeshRpc hop);
8
+ - owned+alive+remote → forward via `deliverMessage` over MeshRpc (idempotent on `messageId`; the router treats the platform offset as advanceable ONLY after the owner's `queued`/`duplicate` ACK — `RouteOutcome.acked`);
9
+ - owner dead / retries exhausted → owner-dead re-placement (mark suspect → re-place → claim);
10
+ - transient (placing/transferring) → queue (ownership-contention);
11
+ - unowned → `PlacementExecutor.decide()` → SYNCHRONOUS CAS-claim → spawn on the winner (CAS-loss → re-read → forward to confirmed owner / queue).
12
+ - Per-session ordering: strictly in-order, at-most-one-in-flight (a per-session promise chain); different sessions dispatch concurrently. All I/O is injected (deterministic, unit-testable); the `deliverMessage` dep owns its own per-attempt timeout (throws on timeout) and the router owns the retry/backoff/fallback loop.
13
+ - `src/core/MeshRpc.ts` — added the `deliverMessage` command to the `MeshCommand` union (`{ session, messageId, payload, ownershipEpoch }`) and its RBAC (router-only, alongside place/transfer). This is the §L4 owner-forward command carried inside the §L0 signed, recipient-bound, replay-protected envelope.
14
+
15
+ ## Risk / blast radius
16
+ None at runtime — `SessionRouter` is not yet imported by any boot path, and the new `deliverMessage` command type is inert until a handler is registered (part 3). The MeshRpc union/RBAC addition is additive; all 27 existing mesh tests still pass.
17
+
18
+ ## Tests
19
+ - `tests/unit/SessionRouter.test.ts` — 15 tests: every dispatch branch (local, remote-forward, duplicate ACK, stale-ownership re-resolve, retry-then-owner-dead, owner-dead, transient-queue, unowned place→CAS won/self/blocked/queued, CAS-lost→forward/queue) + per-session in-order/one-in-flight serialization + cross-session concurrency.
20
+ - `tests/integration/session-router-dispatch.test.ts` — 3 tests over the REAL MeshRpc transport (signed envelopes, POST /mesh/rpc) + a REAL SessionOwnershipRegistry: deliverMessage owner-forward with offset-advance-only-after-ACK, idempotent redelivery (duplicate ACK, not re-processed), and two-router concurrent CAS → exactly-one-owner.
21
+
22
+ ## Follow-ups (tracked in this same Track E / Track H)
23
+ Part 3: register the `deliverMessage` receive handler (owner-side MessageProcessingLedger dedupe) on the boot mesh dispatcher, construct SessionRouter behind the dark flag, expose on RouteContext, add the Tier-3 "router alive when enabled" E2E + agent-awareness (nickname-transfer) blurb. Live-ingress interception ("consult placement instead of always spawning locally") is gated by Track H's StageAdvancer — the designed dark-ship rollout boundary, not a deferral.
@@ -0,0 +1,12 @@
1
+ # Side effects — Track F part 2: transfer integration + §L5 config tunables
2
+
3
+ ## What this adds
4
+ - Tier-2 integration coverage (`tests/integration/transfer-orchestrator-ownership.test.ts`) of the TransferOrchestrator driving the REAL SessionOwnershipRegistry transfer FSM + REAL verifyLedgerSnapshot — no production code change there, it's test-only.
5
+ - `src/config/ConfigDefaults.ts` + `src/core/types.ts` — the §L5 transfer/handoff tunables added to the dark `sessionPool` block: `transferDrainTimeoutMs` (30000), `transferOutputCutoffMs` (1000), `placementCooldownMs` (300000), `topicPlacementUpdateMinIntervalMs` (10000). Added via the add-missing `applyDefaults` path so existing agents backfill ONLY the new sub-fields on update (migration parity), never clobbering tuned values.
6
+
7
+ ## Risk / blast radius
8
+ None — config additions are inert (the whole sessionPool block is enabled:false / stage:dark). No behavior change.
9
+
10
+ ## Tests
11
+ - `tests/integration/transfer-orchestrator-ownership.test.ts` — 2: clean S→T transfer reaches active(T)@epoch4 through the real FSM + source teardown after claim; in_flight ledger snapshot → sync-corrupted, record stays transferring(S) (no no-owner gap), T never claims, source not torn down.
12
+ - `tests/unit/ConfigDefaults.test.ts` — extended backfill assertion: the §L5 tunables backfill into a partial sessionPool block (add-missing, operator stage preserved).
@@ -0,0 +1,17 @@
1
+ # Side effects — Track F part 1: Transfer/Handoff core + transfer-by-nickname (§L3/§L5)
2
+
3
+ ## What this adds
4
+ Two PURE, side-effect-free cores for the L5 Session Transfer / Handoff Orchestrator, shipped DARK (no boot wiring / no live transfer yet — Track-H stage-gated activation). No runtime behavior change.
5
+
6
+ - `src/core/TransferByNickname.ts` — `planTransferByNickname(command, state, sessionKey)`: turns an already-recognized NicknameCommand into a gated, validated TransferPlan. Resolves nickname→machineId; REJECTS unknown nicknames (lists valid ones — never silent mis-route); rate-limits rapid-fire transfers per topic; no-ops when already on the target; and applies the §L4 confirmation gate (offline target, or a different owner while mid-reply). This is the brain behind the headline "move this to <nickname>" swap.
7
+ - `src/core/TransferOrchestrator.ts` — `TransferOrchestrator.transfer(req)`: drives the §L3/§L5 ordered handoff `active(S) → transferring(e+1) → drain → flush ledger → target pulls+verifies → active(T, e+2) → S-release` (claim-before-release, fenced by status+epoch; no double-run, no no-owner gap). Enforces the two timing contracts: drain bound (`transferDrainTimeoutMs` — abandon partial output, never emit past the deadline) and output exclusion (`transferOutputCutoffMs` — T holds its CONTINUATION until the cutoff since `transferring`, so the emission windows are disjoint). Abort paths: CAS-lost, sync-corrupted (escalates, does NOT claim), target-claim-failed (no release). Plus `verifyLedgerSnapshot()` — the §L5 handoff verify (SHA256 match + every entry terminal, reject any in_flight). All I/O injected → deterministic.
8
+
9
+ ## Risk / blast radius
10
+ None — neither module is imported by any boot path yet. Pure functions + new tests only.
11
+
12
+ ## Tests
13
+ - `tests/unit/TransferByNickname.test.ts` — 8 tests: transfer/pin, unknown-nickname rejection, rate-limit (and post-window), already-there no-op, offline + mid-reply confirmation gates.
14
+ - `tests/unit/TransferOrchestrator.test.ts` — 10 tests: full ordered sequence, output-cutoff hold (and skip when already elapsed), drain-abandon, CAS-lost abort, sync-corrupted escalation (no claim/release), target-claim-failed (no release); verifyLedgerSnapshot accept / sha-mismatch / in-flight-reject.
15
+
16
+ ## Follow-ups (this Track F)
17
+ Part 2: Tier-2 integration over the REAL transfer MeshRpc command + real SessionOwnershipRegistry transfer FSM + real ledger-snapshot verify; config tunables (transferDrainTimeoutMs, transferOutputCutoffMs, placementCooldownMs, topicPlacementUpdateMinIntervalMs) with migration parity. Live drain (SessionMigrator) + real git ledger flush + outbound mesh client are Track-H activation (D11).
@@ -0,0 +1,16 @@
1
+ # Side effects — Track G: Multi-Agent-Per-Machine load broker + isolation (§L6)
2
+
3
+ ## What this adds
4
+ Pure cores for L6 (multiple agents sharing one machine), shipped DARK (no boot wiring — consumed by the registry/router when the pool activates in Track H).
5
+
6
+ - `src/core/MachineLoadBroker.ts` — `computeMachineLoad(input)`: the machine-local shared-load accounting. Sums sessions across ALL resident agents (true machine load, not just one agent's view), and — critically — does NOT trust agent self-reports for the authoritative number. It cross-checks each agent's claimed footprint against the OS-measured footprint; an agent measured to use materially MORE than it claims (the lie-idle case) is flagged `suspect-overloaded`, its placement weight reduced, and `sessionCountTrustworthy` set false (the router leans on the authoritative OS `loadAvg`). A lying agent cannot make itself look idle. Plus `checkAgentIsolation(agents)`: the L6 isolation invariant — distinct ports/identities, no nested home dirs (which would let one agent read another's state).
7
+
8
+ ## Risk / blast radius
9
+ None — pure functions, not imported by any boot path yet.
10
+
11
+ ## Tests
12
+ - `tests/unit/MachineLoadBroker.test.ts` — 8: cross-agent session sum, under-report → suspect-overloaded (+ untrustworthy session count), over-report not flagged (conservative), custom tolerance; isolation pass + duplicate-port/duplicate-fingerprint/nested-home detection.
13
+ - `tests/integration/machine-load-placement.test.ts` — 1 (§L6→§L4): a machine whose agent lies idle (high OS loadAvg) is flagged suspect AND NOT selected by PlacementExecutor — the OS truth overrides the faked self-report, so a lying agent cannot attract sessions.
14
+
15
+ ## Activation boundary (Track-H, D11)
16
+ The live OS sampling (process RSS by port/cmdline) + feeding the broker's output into the live MachineCapacity heartbeat are the Track-H wiring; the accounting + verification + isolation logic is fully built + tested now.
@@ -0,0 +1,17 @@
1
+ # Side effects — Track H part 2b: rollout-gate boot wiring + GET /session-pool/e2e-results (§Rollout)
2
+
3
+ ## What this adds
4
+ Wires the rollout gate into the live server (still dark — inert until the pool activates).
5
+ - `src/commands/server.ts` — constructs `SessionPoolE2EResultStore` (HMAC-signed over the agent authToken, file `state/session-pool-e2e-results.json`) + a `StageAdvancer` whose `writeStageConfig` routes through `liveConfig.set(STAGE_CONFIG_PATH, stage, { stageWriteToken })` — the sole stage write path. Passed to AgentServer.
6
+ - `src/server/routes.ts` — `GET /session-pool/e2e-results` (Bearer-auth like all routes): latest result per stage + per-row verify + total history count; 503 when the gate is dark/absent. RouteContext gains `sessionPoolE2EResultStore?`.
7
+ - `src/server/AgentServer.ts` — passthrough option → routeCtx.
8
+
9
+ ## Risk / blast radius
10
+ None — the store is read-only over the route; StageAdvancer is constructed but not driven yet (the rollout job that calls advanceTo/reconcile is a later step). The stage-write path is guarded (Track H part 2a). No behavior change while dark.
11
+
12
+ ## Tests
13
+ - `tests/integration/session-pool-e2e-results-route.test.ts` — 2: 503 when dark; 200 with latest-per-stage + verification + append-only total over a real store + real route.
14
+ - (units from part 1/2a: StageAdvancer 9, E2EResultStore 5, stageWriteGuard 6 — all green.)
15
+
16
+ ## Follow-ups (Track H)
17
+ The rollout job/tick that drives StageAdvancer.advanceTo/reconcile; the CI release-boundary check; rebalance; live-ingress interception + outbound mesh client (D11 activation); real-hardware + test-as-self proof.
@@ -0,0 +1,15 @@
1
+ # Side effects — Track H part 4: outbound MeshRpcClient (§L0 send side, dark)
2
+
3
+ ## What this adds
4
+ `src/core/MeshRpcClient.ts` — the SEND side of MeshRpc, the outbound counterpart to MeshRpcDispatcher (receive). Builds a §L0 envelope (recipient-bound, Ed25519-signed, nonce + timestamp), POSTs to a peer's /mesh/rpc, returns the typed result (maps the dispatcher's reason/status on a non-200; throws only on transport error/timeout so the caller's retry loop catches it). Per-attempt timeout via AbortController; fully injected (fetch/sign/nonce/clock).
5
+
6
+ This is the activation transport (D11): it's what makes the SessionRouter's deliverMessage/spawnOnMachine deps and the TransferOrchestrator's sendTransferRpc dep live ACROSS machines.
7
+
8
+ ## Risk / blast radius
9
+ None — pure transport class, not wired into the router yet (the activation step wires it as the router's outbound deps). No behavior change.
10
+
11
+ ## Tests
12
+ - `tests/integration/mesh-rpc-client-roundtrip.test.ts` — 4 over a real MeshRpcDispatcher + real Ed25519 keys + loopback /mesh/rpc: full router→owner deliverMessage round-trip (200 + result + handler ran); RBAC rejection mapped (non-router → 403 not-router); replayed-nonce → 409 on the second send; transport error throws.
13
+
14
+ ## Status — m2m transport COMPLETE
15
+ Send (MeshRpcClient) + receive (MeshRpcDispatcher) are both built + round-trip-proven. The remaining Track-H work is the ACTIVATION (wire MeshRpcClient as the SessionRouter's outbound deps + flip live-ingress to route through the router) + the staged real-hardware (laptop+mini) + test-as-self nickname-swap proof.
@@ -0,0 +1,13 @@
1
+ # Side effects — Track H part 3: load-rebalance planner (§L4 Stage-3, dark)
2
+
3
+ ## What this adds
4
+ `src/core/RebalancePlanner.ts` — the pure Stage-3 load-rebalance decision. `planRebalance(input, placementExecutor)` proposes a BOUNDED set of transfers off over-saturated machines (session ratio > rebalanceThresholdPercent), only for non-pinned, low-priority, off-cooldown sessions; at most one move per source per cycle (cascade guard); the target is chosen by the same PlacementExecutor.decide(reason:'rebalance') the router uses; a working copy of capacities is updated as moves are proposed so it never piles onto one target. No qualifying session ⇒ no move.
5
+
6
+ ## Risk / blast radius
7
+ None — pure function, not wired into a tick yet. The caller (a heartbeat-interval rebalance tick, post-activation) performs the actual transfers via TransferOrchestrator. Evaluated ONLY on the heartbeat interval by design, never per message.
8
+
9
+ ## Tests
10
+ - `tests/unit/RebalancePlanner.test.ts` — 8: moves off-saturated→free; no-move-under-threshold; never moves pinned/hard-pinned; never moves within cooldown; never moves a non-low-priority/critical session; one-move-per-source cascade cap; distinct-sessions across sources; purity.
11
+
12
+ ## Follow-ups (Track H — the remaining frontier)
13
+ The heartbeat rebalance tick that drives planRebalance → TransferOrchestrator; the live-ingress interception + outbound mesh client (D11 activation); real-hardware (laptop+mini) + test-as-self nickname-swap proof.
@@ -0,0 +1,17 @@
1
+ # Side effects — Track H part 1: rollout gate (StageAdvancer + E2E result store) (§Rollout)
2
+
3
+ ## What this adds
4
+ The mechanical rollout gate for the staged dark→shadow→live-transfer→rebalance ladder. Pure components, shipped DARK (constructed in boot when the pool is enabled; the stage stays 'dark' until a green prior-stage E2E exists).
5
+
6
+ - `src/core/SessionPoolE2EResultStore.ts` — signed, append-only log of each stage's Tier-3 E2E outcome (`StageE2EResult`). recordResult (the E2E harness is the only writer) signs each row; getLatestForStage returns the most recent (a later red supersedes an earlier green); verify() is tamper-evidence; append-only history preserved.
7
+ - `src/core/StageAdvancer.ts` — the SOLE stage-config writer + the gate. advanceTo(stage) REFUSES (e2e-gate-not-passed) unless the prior stage's E2E is green for the CURRENT commit (missing/red/stale-commit/tampered → refused). reconcile() mechanically REVERTS to the prior stage when a live stage records red. 'dark' is the floor.
8
+
9
+ ## Risk / blast radius
10
+ None — pure components, not yet wired into boot (the Config.ts stage-write guard + boot construction + GET /session-pool/e2e-results route land in part 2). No runtime behavior change.
11
+
12
+ ## Tests
13
+ - `tests/unit/SessionPoolE2EResultStore.test.ts` — 5: signed record/read, latest-per-stage (red supersedes green), stage independence, tamper-evidence, missing-file/torn-line tolerance.
14
+ - `tests/unit/StageAdvancer.test.ts` — 9: refuse-no-result / advance-on-green / refuse stale-commit / refuse red / refuse bad-signature / refuse already-at-or-past; reconcile reverts-on-red / stays-on-green / dark-floor-noop.
15
+
16
+ ## Follow-ups (Track H)
17
+ Part 2: Config.ts stage-write guard (stage-write-not-permitted for non-StageAdvancer writes) + boot wiring + GET /session-pool/e2e-results + the CI release-boundary check. The real-hardware + test-as-self proof (nickname-driven mid-conversation swap) is the culmination, which also needs the live-ingress interception + outbound mesh client (D11 activation).
@@ -0,0 +1,16 @@
1
+ # Side effects — Track H part 2a: stage-write guard (§Rollout, Structure > Willpower)
2
+
3
+ ## What this adds
4
+ The structural enforcement that `multiMachine.sessionPool.stage` is StageAdvancer-write-only.
5
+
6
+ - `src/config/stageWriteGuard.ts` (NEW) — `STAGE_CONFIG_PATH`, a module-private `STAGE_WRITE_TOKEN` capability symbol, `assertStageWriteAuthorized(dotPath, token)` (throws `StageWriteNotPermittedError` / code `stage-write-not-permitted` on a stage write without the token; no-op for any other path).
7
+ - `src/config/LiveConfig.ts` — `set(dotPath, value, opts?)` now calls the guard first. A direct runtime write to the rollout stage without the token is refused; every other config write is unaffected (opts is optional, backward-compatible). StageAdvancer's boot wiring passes the token.
8
+
9
+ ## Risk / blast radius
10
+ None for existing callers — the guard only fires for the exact stage path, and `opts` is optional so all current `set()` callsites are unchanged. The stage field is already only written by StageAdvancer; this makes that structural (a stray ad-hoc write now throws instead of silently flipping the rollout).
11
+
12
+ ## Tests
13
+ - `tests/unit/stageWriteGuard.test.ts` — 6: assert throws without/with-forged token, allows with token, no-op for other paths; LiveConfig refuses a direct stage write (unchanged on disk), allows it with the token, and does not gate other writes.
14
+
15
+ ## Follow-ups (Track H)
16
+ StageAdvancer + E2EResultStore boot construction + `GET /session-pool/e2e-results` route + the CI release-boundary check; rebalance; live-ingress interception + outbound mesh client (D11); real-hardware + test-as-self proof.
@@ -0,0 +1,18 @@
1
+ # Side effects — Generalize backoff recovery to the whole transient-API-error class
2
+
3
+ ## What this changes
4
+ Extends the intelligent throttle-recovery lifecycle (RateLimitSentinel) to cover ALL transient API errors (500/502/503, overloaded, timeouts, connection drops), not just rate limits — the 2026-05-29 future-proofing ask (topic 13481). Builds on the per-episode nudge re-arm (same branch).
5
+
6
+ - `src/monitoring/RateLimitSentinel.ts` — new `ApiErrorClass = 'throttle' | 'transient-api'`. `report(name, trigger, { errorClass })` (default 'throttle' → fully back-compatible). Per-class backoff schedule (`transientApiBackoffScheduleMs` = [5s,15s,30s,60s,2m,5m], fast first retry) + per-class user wording in every notice. Lifecycle (backoff→resume→verify→escalate→zombie-veto) unchanged.
7
+ - `src/core/SessionManager.ts` — the generic `TERMINAL_ERROR_PATTERNS` idle path now hands off to the sentinel via a new `apiErrorAtIdle` event when a listener is wired (production), mirroring the existing `rateLimitedAtIdle` handoff; the re-armable immediate nudge remains the fallback when no sentinel is wired (bare/test).
8
+ - `src/commands/server.ts` — wires `sessionManager.on('apiErrorAtIdle', name => rateLimitSentinel.report(name, 'idle-error', { errorClass: 'transient-api' }))`.
9
+
10
+ ## Why
11
+ A generic `API Error: 500` previously only got SessionManager's immediate single nudge — no backoff, no verify, no escalate — while the intelligent RateLimitSentinel was scoped to rate-limit/throttle only. Now a 500 rides the same proven backoff→verify→escalate lifecycle. Spec: docs/specs/SESSION-ERROR-NUDGE-REARM-SPEC.md (Part 2).
12
+
13
+ ## Risk / blast radius
14
+ Low + back-compatible. `errorClass` defaults to 'throttle' so all existing rate-limit behavior is byte-identical (14 existing RateLimitSentinel tests unchanged + green). The generic-error handoff only changes behavior when an `apiErrorAtIdle` listener is wired; otherwise the (now re-armable) immediate nudge fallback runs. Future-proof: driven by the existing TERMINAL_ERROR_PATTERNS list. Server-side → deploys fleet-wide via release.
15
+
16
+ ## Tests
17
+ - `tests/unit/RateLimitSentinel.test.ts` — +4 transient-api tests (fast 5s first backoff vs 30s throttle; transient-API wording; full lifecycle→recovered; state errorClass + listActive short schedule). Throttle suite unchanged.
18
+ - `tests/unit/session-error-nudge.test.ts` — +2: generic-error idle path defers to the sentinel via apiErrorAtIdle when wired; re-armable nudge is the fallback. Plus the Part-1 re-arm + cap behavioral tests.