instar 1.3.907 → 1.3.908

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 (76) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +203 -11
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +31 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/MutualSshRuntime.d.ts +15 -0
  8. package/dist/core/MutualSshRuntime.d.ts.map +1 -1
  9. package/dist/core/MutualSshRuntime.js +123 -10
  10. package/dist/core/MutualSshRuntime.js.map +1 -1
  11. package/dist/core/PeerAuthorizedKeys.d.ts +36 -0
  12. package/dist/core/PeerAuthorizedKeys.d.ts.map +1 -0
  13. package/dist/core/PeerAuthorizedKeys.js +208 -0
  14. package/dist/core/PeerAuthorizedKeys.js.map +1 -0
  15. package/dist/core/PostUpdateMigrator.d.ts +13 -0
  16. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  17. package/dist/core/PostUpdateMigrator.js +41 -0
  18. package/dist/core/PostUpdateMigrator.js.map +1 -1
  19. package/dist/core/ProactiveSwapMonitor.d.ts +10 -0
  20. package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
  21. package/dist/core/ProactiveSwapMonitor.js +29 -4
  22. package/dist/core/ProactiveSwapMonitor.js.map +1 -1
  23. package/dist/core/QuotaAwareScheduler.d.ts +4 -1
  24. package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
  25. package/dist/core/QuotaAwareScheduler.js +18 -9
  26. package/dist/core/QuotaAwareScheduler.js.map +1 -1
  27. package/dist/core/SshBootstrapAdvert.d.ts +6 -0
  28. package/dist/core/SshBootstrapAdvert.d.ts.map +1 -1
  29. package/dist/core/SshBootstrapAdvert.js +21 -1
  30. package/dist/core/SshBootstrapAdvert.js.map +1 -1
  31. package/dist/core/StandingSshVerifier.d.ts +12 -0
  32. package/dist/core/StandingSshVerifier.d.ts.map +1 -0
  33. package/dist/core/StandingSshVerifier.js +53 -0
  34. package/dist/core/StandingSshVerifier.js.map +1 -0
  35. package/dist/core/SubscriptionPool.d.ts +3 -0
  36. package/dist/core/SubscriptionPool.d.ts.map +1 -1
  37. package/dist/core/SubscriptionPool.js +7 -0
  38. package/dist/core/SubscriptionPool.js.map +1 -1
  39. package/dist/core/SwapAntiThrash.d.ts +4 -0
  40. package/dist/core/SwapAntiThrash.d.ts.map +1 -1
  41. package/dist/core/SwapAntiThrash.js +2 -1
  42. package/dist/core/SwapAntiThrash.js.map +1 -1
  43. package/dist/core/SwapLedger.d.ts +2 -0
  44. package/dist/core/SwapLedger.d.ts.map +1 -1
  45. package/dist/core/SwapLedger.js.map +1 -1
  46. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  47. package/dist/core/devGatedFeatures.js +18 -0
  48. package/dist/core/devGatedFeatures.js.map +1 -1
  49. package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
  50. package/dist/core/machineCoherenceManifest.js +9 -0
  51. package/dist/core/machineCoherenceManifest.js.map +1 -1
  52. package/dist/core/sessionPoolFailoverRunnerConfig.d.ts +152 -0
  53. package/dist/core/sessionPoolFailoverRunnerConfig.d.ts.map +1 -0
  54. package/dist/core/sessionPoolFailoverRunnerConfig.js +181 -0
  55. package/dist/core/sessionPoolFailoverRunnerConfig.js.map +1 -0
  56. package/dist/core/types.d.ts +39 -0
  57. package/dist/core/types.d.ts.map +1 -1
  58. package/dist/core/types.js.map +1 -1
  59. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  60. package/dist/monitoring/guardManifest.js +16 -0
  61. package/dist/monitoring/guardManifest.js.map +1 -1
  62. package/dist/server/AgentServer.d.ts +3 -0
  63. package/dist/server/AgentServer.d.ts.map +1 -1
  64. package/dist/server/AgentServer.js +1 -0
  65. package/dist/server/AgentServer.js.map +1 -1
  66. package/dist/server/CapabilityIndex.js +1 -1
  67. package/dist/server/CapabilityIndex.js.map +1 -1
  68. package/dist/server/routes.d.ts +3 -0
  69. package/dist/server/routes.d.ts.map +1 -1
  70. package/dist/server/routes.js +14 -0
  71. package/dist/server/routes.js.map +1 -1
  72. package/package.json +1 -1
  73. package/src/data/builtin-manifest.json +64 -64
  74. package/upgrades/1.3.908.md +33 -0
  75. package/upgrades/side-effects/login-loss-account-swap.md +87 -0
  76. package/upgrades/side-effects/reliable-peer-machine-execution.md +115 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2EH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AA0BjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA8H7D,OAAO,KAAK,EAAW,oBAAoB,EAAe,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsqDD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,oBAAoB,GAAG,IAAI,GACxC;IACD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAqBA;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,EAQrD,oBAAoB,CAAC,EAAE,MAAM;IAC3B,YAAY,EAAE,OAAO,kBAAkB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,GACP,IAAI,CAwtBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkkpBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2EH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AA0BjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA8H7D,OAAO,KAAK,EAAW,oBAAoB,EAAe,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAgFtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA4qDD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,YAAY,EAAE,oBAAoB,GAAG,IAAI,GACxC;IACD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAqBA;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,EAQrD,oBAAoB,CAAC,EAAE,MAAM;IAC3B,YAAY,EAAE,OAAO,kBAAkB,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,GACP,IAAI,CAwtBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgupBtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -518,6 +518,11 @@ let _singleMachineFailoverGap = null;
