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,128 @@
1
+ /**
2
+ * SessionOwnershipRegistry — the distributed per-session ownership registry
3
+ * (Multi-Machine Session Pool §L3). Holds a SessionOwnershipRecord per session;
4
+ * answers "which machine holds session X"; and mutates ownership via a per-session
5
+ * CAS at `ownershipEpoch+1`, reusing the §L−1 single-ref fast-forward discipline
6
+ * (the same pattern as GitLeaseStore/LeaseCoordinator) but at per-session
7
+ * granularity — thousands of independent CAS points, one ref-file per session.
8
+ *
9
+ * The dangerous parts (CAS contention, per-session replay) are driven through
10
+ * injected seams so they are unit-testable with in-memory fakes:
11
+ * - `store` — the durable CAS substrate (git single-ref fast-forward push per
12
+ * session). `casWrite(candidate)` returns ok:true if the candidate landed
13
+ * (fast-forwarded from the current epoch), or ok:false + the freshly-observed
14
+ * record after a reject+reread (a peer won the race). MUST NOT force-push.
15
+ * - `seenNonce`/`recordNonce` — the per-session-scoped replay guard
16
+ * (keyed on {sessionKey, sender, epoch} — see SessionOwnership.ownershipNonceKey).
17
+ *
18
+ * The FSM (legal transitions, run-fence, output-exclusion) lives in
19
+ * SessionOwnership.ts; this module enforces the CAS + replay + retry-backoff +
20
+ * lifecycle around it.
21
+ */
22
+ import { applyOwnershipAction, ownershipNonceKey, } from './SessionOwnership.js';
23
+ /**
24
+ * In-memory per-session ownership store with fast-forward CAS — correct for a
25
+ * single machine (no cross-machine contention) and for the dark v0.1 state.
26
+ * The CROSS-MACHINE durable store (git single-ref-per-session push, mirroring
27
+ * GitLeaseStore) swaps in for the Track-H real-hardware proof; the registry/FSM/
28
+ * CAS logic above is store-agnostic. A candidate lands only if it fast-forwards
29
+ * from the current epoch (`candidate.ownershipEpoch === current.epoch + 1`).
30
+ */
31
+ export class InMemorySessionOwnershipStore {
32
+ recs = new Map();
33
+ read(sessionKey) {
34
+ return this.recs.get(sessionKey) ?? null;
35
+ }
36
+ casWrite(candidate) {
37
+ const current = this.recs.get(candidate.sessionKey) ?? null;
38
+ const curEpoch = current?.ownershipEpoch ?? 0;
39
+ if (candidate.ownershipEpoch === curEpoch + 1) {
40
+ this.recs.set(candidate.sessionKey, candidate);
41
+ return { ok: true, observed: candidate };
42
+ }
43
+ return { ok: false, observed: current };
44
+ }
45
+ all() {
46
+ return [...this.recs.values()];
47
+ }
48
+ }
49
+ export class SessionOwnershipRegistry {
50
+ d;
51
+ casConflicts = 0;
52
+ casRetryExhaustions = 0;
53
+ constructor(deps) {
54
+ this.d = deps;
55
+ }
56
+ now() {
57
+ return (this.d.now ?? Date.now)();
58
+ }
59
+ read(sessionKey) {
60
+ return this.d.store.read(sessionKey);
61
+ }
62
+ /** The current owner machine of a session (null if none / released). */
63
+ ownerOf(sessionKey) {
64
+ const r = this.d.store.read(sessionKey);
65
+ if (!r || r.status === 'released')
66
+ return null;
67
+ return r.ownerMachineId;
68
+ }
69
+ /**
70
+ * The machine the router last assigned for a session (§L4 RBAC `claim` check):
71
+ * the placed-owner while `placing`, or the transfer target while `transferring`.
72
+ */
73
+ placementTargetOf(sessionKey) {
74
+ const r = this.d.store.read(sessionKey);
75
+ if (!r)
76
+ return null;
77
+ if (r.status === 'placing')
78
+ return r.ownerMachineId;
79
+ if (r.status === 'transferring')
80
+ return r.transferTo ?? null;
81
+ return null;
82
+ }
83
+ /**
84
+ * Apply an ownership action via CAS (§L3). Runs the FSM transition, the
85
+ * per-session replay check, then the durable fast-forward CAS. On a lost CAS
86
+ * (a peer advanced the epoch first) returns `cas-lost` + the observed record —
87
+ * the caller backs off (ownershipCasRetryBackoffMs) or stands down. The nonce
88
+ * is recorded ONLY on a landed CAS (a rejected attempt never burns a nonce).
89
+ */
90
+ cas(action, ctx) {
91
+ const current = this.d.store.read(ctx.sessionKey);
92
+ const t = applyOwnershipAction(current, action, { sessionKey: ctx.sessionKey, nonce: ctx.nonce, now: this.now() });
93
+ if (!t.ok)
94
+ return { ok: false, reason: t.reason, observed: current };
95
+ const nkey = ownershipNonceKey(ctx.sessionKey, ctx.sender, t.next.ownershipEpoch, ctx.nonce);
96
+ if (this.d.seenNonce(nkey))
97
+ return { ok: false, reason: 'replayed-nonce', observed: current };
98
+ const res = this.d.store.casWrite(t.next);
99
+ if (!res.ok) {
100
+ this.casConflicts++;
101
+ this.d.logger?.(`[ownership] CAS lost for ${ctx.sessionKey} → observed epoch ${res.observed?.ownershipEpoch ?? 0}`);
102
+ return { ok: false, reason: 'cas-lost', observed: res.observed };
103
+ }
104
+ this.d.recordNonce(nkey);
105
+ return { ok: true, record: t.next };
106
+ }
107
+ /** Observability counters (spec §L3: /pool ownership.casConflicts/casRetryExhaustions). */
108
+ noteRetryExhaustion() {
109
+ this.casRetryExhaustions++;
110
+ }
111
+ metrics() {
112
+ return { casConflicts: this.casConflicts, casRetryExhaustions: this.casRetryExhaustions };
113
+ }
114
+ }
115
+ /**
116
+ * Exponential-jitter retry delay for a lost CAS (spec §L3): `ownershipCasRetryBackoffMs`
117
+ * 50→500ms, biased so the lowest-machineId contender retries first (a client-side
118
+ * ordering hint ONLY — never the CAS arbiter; the remote ref-update decides the winner).
119
+ */
120
+ export function ownershipRetryDelayMs(retryCount, selfMachineId, contenderMachineId, opts = {}) {
121
+ const base = opts.baseMs ?? 50;
122
+ const max = opts.maxMs ?? 500;
123
+ const backoff = Math.min(max, base * 2 ** Math.max(0, retryCount - 1));
124
+ // Lowest machineId retries first (shorter delay) — ordering hint, not arbitration.
125
+ const yieldBias = selfMachineId > contenderMachineId ? backoff : Math.floor(backoff / 2);
126
+ return Math.min(max, yieldBias);
127
+ }
128
+ //# sourceMappingURL=SessionOwnershipRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionOwnershipRegistry.js","sourceRoot":"","sources":["../../src/core/SessionOwnershipRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,oBAAoB,EACpB,iBAAiB,GAIlB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;GAOG;AACH,MAAM,OAAO,6BAA6B;IAChC,IAAI,GAAG,IAAI,GAAG,EAAkE,CAAC;IACzF,IAAI,CAAC,UAAkB;QACrB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IACD,QAAQ,CAAC,SAAiE;QACxE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QAC5D,MAAM,QAAQ,GAAG,OAAO,EAAE,cAAc,IAAI,CAAC,CAAC;QAC9C,IAAI,SAAS,CAAC,cAAc,KAAK,QAAQ,GAAG,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YAC/C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAC3C,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC1C,CAAC;IACD,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;CACF;AA8BD,MAAM,OAAO,wBAAwB;IAClB,CAAC,CAA+B;IACzC,YAAY,GAAG,CAAC,CAAC;IACjB,mBAAmB,GAAG,CAAC,CAAC;IAEhC,YAAY,IAAkC;QAC5C,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IACO,GAAG;QACT,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,UAAkB;QACrB,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAED,wEAAwE;IACxE,OAAO,CAAC,UAAkB;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC;QAC/C,OAAO,CAAC,CAAC,cAAc,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,UAAkB;QAClC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,cAAc,CAAC;QACpD,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc;YAAE,OAAO,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC;QAC7D,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;OAMG;IACH,GAAG,CAAC,MAAuB,EAAE,GAA0D;QACrF,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAClD,MAAM,CAAC,GAAG,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACnH,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAErE,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;QAC7F,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QAE9F,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,4BAA4B,GAAG,CAAC,UAAU,qBAAqB,GAAG,CAAC,QAAQ,EAAE,cAAc,IAAI,CAAC,EAAE,CAAC,CAAC;YACpH,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,CAAC;IAED,2FAA2F;IAC3F,mBAAmB;QACjB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IACD,OAAO;QACL,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC5F,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,aAAqB,EACrB,kBAA0B,EAC1B,OAA4C,EAAE;IAE9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC;IAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;IACvE,mFAAmF;IACnF,MAAM,SAAS,GAAG,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IACzF,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * SessionPoolE2EResultStore — the durable, signed, append-only record of each
3
+ * rollout stage's Tier-3 E2E outcome (Multi-Machine Session Pool §Rollout). It is
4
+ * the mechanical evidence the StageAdvancer gates on: a stage does NOT activate
5
+ * until its prior stage's E2E recorded `green` for the current commit. Per
6
+ * "Structure > Willpower" the gate is grounded in this implemented record, not a
7
+ * human's say-so.
8
+ *
9
+ * Append-only + signed (tamper-evident, AuditTrail-style): a reversion or a new run
10
+ * APPENDS a row, NEVER overwrites a prior one, so the full green/red history per
11
+ * commit is preserved. The Tier-3 E2E harness is the ONLY writer (`recordResult`).
12
+ */
13
+ export type StageE2EOutcome = 'green' | 'red';
14
+ export interface StageE2EResult {
15
+ stage: number;
16
+ result: StageE2EOutcome;
17
+ commitSha: string;
18
+ ranAt: string;
19
+ evidenceRef: string;
20
+ signature: string;
21
+ }
22
+ export interface SessionPoolE2EResultStoreDeps {
23
+ /** Absolute path to the append-only results file. */
24
+ filePath: string;
25
+ /** Sign the canonical (signature-excluded) row. Production: HMAC/Ed25519 over a state secret. */
26
+ sign: (canonical: string) => string;
27
+ /** Verify a signature against the canonical row. */
28
+ verifySig: (canonical: string, signature: string) => boolean;
29
+ now?: () => number;
30
+ }
31
+ /** Canonical bytes a row's signature covers — field-ordered, signature EXCLUDED. */
32
+ export declare function canonicalE2ERow(r: Omit<StageE2EResult, 'signature'>): string;
33
+ export declare class SessionPoolE2EResultStore {
34
+ private readonly d;
35
+ constructor(deps: SessionPoolE2EResultStoreDeps);
36
+ /** Append a signed result row. The ONLY writer (called by the Tier-3 E2E harness). */
37
+ recordResult(stage: number, result: StageE2EOutcome, commitSha: string, evidenceRef: string): StageE2EResult;
38
+ /** All rows (oldest→newest). Tolerant of a missing file / a torn trailing line. */
39
+ all(): StageE2EResult[];
40
+ /** The MOST RECENT row for a stage (a later `red` supersedes an earlier `green`). */
41
+ getLatestForStage(stage: number): StageE2EResult | null;
42
+ /** Verify a row's signature (tamper check). */
43
+ verify(row: StageE2EResult): boolean;
44
+ }
45
+ //# sourceMappingURL=SessionPoolE2EResultStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionPoolE2EResultStore.d.ts","sourceRoot":"","sources":["../../src/core/SessionPoolE2EResultStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,KAAK,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,oDAAoD;IACpD,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG,MAAM,CAE5E;AAED,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAgC;gBACtC,IAAI,EAAE,6BAA6B;IAI/C,sFAAsF;IACtF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,cAAc;IAS5G,mFAAmF;IACnF,GAAG,IAAI,cAAc,EAAE;IAWvB,qFAAqF;IACrF,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAMvD,+CAA+C;IAC/C,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;CAGrC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * SessionPoolE2EResultStore — the durable, signed, append-only record of each
3
+ * rollout stage's Tier-3 E2E outcome (Multi-Machine Session Pool §Rollout). It is
4
+ * the mechanical evidence the StageAdvancer gates on: a stage does NOT activate
5
+ * until its prior stage's E2E recorded `green` for the current commit. Per
6
+ * "Structure > Willpower" the gate is grounded in this implemented record, not a
7
+ * human's say-so.
8
+ *
9
+ * Append-only + signed (tamper-evident, AuditTrail-style): a reversion or a new run
10
+ * APPENDS a row, NEVER overwrites a prior one, so the full green/red history per
11
+ * commit is preserved. The Tier-3 E2E harness is the ONLY writer (`recordResult`).
12
+ */
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ /** Canonical bytes a row's signature covers — field-ordered, signature EXCLUDED. */
16
+ export function canonicalE2ERow(r) {
17
+ return JSON.stringify([r.stage, r.result, r.commitSha, r.ranAt, r.evidenceRef]);
18
+ }
19
+ export class SessionPoolE2EResultStore {
20
+ d;
21
+ constructor(deps) {
22
+ this.d = deps;
23
+ }
24
+ /** Append a signed result row. The ONLY writer (called by the Tier-3 E2E harness). */
25
+ recordResult(stage, result, commitSha, evidenceRef) {
26
+ const ranAt = new Date(this.d.now ? this.d.now() : Date.now()).toISOString();
27
+ const unsigned = { stage, result, commitSha, ranAt, evidenceRef };
28
+ const row = { ...unsigned, signature: this.d.sign(canonicalE2ERow(unsigned)) };
29
+ fs.mkdirSync(path.dirname(this.d.filePath), { recursive: true });
30
+ fs.appendFileSync(this.d.filePath, JSON.stringify(row) + '\n');
31
+ return row;
32
+ }
33
+ /** All rows (oldest→newest). Tolerant of a missing file / a torn trailing line. */
34
+ all() {
35
+ let content;
36
+ try {
37
+ content = fs.readFileSync(this.d.filePath, 'utf8');
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ const out = [];
43
+ for (const line of content.split('\n')) {
44
+ if (!line.trim())
45
+ continue;
46
+ try {
47
+ out.push(JSON.parse(line));
48
+ }
49
+ catch { /* skip a torn line */ }
50
+ }
51
+ return out;
52
+ }
53
+ /** The MOST RECENT row for a stage (a later `red` supersedes an earlier `green`). */
54
+ getLatestForStage(stage) {
55
+ let latest = null;
56
+ for (const r of this.all())
57
+ if (r.stage === stage)
58
+ latest = r;
59
+ return latest;
60
+ }
61
+ /** Verify a row's signature (tamper check). */
62
+ verify(row) {
63
+ return this.d.verifySig(canonicalE2ERow(row), row.signature);
64
+ }
65
+ }
66
+ //# sourceMappingURL=SessionPoolE2EResultStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionPoolE2EResultStore.js","sourceRoot":"","sources":["../../src/core/SessionPoolE2EResultStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAuB7B,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,CAAoC;IAClE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,OAAO,yBAAyB;IACnB,CAAC,CAAgC;IAClD,YAAY,IAAmC;QAC7C,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAChB,CAAC;IAED,sFAAsF;IACtF,YAAY,CAAC,KAAa,EAAE,MAAuB,EAAE,SAAiB,EAAE,WAAmB;QACzF,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7E,MAAM,QAAQ,GAAsC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QACrG,MAAM,GAAG,GAAmB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC/F,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/D,OAAO,GAAG,CAAC;IACb,CAAC;IAED,mFAAmF;IACnF,GAAG;QACD,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,EAAE,CAAC;QAAC,CAAC;QAChF,MAAM,GAAG,GAAqB,EAAE,CAAC;QACjC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,SAAS;YAC3B,IAAI,CAAC;gBAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,qFAAqF;IACrF,iBAAiB,CAAC,KAAa;QAC7B,IAAI,MAAM,GAA0B,IAAI,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE;YAAE,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK;gBAAE,MAAM,GAAG,CAAC,CAAC;QAC9D,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,+CAA+C;IAC/C,MAAM,CAAC,GAAmB;QACxB,OAAO,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * SessionRouter — the L4 inbound-message dispatch engine (Multi-Machine Session
3
+ * Pool §L4 "Message Routing to Owner" + "Ownership CAS at Dispatch"). For every
4
+ * inbound message the router resolves session ownership and dispatches:
5
+ *
6
+ * 1. Owned + owner alive + owner == self → handle locally (no MeshRpc hop).
7
+ * 2. Owned + owner alive + owner != self → forward via deliverMessage over MeshRpc
8
+ * (signed, recipient-bound, idempotent on messageId; ACK before offset advance).
9
+ * 3. Owned + owner DEAD → owner-dead re-placement (re-place + claim).
10
+ * 4. Transient (placing/transferring) → queue (ownership-contention).
11
+ * 5. Unowned (new) → PlacementExecutor.decide() → SYNCHRONOUS
12
+ * CAS-claim → spawn on the winner (or, on CAS loss, forward to the winner / queue).
13
+ *
14
+ * Per-session ordering: messages for ONE sessionKey are dispatched strictly in
15
+ * inbound order, at-most-one-in-flight (a per-session promise chain). Different
16
+ * sessions dispatch concurrently. All I/O is injected so the dispatch logic itself
17
+ * is deterministic + unit-testable; the deliverMessage dep owns its own per-attempt
18
+ * timeout (it throws on timeout) — the router owns the retry/backoff/fallback loop.
19
+ */
20
+ import type { PlacementExecutor, TopicPlacement } from './PlacementExecutor.js';
21
+ import type { MachineCapacity } from './types.js';
22
+ export interface OwnershipView {
23
+ owner: string | null;
24
+ epoch: number;
25
+ status: 'active' | 'placing' | 'transferring' | null;
26
+ target?: string;
27
+ }
28
+ export interface DeliverAck {
29
+ messageId: string;
30
+ accepted: 'queued' | 'duplicate' | 'stale-ownership';
31
+ }
32
+ export interface InboundMessage {
33
+ sessionKey: string;
34
+ messageId: string;
35
+ payload: unknown;
36
+ topicMetadata?: TopicPlacement;
37
+ }
38
+ export type RouteAction = 'handled-locally' | 'forwarded' | 'spawned' | 'queued' | 'duplicate' | 'owner-dead-replaced' | 'placement-blocked';
39
+ export interface RouteOutcome {
40
+ action: RouteAction;
41
+ owner?: string | null;
42
+ detail?: string;
43
+ /** True once the inbound is durably accepted (ledger ACK or local handling) — the
44
+ * caller may advance the platform offset ONLY when this is true (§L4 ACK protocol). */
45
+ acked: boolean;
46
+ }
47
+ export interface SessionRouterConfig {
48
+ deliverMessageMaxRetries: number;
49
+ deliverMessageRetryBackoffStartMs: number;
50
+ deliverMessageRetryBackoffMaxMs: number;
51
+ /** Bound on stale-ownership re-resolution to avoid an unbounded chase. */
52
+ maxReResolveDepth: number;
53
+ }
54
+ export declare const DEFAULT_ROUTER_CONFIG: SessionRouterConfig;
55
+ export interface SessionRouterDeps {
56
+ selfMachineId: string;
57
+ placement: PlacementExecutor;
58
+ machineRegistry: () => MachineCapacity[];
59
+ resolveOwnership: (sessionKey: string) => OwnershipView;
60
+ isMachineAlive: (machineId: string) => boolean;
61
+ /** SYNCHRONOUS per-session CAS-claim (the §L−1 single-ref fast-forward push). */
62
+ casClaimOwnership: (sessionKey: string, machineId: string, expectedEpoch: number) => {
63
+ ok: boolean;
64
+ epoch: number;
65
+ };
66
+ /** ONE deliverMessage attempt over MeshRpc; throws on transport error/timeout. */
67
+ deliverMessage: (target: string, env: {
68
+ sessionKey: string;
69
+ messageId: string;
70
+ payload: unknown;
71
+ ownershipEpoch: number;
72
+ }) => Promise<DeliverAck>;
73
+ /** Router == owner: process the message on this machine. */
74
+ handleLocally: (msg: InboundMessage) => Promise<void>;
75
+ /** Instruct the chosen machine to spawn/resume the session. */
76
+ spawnOnMachine: (machineId: string, msg: InboundMessage) => Promise<void>;
77
+ queueMessage: (msg: InboundMessage, reason: string) => void;
78
+ raiseAttention: (title: string, body: string) => void;
79
+ markOwnerSuspect?: (machineId: string) => void;
80
+ sleep: (ms: number) => Promise<void>;
81
+ log?: (line: string) => void;
82
+ }
83
+ export declare class SessionRouter {
84
+ private readonly deps;
85
+ private readonly cfg;
86
+ private readonly chains;
87
+ constructor(deps: SessionRouterDeps, cfg?: SessionRouterConfig);
88
+ /** Dispatch an inbound message; serialized per-session (in-order, one-in-flight). */
89
+ route(msg: InboundMessage): Promise<RouteOutcome>;
90
+ private backoff;
91
+ private dispatchOne;
92
+ private forwardToOwner;
93
+ private placeAndClaim;
94
+ }
95
+ //# sourceMappingURL=SessionRouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionRouter.d.ts","sourceRoot":"","sources":["../../src/core/SessionRouter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ /**
2
+ * SessionRouter — the L4 inbound-message dispatch engine (Multi-Machine Session
3
+ * Pool §L4 "Message Routing to Owner" + "Ownership CAS at Dispatch"). For every
4
+ * inbound message the router resolves session ownership and dispatches:
5
+ *
6
+ * 1. Owned + owner alive + owner == self → handle locally (no MeshRpc hop).
7
+ * 2. Owned + owner alive + owner != self → forward via deliverMessage over MeshRpc
8
+ * (signed, recipient-bound, idempotent on messageId; ACK before offset advance).
9
+ * 3. Owned + owner DEAD → owner-dead re-placement (re-place + claim).
10
+ * 4. Transient (placing/transferring) → queue (ownership-contention).
11
+ * 5. Unowned (new) → PlacementExecutor.decide() → SYNCHRONOUS
12
+ * CAS-claim → spawn on the winner (or, on CAS loss, forward to the winner / queue).
13
+ *
14
+ * Per-session ordering: messages for ONE sessionKey are dispatched strictly in
15
+ * inbound order, at-most-one-in-flight (a per-session promise chain). Different
16
+ * sessions dispatch concurrently. All I/O is injected so the dispatch logic itself
17
+ * is deterministic + unit-testable; the deliverMessage dep owns its own per-attempt
18
+ * timeout (it throws on timeout) — the router owns the retry/backoff/fallback loop.
19
+ */
20
+ export const DEFAULT_ROUTER_CONFIG = {
21
+ deliverMessageMaxRetries: 3,
22
+ deliverMessageRetryBackoffStartMs: 250,
23
+ deliverMessageRetryBackoffMaxMs: 2000,
24
+ maxReResolveDepth: 3,
25
+ };
26
+ export class SessionRouter {
27
+ deps;
28
+ cfg;
29
+ chains = new Map();
30
+ constructor(deps, cfg = DEFAULT_ROUTER_CONFIG) {
31
+ this.deps = deps;
32
+ this.cfg = cfg;
33
+ }
34
+ /** Dispatch an inbound message; serialized per-session (in-order, one-in-flight). */
35
+ route(msg) {
36
+ const prior = this.chains.get(msg.sessionKey) ?? Promise.resolve();
37
+ const next = prior.catch(() => undefined).then(() => this.dispatchOne(msg));
38
+ // Track the tail so the next message on this session waits for this one to settle.
39
+ this.chains.set(msg.sessionKey, next.then(() => undefined, () => undefined));
40
+ return next;
41
+ }
42
+ backoff(attempt) {
43
+ return Math.min(this.cfg.deliverMessageRetryBackoffStartMs * 2 ** attempt, this.cfg.deliverMessageRetryBackoffMaxMs);
44
+ }
45
+ async dispatchOne(msg) {
46
+ const own = this.deps.resolveOwnership(msg.sessionKey);
47
+ if (own.status === 'active' && own.owner) {
48
+ if (own.owner === this.deps.selfMachineId) {
49
+ await this.deps.handleLocally(msg);
50
+ return { action: 'handled-locally', owner: own.owner, acked: true };
51
+ }
52
+ if (this.deps.isMachineAlive(own.owner)) {
53
+ return this.forwardToOwner(msg, own.owner, own.epoch, 0);
54
+ }
55
+ // Owner is not alive → owner-dead re-placement (§L4 fallback).
56
+ this.deps.markOwnerSuspect?.(own.owner);
57
+ return this.placeAndClaim(msg, 'failover', true);
58
+ }
59
+ if (own.status === 'placing' || own.status === 'transferring') {
60
+ this.deps.queueMessage(msg, 'ownership-contention');
61
+ return { action: 'queued', detail: own.status, acked: false };
62
+ }
63
+ // Unowned → place + claim.
64
+ return this.placeAndClaim(msg, 'new', false);
65
+ }
66
+ async forwardToOwner(msg, owner, epoch, reResolveDepth) {
67
+ for (let attempt = 0; attempt <= this.cfg.deliverMessageMaxRetries; attempt++) {
68
+ try {
69
+ const ack = await this.deps.deliverMessage(owner, { sessionKey: msg.sessionKey, messageId: msg.messageId, payload: msg.payload, ownershipEpoch: epoch });
70
+ if (ack.accepted === 'duplicate')
71
+ return { action: 'duplicate', owner, acked: true };
72
+ if (ack.accepted === 'queued')
73
+ return { action: 'forwarded', owner, acked: true };
74
+ // stale-ownership → re-resolve (bounded) and route to the current owner.
75
+ if (reResolveDepth >= this.cfg.maxReResolveDepth) {
76
+ return this.placeAndClaim(msg, 'failover', true);
77
+ }
78
+ const own2 = this.deps.resolveOwnership(msg.sessionKey);
79
+ if (own2.status === 'active' && own2.owner === this.deps.selfMachineId) {
80
+ await this.deps.handleLocally(msg);
81
+ return { action: 'handled-locally', owner: own2.owner, detail: 'after-stale', acked: true };
82
+ }
83
+ // Re-forward to the current owner when ownership is still active+alive AND
84
+ // something actually changed — a DIFFERENT owner, or the SAME owner whose
85
+ // epoch advanced (the stale-ownership ACK meant our epoch view was behind;
86
+ // re-deliver at the corrected epoch rather than needlessly re-placing). A
87
+ // same-owner/same-epoch stale ACK is spurious → fall through to re-place.
88
+ // Bounded by maxReResolveDepth. (2026-05-29 pre-merge review #7.)
89
+ if (own2.status === 'active' && own2.owner && this.deps.isMachineAlive(own2.owner) && (own2.owner !== owner || own2.epoch !== epoch)) {
90
+ return this.forwardToOwner(msg, own2.owner, own2.epoch, reResolveDepth + 1);
91
+ }
92
+ // Ownership moved to a transient/dead state (or a spurious same-epoch stale) → re-place.
93
+ return this.placeAndClaim(msg, 'failover', true);
94
+ }
95
+ catch (err) {
96
+ this.deps.log?.(`deliverMessage attempt ${attempt} to ${owner} failed: ${String(err?.message ?? err)}`);
97
+ if (attempt < this.cfg.deliverMessageMaxRetries) {
98
+ await this.deps.sleep(this.backoff(attempt));
99
+ continue;
100
+ }
101
+ }
102
+ }
103
+ // Retries exhausted → owner is unreachable → owner-dead re-placement.
104
+ this.deps.markOwnerSuspect?.(owner);
105
+ return this.placeAndClaim(msg, 'failover', true);
106
+ }
107
+ async placeAndClaim(msg, reason, fromDead) {
108
+ const decision = this.deps.placement.decide({
109
+ sessionKey: msg.sessionKey,
110
+ topicMetadata: msg.topicMetadata ?? {},
111
+ machineRegistry: this.deps.machineRegistry(),
112
+ currentOwner: undefined,
113
+ reason,
114
+ });
115
+ if (decision.outcome === 'placement-blocked') {
116
+ this.deps.raiseAttention('Session placement blocked', `${msg.sessionKey}: ${decision.escalationReason ?? decision.reason}`);
117
+ this.deps.queueMessage(msg, `placement-blocked:${decision.escalationReason ?? decision.reason}`);
118
+ return { action: 'placement-blocked', detail: decision.escalationReason ?? decision.reason, acked: false };
119
+ }
120
+ if (decision.outcome === 'queued' || !decision.chosenMachine) {
121
+ this.deps.raiseAttention('No machine available for session', `${msg.sessionKey}: ${decision.escalationReason ?? decision.reason}`);
122
+ this.deps.queueMessage(msg, decision.escalationReason ?? 'no-capable-machine');
123
+ return { action: 'queued', detail: decision.escalationReason ?? decision.reason, acked: false };
124
+ }
125
+ // Synchronous CAS-claim (the dispatch BLOCKS on this — Invariant #2).
126
+ const pre = this.deps.resolveOwnership(msg.sessionKey);
127
+ const cas = this.deps.casClaimOwnership(msg.sessionKey, decision.chosenMachine, pre.epoch);
128
+ if (!cas.ok) {
129
+ // Contention — re-read; route to the confirmed winner or queue if transient.
130
+ const own2 = this.deps.resolveOwnership(msg.sessionKey);
131
+ if (own2.status === 'active' && own2.owner === this.deps.selfMachineId) {
132
+ await this.deps.handleLocally(msg);
133
+ return { action: 'handled-locally', owner: own2.owner, detail: 'cas-lost-self', acked: true };
134
+ }
135
+ if (own2.status === 'active' && own2.owner && this.deps.isMachineAlive(own2.owner)) {
136
+ return this.forwardToOwner(msg, own2.owner, own2.epoch, 0);
137
+ }
138
+ this.deps.queueMessage(msg, 'ownership-contention');
139
+ return { action: 'queued', detail: 'ownership-contention', acked: false };
140
+ }
141
+ // CAS won → spawn on the winner (or handle locally if that's us).
142
+ const action = fromDead ? 'owner-dead-replaced' : 'spawned';
143
+ if (decision.chosenMachine === this.deps.selfMachineId) {
144
+ await this.deps.handleLocally(msg);
145
+ return { action: fromDead ? 'owner-dead-replaced' : 'handled-locally', owner: decision.chosenMachine, detail: 'placed-self', acked: true };
146
+ }
147
+ await this.deps.spawnOnMachine(decision.chosenMachine, msg);
148
+ return { action, owner: decision.chosenMachine, acked: true };
149
+ }
150
+ }
151
+ //# sourceMappingURL=SessionRouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionRouter.js","sourceRoot":"","sources":["../../src/core/SessionRouter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * StageAdvancer — the SOLE writer of `multiMachine.sessionPool.stage` and the
3
+ * mechanical rollout gate (Multi-Machine Session Pool §Rollout). A stage activates
4
+ * ONLY when the prior stage's Tier-3 E2E recorded `green` for the CURRENT commit;
5
+ * otherwise the advance is refused and audited. If a live stage later records `red`,
6
+ * the advancer mechanically REVERTS to the prior (safe) stage. Per "Structure >
7
+ * Willpower" the gate is code, not prose: no other path may flip the stage (enforced
8
+ * by a Config.ts write-guard that only honors writes carrying this module's token).
9
+ */
10
+ import type { SessionPoolE2EResultStore } from './SessionPoolE2EResultStore.js';
11
+ export declare const STAGES: readonly ["dark", "shadow", "live-transfer", "rebalance"];
12
+ export type SessionPoolStage = (typeof STAGES)[number];
13
+ export declare function stageIndex(stage: SessionPoolStage): number;
14
+ export type AdvanceResult = {
15
+ ok: true;
16
+ stage: SessionPoolStage;
17
+ } | {
18
+ ok: false;
19
+ reason: 'e2e-gate-not-passed' | 'already-at-or-past' | 'invalid-stage';
20
+ detail?: string;
21
+ };
22
+ export interface StageAdvancerDeps {
23
+ resultStore: SessionPoolE2EResultStore;
24
+ /** The commit the running build is on — the E2E result must match it. */
25
+ currentCommitSha: () => string;
26
+ /** Read the current configured stage. */
27
+ readStage: () => SessionPoolStage;
28
+ /** The ONLY stage-config write path. Config.ts must reject writes that don't come through here. */
29
+ writeStageConfig: (stage: SessionPoolStage) => void;
30
+ audit?: (event: string, detail: Record<string, unknown>) => void;
31
+ }
32
+ export declare class StageAdvancer {
33
+ private readonly d;
34
+ constructor(d: StageAdvancerDeps);
35
+ /**
36
+ * Advance to `targetStage`, gated on a matching `green` E2E for the PRIOR stage at
37
+ * the current commit. A missing/`red`/stale-commit/tampered prior result → refused.
38
+ */
39
+ advanceTo(targetStage: SessionPoolStage): AdvanceResult;
40
+ /**
41
+ * Reconcile on each cycle: if the CURRENT stage's latest E2E is `red` (regression),
42
+ * mechanically revert to the prior stage until a fresh `green` is recorded. Returns
43
+ * the stage after reconciliation.
44
+ */
45
+ reconcile(): SessionPoolStage;
46
+ }
47
+ //# sourceMappingURL=StageAdvancer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StageAdvancer.d.ts","sourceRoot":"","sources":["../../src/core/StageAdvancer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAEhF,eAAO,MAAM,MAAM,2DAA4D,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,wBAAgB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAE1D;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,GAAG,eAAe,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3G,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,yBAAyB,CAAC;IACvC,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,MAAM,CAAC;IAC/B,yCAAyC;IACzC,SAAS,EAAE,MAAM,gBAAgB,CAAC;IAClC,mGAAmG;IACnG,gBAAgB,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAClE;AAED,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAAD,CAAC,EAAE,iBAAiB;IAEjD;;;OAGG;IACH,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,aAAa;IAwBvD;;;;OAIG;IACH,SAAS,IAAI,gBAAgB;CAiB9B"}