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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +203 -11
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +31 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/MutualSshRuntime.d.ts +15 -0
- package/dist/core/MutualSshRuntime.d.ts.map +1 -1
- package/dist/core/MutualSshRuntime.js +123 -10
- package/dist/core/MutualSshRuntime.js.map +1 -1
- package/dist/core/PeerAuthorizedKeys.d.ts +36 -0
- package/dist/core/PeerAuthorizedKeys.d.ts.map +1 -0
- package/dist/core/PeerAuthorizedKeys.js +208 -0
- package/dist/core/PeerAuthorizedKeys.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +13 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +41 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/ProactiveSwapMonitor.d.ts +10 -0
- package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
- package/dist/core/ProactiveSwapMonitor.js +29 -4
- package/dist/core/ProactiveSwapMonitor.js.map +1 -1
- package/dist/core/QuotaAwareScheduler.d.ts +4 -1
- package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
- package/dist/core/QuotaAwareScheduler.js +18 -9
- package/dist/core/QuotaAwareScheduler.js.map +1 -1
- package/dist/core/SshBootstrapAdvert.d.ts +6 -0
- package/dist/core/SshBootstrapAdvert.d.ts.map +1 -1
- package/dist/core/SshBootstrapAdvert.js +21 -1
- package/dist/core/SshBootstrapAdvert.js.map +1 -1
- package/dist/core/StandingSshVerifier.d.ts +12 -0
- package/dist/core/StandingSshVerifier.d.ts.map +1 -0
- package/dist/core/StandingSshVerifier.js +53 -0
- package/dist/core/StandingSshVerifier.js.map +1 -0
- package/dist/core/SubscriptionPool.d.ts +3 -0
- package/dist/core/SubscriptionPool.d.ts.map +1 -1
- package/dist/core/SubscriptionPool.js +7 -0
- package/dist/core/SubscriptionPool.js.map +1 -1
- package/dist/core/SwapAntiThrash.d.ts +4 -0
- package/dist/core/SwapAntiThrash.d.ts.map +1 -1
- package/dist/core/SwapAntiThrash.js +2 -1
- package/dist/core/SwapAntiThrash.js.map +1 -1
- package/dist/core/SwapLedger.d.ts +2 -0
- package/dist/core/SwapLedger.d.ts.map +1 -1
- package/dist/core/SwapLedger.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +18 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
- package/dist/core/machineCoherenceManifest.js +9 -0
- package/dist/core/machineCoherenceManifest.js.map +1 -1
- package/dist/core/sessionPoolFailoverRunnerConfig.d.ts +152 -0
- package/dist/core/sessionPoolFailoverRunnerConfig.d.ts.map +1 -0
- package/dist/core/sessionPoolFailoverRunnerConfig.js +181 -0
- package/dist/core/sessionPoolFailoverRunnerConfig.js.map +1 -0
- package/dist/core/types.d.ts +39 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts.map +1 -1
- package/dist/monitoring/guardManifest.js +16 -0
- package/dist/monitoring/guardManifest.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -1
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +14 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/upgrades/1.3.908.md +33 -0
- package/upgrades/side-effects/login-loss-account-swap.md +87 -0
- 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;
|
|
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"}
|
package/dist/commands/server.js
CHANGED
|
@@ -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
|
-
|
|
16931
|
-
|
|
16932
|
-
|
|
16933
|
-
|
|
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;
|