518
518
  // `GET /pool/missing-login` can read the detector constructed deep in the mesh
519
519
  // peer-presence wiring. Null when the guard is dark (route 503s).
520
520
  let _missingLoginSession = null;
521
+ // Module-level handle for the SessionPoolFailoverRunner driver (§Rollout, Track H)
522
+ // so `GET /session-pool/failover-runner` can read its status(). Null when the
523
+ // runner is dark (dev-gated) — the route 503s. Constructed inside the rollout-gate
524
+ // boot block next to the E2E result store + StageAdvancer.
525
+ let _sessionPoolFailoverRunnerDriver = null;
521
526
  // The store the unconditional boot sweep opened when the queue will run this
522
527
  // boot — adopted by the engine construction (one open handle, single-writer).
523
528
  let _sweptInboundStore = null;
@@ -12020,7 +12025,7 @@ export async function startServer(options) {
12020
12025
  // Subscription & Auth Standard). Always instantiated; ships DARK because an
12021
12026
  // empty pool is a pure no-op (no background loop, no cost) until an operator
12022
12027
  // enrolls an account. Stores each account's login LOCATION, never tokens.
12023
- const { SubscriptionPool } = await import('../core/SubscriptionPool.js');
12028
+ const { SubscriptionPool, requiresOwnerRelogin } = await import('../core/SubscriptionPool.js');
12024
12029
  const subscriptionPool = new SubscriptionPool({ stateDir: config.stateDir });
12025
12030
  if (subscriptionPool.size() > 0) {
12026
12031
  console.log(pc.green(` Subscription pool: ${subscriptionPool.size()} account(s) registered`));
@@ -16824,11 +16829,17 @@ export async function startServer(options) {
16824
16829
  const k = antiThrashKnobsGetter();
16825
16830
  return { thresholdPct: k.thresholdPct, targetHeadroomPct: k.targetHeadroomPct, minImprovementPct: k.minImprovementPct };
16826
16831
  },
16827
- resolveEffectiveAccountId: async (sessionName, sourceWasUntagged) => {
16832
+ resolveEffectiveAccountId: async (sessionName, sourceWasUntagged, sourceTrigger) => {
16828
16833
  const tagged = state.listSessions({ status: 'running' })
16829
16834
  .find((s) => s.tmuxSession === sessionName)?.subscriptionAccountId ?? null;
16830
16835
  if (!sourceWasUntagged || tagged)
16831
16836
  return tagged;
16837
+ if (sourceTrigger === 'login-loss') {
16838
+ const realHome = sessionManager.configHomeForSession(sessionName);
16839
+ if (!realHome)
16840
+ return null;
16841
+ return subscriptionPool.list().find((a) => a.configHome === realHome)?.id ?? null;
16842
+ }
16832
16843
  return (await inUseAccountResolver.resolve(subscriptionPool.list())).activeAccountId;
16833
16844
  },
16834
16845
  onReactiveExecuted: (a) => _swapAntiThrashEngine?.recordReactiveExecuted(a),
@@ -16926,12 +16937,19 @@ export async function startServer(options) {
16926
16937
  // Only claude-code sessions ride the claude-account pool (legacy
16927
16938
  // records with no framework default to claude-code).
16928
16939
  .filter((s) => s.framework === undefined || s.framework === 'claude-code')
16929
- .map((s) => ({
16930
- sessionName: s.tmuxSession,
16931
- accountId: s.subscriptionAccountId ?? null,
16932
- startedAt: s.startedAt,
16933
- refreshable: _sessionRefresh?.canRefreshSession(s.tmuxSession) ?? false,
16934
- })),
16940
+ .map((s) => {
16941
+ const realHome = sessionManager.configHomeForSession(s.tmuxSession);
16942
+ const loginLossAccountId = realHome
16943
+ ? subscriptionPool.list().find((a) => a.configHome === realHome && requiresOwnerRelogin(a))?.id ?? null
16944
+ : null;
16945
+ return {
16946
+ sessionName: s.tmuxSession,
16947
+ accountId: s.subscriptionAccountId ?? null,
16948
+ loginLossAccountId,
16949
+ startedAt: s.startedAt,
16950
+ refreshable: _sessionRefresh?.canRefreshSession(s.tmuxSession) ?? false,
16951
+ };
16952
+ }),
16935
16953
  resolveDefaultAccountId: async () => (await inUseAccountResolver.resolve(subscriptionPool.list())).activeAccountId,
16936
16954
  swap: (a) => _quotaAwareScheduler
16937
16955
  ? _quotaAwareScheduler.onQuotaPressure(a)
@@ -16949,6 +16967,10 @@ export async function startServer(options) {
16949
16967
  antiThrash: _swapAntiThrashEngine
16950
16968
  ? { engine: _swapAntiThrashEngine, getKnobs: antiThrashKnobsGetter }
16951
16969
  : undefined,
16970
+ loginLoss: {
16971
+ enabled: resolveDevAgentGate(proactiveCfg.loginLoss?.enabled, config),
16972
+ dryRun: proactiveCfg.loginLoss?.dryRun !== false,
16973
+ },
16952
16974
  // §4 — the in-flight work gate (proactive arm: defer, ceiling-drop).
16953
16975
  // The monitor owns the deferral lifecycle; the gate is stateless.
16954
16976
  workGate: _swapWorkGate
@@ -20392,6 +20414,18 @@ export async function startServer(options) {
20392
20414
  // Mutual SSH phase 1: the signed HTTP mesh is the bootstrap carrier;
20393
20415
  // the resulting proof itself traverses the restricted SSH subsystem.
20394
20416
  // Hard-dark on fleet, dry-run first on the development agent.
20417
+ const peerExecutionEnabled = resolveDevAgentGate(config.multiMachine?.peerExecution?.enabled, config);
20418
+ if (!peerExecutionEnabled) {
20419
+ try {
20420
+ const { PeerAuthorizedKeys } = await import('../core/PeerAuthorizedKeys.js');
20421
+ new PeerAuthorizedKeys(os.homedir(), false).revokeUnknown(config.projectName, new Set());
20422
+ }
20423
+ catch (error) {
20424
+ // @silent-fallback-ok — disabled-feature cleanup failure is loud;
20425
+ // no runtime starts and no readiness success can mask stale access.
20426
+ console.warn(`[peer-execution] disabled-grant cleanup blocked: ${error instanceof Error ? error.message : String(error)}`);
20427
+ }
20428
+ }
20395
20429
  if (resolveDevAgentGate(config.multiMachine?.mutualSsh?.enabled, config)) {
20396
20430
  try {
20397
20431
  const sshMod = await import('../core/MutualSshRuntime.js');
@@ -20414,6 +20448,18 @@ export async function startServer(options) {
20414
20448
  ?? privateHosts.find(host => /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(host))
20415
20449
  ?? '127.0.0.1';
20416
20450
  const bindPort = portMod.allocatePort(`${config.projectName}-mutual-ssh`, 42000, 49000);
20451
+ const standingHostKey = (() => {
20452
+ if (!peerExecutionEnabled)
20453
+ return null;
20454
+ try {
20455
+ return fs.readFileSync('/etc/ssh/ssh_host_ed25519_key.pub', 'utf8').trim();
20456
+ }
20457
+ catch {
20458
+ // @silent-fallback-ok — absence is a named standing endpoint
20459
+ // readiness blocker; never substitute an unpinned host key.
20460
+ return null;
20461
+ }
20462
+ })();
20417
20463
  const peers = () => meshIdMgr.getActiveMachines()
20418
20464
  .filter(row => row.machineId !== meshSelfId)
20419
20465
  .map(row => ({
@@ -20429,6 +20475,19 @@ export async function startServer(options) {
20429
20475
  selfMachineFingerprint: machineFingerprint(selfPem),
20430
20476
  observerBootId: `${meshSelfId}:${process.pid}:${Date.now()}`,
20431
20477
  bindHost, bindPort, dryRun: config.multiMachine?.mutualSsh?.dryRun !== false,
20478
+ peerExecution: peerExecutionEnabled ? {
20479
+ // The SSH daemon resolves AuthorizedKeysFile beneath the
20480
+ // account home; never write a system/global sshd path.
20481
+ agentHome: os.homedir(),
20482
+ dryRun: config.multiMachine?.peerExecution?.dryRun !== false,
20483
+ requiredForReadiness: config.multiMachine?.peerExecution?.requiredForReadiness !== false,
20484
+ } : undefined,
20485
+ localStandingSsh: peerExecutionEnabled && standingHostKey && bindHost !== '127.0.0.1' ? {
20486
+ host: bindHost,
20487
+ port: config.multiMachine?.peerExecution?.port ?? 22,
20488
+ username: os.userInfo().username,
20489
+ hostPublicKey: standingHostKey,
20490
+ } : undefined,
20432
20491
  requiredForReadiness: config.multiMachine?.mutualSsh?.requiredForEmployeeRole === true,
20433
20492
  freshnessMs: config.multiMachine?.mutualSsh?.freshnessMs,
20434
20493
  cadenceMs: config.multiMachine?.mutualSsh?.cadenceMs,
@@ -20467,6 +20526,11 @@ export async function startServer(options) {
20467
20526
  enabled: true, dryRun: config.multiMachine?.mutualSsh?.dryRun !== false,
20468
20527
  status: mutualSshRuntime?.status() ?? null,
20469
20528
  }));
20529
+ guardRegistry.register('multiMachine.peerExecution.enabled', () => ({
20530
+ enabled: resolveDevAgentGate(config.multiMachine?.peerExecution?.enabled, config),
20531
+ dryRun: config.multiMachine?.peerExecution?.dryRun !== false,
20532
+ status: mutualSshRuntime?.status() ?? null,
20533
+ }));
20470
20534
  }
20471
20535
  catch (err) {
20472
20536
  // @silent-fallback-ok — the feature fails closed and surfaces its
@@ -22534,7 +22598,7 @@ export async function startServer(options) {
22534
22598
  return s.length === exp.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(exp));
22535
22599
  }
22536
22600
  catch {
22537
- return false;
22601
+ return false; /* @silent-fallback-ok: HMAC verify fails closed — a malformed/short input is treated as an invalid signature, never accepted */
22538
22602
  }
22539
22603
  },
22540
22604
  });
@@ -22562,15 +22626,143 @@ export async function startServer(options) {
22562
22626
  writeStageConfig: (s) => liveConfig.set(guardMod.STAGE_CONFIG_PATH, s, { stageWriteToken: guardMod.STAGE_WRITE_TOKEN }),
22563
22627
  audit: (event, detail) => console.log(pc.dim(` [stage-advancer] ${event} ${JSON.stringify(detail)}`)),
22564
22628
  });
22629
+ // ── SessionPoolFailoverRunner (§Rollout, Track H) — the in-agent PRODUCER of
22630
+ // a real failover-E2E green so a DEPLOYED dev agent can promote its own
22631
+ // sessionPool stage instead of sitting at 'shadow' forever. Dev-gated dark
22632
+ // (enabled OMITTED → resolveDevAgentGate) + dryRun-first: while dryRun holds
22633
+ // the recorded verdict lands in a SIDE store the StageAdvancer never reads,
22634
+ // so nothing promotes until a deliberate dryRun:false. Constructed here so it
22635
+ // shares the E2E result store + boot commit SHA + the slow reconcile tick.
22636
+ const fromMod = await import('../core/sessionPoolFailoverRunnerConfig.js');
22637
+ const failoverRunnerCfg = fromMod.resolveSessionPoolFailoverRunnerConfig(config.multiMachine?.sessionPool?.failoverRunner, (explicit) => resolveDevAgentGate(explicit, config));
22638
+ if (failoverRunnerCfg.enabled) {
22639
+ // The dry-run SIDE store: a separate signed file the promotion path
22640
+ // (StageAdvancer/driver) NEVER reads, so a would-record green captured
22641
+ // during the soak can never wrongly promote the stage.
22642
+ const failoverDryRunStore = new e2eMod.SessionPoolE2EResultStore({
22643
+ filePath: path.join(config.stateDir, 'session-pool-failover-runner-dryrun.json'),
22644
+ sign: hmac,
22645
+ verifySig: (c, s) => {
22646
+ try {
22647
+ const exp = hmac(c);
22648
+ return s.length === exp.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(exp));
22649
+ }
22650
+ catch {
22651
+ return false; /* @silent-fallback-ok: HMAC verify fails closed — a malformed/short input is treated as an invalid signature, never accepted */
22652
+ }
22653
+ },
22654
+ });
22655
+ // The REAL bounded subprocess runner. Resolves the instar SOURCE root (the
22656
+ // agent's own checkout) — a deployed agent with no source/vitest yields
22657
+ // ranToCompletion:false → the check throws → the runner records NOTHING
22658
+ // (honest degrade, never a fabricated verdict). The spawn is fully bounded
22659
+ // (kill on timeout) and its output is streamed to a durable evidence log.
22660
+ const cp = await import('node:child_process');
22661
+ const failoverLogPath = path.join(config.stateDir, 'logs', 'session-pool-failover-runner.log');
22662
+ const runProcess = (args) => new Promise((resolve) => {
22663
+ // Candidate source roots, most-specific first: an explicit env override,
22664
+ // then this build's package root (dist/../..), then cwd. The first that
22665
+ // carries BOTH node_modules/.bin/vitest AND the test file wins.
22666
+ const candidates = [
22667
+ process.env.INSTAR_SOURCE_ROOT,
22668
+ path.resolve(__dirname, '..', '..'),
22669
+ process.cwd(),
22670
+ ].filter((c) => typeof c === 'string' && c.length > 0);
22671
+ let sourceRoot = null;
22672
+ for (const c of candidates) {
22673
+ try {
22674
+ if (fs.existsSync(path.join(c, 'node_modules', '.bin', 'vitest')) && fs.existsSync(path.join(c, args.testPath))) {
22675
+ sourceRoot = c;
22676
+ break;
22677
+ }
22678
+ }
22679
+ catch { /* @silent-fallback-ok — probe next candidate */ }
22680
+ }
22681
+ if (!sourceRoot) {
22682
+ // No source/vitest resolvable → NOT a failover verdict; the check
22683
+ // contract makes this THROW so the runner records nothing.
22684
+ resolve({ ranToCompletion: false, exitCode: null, evidenceRef: `no-source: vitest+${args.testPath} not resolvable (checkout absent)` });
22685
+ return;
22686
+ }
22687
+ const vitestBin = path.join(sourceRoot, 'node_modules', '.bin', 'vitest');
22688
+ let logFd = null;
22689
+ try {
22690
+ fs.mkdirSync(path.dirname(failoverLogPath), { recursive: true });
22691
+ logFd = fs.openSync(failoverLogPath, 'a');
22692
+ }
22693
+ catch {
22694
+ logFd = null; /* @silent-fallback-ok — evidence log optional */
22695
+ }
22696
+ const evidenceRef = `${failoverLogPath}@${new Date().toISOString()}`;
22697
+ let settled = false;
22698
+ const done = (r) => {
22699
+ if (settled)
22700
+ return;
22701
+ settled = true;
22702
+ if (logFd !== null) {
22703
+ try {
22704
+ fs.closeSync(logFd);
22705
+ }
22706
+ catch { /* @silent-fallback-ok */ }
22707
+ }
22708
+ resolve(r);
22709
+ };
22710
+ try {
22711
+ const child = cp.spawn(vitestBin, ['run', args.testPath, '--reporter=dot'], {
22712
+ cwd: sourceRoot,
22713
+ stdio: ['ignore', logFd ?? 'ignore', logFd ?? 'ignore'],
22714
+ env: { ...process.env },
22715
+ });
22716
+ const killTimer = setTimeout(() => {
22717
+ try {
22718
+ child.kill('SIGKILL');
22719
+ }
22720
+ catch { /* @silent-fallback-ok */ }
22721
+ done({ ranToCompletion: false, exitCode: null, evidenceRef: `${evidenceRef} (timeout ${args.timeoutMs}ms)` });
22722
+ }, args.timeoutMs);
22723
+ if (killTimer.unref)
22724
+ killTimer.unref();
22725
+ child.on('error', () => { clearTimeout(killTimer); done({ ranToCompletion: false, exitCode: null, evidenceRef: `${evidenceRef} (spawn-error)` }); });
22726
+ child.on('close', (code) => { clearTimeout(killTimer); done({ ranToCompletion: true, exitCode: code, evidenceRef }); });
22727
+ }
22728
+ catch {
22729
+ // @silent-fallback-ok — a spawn throw is an infra failure, NOT a
22730
+ // failover verdict; ranToCompletion:false → the check throws → nothing recorded.
22731
+ done({ ranToCompletion: false, exitCode: null, evidenceRef: `${evidenceRef} (spawn-threw)` });
22732
+ }
22733
+ });
22734
+ _sessionPoolFailoverRunnerDriver = fromMod.buildSessionPoolFailoverRunnerDriver({
22735
+ config: failoverRunnerCfg,
22736
+ resultStore: sessionPoolE2EResultStore,
22737
+ dryRunResultStore: failoverDryRunStore,
22738
+ runProcess,
22739
+ currentCommitSha: () => bootCommitSha || 'unknown',
22740
+ // The shadow→live-transfer failover is proven at the 'shadow' stage index
22741
+ // (0), which StageAdvancer.advanceTo('live-transfer') reads.
22742
+ provenStage: () => 0,
22743
+ audit: (event, detail) => console.log(pc.dim(` [failover-runner] ${event} ${JSON.stringify(detail)}`)),
22744
+ });
22745
+ if (_sessionPoolFailoverRunnerDriver) {
22746
+ console.log(pc.green(` SessionPoolFailoverRunner enabled (${failoverRunnerCfg.dryRun ? 'dry-run — records to SIDE store' : 'LIVE — records to promotion store'}; cadence ${failoverRunnerCfg.tickIntervalMs}ms)`));
22747
+ }
22748
+ }
22565
22749
  // Revert-ONLY reconcile tick (§Rollout): reconcile() can solely DEMOTE a live
22566
22750
  // stage when the CURRENT commit's Tier-3 E2E goes red — it never advances.
22567
22751
  // Promotion (advanceTo) stays operator-triggered via the rollout route/job.
22568
- // Cheap + inert while stage is 'dark' (reconcile() no-ops at the floor).
22752
+ // Cheap + inert while stage is 'dark' (reconcile() no-ops at the floor). The
22753
+ // failover runner's maybeTick() rides the SAME slow tick — it self-throttles
22754
+ // to its own (far slower) tickIntervalMs, so it is NOT a hot loop here.
22569
22755
  const stageReconcileTimer = setInterval(() => {
22570
22756
  try {
22571
22757
  stageAdvancer.reconcile();
22572
22758
  }
22573
22759
  catch { /* @silent-fallback-ok — retried next tick */ }
22760
+ // Fire-and-forget: the driver owns the in-flight guard + cadence gate; a
22761
+ // heavy E2E subprocess must never block the timer. Fail toward silence.
22762
+ try {
22763
+ void _sessionPoolFailoverRunnerDriver?.maybeTick();
22764
+ }
22765
+ catch { /* @silent-fallback-ok — retried next tick */ }
22574
22766
  }, 60_000);
22575
22767
  if (stageReconcileTimer.unref)
22576
22768
  stageReconcileTimer.unref();
@@ -22933,7 +23125,7 @@ export async function startServer(options) {
22933
23125
  const server = new AgentServer({ config, sessionManager, llmQueue: sharedLlmQueue, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, accountFollowMePeerViews: async () => { const nickById = new Map((_listPoolMachines?.() ?? []).map((m) => [m.machineId, m.nickname ?? m.machineId])); let peers = (_resolvePeerUrls?.() ?? []).map((p) => ({ machineId: p.machineId, nickname: nickById.get(p.machineId) ?? p.machineId, url: p.url })); if (peers.length === 0) {
22934
23126
  peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
22935
23127
  } if (peers.length === 0)
22936
- return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, conversationRegistry, conversationBindAuth, conversationFollowThrough, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, reviewCanaryBattery, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, getMachineCoherence: () => _machineCoherenceSentinel, getSingleMachineFailoverGap: () => _singleMachineFailoverGap, getMissingLoginSession: () => _missingLoginSession, meshRpcDispatcher, deliverA2aToMachine: _deliverA2aToMachine ?? undefined, workingSetPullCoordinator, workingSetArtifactManager, orchestratorPoller, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, sendDrain: _sendDrain ?? undefined, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, duplicateReconciler: _duplicateReconciler ?? undefined, ownerDarkLadder: _ownerDarkLadder ?? undefined, spawnAdmission: _spawnAdmission ?? undefined, judgmentProvenance: _judgmentProvenance ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, externalHogSentinel, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
23128
+ return []; const { fetchPeerSubscriptionViews } = await import('../core/fetchPeerSubscriptionViews.js'); return fetchPeerSubscriptionViews({ peers: () => peers, fetchImpl: fetch, authToken: config.authToken ?? '' }); }, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, accountFollowMeRevocation, credentialRepointing, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? undefined, greenPrAutoMerger: greenPrAutoMerger ?? undefined, guardLatchStore: guardLatchStore ?? undefined, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, topicProfile: _topicProfileCtx ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, meshBindActive: coordinator.managers.identityManager.hasIdentity() && config.multiMachine?.meshTransport?.enabled !== false, localSigningKeyPem, leaseTransport, peerEndpointRecorder, getSelfMeshEndpoints, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, conversationRegistry, conversationBindAuth, conversationFollowThrough, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, reviewCanaryBattery, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, ropeHealthMonitor, writeAdmission: writeAdmission ?? undefined, getInboundQueue: () => _inboundQueue, getMachineCoherence: () => _machineCoherenceSentinel, getSingleMachineFailoverGap: () => _singleMachineFailoverGap, getMissingLoginSession: () => _missingLoginSession, getSessionPoolFailoverRunner: () => _sessionPoolFailoverRunnerDriver?.status() ?? null, meshRpcDispatcher, deliverA2aToMachine: _deliverA2aToMachine ?? undefined, workingSetPullCoordinator, workingSetArtifactManager, orchestratorPoller, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, sendDrain: _sendDrain ?? undefined, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? undefined, duplicateReconciler: _duplicateReconciler ?? undefined, ownerDarkLadder: _ownerDarkLadder ?? undefined, spawnAdmission: _spawnAdmission ?? undefined, judgmentProvenance: _judgmentProvenance ?? undefined, leaseHandback: _leaseHandbackCtx ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, deliverMandateToMachine: _deliverMandateToMachine ?? undefined, poolLink: _poolLink ?? undefined, poolPollCache: _poolPollCache ?? undefined, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, externalHogSentinel, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, autonomousLivenessReconciler, enforcedTerminationStatus: () => enforcedTerminationWatchdog?.guardStatus() ?? null, prHandLease: prHandLease ?? undefined, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode, liveTestRunnerCtx }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
22937
23129
  if (_stateSyncStoresResolved?.classReview?.enabled && replicatedPeerStreamReader) {
22938
23130
  const { CLASS_REVIEW_STORE_KEY, classReviewFromOriginRecord } = await import('../core/ClassReviewReplicatedStore.js');
22939
23131
  const reader = replicatedPeerStreamReader;