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.
- package/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh +17 -0
- package/dashboard/index.html +108 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +214 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +30 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/config/LiveConfig.d.ts +3 -1
- package/dist/config/LiveConfig.d.ts.map +1 -1
- package/dist/config/LiveConfig.js +4 -1
- package/dist/config/LiveConfig.js.map +1 -1
- package/dist/config/stageWriteGuard.d.ts +23 -0
- package/dist/config/stageWriteGuard.d.ts.map +1 -0
- package/dist/config/stageWriteGuard.js +33 -0
- package/dist/config/stageWriteGuard.js.map +1 -0
- package/dist/core/DeliverMessageHandler.d.ts +33 -0
- package/dist/core/DeliverMessageHandler.d.ts.map +1 -0
- package/dist/core/DeliverMessageHandler.js +34 -0
- package/dist/core/DeliverMessageHandler.js.map +1 -0
- package/dist/core/LeaseCoordinator.d.ts +39 -2
- package/dist/core/LeaseCoordinator.d.ts.map +1 -1
- package/dist/core/LeaseCoordinator.js +45 -9
- package/dist/core/LeaseCoordinator.js.map +1 -1
- package/dist/core/MachineIdentity.d.ts +28 -1
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +81 -1
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/MachineLoadBroker.d.ts +94 -0
- package/dist/core/MachineLoadBroker.d.ts.map +1 -0
- package/dist/core/MachineLoadBroker.js +87 -0
- package/dist/core/MachineLoadBroker.js.map +1 -0
- package/dist/core/MachinePoolRegistry.d.ts +94 -0
- package/dist/core/MachinePoolRegistry.d.ts.map +1 -0
- package/dist/core/MachinePoolRegistry.js +150 -0
- package/dist/core/MachinePoolRegistry.js.map +1 -0
- package/dist/core/MeshRpc.d.ts +165 -0
- package/dist/core/MeshRpc.d.ts.map +1 -0
- package/dist/core/MeshRpc.js +156 -0
- package/dist/core/MeshRpc.js.map +1 -0
- package/dist/core/MeshRpcClient.d.ts +59 -0
- package/dist/core/MeshRpcClient.d.ts.map +1 -0
- package/dist/core/MeshRpcClient.js +58 -0
- package/dist/core/MeshRpcClient.js.map +1 -0
- package/dist/core/MultiMachineCoordinator.d.ts +11 -0
- package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
- package/dist/core/MultiMachineCoordinator.js +13 -0
- package/dist/core/MultiMachineCoordinator.js.map +1 -1
- package/dist/core/NicknameAssigner.d.ts +51 -0
- package/dist/core/NicknameAssigner.d.ts.map +1 -0
- package/dist/core/NicknameAssigner.js +91 -0
- package/dist/core/NicknameAssigner.js.map +1 -0
- package/dist/core/NicknameCommand.d.ts +28 -0
- package/dist/core/NicknameCommand.d.ts.map +1 -0
- package/dist/core/NicknameCommand.js +69 -0
- package/dist/core/NicknameCommand.js.map +1 -0
- package/dist/core/PlacementExecutor.d.ts +71 -0
- package/dist/core/PlacementExecutor.d.ts.map +1 -0
- package/dist/core/PlacementExecutor.js +156 -0
- package/dist/core/PlacementExecutor.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +21 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/RebalancePlanner.d.ts +48 -0
- package/dist/core/RebalancePlanner.d.ts.map +1 -0
- package/dist/core/RebalancePlanner.js +71 -0
- package/dist/core/RebalancePlanner.js.map +1 -0
- package/dist/core/SessionManager.d.ts +20 -3
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +54 -6
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionOwnership.d.ts +103 -0
- package/dist/core/SessionOwnership.d.ts.map +1 -0
- package/dist/core/SessionOwnership.js +0 -0
- package/dist/core/SessionOwnership.js.map +1 -0
- package/dist/core/SessionOwnershipRegistry.d.ts +115 -0
- package/dist/core/SessionOwnershipRegistry.d.ts.map +1 -0
- package/dist/core/SessionOwnershipRegistry.js +128 -0
- package/dist/core/SessionOwnershipRegistry.js.map +1 -0
- package/dist/core/SessionPoolE2EResultStore.d.ts +45 -0
- package/dist/core/SessionPoolE2EResultStore.d.ts.map +1 -0
- package/dist/core/SessionPoolE2EResultStore.js +66 -0
- package/dist/core/SessionPoolE2EResultStore.js.map +1 -0
- package/dist/core/SessionRouter.d.ts +95 -0
- package/dist/core/SessionRouter.d.ts.map +1 -0
- package/dist/core/SessionRouter.js +151 -0
- package/dist/core/SessionRouter.js.map +1 -0
- package/dist/core/StageAdvancer.d.ts +47 -0
- package/dist/core/StageAdvancer.d.ts.map +1 -0
- package/dist/core/StageAdvancer.js +70 -0
- package/dist/core/StageAdvancer.js.map +1 -0
- package/dist/core/TransferByNickname.d.ts +51 -0
- package/dist/core/TransferByNickname.d.ts.map +1 -0
- package/dist/core/TransferByNickname.js +50 -0
- package/dist/core/TransferByNickname.js.map +1 -0
- package/dist/core/TransferOrchestrator.d.ts +113 -0
- package/dist/core/TransferOrchestrator.d.ts.map +1 -0
- package/dist/core/TransferOrchestrator.js +79 -0
- package/dist/core/TransferOrchestrator.js.map +1 -0
- package/dist/core/types.d.ts +177 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/RateLimitSentinel.d.ts +24 -2
- package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
- package/dist/monitoring/RateLimitSentinel.js +25 -14
- package/dist/monitoring/RateLimitSentinel.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +5 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +4 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +14 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +12 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +91 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +5 -0
- package/upgrades/1.3.91.md +54 -0
- package/upgrades/1.3.92.md +63 -0
- package/upgrades/side-effects/session-error-nudge-rearm.md +17 -0
- package/upgrades/side-effects/session-pool-capability-classification.md +16 -0
- package/upgrades/side-effects/session-pool-review-fixes.md +23 -0
- package/upgrades/side-effects/session-pool-track-a-router-lease.md +29 -0
- package/upgrades/side-effects/session-pool-track-b-agent-awareness.md +29 -0
- package/upgrades/side-effects/session-pool-track-b-nicknames.md +35 -0
- package/upgrades/side-effects/session-pool-track-b-pool-api.md +36 -0
- package/upgrades/side-effects/session-pool-track-b-registry.md +31 -0
- package/upgrades/side-effects/session-pool-track-b-wiring.md +39 -0
- package/upgrades/side-effects/session-pool-track-c-dispatcher.md +24 -0
- package/upgrades/side-effects/session-pool-track-c-meshrpc.md +35 -0
- package/upgrades/side-effects/session-pool-track-c-route-wiring.md +31 -0
- package/upgrades/side-effects/session-pool-track-d-ownership-fsm.md +31 -0
- package/upgrades/side-effects/session-pool-track-d-registry.md +27 -0
- package/upgrades/side-effects/session-pool-track-d-wiring.md +30 -0
- package/upgrades/side-effects/session-pool-track-e-boot-wiring.md +20 -0
- package/upgrades/side-effects/session-pool-track-e-placement-core.md +17 -0
- package/upgrades/side-effects/session-pool-track-e-router.md +23 -0
- package/upgrades/side-effects/session-pool-track-f-config.md +12 -0
- package/upgrades/side-effects/session-pool-track-f-handoff-core.md +17 -0
- package/upgrades/side-effects/session-pool-track-g-load-broker.md +16 -0
- package/upgrades/side-effects/session-pool-track-h-gate-wiring.md +17 -0
- package/upgrades/side-effects/session-pool-track-h-mesh-client.md +15 -0
- package/upgrades/side-effects/session-pool-track-h-rebalance.md +13 -0
- package/upgrades/side-effects/session-pool-track-h-rollout-gate.md +17 -0
- package/upgrades/side-effects/session-pool-track-h-stage-guard.md +16 -0
- package/upgrades/side-effects/transient-api-error-recovery-generalization.md +18 -0
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
export const STAGES = ['dark', 'shadow', 'live-transfer', 'rebalance'];
|
|
11
|
+
export function stageIndex(stage) {
|
|
12
|
+
return STAGES.indexOf(stage);
|
|
13
|
+
}
|
|
14
|
+
export class StageAdvancer {
|
|
15
|
+
d;
|
|
16
|
+
constructor(d) {
|
|
17
|
+
this.d = d;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Advance to `targetStage`, gated on a matching `green` E2E for the PRIOR stage at
|
|
21
|
+
* the current commit. A missing/`red`/stale-commit/tampered prior result → refused.
|
|
22
|
+
*/
|
|
23
|
+
advanceTo(targetStage) {
|
|
24
|
+
const targetIdx = stageIndex(targetStage);
|
|
25
|
+
if (targetIdx < 0)
|
|
26
|
+
return { ok: false, reason: 'invalid-stage', detail: String(targetStage) };
|
|
27
|
+
const current = this.d.readStage();
|
|
28
|
+
if (stageIndex(current) >= targetIdx) {
|
|
29
|
+
return { ok: false, reason: 'already-at-or-past', detail: `current=${current}` };
|
|
30
|
+
}
|
|
31
|
+
// 'dark' (index 0) is the floor — there is no prior stage to gate on; you never
|
|
32
|
+
// "advance to dark", you revert to it. So any real advance targets index ≥ 1.
|
|
33
|
+
const priorStageIdx = targetIdx - 1;
|
|
34
|
+
const prior = this.d.resultStore.getLatestForStage(priorStageIdx);
|
|
35
|
+
const sha = this.d.currentCommitSha();
|
|
36
|
+
const gateOk = !!prior && prior.result === 'green' && prior.commitSha === sha && this.d.resultStore.verify(prior);
|
|
37
|
+
if (!gateOk) {
|
|
38
|
+
const detail = !prior ? 'no-result' : prior.result !== 'green' ? `result=${prior.result}` : prior.commitSha !== sha ? 'stale-commit' : 'bad-signature';
|
|
39
|
+
this.d.audit?.('stage-advance-refused', { targetStage, priorStage: STAGES[priorStageIdx], reason: 'e2e-gate-not-passed', detail, commitSha: sha });
|
|
40
|
+
return { ok: false, reason: 'e2e-gate-not-passed', detail };
|
|
41
|
+
}
|
|
42
|
+
this.d.writeStageConfig(targetStage);
|
|
43
|
+
this.d.audit?.('stage-advanced', { from: current, to: targetStage, commitSha: sha, evidenceRef: prior.evidenceRef });
|
|
44
|
+
return { ok: true, stage: targetStage };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reconcile on each cycle: if the CURRENT stage's latest E2E is `red` (regression),
|
|
48
|
+
* mechanically revert to the prior stage until a fresh `green` is recorded. Returns
|
|
49
|
+
* the stage after reconciliation.
|
|
50
|
+
*/
|
|
51
|
+
reconcile() {
|
|
52
|
+
const current = this.d.readStage();
|
|
53
|
+
const idx = stageIndex(current);
|
|
54
|
+
if (idx <= 0)
|
|
55
|
+
return current; // 'dark' is the floor — nothing to revert to
|
|
56
|
+
const latest = this.d.resultStore.getLatestForStage(idx);
|
|
57
|
+
// Only revert on a red for the CURRENT commit — a stale red from a prior commit
|
|
58
|
+
// is not a regression of the running build and must not trigger a revert.
|
|
59
|
+
// (2026-05-29 pre-merge review.)
|
|
60
|
+
const regressed = !!latest && latest.result === 'red' && latest.commitSha === this.d.currentCommitSha() && this.d.resultStore.verify(latest);
|
|
61
|
+
if (regressed) {
|
|
62
|
+
const reverted = STAGES[idx - 1];
|
|
63
|
+
this.d.writeStageConfig(reverted);
|
|
64
|
+
this.d.audit?.('stage-reverted', { from: current, to: reverted, reason: 'e2e-red', evidenceRef: latest.evidenceRef });
|
|
65
|
+
return reverted;
|
|
66
|
+
}
|
|
67
|
+
return current;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=StageAdvancer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StageAdvancer.js","sourceRoot":"","sources":["../../src/core/StageAdvancer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransferByNickname — the pure planner behind the headline "move this to
|
|
3
|
+
* <nickname>" mid-conversation swap (Multi-Machine Session Pool §L4 "Topic
|
|
4
|
+
* Placement Updates" + §L5). It turns a recognized NicknameCommand into a gated,
|
|
5
|
+
* validated TransferPlan: resolve the nickname → machineId, enforce the rate limit,
|
|
6
|
+
* the confirmation gate (offline target / mid-reply), and the already-there no-op.
|
|
7
|
+
* Per "Structure > Willpower" a transfer is high-impact, so this is NOT free-text
|
|
8
|
+
* inference — it operates on an ALREADY-recognized command + the registry state,
|
|
9
|
+
* and an unknown/ambiguous nickname is REJECTED (listing valid nicknames), never
|
|
10
|
+
* silently mis-routed.
|
|
11
|
+
*/
|
|
12
|
+
import type { NicknameCommand } from './NicknameCommand.js';
|
|
13
|
+
export interface TransferByNicknameState {
|
|
14
|
+
/** Resolve a nickname → machineId (case-insensitive exact match), or null if unknown. */
|
|
15
|
+
resolveNickname: (nickname: string) => string | null;
|
|
16
|
+
/** The full set of valid nicknames (returned on an unknown-nickname rejection). */
|
|
17
|
+
validNicknames: () => string[];
|
|
18
|
+
isOnline: (machineId: string) => boolean;
|
|
19
|
+
currentOwnerOf: (sessionKey: string) => string | null;
|
|
20
|
+
/** Is the session mid-reply (a turn in flight)? Confirmation is required to interrupt it. */
|
|
21
|
+
isMidReply: (sessionKey: string) => boolean;
|
|
22
|
+
/** When the topic last had a placement update (for the rate limit), or null. */
|
|
23
|
+
lastPlacementUpdateAt: (sessionKey: string) => number | null;
|
|
24
|
+
now: () => number;
|
|
25
|
+
/** Minimum ms between placement updates per topic. Default 10000. */
|
|
26
|
+
minUpdateIntervalMs?: number;
|
|
27
|
+
}
|
|
28
|
+
export type TransferPlanAction = 'transfer' | 'noop' | 'confirm-required' | 'reject';
|
|
29
|
+
export interface TransferPlan {
|
|
30
|
+
action: TransferPlanAction;
|
|
31
|
+
sessionKey: string;
|
|
32
|
+
/** Resolved target machine (present for transfer / confirm-required / noop). */
|
|
33
|
+
targetMachine?: string;
|
|
34
|
+
/** Set the topic's preferredMachine + pinned:true (a hard pin — §L4). */
|
|
35
|
+
setPin?: boolean;
|
|
36
|
+
/** Human-facing confirmation prompt (action === 'confirm-required'). */
|
|
37
|
+
confirmationPrompt?: string;
|
|
38
|
+
/** Rejection reason (action === 'reject'). */
|
|
39
|
+
rejectReason?: 'unknown-machine-nickname' | 'rate-limited';
|
|
40
|
+
/** On unknown-nickname: the valid nicknames to surface to the user. */
|
|
41
|
+
validNicknames?: string[];
|
|
42
|
+
detail?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Plan a transfer-by-nickname. Pure over (command, state). Both "move this to
|
|
46
|
+
* <nick>" and "run this on <nick>" set a hard pin (pinned:true) per §L4; the only
|
|
47
|
+
* intent difference is cosmetic. Confirmation is required when the target is offline,
|
|
48
|
+
* or when it differs from the current owner AND the session is mid-reply (§L4 line 456).
|
|
49
|
+
*/
|
|
50
|
+
export declare function planTransferByNickname(command: NicknameCommand, state: TransferByNicknameState, sessionKey: string): TransferPlan;
|
|
51
|
+
//# sourceMappingURL=TransferByNickname.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferByNickname.d.ts","sourceRoot":"","sources":["../../src/core/TransferByNickname.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,yFAAyF;IACzF,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,mFAAmF;IACnF,cAAc,EAAE,MAAM,MAAM,EAAE,CAAC;IAC/B,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,cAAc,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtD,6FAA6F;IAC7F,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,gFAAgF;IAChF,qBAAqB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC7D,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,MAAM,GAAG,kBAAkB,GAAG,QAAQ,CAAC;AAErF,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,CAAC,EAAE,0BAA0B,GAAG,cAAc,CAAC;IAC3D,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAmCjI"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransferByNickname — the pure planner behind the headline "move this to
|
|
3
|
+
* <nickname>" mid-conversation swap (Multi-Machine Session Pool §L4 "Topic
|
|
4
|
+
* Placement Updates" + §L5). It turns a recognized NicknameCommand into a gated,
|
|
5
|
+
* validated TransferPlan: resolve the nickname → machineId, enforce the rate limit,
|
|
6
|
+
* the confirmation gate (offline target / mid-reply), and the already-there no-op.
|
|
7
|
+
* Per "Structure > Willpower" a transfer is high-impact, so this is NOT free-text
|
|
8
|
+
* inference — it operates on an ALREADY-recognized command + the registry state,
|
|
9
|
+
* and an unknown/ambiguous nickname is REJECTED (listing valid nicknames), never
|
|
10
|
+
* silently mis-routed.
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_MIN_UPDATE_INTERVAL_MS = 10000;
|
|
13
|
+
/**
|
|
14
|
+
* Plan a transfer-by-nickname. Pure over (command, state). Both "move this to
|
|
15
|
+
* <nick>" and "run this on <nick>" set a hard pin (pinned:true) per §L4; the only
|
|
16
|
+
* intent difference is cosmetic. Confirmation is required when the target is offline,
|
|
17
|
+
* or when it differs from the current owner AND the session is mid-reply (§L4 line 456).
|
|
18
|
+
*/
|
|
19
|
+
export function planTransferByNickname(command, state, sessionKey) {
|
|
20
|
+
const target = state.resolveNickname(command.nickname);
|
|
21
|
+
if (!target) {
|
|
22
|
+
return { action: 'reject', sessionKey, rejectReason: 'unknown-machine-nickname', validNicknames: state.validNicknames(), detail: command.nickname };
|
|
23
|
+
}
|
|
24
|
+
// Rate limit — at most one placement update per topic per interval (defeats rapid-fire transfers).
|
|
25
|
+
const minInterval = state.minUpdateIntervalMs ?? DEFAULT_MIN_UPDATE_INTERVAL_MS;
|
|
26
|
+
const last = state.lastPlacementUpdateAt(sessionKey);
|
|
27
|
+
if (last != null && state.now() - last < minInterval) {
|
|
28
|
+
return { action: 'reject', sessionKey, targetMachine: target, rejectReason: 'rate-limited' };
|
|
29
|
+
}
|
|
30
|
+
const owner = state.currentOwnerOf(sessionKey);
|
|
31
|
+
// Already on the target → no transfer needed (still applies the pin if requested).
|
|
32
|
+
if (owner === target) {
|
|
33
|
+
return { action: 'noop', sessionKey, targetMachine: target, setPin: true, detail: 'already-on-target' };
|
|
34
|
+
}
|
|
35
|
+
// Confirmation gate (§L4): offline target, OR a different owner while mid-reply.
|
|
36
|
+
if (!state.isOnline(target)) {
|
|
37
|
+
return {
|
|
38
|
+
action: 'confirm-required', sessionKey, targetMachine: target, setPin: true,
|
|
39
|
+
confirmationPrompt: `${command.nickname} is offline right now. Move this conversation there anyway? It'll pick up like a fresh-session catch-up once it's back.`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (owner != null && owner !== target && state.isMidReply(sessionKey)) {
|
|
43
|
+
return {
|
|
44
|
+
action: 'confirm-required', sessionKey, targetMachine: target, setPin: true,
|
|
45
|
+
confirmationPrompt: `Move this to ${command.nickname}? I'm mid-reply — it'll be like a fresh-session catch-up on the other machine.`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { action: 'transfer', sessionKey, targetMachine: target, setPin: true };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=TransferByNickname.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferByNickname.js","sourceRoot":"","sources":["../../src/core/TransferByNickname.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsCH,MAAM,8BAA8B,GAAG,KAAK,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAwB,EAAE,KAA8B,EAAE,UAAkB;IACjH,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,0BAA0B,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;IACtJ,CAAC;IAED,mGAAmG;IACnG,MAAM,WAAW,GAAG,KAAK,CAAC,mBAAmB,IAAI,8BAA8B,CAAC;IAChF,MAAM,IAAI,GAAG,KAAK,CAAC,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACrD,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,WAAW,EAAE,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/F,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAE/C,mFAAmF;IACnF,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC1G,CAAC;IAED,iFAAiF;IACjF,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;YAC3E,kBAAkB,EAAE,GAAG,OAAO,CAAC,QAAQ,yHAAyH;SACjK,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACtE,OAAO;YACL,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;YAC3E,kBAAkB,EAAE,gBAAgB,OAAO,CAAC,QAAQ,gFAAgF;SACrI,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransferOrchestrator — drives a planned session handoff through the §L3/§L5
|
|
3
|
+
* ordered sequence: active(S) → transferring(e+1) → [drain, flush ledger, target
|
|
4
|
+
* pulls+verifies] → active(T, e+2) → S-release. Claim-before-release, fenced by
|
|
5
|
+
* (status, epoch): there is no double-run (only one machine is `active` at the top
|
|
6
|
+
* epoch) and no no-owner gap (`transferring` still names S as the draining owner).
|
|
7
|
+
*
|
|
8
|
+
* The orchestrator enforces the two §L5 timing contracts:
|
|
9
|
+
* - Drain bound (`transferDrainTimeoutMs`): a long reply/tool-call must NOT block a
|
|
10
|
+
* transfer; on timeout the source abandons its partial output (NOT emitted).
|
|
11
|
+
* - Output exclusion (`transferOutputCutoffMs`): T holds its CONTINUATION until the
|
|
12
|
+
* cutoff has elapsed since the `transferring` write, so S's drain window and T's
|
|
13
|
+
* emission window are disjoint by construction — exactly one continuation per turn.
|
|
14
|
+
*
|
|
15
|
+
* All I/O (CAS, drain, ledger flush, mesh transfer, claim, clock) is injected so the
|
|
16
|
+
* sequence + timing logic is deterministic and unit-testable; the real wiring (real
|
|
17
|
+
* SessionMigrator drain, real git ledger flush, real mesh client) lands in Track H.
|
|
18
|
+
*/
|
|
19
|
+
export type LedgerEntryStatus = 'reply_committed' | 'cursor_advanced' | 'in_flight';
|
|
20
|
+
export interface LedgerSnapshot {
|
|
21
|
+
sessionKey: string;
|
|
22
|
+
generatedAt: number;
|
|
23
|
+
entries: Array<{
|
|
24
|
+
messageId: string;
|
|
25
|
+
status: LedgerEntryStatus;
|
|
26
|
+
replyMarker?: string;
|
|
27
|
+
updatedAt: number;
|
|
28
|
+
}>;
|
|
29
|
+
snapshotSha256: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Verify a ledger snapshot at the transfer handoff (§L5): the SHA256 must match the
|
|
33
|
+
* SyncManifest-covered value AND every entry must be terminal (nothing `in_flight`).
|
|
34
|
+
* An `in_flight` entry means a turn was still processing at flush time — the target
|
|
35
|
+
* must NOT resume from it (it waits for a terminal re-flush, or treats it as the
|
|
36
|
+
* interrupted-drain case on failover). Pure.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyLedgerSnapshot(snapshot: LedgerSnapshot, expectedSha256: string, computeSha256: (s: LedgerSnapshot) => string): {
|
|
39
|
+
ok: true;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
reason: 'sha-mismatch' | 'in-flight-entry';
|
|
43
|
+
};
|
|
44
|
+
export interface TransferRequest {
|
|
45
|
+
sessionKey: string;
|
|
46
|
+
source: string;
|
|
47
|
+
target: string;
|
|
48
|
+
reason: 'pin' | 'rebalance' | 'failover';
|
|
49
|
+
/** Whether the source machine is alive (planned transfer) or gone (failover). */
|
|
50
|
+
sourceAlive: boolean;
|
|
51
|
+
/** The ownership epoch the orchestrator observed before starting. */
|
|
52
|
+
baseEpoch: number;
|
|
53
|
+
}
|
|
54
|
+
export interface TransferOrchestratorConfig {
|
|
55
|
+
transferDrainTimeoutMs: number;
|
|
56
|
+
transferOutputCutoffMs: number;
|
|
57
|
+
}
|
|
58
|
+
export declare const DEFAULT_TRANSFER_CONFIG: TransferOrchestratorConfig;
|
|
59
|
+
export interface TransferOrchestratorDeps {
|
|
60
|
+
/** CAS the record source→transferring(epoch+1). */
|
|
61
|
+
casToTransferring: (sessionKey: string, target: string, expectedEpoch: number) => {
|
|
62
|
+
ok: boolean;
|
|
63
|
+
epoch: number;
|
|
64
|
+
};
|
|
65
|
+
/** Quiesce + drain the in-flight reply on the source, bounded by timeoutMs. */
|
|
66
|
+
drain: (sessionKey: string, timeoutMs: number) => Promise<{
|
|
67
|
+
drained: boolean;
|
|
68
|
+
abandonedPartial: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
/** Source flushes its ledger snapshot synchronously (returns the snapshot + its ref). */
|
|
71
|
+
flushLedger: (sessionKey: string) => Promise<{
|
|
72
|
+
snapshot: LedgerSnapshot;
|
|
73
|
+
ledgerSnapshotRef: string;
|
|
74
|
+
syncManifestRef: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Send the transfer MeshRpc to the target; the target pulls + verifies the snapshot. */
|
|
77
|
+
sendTransferRpc: (target: string, refs: {
|
|
78
|
+
sessionKey: string;
|
|
79
|
+
ledgerSnapshotRef: string;
|
|
80
|
+
syncManifestRef: string;
|
|
81
|
+
}) => Promise<{
|
|
82
|
+
ok: boolean;
|
|
83
|
+
verified: boolean;
|
|
84
|
+
reason?: string;
|
|
85
|
+
}>;
|
|
86
|
+
/** Target CASes itself to active(owner=T, epoch+2). */
|
|
87
|
+
targetClaim: (sessionKey: string, expectedEpoch: number) => {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
epoch: number;
|
|
90
|
+
};
|
|
91
|
+
/** Cleanup: source releases (NOT a precondition for T's claim — runs after). */
|
|
92
|
+
releaseSource: (sessionKey: string) => void;
|
|
93
|
+
/** Raise a deduped Attention item on sync corruption / unrecoverable handoff. */
|
|
94
|
+
raiseAttention?: (title: string, body: string) => void;
|
|
95
|
+
now: () => number;
|
|
96
|
+
sleep: (ms: number) => Promise<void>;
|
|
97
|
+
log?: (line: string) => void;
|
|
98
|
+
}
|
|
99
|
+
export type TransferStatus = 'transferred' | 'aborted-cas-lost' | 'sync-corrupted' | 'target-claim-failed';
|
|
100
|
+
export interface TransferOutcome {
|
|
101
|
+
ok: boolean;
|
|
102
|
+
status: TransferStatus;
|
|
103
|
+
drainAbandoned?: boolean;
|
|
104
|
+
finalEpoch?: number;
|
|
105
|
+
detail?: string;
|
|
106
|
+
}
|
|
107
|
+
export declare class TransferOrchestrator {
|
|
108
|
+
private readonly deps;
|
|
109
|
+
private readonly cfg;
|
|
110
|
+
constructor(deps: TransferOrchestratorDeps, cfg?: TransferOrchestratorConfig);
|
|
111
|
+
transfer(req: TransferRequest): Promise<TransferOutcome>;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=TransferOrchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferOrchestrator.d.ts","sourceRoot":"","sources":["../../src/core/TransferOrchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TransferOrchestrator — drives a planned session handoff through the §L3/§L5
|
|
3
|
+
* ordered sequence: active(S) → transferring(e+1) → [drain, flush ledger, target
|
|
4
|
+
* pulls+verifies] → active(T, e+2) → S-release. Claim-before-release, fenced by
|
|
5
|
+
* (status, epoch): there is no double-run (only one machine is `active` at the top
|
|
6
|
+
* epoch) and no no-owner gap (`transferring` still names S as the draining owner).
|
|
7
|
+
*
|
|
8
|
+
* The orchestrator enforces the two §L5 timing contracts:
|
|
9
|
+
* - Drain bound (`transferDrainTimeoutMs`): a long reply/tool-call must NOT block a
|
|
10
|
+
* transfer; on timeout the source abandons its partial output (NOT emitted).
|
|
11
|
+
* - Output exclusion (`transferOutputCutoffMs`): T holds its CONTINUATION until the
|
|
12
|
+
* cutoff has elapsed since the `transferring` write, so S's drain window and T's
|
|
13
|
+
* emission window are disjoint by construction — exactly one continuation per turn.
|
|
14
|
+
*
|
|
15
|
+
* All I/O (CAS, drain, ledger flush, mesh transfer, claim, clock) is injected so the
|
|
16
|
+
* sequence + timing logic is deterministic and unit-testable; the real wiring (real
|
|
17
|
+
* SessionMigrator drain, real git ledger flush, real mesh client) lands in Track H.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Verify a ledger snapshot at the transfer handoff (§L5): the SHA256 must match the
|
|
21
|
+
* SyncManifest-covered value AND every entry must be terminal (nothing `in_flight`).
|
|
22
|
+
* An `in_flight` entry means a turn was still processing at flush time — the target
|
|
23
|
+
* must NOT resume from it (it waits for a terminal re-flush, or treats it as the
|
|
24
|
+
* interrupted-drain case on failover). Pure.
|
|
25
|
+
*/
|
|
26
|
+
export function verifyLedgerSnapshot(snapshot, expectedSha256, computeSha256) {
|
|
27
|
+
if (computeSha256(snapshot) !== expectedSha256 || snapshot.snapshotSha256 !== expectedSha256) {
|
|
28
|
+
return { ok: false, reason: 'sha-mismatch' };
|
|
29
|
+
}
|
|
30
|
+
if (snapshot.entries.some((e) => e.status === 'in_flight')) {
|
|
31
|
+
return { ok: false, reason: 'in-flight-entry' };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
export const DEFAULT_TRANSFER_CONFIG = {
|
|
36
|
+
transferDrainTimeoutMs: 30000,
|
|
37
|
+
transferOutputCutoffMs: 1000,
|
|
38
|
+
};
|
|
39
|
+
export class TransferOrchestrator {
|
|
40
|
+
deps;
|
|
41
|
+
cfg;
|
|
42
|
+
constructor(deps, cfg = DEFAULT_TRANSFER_CONFIG) {
|
|
43
|
+
this.deps = deps;
|
|
44
|
+
this.cfg = cfg;
|
|
45
|
+
}
|
|
46
|
+
async transfer(req) {
|
|
47
|
+
// 1. CAS source→transferring(e+1). Stamp the moment for the output-exclusion window.
|
|
48
|
+
const t1 = this.deps.casToTransferring(req.sessionKey, req.target, req.baseEpoch);
|
|
49
|
+
if (!t1.ok) {
|
|
50
|
+
return { ok: false, status: 'aborted-cas-lost', detail: 'could-not-enter-transferring' };
|
|
51
|
+
}
|
|
52
|
+
const transferringAt = this.deps.now();
|
|
53
|
+
// 2. Drain the in-flight reply on the source, bounded (a long reply must not block).
|
|
54
|
+
const drainRes = await this.deps.drain(req.sessionKey, this.cfg.transferDrainTimeoutMs);
|
|
55
|
+
// 3. Source flushes its ledger snapshot synchronously.
|
|
56
|
+
const flush = await this.deps.flushLedger(req.sessionKey);
|
|
57
|
+
// 4. Send transfer to the target; the target pulls + verifies (SHA + all-terminal).
|
|
58
|
+
const sent = await this.deps.sendTransferRpc(req.target, { sessionKey: req.sessionKey, ledgerSnapshotRef: flush.ledgerSnapshotRef, syncManifestRef: flush.syncManifestRef });
|
|
59
|
+
if (!sent.ok || !sent.verified) {
|
|
60
|
+
this.deps.raiseAttention?.('Session transfer sync corrupted', `${req.sessionKey} → ${req.target}: ${sent.reason ?? 'verify-failed'}`);
|
|
61
|
+
return { ok: false, status: 'sync-corrupted', drainAbandoned: drainRes.abandonedPartial, detail: sent.reason ?? 'verify-failed' };
|
|
62
|
+
}
|
|
63
|
+
// 5. Output exclusion: T must NOT emit until transferOutputCutoffMs has elapsed
|
|
64
|
+
// since the transferring write, so S's drain window + T's emission are disjoint.
|
|
65
|
+
const elapsed = this.deps.now() - transferringAt;
|
|
66
|
+
const remaining = this.cfg.transferOutputCutoffMs - elapsed;
|
|
67
|
+
if (remaining > 0)
|
|
68
|
+
await this.deps.sleep(remaining);
|
|
69
|
+
// 6. Target claims active(owner=T, e+2). Then the source releases (cleanup only).
|
|
70
|
+
const t2 = this.deps.targetClaim(req.sessionKey, t1.epoch);
|
|
71
|
+
if (!t2.ok) {
|
|
72
|
+
return { ok: false, status: 'target-claim-failed', drainAbandoned: drainRes.abandonedPartial, detail: 'target-cas-failed' };
|
|
73
|
+
}
|
|
74
|
+
this.deps.releaseSource(req.sessionKey);
|
|
75
|
+
this.deps.log?.(`transfer ${req.sessionKey} ${req.source}→${req.target} complete @ epoch ${t2.epoch}`);
|
|
76
|
+
return { ok: true, status: 'transferred', drainAbandoned: drainRes.abandonedPartial, finalEpoch: t2.epoch };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=TransferOrchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TransferOrchestrator.js","sourceRoot":"","sources":["../../src/core/TransferOrchestrator.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1347,8 +1347,23 @@ export type MachineRole = 'awake' | 'standby';
|
|
|
1347
1347
|
*/
|
|
1348
1348
|
export type CoordinationMode = 'primary-standby' | 'independent';
|
|
1349
1349
|
export interface MachineRegistryEntry {
|
|
1350
|
-
/** Human-friendly machine name */
|
|
1350
|
+
/** Human-friendly machine name (auto-detected hostname at pairing; static). */
|
|
1351
1351
|
name: string;
|
|
1352
|
+
/**
|
|
1353
|
+
* User-facing, EDITABLE nickname — the handle a user types in "run this on
|
|
1354
|
+
* <nickname>" / "move this to <nickname>" (Multi-Machine Session Pool §L2).
|
|
1355
|
+
* Auto-assigned at registration via NicknameAssigner (idempotent — kept if
|
|
1356
|
+
* already set), editable via PATCH /pool/machines/:id, unique within the pool.
|
|
1357
|
+
* Optional for backward-compat with registries written before §L2.
|
|
1358
|
+
*/
|
|
1359
|
+
nickname?: string;
|
|
1360
|
+
/**
|
|
1361
|
+
* Self-attested static hardware properties (Session Pool §L2) — captured by
|
|
1362
|
+
* the machine from `os` and recorded into ITS OWN registry entry, then synced
|
|
1363
|
+
* to peers via the registry. Surfaced on the Machines dashboard tab + GET /pool.
|
|
1364
|
+
* Optional (absent until the machine records it).
|
|
1365
|
+
*/
|
|
1366
|
+
hardware?: MachineHardware;
|
|
1352
1367
|
/** Current trust status */
|
|
1353
1368
|
status: MachineStatus;
|
|
1354
1369
|
/** Current operational role */
|
|
@@ -1427,6 +1442,71 @@ export interface MachineRegistry {
|
|
|
1427
1442
|
*/
|
|
1428
1443
|
lease?: LeaseRecord;
|
|
1429
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Static hardware properties of a machine, captured once at registration (cheap,
|
|
1447
|
+
* stable `os` reads) and surfaced on the Machines dashboard tab + as placement
|
|
1448
|
+
* signal (§L2). Self-reported → advisory for display, never authority.
|
|
1449
|
+
*/
|
|
1450
|
+
export interface MachineHardware {
|
|
1451
|
+
/** os.platform() — e.g. "darwin", "linux", "win32". */
|
|
1452
|
+
platform: string;
|
|
1453
|
+
/** os.arch() — e.g. "arm64", "x64". */
|
|
1454
|
+
arch: string;
|
|
1455
|
+
/** os.cpus()[0].model — e.g. "Apple M2". */
|
|
1456
|
+
cpuModel: string;
|
|
1457
|
+
/** os.cpus().length. */
|
|
1458
|
+
cpuCores: number;
|
|
1459
|
+
/** os.totalmem() in bytes. */
|
|
1460
|
+
totalMemBytes: number;
|
|
1461
|
+
/** os.hostname(). */
|
|
1462
|
+
hostname: string;
|
|
1463
|
+
/** The instar version this machine reported. */
|
|
1464
|
+
instarVersion?: string;
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Clock-skew quarantine state (§L2). An explicit three-value FSM so every
|
|
1468
|
+
* implementation + recovery path is identical. A SINGLE divergent heartbeat
|
|
1469
|
+
* never removes a machine; removal requires 2 consecutive divergent beats;
|
|
1470
|
+
* re-admission requires 2 consecutive in-tolerance beats.
|
|
1471
|
+
*/
|
|
1472
|
+
export type ClockSkewStatus = 'ok' | 'divergence-detected-once' | 'suspect-clock-removed';
|
|
1473
|
+
/**
|
|
1474
|
+
* Live per-machine capacity record (§L2) — the input to placement and the
|
|
1475
|
+
* Machines dashboard tab. Assembled by MachinePoolRegistry from the machine
|
|
1476
|
+
* registry (nickname), MachineHeartbeat (liveness), `os` (hardware/load), and
|
|
1477
|
+
* SessionManager diagnostics (sessions/memPressure). Liveness + freshness key
|
|
1478
|
+
* on `routerReceivedAt` (the router's own clock), NEVER the machine's
|
|
1479
|
+
* self-reported timestamp (clock-skew safety).
|
|
1480
|
+
*/
|
|
1481
|
+
export interface MachineCapacity {
|
|
1482
|
+
machineId: string;
|
|
1483
|
+
/** User-facing nickname (§L2), mirrored from the registry entry. */
|
|
1484
|
+
nickname?: string;
|
|
1485
|
+
/** Liveness, computed as (now(router) − routerReceivedAt) < failoverThreshold. */
|
|
1486
|
+
online: boolean;
|
|
1487
|
+
/** The machine's own last-heartbeat timestamp (ISO) — debugging only. */
|
|
1488
|
+
selfReportedLastSeen?: string;
|
|
1489
|
+
/** When the router last observed this machine, on the ROUTER's clock (ISO). */
|
|
1490
|
+
routerReceivedAt?: string;
|
|
1491
|
+
/** 1-minute OS load average from the machine (os.loadavg()[0]). */
|
|
1492
|
+
loadAvg?: number;
|
|
1493
|
+
/** Memory pressure bucket from SessionManager diagnostics. */
|
|
1494
|
+
memPressure?: 'low' | 'moderate' | 'high' | 'critical';
|
|
1495
|
+
/** Active session count on the machine. */
|
|
1496
|
+
activeSessionCount?: number;
|
|
1497
|
+
/** Configured max sessions. */
|
|
1498
|
+
maxSessions?: number;
|
|
1499
|
+
/** Capabilities (e.g. "gpu", "local-model:llama3", "fast-cpu"). */
|
|
1500
|
+
capabilities?: string[];
|
|
1501
|
+
/** Local models available. */
|
|
1502
|
+
modelsAvailable?: string[];
|
|
1503
|
+
/** Agents resident on the machine (multi-agent-per-machine, §L6). */
|
|
1504
|
+
agentsResident?: string[];
|
|
1505
|
+
/** Static hardware properties (§L2). */
|
|
1506
|
+
hardware?: MachineHardware;
|
|
1507
|
+
/** Clock-skew quarantine state (§L2 FSM). */
|
|
1508
|
+
clockSkewStatus: ClockSkewStatus;
|
|
1509
|
+
}
|
|
1430
1510
|
export interface MultiMachineConfig {
|
|
1431
1511
|
/** Whether multi-machine is enabled */
|
|
1432
1512
|
enabled: boolean;
|
|
@@ -1484,6 +1564,102 @@ export interface MultiMachineConfig {
|
|
|
1484
1564
|
* SEAMLESSNESS_PROTOCOL_VERSION applies); present so migrations/tests can pin it.
|
|
1485
1565
|
*/
|
|
1486
1566
|
protocolVersion?: number;
|
|
1567
|
+
/**
|
|
1568
|
+
* Multi-Machine Session Pool (spec docs/specs/MULTI-MACHINE-SESSION-POOL-SPEC.md).
|
|
1569
|
+
* Active-active per-session placement + transfer on top of the router lease.
|
|
1570
|
+
* Ships DARK: the entire layer is inert unless `enabled` is true AND the
|
|
1571
|
+
* graduated `stage` has been advanced past 'dark' (the stage is written ONLY
|
|
1572
|
+
* by StageAdvancer — Track H — gated on a green E2E result). Independent of
|
|
1573
|
+
* the single-awake seamlessness model above; a 1-machine agent is a no-op.
|
|
1574
|
+
*/
|
|
1575
|
+
sessionPool?: SessionPoolConfig;
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Multi-Machine Session Pool config block. All optional with safe dark defaults
|
|
1579
|
+
* (see ConfigDefaults `SHARED_DEFAULTS.multiMachine.sessionPool`). Tunables for
|
|
1580
|
+
* later layers (placement, transfer, registry, clock-skew) are added to this
|
|
1581
|
+
* interface by their tracks as they land.
|
|
1582
|
+
*/
|
|
1583
|
+
export interface SessionPoolConfig {
|
|
1584
|
+
/** Master switch. Default false — the entire session-pool layer is inert when false. */
|
|
1585
|
+
enabled?: boolean;
|
|
1586
|
+
/**
|
|
1587
|
+
* Graduated rollout stage (spec §Rollout). 'dark' (code shipped, placement
|
|
1588
|
+
* dry-run, always local) → 'shadow' (real placement + ownership, no transfer)
|
|
1589
|
+
* → 'live-transfer' (failover + pin transfers) → 'rebalance' (load-driven).
|
|
1590
|
+
* Written ONLY by StageAdvancer via a Config.ts write-guard (Track H);
|
|
1591
|
+
* a direct write is rejected. Default 'dark'.
|
|
1592
|
+
*/
|
|
1593
|
+
stage?: 'dark' | 'shadow' | 'live-transfer' | 'rebalance';
|
|
1594
|
+
/**
|
|
1595
|
+
* When true, the placement engine LOGS the decision it would make but always
|
|
1596
|
+
* places locally (Stage-0 behavior). Default true.
|
|
1597
|
+
*/
|
|
1598
|
+
dryRun?: boolean;
|
|
1599
|
+
/**
|
|
1600
|
+
* Clock-skew divergence tolerance (ms) — a machine whose self-reported vs
|
|
1601
|
+
* router-observed timestamps diverge beyond this on 2 consecutive heartbeats
|
|
1602
|
+
* is quarantined from placement (§L2). Default 300000 (5 min). Must be
|
|
1603
|
+
* ≥ 2× maxExpectedNtpDriftMs (validated at startup).
|
|
1604
|
+
*/
|
|
1605
|
+
clockSkewToleranceMs?: number;
|
|
1606
|
+
/**
|
|
1607
|
+
* Max expected NTP drift (ms) on a healthy host. `clockSkewToleranceMs` must
|
|
1608
|
+
* be ≥ 2× this. Default 250.
|
|
1609
|
+
*/
|
|
1610
|
+
maxExpectedNtpDriftMs?: number;
|
|
1611
|
+
/**
|
|
1612
|
+
* How long an offline machine's capacity record is retained before eviction
|
|
1613
|
+
* (ms). Default 86400000 (24h) — a briefly-offline machine keeps its
|
|
1614
|
+
* nickname/hardware/history for fast re-placement.
|
|
1615
|
+
*/
|
|
1616
|
+
machineRecordEvictionMs?: number;
|
|
1617
|
+
/**
|
|
1618
|
+
* MeshRpc (§L0) command timestamp tolerance (ms) — a signed command whose
|
|
1619
|
+
* timestamp is outside |now - ts| is rejected `stale-timestamp`. Default 30000.
|
|
1620
|
+
*/
|
|
1621
|
+
meshRpcClockToleranceMs?: number;
|
|
1622
|
+
/**
|
|
1623
|
+
* §L4 deliverMessage per-attempt timeout (ms) — the router treats a forward that
|
|
1624
|
+
* does not ACK within this as failed and retries. Default 5000.
|
|
1625
|
+
*/
|
|
1626
|
+
deliverMessageTimeoutMs?: number;
|
|
1627
|
+
/**
|
|
1628
|
+
* §L4 max deliverMessage retries before the router falls back to owner-dead
|
|
1629
|
+
* re-placement. Default 3.
|
|
1630
|
+
*/
|
|
1631
|
+
deliverMessageMaxRetries?: number;
|
|
1632
|
+
/**
|
|
1633
|
+
* §L4 placement stickiness margin — the current owner is kept unless another
|
|
1634
|
+
* machine is better by more than this score delta (prevents flapping). Default 0.15.
|
|
1635
|
+
*/
|
|
1636
|
+
placementHysteresisDelta?: number;
|
|
1637
|
+
/**
|
|
1638
|
+
* §L3 max per-session ownership CAS retries on non-fast-forward contention before
|
|
1639
|
+
* the router queues the message. Default 5.
|
|
1640
|
+
*/
|
|
1641
|
+
ownershipCasMaxRetries?: number;
|
|
1642
|
+
/**
|
|
1643
|
+
* §L5 max time (ms) the source drains an in-flight reply before cancelling and
|
|
1644
|
+
* proceeding with the transfer — a long reply/tool-call must not block. Default 30000.
|
|
1645
|
+
*/
|
|
1646
|
+
transferDrainTimeoutMs?: number;
|
|
1647
|
+
/**
|
|
1648
|
+
* §L5 output-exclusion window (ms): the source emits no NEW output after entering
|
|
1649
|
+
* `transferring` and the target holds its CONTINUATION until this elapses, so the
|
|
1650
|
+
* two emission windows are disjoint (no double-send). Default 1000.
|
|
1651
|
+
*/
|
|
1652
|
+
transferOutputCutoffMs?: number;
|
|
1653
|
+
/**
|
|
1654
|
+
* §L4 cool-down (ms) after a transfer before a session is eligible for re-placement
|
|
1655
|
+
* (a hard user-pin bypasses it). Default 300000.
|
|
1656
|
+
*/
|
|
1657
|
+
placementCooldownMs?: number;
|
|
1658
|
+
/**
|
|
1659
|
+
* §L4 minimum interval (ms) between placement updates per topic — defeats rapid-fire
|
|
1660
|
+
* transfers. Default 10000.
|
|
1661
|
+
*/
|
|
1662
|
+
topicPlacementUpdateMinIntervalMs?: number;
|
|
1487
1663
|
}
|
|
1488
1664
|
export type AgentAutonomyLevel = 'supervised' | 'collaborative' | 'autonomous';
|
|
1489
1665
|
export type UserRegistrationPolicy = 'open' | 'invite-only' | 'admin-only';
|