instar 1.3.896 → 1.3.897

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 (81) hide show
  1. package/dashboard/index.html +30 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +111 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +12 -0
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/CoherenceJournal.d.ts +17 -1
  9. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  10. package/dist/core/CoherenceJournal.js +29 -2
  11. package/dist/core/CoherenceJournal.js.map +1 -1
  12. package/dist/core/JournalSyncApplier.d.ts.map +1 -1
  13. package/dist/core/JournalSyncApplier.js +11 -0
  14. package/dist/core/JournalSyncApplier.js.map +1 -1
  15. package/dist/core/MachineSshEndpoint.d.ts +47 -0
  16. package/dist/core/MachineSshEndpoint.d.ts.map +1 -0
  17. package/dist/core/MachineSshEndpoint.js +221 -0
  18. package/dist/core/MachineSshEndpoint.js.map +1 -0
  19. package/dist/core/MachineSshIdentity.d.ts +26 -0
  20. package/dist/core/MachineSshIdentity.d.ts.map +1 -0
  21. package/dist/core/MachineSshIdentity.js +97 -0
  22. package/dist/core/MachineSshIdentity.js.map +1 -0
  23. package/dist/core/MeshRpc.d.ts +6 -0
  24. package/dist/core/MeshRpc.d.ts.map +1 -1
  25. package/dist/core/MeshRpc.js +2 -0
  26. package/dist/core/MeshRpc.js.map +1 -1
  27. package/dist/core/MutualSshHealthController.d.ts +33 -0
  28. package/dist/core/MutualSshHealthController.d.ts.map +1 -0
  29. package/dist/core/MutualSshHealthController.js +113 -0
  30. package/dist/core/MutualSshHealthController.js.map +1 -0
  31. package/dist/core/MutualSshProbeScheduler.d.ts +21 -0
  32. package/dist/core/MutualSshProbeScheduler.d.ts.map +1 -0
  33. package/dist/core/MutualSshProbeScheduler.js +57 -0
  34. package/dist/core/MutualSshProbeScheduler.js.map +1 -0
  35. package/dist/core/MutualSshRuntime.d.ts +106 -0
  36. package/dist/core/MutualSshRuntime.d.ts.map +1 -0
  37. package/dist/core/MutualSshRuntime.js +423 -0
  38. package/dist/core/MutualSshRuntime.js.map +1 -0
  39. package/dist/core/MutualSshVerifier.d.ts +44 -0
  40. package/dist/core/MutualSshVerifier.d.ts.map +1 -0
  41. package/dist/core/MutualSshVerifier.js +97 -0
  42. package/dist/core/MutualSshVerifier.js.map +1 -0
  43. package/dist/core/SshBootstrapAdvert.d.ts +23 -0
  44. package/dist/core/SshBootstrapAdvert.d.ts.map +1 -0
  45. package/dist/core/SshBootstrapAdvert.js +43 -0
  46. package/dist/core/SshBootstrapAdvert.js.map +1 -0
  47. package/dist/core/SshHostKeyLifecycle.d.ts +43 -0
  48. package/dist/core/SshHostKeyLifecycle.d.ts.map +1 -0
  49. package/dist/core/SshHostKeyLifecycle.js +68 -0
  50. package/dist/core/SshHostKeyLifecycle.js.map +1 -0
  51. package/dist/core/SshPeerAdmissionStore.d.ts +29 -0
  52. package/dist/core/SshPeerAdmissionStore.d.ts.map +1 -0
  53. package/dist/core/SshPeerAdmissionStore.js +86 -0
  54. package/dist/core/SshPeerAdmissionStore.js.map +1 -0
  55. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  56. package/dist/core/devGatedFeatures.js +6 -0
  57. package/dist/core/devGatedFeatures.js.map +1 -1
  58. package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
  59. package/dist/core/machineCoherenceManifest.js +8 -0
  60. package/dist/core/machineCoherenceManifest.js.map +1 -1
  61. package/dist/core/types.d.ts +10 -0
  62. package/dist/core/types.d.ts.map +1 -1
  63. package/dist/core/types.js.map +1 -1
  64. package/dist/server/AgentServer.d.ts +1 -0
  65. package/dist/server/AgentServer.d.ts.map +1 -1
  66. package/dist/server/AgentServer.js +22 -0
  67. package/dist/server/AgentServer.js.map +1 -1
  68. package/dist/server/CapabilityIndex.js +2 -2
  69. package/dist/server/CapabilityIndex.js.map +1 -1
  70. package/dist/server/routes.d.ts +2 -0
  71. package/dist/server/routes.d.ts.map +1 -1
  72. package/dist/server/routes.js +9 -0
  73. package/dist/server/routes.js.map +1 -1
  74. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  75. package/dist/testing/selfActionRegistry.js +39 -0
  76. package/dist/testing/selfActionRegistry.js.map +1 -1
  77. package/package.json +3 -1
  78. package/src/data/builtin-manifest.json +47 -47
  79. package/src/data/state-coherence-registry.json +36 -0
  80. package/upgrades/1.3.897.md +39 -0
  81. package/upgrades/side-effects/mutual-ssh-autobootstrap.md +137 -0
