instar 1.3.490 → 1.3.491

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 (95) 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 +146 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/GuardPostureStore.d.ts +38 -0
  6. package/dist/core/GuardPostureStore.d.ts.map +1 -0
  7. package/dist/core/GuardPostureStore.js +87 -0
  8. package/dist/core/GuardPostureStore.js.map +1 -0
  9. package/dist/core/MachinePoolRegistry.d.ts +16 -0
  10. package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
  11. package/dist/core/MachinePoolRegistry.js +24 -1
  12. package/dist/core/MachinePoolRegistry.js.map +1 -1
  13. package/dist/core/PeerPresencePuller.d.ts +9 -0
  14. package/dist/core/PeerPresencePuller.d.ts.map +1 -1
  15. package/dist/core/PeerPresencePuller.js +1 -1
  16. package/dist/core/PeerPresencePuller.js.map +1 -1
  17. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  18. package/dist/core/PostUpdateMigrator.js +30 -2
  19. package/dist/core/PostUpdateMigrator.js.map +1 -1
  20. package/dist/core/types.d.ts +29 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/core/types.js.map +1 -1
  23. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts +8 -0
  24. package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts.map +1 -1
  25. package/dist/monitoring/ActiveWorkSilenceSentinel.js +8 -0
  26. package/dist/monitoring/ActiveWorkSilenceSentinel.js.map +1 -1
  27. package/dist/monitoring/ContextWedgeSentinel.d.ts +8 -0
  28. package/dist/monitoring/ContextWedgeSentinel.d.ts.map +1 -1
  29. package/dist/monitoring/ContextWedgeSentinel.js +8 -0
  30. package/dist/monitoring/ContextWedgeSentinel.js.map +1 -1
  31. package/dist/monitoring/GuardPostureTripwire.d.ts +3 -27
  32. package/dist/monitoring/GuardPostureTripwire.d.ts.map +1 -1
  33. package/dist/monitoring/GuardPostureTripwire.js +8 -76
  34. package/dist/monitoring/GuardPostureTripwire.js.map +1 -1
  35. package/dist/monitoring/GuardRegistry.d.ts +48 -0
  36. package/dist/monitoring/GuardRegistry.d.ts.map +1 -0
  37. package/dist/monitoring/GuardRegistry.js +49 -0
  38. package/dist/monitoring/GuardRegistry.js.map +1 -0
  39. package/dist/monitoring/SessionReaper.d.ts +7 -0
  40. package/dist/monitoring/SessionReaper.d.ts.map +1 -1
  41. package/dist/monitoring/SessionReaper.js +9 -0
  42. package/dist/monitoring/SessionReaper.js.map +1 -1
  43. package/dist/monitoring/SessionWatchdog.d.ts +8 -0
  44. package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
  45. package/dist/monitoring/SessionWatchdog.js +8 -0
  46. package/dist/monitoring/SessionWatchdog.js.map +1 -1
  47. package/dist/monitoring/SocketDisconnectSentinel.d.ts +8 -0
  48. package/dist/monitoring/SocketDisconnectSentinel.d.ts.map +1 -1
  49. package/dist/monitoring/SocketDisconnectSentinel.js +8 -0
  50. package/dist/monitoring/SocketDisconnectSentinel.js.map +1 -1
  51. package/dist/monitoring/guardManifest.d.ts +67 -0
  52. package/dist/monitoring/guardManifest.d.ts.map +1 -0
  53. package/dist/monitoring/guardManifest.js +536 -0
  54. package/dist/monitoring/guardManifest.js.map +1 -0
  55. package/dist/monitoring/guardPosture.d.ts +77 -0
  56. package/dist/monitoring/guardPosture.d.ts.map +1 -0
  57. package/dist/monitoring/guardPosture.js +198 -0
  58. package/dist/monitoring/guardPosture.js.map +1 -0
  59. package/dist/monitoring/guardPostureView.d.ts +100 -0
  60. package/dist/monitoring/guardPostureView.d.ts.map +1 -0
  61. package/dist/monitoring/guardPostureView.js +294 -0
  62. package/dist/monitoring/guardPostureView.js.map +1 -0
  63. package/dist/monitoring/probes/GuardPostureProbe.d.ts +82 -0
  64. package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -0
  65. package/dist/monitoring/probes/GuardPostureProbe.js +384 -0
  66. package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -0
  67. package/dist/scaffold/templates.d.ts.map +1 -1
  68. package/dist/scaffold/templates.js +9 -0
  69. package/dist/scaffold/templates.js.map +1 -1
  70. package/dist/scheduler/JobScheduler.d.ts +10 -0
  71. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  72. package/dist/scheduler/JobScheduler.js +12 -0
  73. package/dist/scheduler/JobScheduler.js.map +1 -1
  74. package/dist/server/AgentServer.d.ts +8 -0
  75. package/dist/server/AgentServer.d.ts.map +1 -1
  76. package/dist/server/AgentServer.js +2 -0
  77. package/dist/server/AgentServer.js.map +1 -1
  78. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  79. package/dist/server/CapabilityIndex.js +11 -0
  80. package/dist/server/CapabilityIndex.js.map +1 -1
  81. package/dist/server/peerUrlGuard.d.ts +35 -0
  82. package/dist/server/peerUrlGuard.d.ts.map +1 -0
  83. package/dist/server/peerUrlGuard.js +93 -0
  84. package/dist/server/peerUrlGuard.js.map +1 -0
  85. package/dist/server/routes.d.ts +14 -0
  86. package/dist/server/routes.d.ts.map +1 -1
  87. package/dist/server/routes.js +136 -0
  88. package/dist/server/routes.js.map +1 -1
  89. package/package.json +3 -2
  90. package/scripts/lint-guard-manifest.js +264 -0
  91. package/src/data/builtin-manifest.json +65 -65
  92. package/src/data/state-coherence-registry.json +54 -18
  93. package/src/scaffold/templates.ts +9 -0
  94. package/upgrades/1.3.491.md +37 -0
  95. package/upgrades/side-effects/guard-posture-endpoint.md +101 -0
