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,63 @@
1
+ # Instar Upgrade Guide — NEXT
2
+
3
+ > In-flight shipment notes for the **Multi-Machine Session Pool** (active-active,
4
+ > per-session placement + transfer). Built track-by-track behind a dark stage
5
+ > gate; this guide grows as tracks land and becomes the versioned guide at the
6
+ > release cut. Spec: docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md (approved).
7
+
8
+ ## What Changed
9
+
10
+ **Multi-Machine Session Pool — foundation (Track A: Router-Leader Lease).** The
11
+ groundwork for one agent identity running across several machines, all awake at
12
+ once, with each conversation placed on the best-fit machine and transferable
13
+ between machines. This shipment lands the foundation and ships **dark** (the
14
+ entire layer is inert unless explicitly enabled and its rollout stage advanced):
15
+
16
+ - **Clock-jump-proof router lease.** The machine that holds the "who's in charge"
17
+ lease now judges its own lease expiry on a **monotonic clock** instead of the
18
+ wall clock. A wall-clock jump — an NTP correction, a VM pause/resume, a
19
+ sleep/wake, or a CPU-starvation timer slip — can no longer fool a partitioned
20
+ machine into thinking it still holds the lease (or into giving it up early).
21
+ This is the lease-substrate-robustness hardening that the whole session pool
22
+ rests on, and it directly answers the SleepWakeDetector CPU-starvation lesson.
23
+ - **Router role.** The existing leader lease is now also addressable as the
24
+ "router" role (the machine that dispatches conversations to machines). On a
25
+ single machine this is a no-op — it is its own router.
26
+ - **Dark config block.** A new `multiMachine.sessionPool` config block ships
27
+ turned off (`enabled: false`, `stage: 'dark'`, `dryRun: true`). Existing
28
+ agents receive these dark defaults automatically on update.
29
+
30
+ **Track B (part 1: machine nicknames).** Every machine the agent is installed on
31
+ now gets a friendly, auto-assigned **nickname** (e.g. "Mac Mini", "Justins Macbook
32
+ Pro") — the handle you'll use to say "run this on the mini" / "move this to <name>".
33
+ Nicknames are unique within the pool, editable, and derived deterministically from
34
+ the machine's hostname. Still dark (the Machines dashboard tab + the placement
35
+ commands that consume nicknames land in the next part of Track B).
36
+
37
+ ## What to Tell Your User
38
+
39
+ Nothing changes for you yet — this ships turned off. When the full feature is
40
+ ready, your agent will be able to spread conversations across all the machines
41
+ it's installed on (and you'll get a Machines tab in the dashboard to name them
42
+ and move conversations between them by nickname). For now, this update just makes
43
+ the under-the-hood "who's in charge" machinery immune to clock jumps, which makes
44
+ everything steadier. A one-machine agent behaves exactly as before.
45
+
46
+ ## Summary of New Capabilities
47
+
48
+ - Monotonic, clock-jump-proof self-fence for the router/leader lease
49
+ (`LeaseCoordinator`).
50
+ - `MultiMachineCoordinator.isRouter()` — router-role accessor (alias of
51
+ `holdsLease()`).
52
+ - `multiMachine.sessionPool` dark config block (`enabled`/`stage`/`dryRun`) with
53
+ migration parity for existing agents.
54
+
55
+ ## Evidence
56
+
57
+ - Tier-1 unit tests: monotonic self-fence (incl. the backward-wall-clock-jump
58
+ immunity proof and the mid-tick `holdsLease` fence), real-monotonic default
59
+ wiring integrity, `isRouter()`, and the dark config defaults + migration parity
60
+ (adds-into-existing-multiMachine, no-clobber, inert, idempotent).
61
+ - 155 tests green across the lease/config/coordinator/seamlessness cluster after
62
+ the change; tsc clean. Full suite runs in CI.
63
+ - Side-effects review: upgrades/side-effects/session-pool-track-a-router-lease.md
@@ -0,0 +1,17 @@
1
+ # Side effects — Session error-nudge re-arm (fleet-wide fix)
2
+
3
+ ## What this changes
4
+ `src/core/SessionManager.ts` — the post-API-error idle nudge is now re-armable instead of once-per-session-forever.
5
+ - `errorNudgedSessions` is now a per-idle-episode flag: set on nudge, CLEARED in the existing "Session is active" branch (recovery), so a long-running session that hits a SECOND transient API error is nudged again.
6
+ - New `errorNudgeTotal: Map` + `MAX_ERROR_NUDGES_PER_SESSION` (50) bound runaway: a session flapping error→nudge→error without truly recovering stops being nudged and falls through to the normal zombie-kill path. Cleared on `sessionComplete`.
7
+ - New pure exported `shouldErrorNudge(armedThisEpisode, totalNudges, max)` is the gate the production code routes through (unit-testable boundary).
8
+
9
+ ## Why
10
+ A 2026-05-29 autonomous run was found idle after an `API Error: 500` — the SECOND such stop. Root cause: the nudge fired once per session, ever (the guard set was only cleared on sessionComplete). The in-session autonomous Stop hook cannot cover this (it fires only on a clean Stop, not an errored turn). Spec: docs/specs/SESSION-ERROR-NUDGE-REARM-SPEC.md.
11
+
12
+ ## Risk / blast radius
13
+ Low + bounded. Behavior change is strictly MORE recovery (re-nudge on a fresh error episode) with a hard lifetime cap (50) so it can never nudge forever or burn quota. The rate-limit/throttle path is untouched (still hands off to RateLimitSentinel, consumes no nudge token). Server-side code → deploys fleet-wide via the normal release/auto-update; no agent-file or config change, no migrator entry needed.
14
+
15
+ ## Tests
16
+ - `tests/unit/session-error-nudge.test.ts` — +behavioral coverage of shouldErrorNudge (both sides of every branch: armed/not-armed, under/over cap, re-arm-after-recovery) + structural pins (episode flag cleared in the active branch; gate routes through shouldErrorNudge).
17
+ - SessionManager-adjacent suites (behavioral, terminate, zombie-kill-topic-binding, reap-detect, injection, multishot-recovery) all green — no regression to the idle/kill path.
@@ -0,0 +1,16 @@
1
+ # Side effects — classify the session-pool route prefixes (capability-discoverability lint)
2
+
3
+ ## What this fixes
4
+ The CI `capabilities-discoverability` lint requires every route prefix in routes.ts to be classified in `src/server/CapabilityIndex.ts` — either discoverable (CAPABILITY_INDEX) or explicitly internal (INTERNAL_PREFIXES). The session-pool added three unclassified prefixes (`/pool`, `/mesh`, `/session-pool`), failing shard 1/4 on #506. Classified now:
5
+ - **`/pool` → discoverable** (new CAPABILITY_INDEX entry `multiMachinePool`): it's the agent-facing "where is this running? / move this to <nickname>" + Machines-tab status surface. build reports `configured: !!machinePoolRegistry` + endpoints.
6
+ - **`/mesh` → INTERNAL**: machine-to-machine MeshRpc transport (Ed25519-signed peer traffic), never an agent/user capability.
7
+ - **`/session-pool` → INTERNAL**: rollout-gate E2E results — operator observability for a dark feature.
8
+
9
+ ## Risk / blast radius
10
+ None — classification metadata only; no runtime behavior change. `/pool` now appears in /capabilities (correct — it's the documented multi-machine status surface).
11
+
12
+ ## Tests
13
+ - `tests/unit/capabilities-discoverability.test.ts` — 98 pass (was 3 failing on /mesh, /pool, /session-pool). This is the lint that gates route-prefix classification fleet-wide.
14
+
15
+ ## Lesson
16
+ Ran only the pool test files locally, not the cross-cutting lint — the capabilities-discoverability lint runs against ALL routes, so new routes need classification. CI caught it; running the full unit suite (or this lint) locally before push would have caught it sooner.
@@ -0,0 +1,23 @@
1
+ # Side effects — pre-merge review fixes (Multi-Machine Session Pool, PR #506)
2
+
3
+ ## Context
4
+ A 23-agent adversarial review of the session-pool components (each finding verified against the code) confirmed 14 issues. This commit fixes the genuine CORRECTNESS bugs in the dark code before #506 merges; the integration-wiring findings (load-broker → router, isolation-at-join) are the L6 activation phase (D11), tracked separately.
5
+
6
+ ## Fixes (4 correctness bugs)
7
+ - **`src/core/SessionOwnership.ts` (CRITICAL):** `release` now requires `status === 'active'`. Previously a source could release WHILE transferring, advancing the record to `released` before the target T claimed — T's claim was then rejected (claim-out-of-sequence), orphaning the session. New reason `release-requires-active`. Matches the §L3 handoff order (release is the last step, after T is active).
8
+ - **`src/core/PlacementExecutor.ts` (high):** `validateTopicPlacement` now rejects `pinned:true` without a `preferredMachine` (`pinned-without-target`) → placement-blocked, instead of silently treating it as unpinned and letting placement drift off the intended machine.
9
+ - **`src/core/StageAdvancer.ts` (high):** `reconcile()` only reverts on a `red` E2E for the CURRENT commit — a stale red from a prior commit is no longer a false regression signal.
10
+ - **`src/core/SessionRouter.ts` (high #7):** on a stale-ownership ACK, the router now re-forwards to the current owner when the owner OR the epoch changed (previously only on a different owner) — so a SAME-owner epoch advance re-delivers at the corrected epoch instead of needlessly re-placing. A spurious same-owner/same-epoch stale ACK still falls through to re-place (bounded by maxReResolveDepth).
11
+
12
+ ## Risk / blast radius
13
+ None at runtime — all four files are dark (the pool is enabled:false). These are pure-logic corrections with regression tests; no behavior change for a single-machine agent.
14
+
15
+ ## Tests (regression, both sides)
16
+ - SessionOwnership: rejects release-while-transferring (+ T can still claim); allows release from active.
17
+ - PlacementExecutor: pinned-without-target → blocked; pinned+target → placed.
18
+ - StageAdvancer: reconcile does NOT revert on a stale-commit red.
19
+ - SessionRouter: same-owner epoch-advance re-forwards at the new epoch; spurious same-epoch stale → re-place (no loop).
20
+ - Plus closed two flagged test gaps: TransferOrchestrator transport-failed sendTransferRpc → sync-corrupted (no claim); RebalancePlanner spreads moves across free targets (pile-on guard).
21
+
22
+ ## Tracked (activation phase — D11)
23
+ Load-broker → router integration (computeMachineLoad feeding placement weight), checkAgentIsolation at join, sessionCountTrustworthy consumption, and the persistent NonceStore for mesh (vs in-memory) land when the live router/registration is wired during activation.
@@ -0,0 +1,29 @@
1
+ # Side-Effects Review — Session Pool Track A: Router-Leader Lease (monotonic self-fence)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md (approved, review-convergence stamped)
4
+ **Track:** A (L1 — Router-Leader Lease + durable renewal). Ships DARK.
5
+ **Files:** src/core/LeaseCoordinator.ts, src/core/MultiMachineCoordinator.ts, src/core/types.ts, src/config/ConfigDefaults.ts
6
+
7
+ ## What changed
8
+ 1. **`LeaseCoordinator` monotonic self-fence (the core change).** The holder's self-expiry / self-suspend decision — "have I confirmed a renewal within `leaseTtlMs`?" — now measures elapsed on a **monotonic clock** (`monotonicNow()`, default `process.hrtime`) instead of the wall clock (`Date.now()`). A new injectable `monotonicNow` dep; a `markRenewOk()` mutator records the monotonic timestamp at every confirmed acquire/renew/broadcast; `holdsLease()` additionally fences a holder whose monotonic elapsed exceeds the TTL (even before the next `renew()` tick). The wall clock is retained ONLY for the human-readable `expiresAt`/`acquiredAt` display fields and as a conservative second gate in `holdsValidLease`.
9
+ 2. **`MultiMachineCoordinator.isRouter()`** — a semantic alias of `holdsLease()` so session-pool code can ask "am I the router?" (spec §L1: in v0.1 the router lease IS the fenced leader lease). No behavior change.
10
+ 3. **`SessionPoolConfig` type + dark config defaults** — `multiMachine.sessionPool: { enabled: false, stage: 'dark', dryRun: true }` added to `SHARED_DEFAULTS`. Migration parity via the centralized `applyDefaults` path (existence-checked).
11
+
12
+ ## Blast radius
13
+ - **`LeaseCoordinator`** is on the hot path (`holdsLease()` gates ingress polls, scheduler ticks, outbound sends, registry writes). The added monotonic check is a cheap arithmetic compare on each call. Authority (epoch + push-rejection CAS) is UNCHANGED — only the holder's own self-expiry clock source changed.
14
+ - **Wall-clock removal risk:** the wall-clock `lastRenewOkAt` field was removed (it was write-only after the self-suspend moved to monotonic). No other reader existed (verified by grep).
15
+ - **Config:** adding `multiMachine.sessionPool` ONLY (never `multiMachine.enabled`) means it is inert — `applyDefaults` merges it under an existing multiMachine block without clobbering, and adds an inert block to agents without multiMachine. No agent's multi-machine state is switched on.
16
+
17
+ ## Risk + mitigation
18
+ - **Risk:** a holder that previously relied on a wall-clock-forward jump to self-expire early would now wait the full monotonic TTL. **Mitigation:** the monotonic TTL is the CORRECT bound; the wall-clock `isExpired` second gate still fences early on a forward jump (conservative). Net behavior is strictly safer (immune to backward jumps that previously could SUPPRESS the self-fence — the bug this fixes).
19
+ - **Risk:** `process.hrtime` availability. **Mitigation:** present in all supported Node versions; injectable for tests.
20
+ - **Regression surface:** existing `LeaseCoordinator.test.ts` self-suspend tests drove the injected wall clock; updated to inject `monotonicNow` tied to the same fake clock. Verified: 155 tests green across the lease/config/coordinator/seamlessness cluster (LeaseCoordinator, FencedLease, ConfigDefaults, multi-machine-coordinator, multimachine-syncstatus, seamlessness-fault-injection, telegram-seamless-contract, seamlessnessConfig, HttpLeaseTransport, coordinator-independent-mode, poll-owner-lease-wiring, PrimaryAggregatorLease).
21
+
22
+ ## Migration parity
23
+ - Config default added to `ConfigDefaults.SHARED_DEFAULTS` → auto-applied to existing agents on update via `PostUpdateMigrator.migrateConfig` → `applyDefaults`. Idempotent, existence-checked, never clobbers an operator-tuned value. Test coverage in ConfigDefaults.test.ts (adds-into-existing-multiMachine, no-clobber, inert, idempotent).
24
+
25
+ ## Rollback
26
+ - The whole layer is dark (`sessionPool.enabled:false`, `stage:'dark'`). The monotonic self-fence is a pure robustness improvement to the existing lease and is safe to keep regardless; if a revert were ever needed, restore the wall-clock `lastRenewOkAt` self-suspend (single field + the `renew()` comparison). The `monotonicNow` seam is additive (optional dep).
27
+
28
+ ## Agent awareness
29
+ - Track A introduces no user-facing surface (internal lease hardening + dark config). The CLAUDE.md Tier-0 blurb + Playbook deep entry land with Track B's `GET /pool` route (the first user-facing surface) and the Machines dashboard tab, per the spec's Migration Parity & Agent Awareness section.
@@ -0,0 +1,29 @@
1
+ # Side-Effects Review — Session Pool Track B: agent awareness (CLAUDE.md Tier-0 blurb)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §"Migration Parity & Agent Awareness" (approved)
4
+ **Track:** B (agent awareness). Completes Track B's user-facing + awareness surface.
5
+ **Files:** src/scaffold/templates.ts (generateClaudeMd), src/core/PostUpdateMigrator.ts (migrateClaudeMd)
6
+
7
+ ## What changed
8
+ 1. **`generateClaudeMd()` (templates.ts):** added a Tier-0 "Multi-Machine Session Pool" blurb to the capabilities section (right after Cross-Machine Seamlessness) — explains the active-active pool, the **Machines tab**, `GET /pool`, `PATCH /pool/machines/:id` (with curl), and the proactive triggers ("run this on / move this to <nickname>", "where is this running?"). New agents get it via `init`.
9
+ 2. **`migrateClaudeMd()` (PostUpdateMigrator.ts):** content-sniff append (idempotent on the marker `Multi-Machine Session Pool (active-active` / `/pool/machines/`) so EXISTING agents get the same section on update — Migration Parity.
10
+
11
+ ## Blast radius
12
+ - **Docs/awareness only** — no behavioral code path. `generateClaudeMd` produces the CLAUDE.md template for new agents; `migrateClaudeMd` appends to existing agents' CLAUDE.md (idempotent, content-sniffed — never duplicates). No runtime behavior changes.
13
+ - The blurb describes a DARK feature (sessionPool ships off) + points to real endpoints (`/pool`, `/pool/machines/:id`) that exist (Track B(2b)) and the Machines tab that exists (Track B(2c)). No dangling references: "deep mechanics" points at the Machines tab + the spec (the code-enforced Playbook deep-entry is a tracked enhancement, not asserted here).
14
+
15
+ ## Risk + mitigation
16
+ - **Risk:** migration appends to existing CLAUDE.md repeatedly. **Mitigation:** idempotent content-sniff on the unique marker (skips if present) — verified by the existing migration-parity test (13 green).
17
+ - **Risk:** blurb claims a capability that isn't live. **Mitigation:** the blurb explicitly states "Ships DARK behind `multiMachine.sessionPool.stage`" + "a single-machine agent is a no-op" — honest about the dark state.
18
+
19
+ ## Migration parity
20
+ - This IS the migration-parity work for the Agent Awareness Standard: `generateClaudeMd` (init) + `migrateClaudeMd` (existing agents) both add the section, sniff-guarded. migration-parity.test.ts green.
21
+
22
+ ## Rollback
23
+ - Remove the blurb from `generateClaudeMd` + the `migrateClaudeMd` block. Docs-only; nothing depends on it.
24
+
25
+ ## Tests
26
+ - tests/unit/migration-parity.test.ts (13) — enforces generateClaudeMd↔migrateClaudeMd consistency; green with the additions. tsc clean.
27
+
28
+ ## Agent awareness
29
+ - This change IS the agent-awareness deliverable. The code-enforced Playbook deep-trigger (`multiMachine-placement-deep` + SelfKnowledgeTree probe — the spec's Structure>Willpower enhancement for on-demand deep mechanics) is tracked as a follow-up (decision D9). <!-- tracked: session-pool-track-b -->
@@ -0,0 +1,35 @@
1
+ # Side-Effects Review — Session Pool Track B (part 1): Machine Nicknames
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L2 (approved, review-convergence stamped)
4
+ **Track:** B part 1 (machine nicknames — the user-facing handle). Ships DARK (no route/UI yet).
5
+ **Files:** src/core/NicknameAssigner.ts (new), src/core/MachineIdentity.ts, src/core/types.ts
6
+
7
+ ## What changed
8
+ 1. **`NicknameAssigner.ts` (new, pure):** `assignNickname()` / `deriveBaseNickname()` / `isValidNickname()`. Derives a friendly, deterministic, collision-free nickname from a machine's hostname (title-cased) → platform-arch fallback → numeric suffix on collision. No I/O.
9
+ 2. **`MachineRegistryEntry.nickname?` (types.ts):** new OPTIONAL field (backward-compatible — registries written before §L2 simply have no nickname).
10
+ 3. **`MachineIdentityManager` (MachineIdentity.ts):**
11
+ - `registerMachine()` now auto-assigns a nickname when absent — IDEMPOTENT (a re-register keeps the existing nickname and preserves `pairedAt`; it spreads the existing entry so no field is lost). Collision set = every other machine's nickname.
12
+ - `updateNickname(machineId, nickname)` — validates format + pool-uniqueness (case-insensitive, excluding self); a collision THROWS (not silently suffixed). Metadata-only — never moves a session or touches lease/ownership.
13
+ - `resolveNickname(name)` — case-insensitive, trimmed, active-only lookup → machineId | null (consumed by L4 placement/transfer commands; never silently mis-routes).
14
+
15
+ ## Blast radius
16
+ - `registerMachine` is called at identity creation + pairing + role changes. The change is additive (adds a nickname field) and idempotent. The entry-spread (`...(existing ?? {})`) preserves all prior fields incl. seamlessness fields (syncSequence, authoredUnderEpoch, etc.) — verified the existing 64 machine-identity tests pass unchanged, plus `pairedAt` is now preserved on re-register (previously reset — a latent improvement).
17
+ - The nickname field is optional + unused by any runtime path yet (no route/UI until Track B part 2) — purely additive data.
18
+
19
+ ## Risk + mitigation
20
+ - **Risk:** `registerMachine` re-register previously reset `pairedAt`/role to fresh; now it preserves `pairedAt` and existing fields. **Mitigation:** this is strictly more correct (pairing time shouldn't reset on a role change); covered by the idempotency test. Role/lastSeen still update.
21
+ - **Risk:** nickname collisions across machines. **Mitigation:** auto-assign disambiguates with a numeric suffix; manual updateNickname rejects collisions outright. Tested both paths.
22
+
23
+ ## Migration parity
24
+ - No config/migration needed — the `nickname` field is optional and auto-populated on the next `registerMachine` (which runs at boot via `ensureSelfRegistered`). Existing registries without nicknames get one assigned on the next registration; absent until then (handled as optional everywhere).
25
+
26
+ ## Rollback
27
+ - Additive + dark. Remove the field + the three manager methods + NicknameAssigner.ts to revert; no data migration to undo (the field is optional).
28
+
29
+ ## Tests
30
+ - tests/unit/NicknameAssigner.test.ts (9) — derivation, collision, validation, determinism.
31
+ - tests/unit/machine-nickname.test.ts (9) — auto-assign + idempotency + disambiguation + update validation/uniqueness + resolve (case-insensitive, active-only, revoked-excluded).
32
+ - 18 new green; existing 64 machine-identity tests unchanged; tsc clean.
33
+
34
+ ## Agent awareness
35
+ - Deferred to Track B part 2 (the `GET /pool` route + Machines dashboard tab) where the nickname becomes user-visible — that's where the CLAUDE.md Tier-0 blurb + Playbook trigger land, per the spec's Agent Awareness section. <!-- tracked: session-pool-track-b -->
@@ -0,0 +1,36 @@
1
+ # Side-Effects Review — Session Pool Track B (part 2b): /pool API + hardware
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L2 (approved, review-convergence stamped)
4
+ **Track:** B part 2b (the /pool HTTP surface + hardware self-attestation). Ships DARK (registry unwired in server until 2c).
5
+ **Files:** src/core/types.ts, src/core/MachineIdentity.ts, src/server/routes.ts
6
+
7
+ ## What changed
8
+ 1. **`MachineRegistryEntry.hardware?` (types.ts):** new optional field — a machine's self-attested static hardware (`MachineHardware`), synced to peers via the registry. Additive, backward-compatible.
9
+ 2. **`MachineIdentityManager.recordSelfHardware(machineId, hardware)` (MachineIdentity.ts):** writes the machine's own hardware into its registry entry. Caller-supplies the hardware (manager stays free of `os`/assembly concerns). Idempotent — only writes when changed (avoids registry churn/sync on every boot); no-op for an unknown id.
10
+ 3. **Routes (routes.ts):**
11
+ - `RouteContext.machinePoolRegistry?` — optional dep (null/absent ships dark; existing ctx builders unaffected).
12
+ - **`GET /pool`** — router holder (from `coordinator.getSyncStatus()`) + `MachineCapacity[]` (from the registry). Always 200; `enabled:false` + empty view when unwired.
13
+ - **`PATCH /pool/machines/:id`** — `{ nickname }` → `identityManager.updateNickname` (via `ctx.coordinator.managers.identityManager`). 200 ok / 400 malformed-or-collision / 404 unknown / 503 single-machine. Metadata-only — never moves a session or touches lease/ownership.
14
+
15
+ ## Blast radius
16
+ - **Routes are additive + Bearer-auth-protected** (the global server auth middleware; verified the router itself carries no auth, matching every other instar route — the integration test mounts the router bare and exercises it). `GET /pool` reads only `coordinator.getSyncStatus()` (already exists) + the registry; `PATCH` calls the already-tested `updateNickname`.
17
+ - **`machinePoolRegistry` is OPTIONAL** in RouteContext → no existing ctx construction site breaks (tsc clean across the repo).
18
+ - **`recordSelfHardware`** only writes when hardware changed — no extra registry-sync churn on steady-state boots.
19
+ - Production wiring (server instantiates the registry + feeds heartbeats + calls recordSelfHardware at boot) lands in part 2c alongside the Machines dashboard tab; until then `GET /pool` reports `enabled:false`.
20
+
21
+ ## Risk + mitigation
22
+ - **Risk:** `PATCH` reaching through `ctx.coordinator.managers.identityManager`. **Mitigation:** guarded (503 when coordinator/idMgr absent — single-machine install); the underlying `updateNickname` enforces format + uniqueness (rejects collisions). Error mapping verified (400/404/503) by the integration test.
23
+ - **Risk:** hardware field on a synced registry record. **Mitigation:** optional + self-attested + advisory-only (never authority); `recordSelfHardware` idempotency prevents sync churn.
24
+
25
+ ## Migration parity
26
+ - The `hardware` field is optional + auto-populated by `recordSelfHardware` at boot (wired in 2c) — existing registries get it on the next boot; absent until then (handled as optional everywhere).
27
+
28
+ ## Rollback
29
+ - Additive + dark + (registry) unwired. Remove the routes + the optional ctx field + the hardware field/method to revert; no data migration to undo.
30
+
31
+ ## Tests
32
+ - tests/integration/pool-routes.test.ts (6) — the "feature alive" surface: GET /pool 200 with router holder + machine capacities (nickname/hardware/liveness/clock-status); PATCH renames + GET reflects; 400 malformed; 400 collision; 404 unknown; 400 non-string. (This is the Tier-2/3 surface deferred from Track A per decision D5.)
33
+ - tests/unit/machine-nickname.test.ts — recordSelfHardware (stores; idempotent-when-unchanged; unknown-id no-op). 74 manager tests green; tsc clean.
34
+
35
+ ## Agent awareness
36
+ - The CLAUDE.md Tier-0 blurb + Playbook trigger land with part 2c (the dashboard tab + server wiring — the moment the surface is user-reachable), per the spec's Agent Awareness section. <!-- tracked: session-pool-track-b -->
@@ -0,0 +1,31 @@
1
+ # Side-Effects Review — Session Pool Track B (part 2a): Machine-Pool Registry
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L2 (approved, review-convergence stamped)
4
+ **Track:** B part 2a (the live machine-pool registry foundation). Ships DARK (no route/UI/wiring yet).
5
+ **Files:** src/core/MachinePoolRegistry.ts (new), src/core/types.ts
6
+
7
+ ## What changed
8
+ 1. **Types (types.ts):** `MachineHardware` (platform/arch/cpuModel/cpuCores/totalMemBytes/hostname/instarVersion), `ClockSkewStatus` (`ok` | `divergence-detected-once` | `suspect-clock-removed`), `MachineCapacity` (the live per-machine record — nickname + liveness + load + sessions + hardware + clockSkewStatus). All additive.
9
+ 2. **`MachinePoolRegistry.ts` (new):**
10
+ - `captureHardware()` — pure-ish `os` reads → MachineHardware.
11
+ - `clockSkewTransition()` — PURE FSM implementing the §L2 transition table (2-divergent-beats-out / 2-clean-beats-in); `isPlacementEligibleByClock()`.
12
+ - `MachinePoolRegistry` class — records heartbeat observations (stamps `routerReceivedAt` on the ROUTER's clock, runs the clock-skew FSM, fires `onClockQuarantine` on removal), and assembles `MachineCapacity[]` (the data behind GET /pool). Liveness keys on `routerReceivedAt`, NEVER the machine's self-reported time (§L2 clock-skew safety).
13
+
14
+ ## Blast radius
15
+ - **None at runtime yet.** Nothing instantiates `MachinePoolRegistry` — it ships dark, exercised only by its unit tests. Wiring (server instantiation + heartbeat feed) and the `GET /pool` / `PATCH /pool/machines/:id` routes land in part 2b; the Machines dashboard tab in part 2c. The types are additive (optional fields, new interfaces).
16
+
17
+ ## Risk + mitigation
18
+ - **Risk:** clock-skew FSM correctness (a wrong transition could wrongly quarantine or fail to quarantine). **Mitigation:** the FSM is a pure function with a test covering BOTH sides of every transition (1-divergent-then-clean forgiven; 2-divergent removed; 2-clean re-admitted; removed+divergent stays removed). 13 tests green.
19
+ - **Risk:** liveness fooled by a fast machine clock. **Mitigation:** liveness computed from `routerReceivedAt` (router's own clock at heartbeat arrival), tested explicitly (a far-future self-reported time still reads online by router clock, then offline once the router clock passes the threshold).
20
+
21
+ ## Migration parity
22
+ - No config/migration in 2a (no new persisted config; the registry is in-memory, rebuilt from heartbeats). The clock-skew tolerance / NTP-drift config knobs land with the wiring (2b) under the `multiMachine.sessionPool` block via the ConfigDefaults path.
23
+
24
+ ## Rollback
25
+ - Additive + dark + unwired. Delete `MachinePoolRegistry.ts` + the three §L2 types to revert; nothing depends on them yet.
26
+
27
+ ## Tests
28
+ - tests/unit/MachinePoolRegistry.test.ts (13) — captureHardware, the full clock-skew FSM (both sides of each boundary), liveness-by-router-clock, assembly (nickname/capabilities from registry + load from heartbeat), quarantine→re-admit flow, unseen-machine handling. tsc clean.
29
+
30
+ ## Agent awareness
31
+ - Deferred to part 2b/2c (the route + dashboard tab — the first user-facing surface), where the CLAUDE.md Tier-0 blurb + Playbook trigger land per the spec's Agent Awareness section. <!-- tracked: session-pool-track-b -->
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — Session Pool Track B (part 2c-wiring): server wiring + config knobs
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L2 (approved, review-convergence stamped)
4
+ **Track:** B part 2c (wiring) — instantiate the pool registry at server boot. Ships DARK.
5
+ **Files:** src/server/AgentServer.ts, src/commands/server.ts, src/core/types.ts, src/config/ConfigDefaults.ts
6
+
7
+ ## What changed
8
+ 1. **AgentServer:** new optional `machinePoolRegistry?` constructor option → passed straight into the RouteContext (`machinePoolRegistry: options.machinePoolRegistry ?? null`). Pure passthrough, mirrors `coordinator`.
9
+ 2. **server.ts boot:** instantiate `MachinePoolRegistry` (guarded, best-effort try/catch so it can never break boot):
10
+ - `listMachines` reads `coordinator.managers.identityManager.getActiveMachines()` → {machineId, nickname, hardware}.
11
+ - `clockSkewToleranceMs` / `failoverThresholdMs` from config.
12
+ - `recordSelfHardware(self, captureHardware())` once at boot (idempotent — only writes when changed).
13
+ - A 30s **unref'd** interval re-records a self-heartbeat (so this machine shows online with live load) and feeds peer heartbeats from `MachineHeartbeat.listAll()` (peer liveness + the clock-skew FSM). Passed to AgentServer.
14
+ 3. **Config knobs (types.ts + ConfigDefaults):** `SessionPoolConfig` gains `clockSkewToleranceMs` (300000), `maxExpectedNtpDriftMs` (250), `machineRecordEvictionMs` (86400000); added to the dark `multiMachine.sessionPool` ConfigDefaults block → migration parity (existing agents get them on update).
15
+
16
+ ## Blast radius
17
+ - **Boot:** the registry block is fully wrapped in try/catch + every sub-step guarded; a failure logs `[pool] registry not wired` and continues — it cannot break server startup. The interval is `unref()`'d so it never holds the process open.
18
+ - **RouteContext:** `machinePoolRegistry` is optional → no other ctx builder affected. tsc clean across the repo.
19
+ - **Cost:** one registry instance + a 30s timer reading in-memory heartbeat records + `os.loadavg()` — negligible. `recordSelfHardware` writes the registry only when hardware changed (no steady-state sync churn).
20
+ - **Dark:** the registry is always instantiated for observability (GET /pool), but the session-pool BEHAVIOR (placement/transfer) stays gated by `sessionPool.stage` (dark). This wiring only powers the read-only /pool view + Machines tab.
21
+
22
+ ## Risk + mitigation
23
+ - **Risk:** `coordinator.managers.identityManager` access at boot. **Mitigation:** guarded (`coordinator?.managers?.identityManager`); if absent (single-machine without a coordinator) the registry simply isn't wired and GET /pool reports `enabled:false`.
24
+ - **Risk:** the self-heartbeat interval drifting/erroring. **Mitigation:** wrapped in try/catch (best-effort); unref'd; 30s cadence.
25
+ - **Risk:** clock-skew tolerance vs NTP drift misconfiguration. **Mitigation:** defaults honor `tolerance ≥ 2× drift`; asserted in the ConfigDefaults test (the §L2 startup invariant).
26
+
27
+ ## Migration parity
28
+ - The three clock knobs are in the `multiMachine.sessionPool` ConfigDefaults block → auto-applied to existing agents via `applyDefaults` (existence-checked, idempotent). Asserted in ConfigDefaults.test.ts.
29
+
30
+ ## Rollback
31
+ - Remove the boot block + the AgentServer option/ctx field to revert; the config knobs are inert defaults. All dark.
32
+
33
+ ## Tests
34
+ - tests/integration/pool-routes.test.ts (6, prior) covers the route+registry behavior the wiring exposes.
35
+ - ConfigDefaults.test.ts asserts the clock knobs + the `tolerance ≥ 2× drift` invariant. 27 green; tsc clean.
36
+ - The full-boot "GET /pool returns enabled:true on a live server" assertion rides the Track-H real-hardware E2E (booting real servers); the wiring is otherwise simple guarded passthrough. <!-- tracked: session-pool-track-b -->
37
+
38
+ ## Agent awareness
39
+ - The CLAUDE.md Tier-0 blurb + Playbook trigger land with the Machines dashboard tab (next, the user-reachable surface), per the spec's Agent Awareness section. <!-- tracked: session-pool-track-b -->
@@ -0,0 +1,24 @@
1
+ # Side-Effects Review — Session Pool Track C(2): MeshRpc dispatcher (L0)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L0 (approved). **Track:** C part 2 (the receive side). Ships DARK (no HTTP route wired yet — that's the next step).
4
+ **Files:** src/core/MeshRpc.ts (added MeshRpcDispatcher)
5
+
6
+ ## What changed
7
+ - **`MeshRpcDispatcher`** (added to MeshRpc.ts): the transport-agnostic receive side. `dispatch(env)` runs the two gates (verify THEN rbac via `acceptEnvelope`), records the nonce ONLY on full accept (a rejected command never burns a nonce — anti-DoS), audits rejections via an injected `onReject` (SecurityLog), then routes to a registered per-command handler. Returns `{ok, result}` or `{ok:false, reason, status}` with an HTTP status mapping (auth → 401/403, freshness → 409, unimplemented handler → 501). Handlers, verify/rbac deps, nonce-record, and the audit sink are all injected.
8
+ - **Layering (not a stub):** the dispatcher carries the command set; the MUTATION handlers (place/claim/release/transfer) are registered by their owning layers (L3/L4/L5 — Tracks D/E/F). A verified+authorized command with no handler yet returns `no-handler` (501) — the honest "this layer isn't wired yet" state, not a silent stub. Read-class handlers (capacity-report/session-status) can be backed by the L2 registry now.
9
+
10
+ ## Blast radius
11
+ - **None at runtime yet** — nothing constructs a `MeshRpcDispatcher`. Additive to the (already-dark) MeshRpc.ts. The HTTP route (`POST /mesh/rpc`) + production wiring (Ed25519 verify, NonceStore, router-lease read, handler registry) land in the next step.
12
+
13
+ ## Risk + mitigation
14
+ - **Risk:** a rejected command consuming a nonce (replay-DoS) or an accepted one not recording (replay hole). **Mitigation:** nonce recorded EXACTLY on accept (after both gates), before handler dispatch — tested both ways (accepted place burns the nonce; rejected place does NOT; no-handler still burns it because it WAS authorized).
15
+ - **Risk:** wrong status codes leaking auth info. **Mitigation:** explicit reason→status map, tested (401 wrong-recipient, 403 not-router, 409 replayed-nonce, 501 no-handler).
16
+
17
+ ## Rollback
18
+ - Additive + dark + unwired. Remove `MeshRpcDispatcher` to revert.
19
+
20
+ ## Tests
21
+ - tests/unit/MeshRpc.test.ts now 21 (15 prior + 6 dispatcher): accept→record→dispatch; reject-unauthorized audits + no-nonce-burn (403); wrong-recipient (401); no-handler burns nonce (501); read-class routes; replayed-nonce (409). tsc clean.
22
+
23
+ ## Agent awareness
24
+ - Internal m2m infra; agent awareness covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-c -->
@@ -0,0 +1,35 @@
1
+ # Side-Effects Review — Session Pool Track C(1): MeshRpc envelope + RBAC (L0)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L0 (approved, review-convergence stamped)
4
+ **Track:** C part 1 (the signed m2m command layer — pure core). Ships DARK (no HTTP route wired yet).
5
+ **Files:** src/core/MeshRpc.ts (new)
6
+
7
+ ## What changed
8
+ 1. **`MeshRpc.ts` (new, pure):** the §L0 command layer's correctness core — no I/O.
9
+ - `MeshCommand` (place/claim/release/transfer/capacity-report/session-status/secret-share) + `MeshEnvelope` ({sender, recipient, command, epoch, nonce, timestamp, signature}).
10
+ - `canonicalizeEnvelope` — recipient INCLUDED in the signed bytes (recipient-binding).
11
+ - `signEnvelope` — build + Ed25519-sign (crypto injected).
12
+ - `verifyEnvelope` — the 5-step receipt check IN ORDER: (1) recipient===self, (2) signature valid for sender's registered key, (3) sender is a registered peer, (4) nonce unseen, (5) timestamp within tolerance. Returns a typed reason; does NOT record the nonce (caller records only on full accept, so a rejected command never burns a nonce).
13
+ - `checkCommandRBAC` — the per-command role gate (place/transfer → router; claim → placement-target or failover-router; release → owner or failover-router; reports/secret-share → any peer).
14
+ - `acceptEnvelope` — verify THEN rbac (convenience).
15
+ - Crypto (sign/verify), nonce store, peer registry, and router/ownership reads are all INJECTED seams (like FencedLease's LeaseCrypto).
16
+
17
+ ## Blast radius
18
+ - **None at runtime yet.** Nothing imports `MeshRpc` — it ships dark, exercised only by its unit tests. The HTTP transport (`POST /mesh/rpc` + a dispatcher adapting machineAuth's Ed25519 sign/verify + NonceStore + the router/ownership reads) and production wiring land in part 2. Pure additive module.
19
+
20
+ ## Risk + mitigation
21
+ - **Risk:** an authorization hole (the whole point of §L0). **Mitigation:** two independent gates (verify = who; RBAC = may), both unit-tested on BOTH sides of every boundary — incl. the spec's headline cases: a command signed for A replayed verbatim to C → `wrong-recipient` (caught at step 1, before signature); a non-router issuing place/claim → `not-router`/`claim-unauthorized`; a stale-timestamp + a reused-nonce → rejected. 15 tests.
22
+ - **Risk:** recipient-binding bypass. **Mitigation:** the recipient is part of the canonical signed bytes AND checked first; a verbatim replay to a different machine fails the recipient check before the signature is even evaluated (test: "checks recipient BEFORE signature").
23
+ - **Risk:** a rejected command consuming a nonce (DoS). **Mitigation:** verifyEnvelope is pure (reads `seenNonce`, never records); the caller records the nonce only on full acceptance.
24
+
25
+ ## Migration parity
26
+ - None in part 1 (pure module, unwired, no config). The `meshRpcClockToleranceMs` config knob + any route config land with the HTTP wiring (part 2) via the `multiMachine.sessionPool` ConfigDefaults path.
27
+
28
+ ## Rollback
29
+ - Additive + dark + unwired. Delete `MeshRpc.ts` to revert; nothing depends on it.
30
+
31
+ ## Tests
32
+ - tests/unit/MeshRpc.test.ts (15) — recipient-binding in the canonical bytes; verify (happy + wrong-recipient replay + bad-sig + unknown-sender + replayed-nonce + stale-timestamp + recipient-before-sig ordering); RBAC both sides for place/transfer/claim/release + read-class; acceptEnvelope verify-then-rbac ordering. tsc clean.
33
+
34
+ ## Agent awareness
35
+ - No user-facing surface in part 1. The m2m backbone is internal infra; agent awareness for the session pool is covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-c -->
@@ -0,0 +1,31 @@
1
+ # Side-Effects Review — Session Pool Track C(3): /mesh/rpc route + boot wiring (L0)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L0 (approved). **Track:** C part 3 — completes Track C. Ships DARK (transport live; mutation handlers come from D/E/F).
4
+ **Files:** src/server/routes.ts, src/server/AgentServer.ts, src/commands/server.ts, src/core/types.ts, src/config/ConfigDefaults.ts
5
+
6
+ ## What changed
7
+ 1. **`POST /mesh/rpc` route (routes.ts):** parses a signed MeshEnvelope → `ctx.meshRpcDispatcher.dispatch` → maps `{ok,result}` or `{ok:false,reason,status}` (401/403/409/501). 503 when unwired. Auth is the ENVELOPE's own Ed25519 sig (dispatcher), independent of the Bearer middleware. `RouteContext.meshRpcDispatcher?` (optional).
8
+ 2. **AgentServer:** optional `meshRpcDispatcher` option → RouteContext passthrough (mirrors machinePoolRegistry).
9
+ 3. **server.ts boot (guarded):** constructs a `MeshRpcDispatcher` when a machine identity exists — verify adapter (`getSigningPublicKeyPem(sender)` + Ed25519 `verify`), `isRegisteredPeer` = `isMachineActive`, an age-pruned in-memory seen-nonce map, `routerHolder` = `coordinator.getSyncStatus().leaseHolder`, `ownerOf`/`placementTargetOf` = null (wired by D/E), and LIVE read-class handlers (`capacity-report` → registry capacities, `session-status` → self capacity). Mutation handlers register from D/E/F → no-handler (501) until then.
10
+ 4. **Config:** `meshRpcClockToleranceMs` (30000) added to SessionPoolConfig + ConfigDefaults (migration parity).
11
+
12
+ ## Blast radius
13
+ - `/mesh/rpc` is a NEW route guarded by the envelope's own Ed25519 signature + the dispatcher's verify/RBAC (independent of Bearer). Until a machine identity exists it's unwired → 503 (single-machine no-op).
14
+ - Boot block fully try/catch-guarded (`[mesh-rpc] dispatcher not wired` on failure) — cannot break startup. The seen-nonce map is bounded (age-pruned at >5000 entries).
15
+ - `ownerOf`/`placementTargetOf` are null until L3/L4 → `claim`/`release` correctly reject (`claim-unauthorized`) — honest (no ownership data yet), not a bug.
16
+
17
+ ## Risk + mitigation
18
+ - **Risk:** the verify adapter mis-binding a key. **Mitigation:** reuses the exact `getSigningPublicKeyPem` + `verify` path machineAuthMiddleware uses for peer signatures; proven end-to-end with REAL Ed25519 keys over HTTP in the integration test.
19
+ - **Risk:** in-memory nonce set lost on restart (replay window across restart). **Mitigation:** acceptable for the dark read-class transport + the 30s timestamp tolerance bounds any replay window; a durable NonceStore-backed variant is a noted refinement (NonceStore.validate is sequence-oriented, doesn't fit the nonce-only model cleanly).
20
+
21
+ ## Migration parity
22
+ - `meshRpcClockToleranceMs` in the `multiMachine.sessionPool` ConfigDefaults block → existing agents get it on update.
23
+
24
+ ## Rollback
25
+ - Remove the route + AgentServer option + boot block to revert. Dark + guarded.
26
+
27
+ ## Tests
28
+ - tests/integration/mesh-rpc-route.test.ts (6, feature-alive): a REAL Ed25519-signed capacity-report → 200; wrong-recipient replay → 401; non-router place → 403; replayed nonce → 200 then 409; forged sig → 401; malformed body → 400. Plus MeshRpc unit (21) + ConfigDefaults (27). tsc clean.
29
+
30
+ ## Agent awareness
31
+ - Internal m2m infra; covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-c -->
@@ -0,0 +1,31 @@
1
+ # Side-Effects Review — Session Pool Track D(1): SessionOwnership FSM (L3)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L3 (approved). **Track:** D part 1 (the per-session ownership state machine — pure core). Ships DARK (no CAS store/registry wired yet).
4
+ **Files:** src/core/SessionOwnership.ts (new)
5
+
6
+ ## What changed
7
+ - **`SessionOwnership.ts` (new, pure):** the §L3 ownership correctness core — no I/O.
8
+ - `SessionOwnershipRecord` ({sessionKey, ownerMachineId, ownershipEpoch, status: placing|active|transferring|released, transferTo?, nonce, timestamp, updatedAt, signature?}).
9
+ - `applyOwnershipAction(current, action, ctx)` — the pure transition: place (only null/released → no stealing a live session), claim (placing→active by the placed-owner, OR transferring→active by the named target — out-of-sequence/wrong-machine rejected), transfer (active→transferring), release (owner→released). Every transition advances the epoch by +1 (the CAS candidate).
10
+ - `mayRun(record, machineId, epoch)` — the run-fence: a worker runs ONLY while it observes itself `active` owner at the current epoch (no double-run; stale epoch grants nothing).
11
+ - `mayEmit(record, machineId, {now, transferringStartedAt, cutoffMs})` — the output-exclusion contract: the draining source emits tail-only within the cutoff window then nothing; the target holds until the cutoff elapses (disjoint windows); the steady-state active owner emits freely.
12
+ - `ownershipNonceKey(sessionKey, sender, epoch, nonce)` — per-session-scoped nonce key (the same nonce in two sessions does not collide; replay within a session is caught).
13
+
14
+ ## Blast radius
15
+ - **None at runtime yet.** Nothing imports SessionOwnership — it ships dark, exercised only by its unit tests. The CAS store (per-session git single-ref fast-forward push, reusing the §L−1/GitLeaseStore discipline), the distributed registry, and the MeshRpc claim/release handlers (which wire `ownerOf`/`placementTargetOf` into the dispatcher) land in part 2. Pure additive module.
16
+
17
+ ## Risk + mitigation
18
+ - **Risk:** a transition hole that allows two active owners or a no-owner gap (the core §L3 invariant). **Mitigation:** the FSM is pure + tested through the full transfer sequence (active(S)→transferring→active(T)): asserted no state has two active owners at the top epoch and `transferring` always still names S as the draining owner (no gap); out-of-sequence claim (T before transferring) → `claim-out-of-sequence`; wrong-machine claim → `claim-wrong-machine`; place won't steal a live session. 11 tests.
19
+ - **Risk:** interleaved/double user output during the S→T overlap. **Mitigation:** the output-exclusion is a separate fence from the run-fence, tested (source tail-only-within-cutoff, target-holds-until-cutoff, disjoint windows).
20
+
21
+ ## Migration parity
22
+ - None in part 1 (pure module, unwired, no config). The CAS retry/backoff + clock-tolerance config knobs land with the store (part 2) via the `multiMachine.sessionPool` ConfigDefaults path.
23
+
24
+ ## Rollback
25
+ - Additive + dark + unwired. Delete SessionOwnership.ts to revert.
26
+
27
+ ## Tests
28
+ - tests/unit/SessionOwnership.test.ts (11): place→claim→active; place-won't-steal-live; the transfer sequence (one-owner-throughout, claim-before-release); out-of-sequence + wrong-machine claim rejected; transfer-requires-active; release-requires-owner; run-fence (no two active owners, stale epoch fenced); output-exclusion (source tail-only, target holds, steady-state free); per-session nonce isolation. tsc clean.
29
+
30
+ ## Agent awareness
31
+ - Internal ownership layer; session-pool agent awareness covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-d -->
@@ -0,0 +1,27 @@
1
+ # Side-Effects Review — Session Pool Track D(2): SessionOwnershipRegistry (L3)
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L3 (approved). **Track:** D part 2 (the per-session CAS registry). Ships DARK (no server wiring yet).
4
+ **Files:** src/core/SessionOwnershipRegistry.ts (new)
5
+
6
+ ## What changed
7
+ - **`SessionOwnershipRegistry.ts` (new):** the distributed per-session ownership registry. `cas(action, ctx)` runs the §L3 FSM transition (SessionOwnership.applyOwnershipAction), the per-session replay check (ownershipNonceKey), then the durable fast-forward CAS via an injected `store.casWrite` (the git single-ref push, mirroring GitLeaseStore). On a lost CAS (peer advanced first) → `cas-lost` + observed record; the nonce is recorded ONLY on a landed CAS. `ownerOf`/`placementTargetOf` (the §L4 RBAC reads), casConflicts/casRetryExhaustions metrics, and `ownershipRetryDelayMs` (lowest-machineId-first ordering HINT — never the arbiter; the remote ref-update decides). Store + nonce store are injected seams.
8
+
9
+ ## Blast radius
10
+ - **None at runtime yet** — nothing constructs the registry. The server wiring (instantiate with a git-backed store + the per-session NonceStore + feed `ownerOf`/`placementTargetOf` into the MeshRpc dispatcher RBAC + register the `claim`/`release` handlers) lands in part 3. Pure additive module; reuses the proven GitLeaseStore CAS discipline per-session.
11
+
12
+ ## Risk + mitigation
13
+ - **Risk:** split ownership under cross-machine contention. **Mitigation:** the CAS is the §L−1 single-ref fast-forward — exactly one candidate fast-forwards from epoch e; the loser is rejected non-fast-forward and observes the winner's e+1. Tested with a FakeStore that simulates a peer landing mid-flight: the loser gets `cas-lost` + observed the winner's record (NOT chosen by machineId).
14
+ - **Risk:** replay re-opening a window. **Mitigation:** per-session nonce key {sessionKey,sender,epoch} (tested: same nonce across two sessions both land; replay within a session caught); nonce recorded only on a landed CAS.
15
+ - **Risk:** retry storms. **Mitigation:** `ownershipRetryDelayMs` bounded exponential jitter; lowest-machineId-first is an ordering hint only.
16
+
17
+ ## Migration parity
18
+ - None in part 2 (pure module, unwired). The CAS retry/backoff + clock-tolerance config knobs land with the server wiring (part 3) via the `multiMachine.sessionPool` ConfigDefaults path.
19
+
20
+ ## Rollback
21
+ - Additive + dark + unwired. Delete SessionOwnershipRegistry.ts to revert.
22
+
23
+ ## Tests
24
+ - tests/unit/SessionOwnershipRegistry.test.ts (6): place→claim lands + ownerOf; two-machine CAS at epoch+1 → one wins via ref-update + loser cas-lost + observed-the-winner; per-session nonce isolation (same nonce, two sessions, both land); FSM rejection propagated (out-of-sequence claim); retry-delay ordering hint bounded. tsc clean.
25
+
26
+ ## Agent awareness
27
+ - Internal ownership layer; covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-d -->
@@ -0,0 +1,30 @@
1
+ # Side-Effects Review — Session Pool Track D(3): ownership wiring + MeshRpc handlers (L3) — Track D complete
2
+
3
+ **Spec:** docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md §L3 (approved). **Track:** D part 3 — completes Track D. Ships DARK.
4
+ **Files:** src/core/MeshRpc.ts, src/core/SessionOwnershipRegistry.ts, src/commands/server.ts, src/server/routes.ts, src/server/AgentServer.ts
5
+
6
+ ## What changed
7
+ 1. **MeshRpc:** `MeshCommandHandler` now receives the full envelope (so handlers can use `env.nonce` for the per-session ownership CAS); dispatcher passes it.
8
+ 2. **SessionOwnershipRegistry:** added `InMemorySessionOwnershipStore` (fast-forward CAS, single-machine-correct; the cross-machine git-backed store is the Track-H swap — the registry/FSM/CAS are store-agnostic).
9
+ 3. **server.ts boot:** constructs the `SessionOwnershipRegistry` (in-memory store + a per-session nonce set), feeds `ownerOf`/`placementTargetOf` into the MeshRpc dispatcher RBAC (replacing the prior null stubs), and registers the place/claim/transfer/release MeshRpc handlers → the registry CAS. Guarded; dark.
10
+ 4. **RouteContext + AgentServer:** `sessionOwnershipRegistry?` passthrough (for L4 placement + observability).
11
+
12
+ ## Blast radius
13
+ - All within the existing guarded MeshRpc boot block (dark; built only when a machine identity exists). The mutation handlers route to the registry CAS; on a single machine the in-memory store is correct (no cross-machine contention). `/mesh/rpc` place/claim/transfer/release are now live (RBAC-gated); read-class unchanged.
14
+ - `MeshCommandHandler` signature change is additive (handlers may ignore the new `env` arg); the dispatcher + its tests updated.
15
+
16
+ ## Risk + mitigation
17
+ - **Risk:** an unauthorized claim/place mutating ownership. **Mitigation:** TWO gates — MeshRpc RBAC (place→router, claim→placement-target) refuses at the door (403), THEN the registry CAS (FSM + fast-forward) is the correctness fence. Proven end-to-end: integration test shows non-router place → 403 (no ownership written), non-target claim → 403 (ownership unchanged), router-place→target-claim → ownerOf reflects.
18
+ - **Risk:** single-machine in-memory store ≠ cross-machine durability. **Mitigation:** correct for single-machine + dark v0.1; the git-backed cross-machine store is a tracked Track-H-proof swap (store is an injected seam) — see decision D10.
19
+
20
+ ## Migration parity
21
+ - No new config (meshRpcClockToleranceMs already in ConfigDefaults from Track C). The ownership store is in-memory (no persisted config).
22
+
23
+ ## Rollback
24
+ - Remove the registry construction + handler wiring from the boot block + the ctx field to revert. Dark + guarded.
25
+
26
+ ## Tests
27
+ - tests/integration/session-ownership-mesh.test.ts (3, feature-alive): router place→target claim→ownerOf reflects; non-router place → 403 not-router (no write); non-target claim → 403 claim-unauthorized (ownership unchanged) — real Ed25519 over HTTP. Plus SessionOwnership (11) + SessionOwnershipRegistry (6) + MeshRpc (21). tsc clean.
28
+
29
+ ## Agent awareness
30
+ - Internal ownership/mesh layer; covered by Track B's CLAUDE.md blurb. <!-- tracked: session-pool-track-d -->
@@ -0,0 +1,20 @@
1
+ # Side effects — Track E part 3: deliverMessage boot wiring + factory + config tunables (§L4)
2
+
3
+ ## What this adds
4
+ The owner-side receive wiring for the §L4 deliverMessage command, plus the §L4 router/placement config tunables. Ships DARK — the receive handler is registered on the boot mesh dispatcher (already gated by machine-identity presence; inert on a single-machine agent), and the config block stays `enabled:false, stage:dark`.
5
+
6
+ - `src/core/DeliverMessageHandler.ts` (NEW) — `createDeliverMessageHandler(deps)`: the single shared factory for the owner-side receive contract (stale-ownership fence → idempotent durable-receipt-before-processing → queued/duplicate ACK). Imported by BOTH `server.ts` boot AND the tests, so the production handler and the tested handler cannot drift (Structure > Willpower).
7
+ - `src/commands/server.ts` — the boot mesh dispatcher now registers `deliverMessage` via the factory, wired to the real `SessionOwnershipRegistry` (owner epoch) + the real `MessageProcessingLedger` (idempotency key = messageId; SQLite-backed durable receipt), with an in-memory fallback set when the ledger is unavailable.
8
+ - `src/config/ConfigDefaults.ts` + `src/core/types.ts` — added the §L4 tunables to the dark `sessionPool` block: `deliverMessageTimeoutMs` (5000), `deliverMessageMaxRetries` (3), `placementHysteresisDelta` (0.15), `ownershipCasMaxRetries` (5). `applyDefaults` is add-missing-only + recursive, so existing agents backfill ONLY the new sub-fields on update without clobbering an operator's tuned values (migration parity).
9
+
10
+ ## Risk / blast radius
11
+ None at runtime for a single-machine agent: the deliverMessage handler is only reachable through `/mesh/rpc`, which requires a registered peer machine + a router-holder RBAC check (router-only). The config additions are inert (enabled:false). No live-ingress path is touched — live-ingress interception + the outbound mesh client are Track-H's stage-gated activation (decision D11), not a deferral.
12
+
13
+ ## Tests
14
+ - `tests/unit/DeliverMessageHandler.test.ts` — 5 tests: queued (first + onAccepted-once), duplicate (idempotent, no re-process), stale-ownership, not-stale-on-match/lead, null-epoch.
15
+ - `tests/e2e/session-pool-delivermessage-e2e.test.ts` — 4 "feature alive" tests over the PRODUCTION factory + real MeshRpcDispatcher + real /mesh/rpc HTTP route + real SQLite MessageProcessingLedger + real SessionOwnershipRegistry: endpoint alive (200 + queued), redelivery → duplicate (ledger dedupe), stale-ownership after the owner epoch advances, non-router → 403.
16
+ - `tests/unit/ConfigDefaults.test.ts` — +1: the Track-E tunables backfill into a partial sessionPool block (add-missing, operator stage preserved).
17
+
18
+ ## Agent awareness / migration parity
19
+ - Agent awareness: already delivered in Track B's Tier-0 CLAUDE.md blurb (template + migrator) — it documents the "run this on / move this to <nickname>" triggers; Track E's recognizer makes them fire. No new CLAUDE.md change.
20
+ - Migration parity: config sub-fields auto-backfill via `applyDefaults` (asserted by the new test). The deliverMessage receive handler is core boot code (no agent-installed-file change).
@@ -0,0 +1,17 @@
1
+ # Side effects — Track E part 1: Placement core + nickname recognizer (§L4)
2
+
3
+ ## What this adds
4
+ Two PURE, side-effect-free decision components for the Session Router (Multi-Machine Session Pool §L4). Both ship DARK (no wiring into the live router yet — that lands in Track E part 2 with the dispatch path + integration test). No runtime behavior changes for any deployed agent.
5
+
6
+ - `src/core/PlacementExecutor.ts` — the single canonical placement component. `decide(PlacementRequest): PlacementDecision` is pure and deterministic; the caller performs the CAS + side effects. Placement policy is structured DATA (`PlacementPolicy`), schema-validated at construction via `validatePlacementPolicy()` (a malformed policy throws — the router refuses to act rather than silently defaulting). Ordering is `hard-constraint → pin → sticky → least-loaded`. Hard pin / unmet capability with no capable machine returns `outcome: 'queued'` + an `escalationReason` — it NEVER silently mis-places. `validateTopicPlacement()` validates per-topic metadata on read; corrupt metadata returns `outcome: 'placement-blocked'`.
7
+ - `src/core/NicknameCommand.ts` — `recognizeNicknameCommand(text, knownNicknames)`: a conservative, deterministic recognizer for the user's "move/run this on <nickname>" requests (the headline test-as-self swap). It only matches when an explicit relocation verb AND a known nickname are both present, so a bare machine mention ("the mini is fast") never triggers a transfer. Resolves the longest matching nickname; classifies `pin` vs `transfer` intent.
8
+
9
+ ## Risk / blast radius
10
+ None at runtime — neither module is imported by any boot path yet. Pure functions + new test files only. No config, no migration, no API surface in this commit.
11
+
12
+ ## Tests
13
+ - `tests/unit/PlacementExecutor.test.ts` — 15 tests: least-loaded, offline/clock-quarantine exclusion, hard-pin placed/queued, soft-preference degrade, capability filter + escalation, sticky + hysteresis + rebalance-bypass, corrupt-metadata block, no-online-machine queue, decide() purity, and policy-validation rejection on every malformed field.
14
+ - `tests/unit/NicknameCommand.test.ts` — 11 tests: verb coverage, run-on/pin intents, longest-nickname resolution, case-insensitivity, and the critical negatives (bare mention, no preposition, unknown target, empty input).
15
+
16
+ ## Follow-ups (tracked in this same Track E)
17
+ Part 2 wires PlacementExecutor + NicknameCommand into the router dispatch path (owned→forward over MeshRpc, unowned→decide→CAS-claim→spawn) with the deliverMessage contract and a Tier-2 integration test.