@@ -3779,6 +3779,11 @@
3779
3779
  headline + tiles + drill-down here. Rebuilt from the old insider "guards" line.
3780
3780
  Nickname editing (issue #1429) lives one drill down, commits on Enter/blur. -->
3781
3781
  <div id="machinesGlance" class="glance-root"></div>
3782
+ <section aria-labelledby="mutualSshTitle" style="border:1px solid var(--border);border-radius:10px;padding:16px">
3783
+ <h3 id="mutualSshTitle" style="margin:0 0 8px">Machine-to-machine SSH</h3>
3784
+ <p style="margin:0 0 10px;color:var(--text-dim)">Restricted Instar subsystem connectivity. This does not enable Remote Login or change personal SSH files.</p>
3785
+ <div id="mutualSshStatus" class="glance-empty" aria-live="polite">Loading connectivity…</div>
3786
+ </section>
3782
3787
  <section aria-labelledby="throughputFloorTitle" style="border:1px solid var(--border);border-radius:10px;padding:16px">
3783
3788
  <h3 id="throughputFloorTitle" style="margin:0 0 8px">Autonomous throughput observations</h3>
3784
3789
  <p style="margin:0 0 10px;color:var(--text-dim)">Pull-only evidence of project PR movement and manager communication. This view never notifies or changes a run.</p>
@@ -5541,6 +5546,7 @@
5541
5546
  return;
5542
5547
  }
5543
5548
  if (stamp) stamp.textContent = new Date().toLocaleTimeString();
5549
+ await loadMutualSshStatus();
5544
5550
  await loadThroughputFloor();
5545
5551
 
5546
5552
  if (!pool.enabled) {
@@ -5564,6 +5570,30 @@
5564
5570
  glance.renderGlance(document, glanceRoot, spec);
5565
5571
  }
5566
5572
 