@@ -4976,6 +4976,35 @@
4976
4976
  if (m.loadAvg < 4) return 'working';
4977
4977
  return 'busy';
4978
4978
  }
4979
+ function machineGuardsLine(m) {
4980
+ // Guard posture (GUARD-POSTURE-ENDPOINT-SPEC §2.3): last-known summary
4981
+ // with RECEIVER-side age. No posture ever received → honest "unknown",
4982
+ // never "0 on / 0 off". Only problem classes get named; dark-default
4983
+ // offs stay quiet (alarm-fatigue is how the next Mini stays off while
4984
+ // displayed).
4985
+ const p = m.guardPosture;
4986
+ if (!p) return 'guards: unknown';
4987
+ const problems = [];
4988
+ if (p.offDeviant) problems.push(p.offDeviant + ' off that should be on');
4989
+ if (p.offRuntimeDivergent) problems.push(p.offRuntimeDivergent + ' off despite settings');
4990
+ if (p.onStale) problems.push(p.onStale + ' frozen');
4991
+ if (p.missing) problems.push(p.missing + ' missing');
4992
+ if (p.errored) problems.push(p.errored + ' erroring');
4993
+ if (p.divergedPendingRestart) problems.push(p.divergedPendingRestart + ' awaiting restart');
4994
+ const okPart = 'guards: ' + ((p.onConfirmed ?? 0) + (p.onUnverified ?? 0) + (p.onDryRun ?? 0)) + ' on' +
4995
+ (p.onConfirmed ? ' (' + p.onConfirmed + ' confirmed)' : '');
4996
+ let age = '';
4997
+ if (m.guardPostureReceivedAt) {
4998
+ const ms = Date.now() - Date.parse(m.guardPostureReceivedAt);
4999
+ if (isFinite(ms) && ms >= 0) {
5000
+ const mins = Math.round(ms / 60000);
5001
+ age = mins < 2 ? 'just now' : mins < 90 ? 'as of ' + mins + 'm ago'
5002
+ : mins < 2880 ? 'as of ' + Math.round(mins / 60) + 'h ago'
5003
+ : 'as of ' + Math.round(mins / 1440) + 'd ago';
5004
+ }
5005
+ }
5006
+ return [okPart, problems.length ? '⚠ ' + problems.join(', ') : '', age].filter(Boolean).join(' · ');
5007
+ }
4979
5008
  function renderMachineCard(m, routerHolder) {
4980
5009
  const online = !!m.online;
4981
5010
  const dot = !online ? 'off' : (m.clockSkewStatus === 'suspect-clock-removed' ? 'warn' : 'ok');
@@ -4994,6 +5023,7 @@
4994
5023
  '</div>' +
4995
5024
  '<div class="machine-meta">' + escapeHtml(machineSpecLine(m.hardware)) + '</div>' +
4996
5025
  '<div class="machine-meta">' + tail + '</div>' +
5026
+ '<div class="machine-meta">' + escapeHtml(machineGuardsLine(m)) + '</div>' +
4997
5027
  '</div>';
4998
5028
  }