5573
+ async function loadMutualSshStatus() {
5574
+ const root = document.getElementById('mutualSshStatus');
5575
+ if (!root) return;
5576
+ root.textContent = '';
5577
+ let status;
5578
+ try { status = await apiFetch('/machines/ssh-health?scope=pool'); }
5579
+ catch { root.textContent = 'Not enabled on this agent.'; return; }
5580
+ if (status.dryRun) { root.textContent = 'Observation only — no peer SSH sessions are accepted.'; return; }
5581
+ const pairs = Array.isArray(status.pairs) ? status.pairs : [];
5582
+ if (pairs.length === 0) { root.textContent = status.listener ? 'Ready; no paired machines to verify.' : 'Waiting for the restricted listener.'; return; }
5583
+ const healthy = pairs.filter(pair => pair && pair.mutual === true).length;
5584
+ const summary = document.createElement('div');
5585
+ summary.textContent = healthy === pairs.length
5586
+ ? 'Mutual proof is current for all ' + String(pairs.length) + ' machine pair' + (pairs.length === 1 ? '.' : 's.')
5587
+ : String(healthy) + ' of ' + String(pairs.length) + ' machine pairs have current proof in both directions.';
5588
+ root.appendChild(summary);
5589
+ for (const pair of pairs) {
5590
+ const row = document.createElement('div');
5591
+ row.style.marginTop = '8px';
5592
+ row.textContent = (pair.mutual ? 'Verified: ' : 'Blocked: ') + (Array.isArray(pair.machines) ? pair.machines.join(' ↔ ') : 'machine pair');
5593
+ root.appendChild(row);
5594
+ }
5595
+ }
5596
+
5567
5597
  async function loadThroughputFloor() {
5568
5598
  const root = document.getElementById('throughputFloorStatus');
5569
5599
  if (!root) return;
@@ -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;AA8pDD;;;;;;;;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,CA41oBtE;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;AA8pDD;;;;;;;;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,CA47oBtE;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"}
@@ -4304,9 +4304,15 @@ export async function startServer(options) {
4304
4304
  }
4305
4305
  coherenceJournal.setReplicatedRecordCommitObserver((kind, entries) => {
4306
4306
  replicatedPeerStreamReader?.observeCommittedEntries(kind, entries);
4307
+ if (kind === 'ssh-direction-proof')
4308
+ for (const entry of entries)
4309
+ mutualSshRuntime?.handleJournalProof(entry.data, entry.machine);
4307
4310
  });
4308
- journalSyncApplier?.setReplicatedRecordCommitObserver((_senderMachineId, kind, entries) => {
4311
+ journalSyncApplier?.setReplicatedRecordCommitObserver((senderMachineId, kind, entries) => {
4309
4312
  replicatedPeerStreamReader?.observeCommittedEntries(kind, entries);
4313
+ if (kind === 'ssh-direction-proof')
4314
+ for (const entry of entries)
4315
+ mutualSshRuntime?.handleJournalProof(entry.data, senderMachineId);
4310
4316
  });
4311
4317
  // Author-side HLC clock — persisted under the journal dir (atomic temp+rename)
4312
4318
  // so the merge total order survives restarts (§3.5). node = this machine's id.
@@ -18402,6 +18408,7 @@ export async function startServer(options) {
18402
18408
  // are live too. Single-machine uses the in-memory store; the cross-machine
18403
18409
  // git-backed store swaps in for the Track-H proof (the registry is store-agnostic).
18404
18410
  let meshRpcDispatcher;
18411
+ let mutualSshRuntime = null;
18405
18412
  let sessionOwnershipRegistry;
18406
18413
  /**
18407
18414
  * Ownership Follows Live Work — Part B (claim-on-spawn). Assigned inside the
@@ -19810,6 +19817,18 @@ export async function startServer(options) {
19810
19817
  }
19811
19818
  },
19812
19819
  handlers: {
19820
+ 'ssh-bootstrap-advert': (cmd, sender) => {
19821
+ if (!mutualSshRuntime)
19822
+ return { ok: false, reason: 'mutual-ssh-disabled' };
19823
+ const c = cmd;
19824
+ return mutualSshRuntime.handleAdvert(c.advert, sender);
19825
+ },
19826
+ 'ssh-proof-publish': (cmd, sender) => {
19827
+ if (!mutualSshRuntime)
19828
+ return { ok: false, reason: 'mutual-ssh-disabled' };
19829
+ const c = cmd;
19830
+ return mutualSshRuntime.handleProof(c.proof, sender);
19831
+ },
19813
19832
  'capacity-report': () => machinePoolRegistry?.getCapacities() ?? [],
19814
19833
  'session-status': () => {
19815
19834
  const base = machinePoolRegistry?.getCapacity(meshSelfId) ?? { machineId: meshSelfId };
@@ -20355,6 +20374,92 @@ export async function startServer(options) {
20355
20374
  nickname: m.entry.nickname,
20356
20375
  lastKnownUrl: m.entry.lastKnownUrl ?? null,
20357
20376
  }));
20377
+ // Mutual SSH phase 1: the signed HTTP mesh is the bootstrap carrier;
20378
+ // the resulting proof itself traverses the restricted SSH subsystem.
20379
+ // Hard-dark on fleet, dry-run first on the development agent.
20380
+ if (resolveDevAgentGate(config.multiMachine?.mutualSsh?.enabled, config)) {
20381
+ try {
20382
+ const sshMod = await import('../core/MutualSshRuntime.js');
20383
+ const portMod = await import('../core/PortRegistry.js');
20384
+ const cryptoMod = await import('node:crypto');
20385
+ const selfPem = meshIdMgr.getSigningPublicKeyPem(meshSelfId) ?? '';
20386
+ const machineFingerprint = (pem) => cryptoMod.createHash('sha256').update(pem).digest('hex');
20387
+ const privateHosts = (getSelfMeshEndpoints?.() ?? []).flatMap((endpoint) => {
20388
+ try {
20389
+ const host = new URL(endpoint.url).hostname;
20390
+ return host ? [host] : [];
20391
+ }
20392
+ catch {
20393
+ // @silent-fallback-ok — malformed advertised URLs are omitted;
20394
+ // readiness names the peer and reports the missing endpoint.
20395
+ return [];
20396
+ }
20397
+ });
20398
+ const bindHost = privateHosts.find(host => host.startsWith('100.'))
20399
+ ?? privateHosts.find(host => /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(host))
20400
+ ?? '127.0.0.1';
20401
+ const bindPort = portMod.allocatePort(`${config.projectName}-mutual-ssh`, 42000, 49000);
20402
+ const peers = () => meshIdMgr.getActiveMachines()
20403
+ .filter(row => row.machineId !== meshSelfId)
20404
+ .map(row => ({
20405
+ machineId: row.machineId,
20406
+ // Millisecond precision is part of the revoke→re-pair replay fence;
20407
+ // second truncation could accidentally reuse an epoch during a fast re-pair.
20408
+ pairingEpoch: Math.max(1, Date.parse(row.entry.pairedAt)),
20409
+ machineFingerprint: machineFingerprint(meshIdMgr.getSigningPublicKeyPem(row.machineId) ?? ''),
20410
+ endpoints: (row.entry.endpoints ?? []).map(endpoint => endpoint.url),
20411
+ }));
20412
+ mutualSshRuntime = new sshMod.MutualSshRuntime({
20413
+ stateDir: config.stateDir, agentId: config.projectName, selfMachineId: meshSelfId,
20414
+ selfMachineFingerprint: machineFingerprint(selfPem),
20415
+ observerBootId: `${meshSelfId}:${process.pid}:${Date.now()}`,
20416
+ bindHost, bindPort, dryRun: config.multiMachine?.mutualSsh?.dryRun !== false,
20417
+ requiredForReadiness: config.multiMachine?.mutualSsh?.requiredForEmployeeRole === true,
20418
+ freshnessMs: config.multiMachine?.mutualSsh?.freshnessMs,
20419
+ cadenceMs: config.multiMachine?.mutualSsh?.cadenceMs,
20420
+ concurrency: config.multiMachine?.mutualSsh?.concurrency,
20421
+ listPeers: peers,
20422
+ send: async (machineId, command) => {
20423
+ const url = peerUrl(machineId);
20424
+ if (!url)
20425
+ throw new Error('mutual-ssh-control-plane-unavailable');
20426
+ const result = await meshClient.send({ machineId, url }, command, 0, { timeoutMs: 15_000 });
20427
+ if (!result.ok)
20428
+ throw new Error(result.reason ?? `mutual-ssh-mesh-${result.status}`);
20429
+ return result.result;
20430
+ },
20431
+ sign: payload => idMod.sign(payload, localSigningKeyPem),
20432
+ verify: (machineId, payload, signature) => {
20433
+ const pem = meshIdMgr.getSigningPublicKeyPem(machineId);
20434
+ return pem ? idMod.verify(payload, signature, pem) : false;
20435
+ },
20436
+ emitJournalProof: proof => {
20437
+ if (!proof.machineSignature)
20438
+ return;
20439
+ const { monotonicDeadlineMs: _, ...durable } = proof;
20440
+ coherenceJournal?.emitSshDirectionProof(durable);
20441
+ },
20442
+ audit: event => {
20443
+ const file = path.join(config.stateDir, 'logs', 'mutual-ssh.jsonl');
20444
+ fs.mkdirSync(path.dirname(file), { recursive: true });
20445
+ /* state-registry: audit-mutual-ssh */ fs.appendFileSync(file, `${JSON.stringify(event)}\n`);
20446
+ },
20447
+ notifySecurity: event => notify('IMMEDIATE', 'mutual-ssh-host-key-change', JSON.stringify(event)),
20448
+ });
20449
+ await mutualSshRuntime.start();
20450
+ globalThis.__instarMutualSshRuntime = mutualSshRuntime;
20451
+ guardRegistry.register('multiMachine.mutualSsh.enabled', () => ({
20452
+ enabled: true, dryRun: config.multiMachine?.mutualSsh?.dryRun !== false,
20453
+ status: mutualSshRuntime?.status() ?? null,
20454
+ }));
20455
+ }
20456
+ catch (err) {
20457
+ // @silent-fallback-ok — the feature fails closed and surfaces its
20458
+ // stable bootstrap reason while the rest of the server remains live.
20459
+ mutualSshRuntime = null;
20460
+ console.warn(`[mutual-ssh] initialization blocked: ${err instanceof Error ? err.message : String(err)}`);
20461
+ }
20462
+ }
20358
20463
  // Pool Dashboard Streaming requesting side (§2.2): build the connector
20359
20464
  // the WebSocketManager uses to open an upstream /pool-stream to a peer.
20360
20465
  // connect() is synchronous (PeerStreamProxy contract) but the mint +
@@ -23530,6 +23635,11 @@ export async function startServer(options) {
23530
23635
  stopHeartbeat?.();
23531
23636
  }
23532
23637
  catch { /* non-critical during shutdown */ }
23638
+ try {
23639
+ await globalThis.__instarMutualSshRuntime?.rollback();
23640
+ }
23641
+ catch { /* @silent-fallback-ok: shutdown continues; endpoint sockets also die with the process */ }
23642
+ delete globalThis.__instarMutualSshRuntime;
23533
23643
  // pid-guarded: only remove OUR OWN registration. An unguarded
23534
23644
  // unregister-by-path here deletes the successor generation's fresh
23535
23645
  // entry during back-to-back update restarts (registry lost-update