4999
5029
  async function saveMachineNickname(input) {
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAmG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;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;AAkyCD,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,GACpD,IAAI,CAwVN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAujWtE;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;AAgCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAyG7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;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;AAmyCD,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,GACpD,IAAI,CAwVN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmrWtE;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"}
@@ -65,6 +65,12 @@ import { QuotaNotifier } from '../monitoring/QuotaNotifier.js';
65
65
  import { QuotaManager } from '../monitoring/QuotaManager.js';
66
66
  import { classifySessionDeath, detectContextExhaustion } from '../monitoring/QuotaExhaustionDetector.js';
67
67
  import { SessionWatchdog } from '../monitoring/SessionWatchdog.js';
68
+ import { GuardRegistry } from '../monitoring/GuardRegistry.js';
69
+ import { resolveGuardConfigSnapshot, readGuardPostureBootSnapshot } from '../monitoring/guardPosture.js';
70
+ import { buildGuardInventory, buildHeartbeatPostureBlock } from '../monitoring/guardPostureView.js';
71
+ import { createGuardPostureProbes } from '../monitoring/probes/GuardPostureProbe.js';
72
+ import { GuardPostureStore } from '../core/GuardPostureStore.js';
73
+ import { isPeerUrlAllowedForCredentials } from '../server/peerUrlGuard.js';
68
74
  import { formatWatchdogUserMessage } from '../monitoring/watchdog-notifications.js';
69
75
  import { StallTriageNurse } from '../monitoring/StallTriageNurse.js';
70
76
  import { TriageOrchestrator } from '../monitoring/TriageOrchestrator.js';
@@ -372,6 +378,7 @@ let _resolveRouterUrl = null;
372
378
  /** Every OTHER active machine with a known URL — backs GET /sessions?scope=pool
373
379
  * (pool-wide session aggregation for the dashboard). Set in the same mesh block. */
374
380
  let _resolvePeerUrls = null;
381
+ let _listPoolMachines = null;
375
382
  /** Multi-Machine Session Pool §L4: per-topic placement pin store ("move this to <nickname>"). */
376
383
  let _topicPinStore = null;
377
384
  /** Cross-machine secret-sync (spec Phase 4): route-facing handle (push lever + read-only status). */
@@ -4029,9 +4036,15 @@ export async function startServer(options) {
4029
4036
  : 'detect-only';
4030
4037
  console.log(pc.green(` Prompt Gate: enabled (mode: ${mode})`));
4031
4038
  }
4039
+ // ── GuardRegistry (GUARD-POSTURE-ENDPOINT-SPEC §2.1) ──────────────
4040
+ // Constructed guard components self-register SYNC runtime getters at
4041
+ // their construction sites below; GET /guards reconciles registrations
4042
+ // against the declared manifest (missing = a state, never an omission).
4043
+ const guardRegistry = new GuardRegistry();
4032
4044
  let scheduler;
4033
4045
  if (config.scheduler.enabled && coordinator.isAwake) {
4034
4046
  scheduler = new JobScheduler(config.scheduler, sessionManager, state, config.stateDir);
4047
+ guardRegistry.register('scheduler.enabled', () => scheduler.guardStatus());
4035
4048
  // Wire machine identity for machine-scoped job filtering
4036
4049
  if (coordinator.identity) {
4037
4050
  scheduler.setMachineIdentity(coordinator.identity.machineId, coordinator.identity.name);
@@ -5805,6 +5818,7 @@ export async function startServer(options) {
5805
5818
  if (config.monitoring.watchdog?.enabled) {
5806
5819
  watchdog = new SessionWatchdog(config, sessionManager, state);
5807
5820
  watchdog.intelligence = sharedIntelligence ?? null;
5821
+ guardRegistry.register('monitoring.watchdog.enabled', () => watchdog.guardStatus());
5808
5822
  watchdog.on('intervention', (event) => {
5809
5823
  // Routine recovery (Ctrl+C, SIGTERM) stays as console diagnostics only.
5810
5824
  // The user only hears when we had to force-kill (SIGKILL / session-kill) —
@@ -6990,6 +7004,7 @@ export async function startServer(options) {
6990
7004
  socketSentinel.on('recovered', (n) => notifier.record('recovered', 'socket-disconnect', n));
6991
7005
  socketSentinel.on('recovery-error', (e) => notifier.record('recovery-error', 'socket-disconnect', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
6992
7006
  socketSentinel.start();
7007
+ guardRegistry.register('monitoring.socketDisconnectSentinel.enabled', () => socketSentinel.guardStatus());
6993
7008
  socketRecoveryActive = (s) => socketSentinel.isRecoveryActive(s);
6994
7009
  console.log(pc.green(' SocketDisconnectSentinel enabled (connection-drop recovery)'));
6995
7010
  }
@@ -7044,6 +7059,7 @@ export async function startServer(options) {
7044
7059
  silenceSentinel.on('recover-error', (e) => notifier.record('recovery-error', 'active-silence', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
7045
7060
  silenceSentinel.on('nudge-error', (e) => notifier.record('nudge-error', 'active-silence', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
7046
7061
  silenceSentinel.start();
7062
+ guardRegistry.register('monitoring.activeWorkSilenceSentinel.enabled', () => silenceSentinel.guardStatus());
7047
7063
  silenceRecoveryActive = (s) => silenceSentinel.isRecoveryActive(s);
7048
7064
  const silenceMode = silenceAutoRecover ? ' — auto-heal LIVE' : '';
7049
7065
  console.log(pc.green(telegramEscalation
@@ -7086,6 +7102,16 @@ export async function startServer(options) {
7086
7102
  wedgeSentinel.on('false-alarm', (e) => notifier.record('false-alarm', 'context-wedge', e.sessionName, 'signature scrolled out of tail'));
7087
7103
  wedgeSentinel.on('recovery-error', (e) => notifier.record('recovery-error', 'context-wedge', e.sessionName, e.err instanceof Error ? e.err.message : String(e.err)));
7088
7104
  wedgeSentinel.start();
7105
+ guardRegistry.register('monitoring.contextWedgeSentinel.enabled', () => wedgeSentinel.guardStatus());
7106
+ // Sub-guard row (spec §2.1): the destructive auto-recovery arm reports
7107
+ // its OWN posture so 'autoRecovery silently off inside an on-confirmed
7108
+ // sentinel' cannot hide.
7109
+ guardRegistry.register('monitoring.contextWedgeSentinel.autoRecovery.enabled', () => ({
7110
+ enabled: autoRecovery.enabled === true,
7111
+ dryRun: autoRecovery.dryRun !== false,
7112
+ // Deliberately no lastTickAt: the autoRecovery arm is config-derived
7113
+ // (no tick loop of its own); the parent sentinel row carries liveness.
7114
+ }));
7089
7115
  wedgeRecoveryActive = (s) => wedgeSentinel.isRecoveryActive(s);
7090
7116
  const mode = autoRecovery.enabled ? (autoRecovery.dryRun ? 'auto-recover dry-run' : 'auto-recover LIVE') : 'detect-only';
7091
7117
  console.log(pc.green(` ContextWedgeSentinel enabled (thinking-block-400 + aup-rejection wedges — ${mode})`));
@@ -9230,6 +9256,68 @@ export async function startServer(options) {
9230
9256
  lockFilePath: path.join(config.stateDir, 'lifeline.lock'),
9231
9257
  isEnabled: () => fs.existsSync(path.join(config.stateDir, 'lifeline.lock')),
9232
9258
  }),
9259
+ ...createGuardPostureProbes({
9260
+ // Same one-read inventory GET /guards serves; null on read failure.
9261
+ getLocalPosture: () => {
9262
+ try {
9263
+ const snap = resolveGuardConfigSnapshot(config.projectDir);
9264
+ if (snap.readError)
9265
+ return null;
9266
+ return buildGuardInventory({
9267
+ snapshot: snap,
9268
+ bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
9269
+ registry: guardRegistry,
9270
+ });
9271
+ }
9272
+ catch {
9273
+ return null; /* @silent-fallback-ok — probe degrades to no-local-posture for this tick; the route surfaces config errors loudly */
9274
+ }
9275
+ },
9276
+ // Heartbeat-sourced (durable last-known for offline peers) — never a
9277
+ // doomed fan-out for a dark peer (spec §2.4 data-source rule).
9278
+ getPeerPostures: () => {
9279
+ try {
9280
+ return (machinePoolRegistry?.getCapacities() ?? [])
9281
+ .filter((m) => m.machineId !== (_meshSelfId ?? ''))
9282
+ .map((m) => ({
9283
+ machineId: m.machineId,
9284
+ nickname: m.nickname,
9285
+ online: m.online,
9286
+ posture: m.guardPosture ?? null,
9287
+ postureAgeMs: m.guardPostureReceivedAt ? Date.now() - Date.parse(m.guardPostureReceivedAt) : null,
9288
+ }));
9289
+ }
9290
+ catch {
9291
+ return []; /* @silent-fallback-ok — pool not wired yet (boot order): peers none this tick, next tick reads the live registry */
9292
+ }
9293
+ },
9294
+ // Spec §2.4 deep-read fallback: ONLY for an ONLINE peer whose
9295
+ // heartbeat posture block is missing/stale — a plain GET /guards
9296
+ // (never ?scope=pool), token attached only past the URL guard.
9297
+ deepReadPeer: async (machineId) => {
9298
+ if (!_listPoolMachines)
9299
+ return null; // pool not wired on this host — explicit, not incidental
9300
+ const m = _listPoolMachines().find((x) => x.machineId === machineId);
9301
+ if (!m?.lastKnownUrl)
9302
+ return null;
9303
+ const extra = config.multiMachine?.peerUrlAllowlist;
9304
+ if (!isPeerUrlAllowedForCredentials(m.lastKnownUrl, extra).ok)
9305
+ return null;
9306
+ const r = await fetch(`${m.lastKnownUrl}/guards`, {
9307
+ headers: { Authorization: `Bearer ${config.authToken}` },
9308
+ signal: AbortSignal.timeout(5000),
9309
+ });
9310
+ if (!r.ok)
9311
+ return null;
9312
+ return r.json();
9313
+ },
9314
+ emitAttention: async (item) => {
9315
+ if (!telegram)
9316
+ return;
9317
+ await telegram.createAttentionItem(item);
9318
+ },
9319
+ stateDir: config.stateDir,
9320
+ }),
9233
9321
  ...createPlatformProbes({
9234
9322
  tmuxPath,
9235
9323
  }),
@@ -10755,6 +10843,7 @@ export async function startServer(options) {
10755
10843
  console.log(pc.green(' Unkillability backstop enabled (§P5 — signal-only, never auto-kills)'));
10756
10844
  }
10757
10845
  }
10846
+ guardRegistry.register('monitoring.sessionReaper.enabled', () => sessionReaper.guardStatus());
10758
10847
  if (config.monitoring?.sessionReaper?.enabled) {
10759
10848
  console.log(pc.green(config.monitoring.sessionReaper.dryRun === false
10760
10849
  ? ' SessionReaper enabled (idle-session reaper — LIVE)'
@@ -10936,6 +11025,9 @@ export async function startServer(options) {
10936
11025
  const failoverThresholdMs = (config.multiMachine?.failoverTimeoutMinutes ?? 15) * 60_000;
10937
11026
  const clockSkewToleranceMs = config.multiMachine?.sessionPool?.clockSkewToleranceMs ?? 300_000;
10938
11027
  machinePoolRegistry = new poolMod.MachinePoolRegistry({
11028
+ // Durable last-known posture (GUARD-POSTURE-ENDPOINT-SPEC §2.3(c)) —
11029
+ // survives local restarts so a dark peer renders with its real age.
11030
+ postureStore: new GuardPostureStore(config.stateDir),
10939
11031
  listMachines: () => poolIdMgr.getActiveMachines().map(({ machineId, entry }) => ({
10940
11032
  machineId,
10941
11033
  nickname: entry.nickname,
@@ -10978,6 +11070,47 @@ export async function startServer(options) {
10978
11070
  return undefined; /* unknown ≠ blocked */
10979
11071
  }
10980
11072
  };
11073
+ // Self guard-posture block riding the capacity heartbeat (spec §2.3).
11074
+ // Computed per beat from the same one-read snapshot GET /guards uses;
11075
+ // a failed compute omits the block (older-peer semantics), never throws.
11076
+ // The resolved-config snapshot is the expensive half (defaults clone
11077
+ // + deep merge); cache it keyed on config.json mtime so the 30s
11078
+ // beat pays one cheap fs.stat instead (perf review 2026-06-12 #1).
11079
+ // The INVENTORY still rebuilds every beat — runtime states
11080
+ // (lastTickAt staleness, self-reported enabled) must stay live.
11081
+ let _postureComputeWarned = false;
11082
+ let _postureSnapCache = null;
11083
+ const selfGuardPosture = () => {
11084
+ try {
11085
+ let mtimeMs = -1;
11086
+ try {
11087
+ mtimeMs = fs.statSync(path.join(config.stateDir, 'config.json')).mtimeMs;
11088
+ }
11089
+ catch { /* @silent-fallback-ok — absent config file: mtime -1 still caches the defaults-only snapshot */ }
11090
+ if (!_postureSnapCache || _postureSnapCache.mtimeMs !== mtimeMs) {
11091
+ _postureSnapCache = { mtimeMs, snap: resolveGuardConfigSnapshot(config.projectDir) };
11092
+ }
11093
+ const snap = _postureSnapCache.snap;
11094
+ if (snap.readError)
11095
+ return undefined;
11096
+ const inv = buildGuardInventory({
11097
+ snapshot: snap,
11098
+ bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
11099
+ registry: guardRegistry,
11100
+ });
11101
+ return buildHeartbeatPostureBlock(inv, new Date().toISOString());
11102
+ }
11103
+ catch (err) {
11104
+ // @silent-fallback-ok — posture is optional on a beat (the pool
11105
+ // renders "unknown" honestly), and a PERSISTENT compute failure
11106
+ // is not invisible: the first occurrence logs below.
11107
+ if (!_postureComputeWarned) {
11108
+ _postureComputeWarned = true;
11109
+ console.log(pc.yellow(` [guards] heartbeat posture compute failed (beats will omit posture until it recovers): ${err instanceof Error ? err.message : String(err)}`));
11110
+ }
11111
+ return undefined;
11112
+ }
11113
+ };
10981
11114
  const refreshPool = () => {
10982
11115
  try {
10983
11116
  machinePoolRegistry.recordHeartbeat({
@@ -10985,6 +11118,7 @@ export async function startServer(options) {
10985
11118
  selfReportedLastSeen: new Date().toISOString(),
10986
11119
  loadAvg: osMod.loadavg()[0],
10987
11120
  quotaState: selfQuotaState(),
11121
+ guardPosture: selfGuardPosture(),
10988
11122
  });
10989
11123
  const hbApi = machineHeartbeat?.api;
10990
11124
  if (hbApi) {
@@ -11427,6 +11561,14 @@ export async function startServer(options) {
11427
11561
  .getActiveMachines()
11428
11562
  .filter((m) => m.machineId !== meshSelfId && !!m.entry.lastKnownUrl)
11429
11563
  .map((m) => ({ machineId: m.machineId, url: m.entry.lastKnownUrl }));
11564
+ // EVERY registered non-revoked machine — URL or not — so the /guards
11565
+ // pool view can account for each by name (no-known-url is a NAMED row,
11566
+ // never a silent omission — GUARD-POSTURE-ENDPOINT-SPEC §2.3).
11567
+ _listPoolMachines = () => meshIdMgr.getActiveMachines().map((m) => ({
11568
+ machineId: m.machineId,
11569
+ nickname: m.entry.nickname,
11570
+ lastKnownUrl: m.entry.lastKnownUrl ?? null,
11571
+ }));
11430
11572
  // Pool Dashboard Streaming requesting side (§2.2): build the connector
11431
11573
  // the WebSocketManager uses to open an upstream /pool-stream to a peer.
11432
11574
  // connect() is synchronous (PeerStreamProxy contract) but the mint +
@@ -12203,6 +12345,9 @@ export async function startServer(options) {
12203
12345
  journalAdvert,
12204
12346
  ...(cap.commitmentsAdvert ? { commitmentsAdvert: cap.commitmentsAdvert } : {}),
12205
12347
  ...(cap.quotaState ? { quotaState: cap.quotaState } : {}),
12348
+ // Guard posture rides the same pass-through (the A2 narrowing
12349
+ // lesson): dropping it here would blind the pool to peers' posture.
12350
+ ...(cap.guardPosture ? { guardPosture: cap.guardPosture } : {}),
12206
12351
  };
12207
12352
  }
12208
12353
  return null;
@@ -12496,7 +12641,7 @@ export async function startServer(options) {
12496
12641
  carrier: _topicProfileCarrier,
12497
12642
  }
12498
12643
  : null;
12499
- const server = new AgentServer({ config, sessionManager, 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, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? 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, localSigningKeyPem, leaseTransport, 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, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? 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, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
12644
+ const server = new AgentServer({ config, sessionManager, 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, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, semanticMemory, activitySentinel, rateLimitSentinel, releaseReadinessSentinel: releaseReadinessSentinel ?? 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, localSigningKeyPem, leaseTransport, 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, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? 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, sessionPoolE2EResultStore, proxyCoordinator, topicIntentStore, topicIntentArcCheck, usherSignalStore, intelligence: sharedIntelligence ?? undefined, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, briefDeps, workingMemory, taskFlowRegistry, threadlineFlowBridge, sessionReaper, agentWorktreeReaper, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier });
12500
12645
  // Resolve the late-bound topic-operator getter (increment 2e): routing was
12501
12646
  // wired before the server existed; from here on inbound binds use the
12502
12647
  // server's own store instance.