instar 1.3.490 → 1.3.492
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/dashboard/index.html +30 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +3 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +199 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +11 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CartographerSweepEngine.d.ts +55 -11
- package/dist/core/CartographerSweepEngine.d.ts.map +1 -1
- package/dist/core/CartographerSweepEngine.js +276 -95
- package/dist/core/CartographerSweepEngine.js.map +1 -1
- package/dist/core/CartographerTree.d.ts +77 -1
- package/dist/core/CartographerTree.d.ts.map +1 -1
- package/dist/core/CartographerTree.js +257 -5
- package/dist/core/CartographerTree.js.map +1 -1
- package/dist/core/GuardPostureStore.d.ts +38 -0
- package/dist/core/GuardPostureStore.d.ts.map +1 -0
- package/dist/core/GuardPostureStore.js +87 -0
- package/dist/core/GuardPostureStore.js.map +1 -0
- package/dist/core/MachinePoolRegistry.d.ts +16 -0
- package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
- package/dist/core/MachinePoolRegistry.js +24 -1
- package/dist/core/MachinePoolRegistry.js.map +1 -1
- package/dist/core/PeerPresencePuller.d.ts +9 -0
- package/dist/core/PeerPresencePuller.d.ts.map +1 -1
- package/dist/core/PeerPresencePuller.js +1 -1
- package/dist/core/PeerPresencePuller.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +43 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/cartographerDetect.d.ts +133 -0
- package/dist/core/cartographerDetect.d.ts.map +1 -0
- package/dist/core/cartographerDetect.js +431 -0
- package/dist/core/cartographerDetect.js.map +1 -0
- package/dist/core/cartographerDetect.worker.d.ts +2 -0
- package/dist/core/cartographerDetect.worker.d.ts.map +1 -0
- package/dist/core/cartographerDetect.worker.js +37 -0
- package/dist/core/cartographerDetect.worker.js.map +1 -0
- package/dist/core/types.d.ts +29 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts +8 -0
- package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts.map +1 -1
- package/dist/monitoring/ActiveWorkSilenceSentinel.js +8 -0
- package/dist/monitoring/ActiveWorkSilenceSentinel.js.map +1 -1
- package/dist/monitoring/ContextWedgeSentinel.d.ts +8 -0
- package/dist/monitoring/ContextWedgeSentinel.d.ts.map +1 -1
- package/dist/monitoring/ContextWedgeSentinel.js +8 -0
- package/dist/monitoring/ContextWedgeSentinel.js.map +1 -1
- package/dist/monitoring/GuardPostureTripwire.d.ts +3 -27
- package/dist/monitoring/GuardPostureTripwire.d.ts.map +1 -1
- package/dist/monitoring/GuardPostureTripwire.js +8 -76
- package/dist/monitoring/GuardPostureTripwire.js.map +1 -1
- package/dist/monitoring/GuardRegistry.d.ts +48 -0
- package/dist/monitoring/GuardRegistry.d.ts.map +1 -0
- package/dist/monitoring/GuardRegistry.js +49 -0
- package/dist/monitoring/GuardRegistry.js.map +1 -0
- package/dist/monitoring/SessionReaper.d.ts +7 -0
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +9 -0
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/dist/monitoring/SessionWatchdog.d.ts +8 -0
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +8 -0
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/monitoring/SocketDisconnectSentinel.d.ts +8 -0
- package/dist/monitoring/SocketDisconnectSentinel.d.ts.map +1 -1
- package/dist/monitoring/SocketDisconnectSentinel.js +8 -0
- package/dist/monitoring/SocketDisconnectSentinel.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts +67 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -0
- package/dist/monitoring/guardManifest.js +536 -0
- package/dist/monitoring/guardManifest.js.map +1 -0
- package/dist/monitoring/guardPosture.d.ts +77 -0
- package/dist/monitoring/guardPosture.d.ts.map +1 -0
- package/dist/monitoring/guardPosture.js +198 -0
- package/dist/monitoring/guardPosture.js.map +1 -0
- package/dist/monitoring/guardPostureView.d.ts +100 -0
- package/dist/monitoring/guardPostureView.d.ts.map +1 -0
- package/dist/monitoring/guardPostureView.js +294 -0
- package/dist/monitoring/guardPostureView.js.map +1 -0
- package/dist/monitoring/probes/GuardPostureProbe.d.ts +82 -0
- package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -0
- package/dist/monitoring/probes/GuardPostureProbe.js +384 -0
- package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +9 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts +10 -0
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +12 -0
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +2 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +11 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/peerUrlGuard.d.ts +35 -0
- package/dist/server/peerUrlGuard.d.ts.map +1 -0
- package/dist/server/peerUrlGuard.js +93 -0
- package/dist/server/peerUrlGuard.js.map +1 -0
- package/dist/server/routes.d.ts +14 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +239 -25
- package/dist/server/routes.js.map +1 -1
- package/package.json +3 -2
- package/scripts/lint-guard-manifest.js +264 -0
- package/scripts/lint-no-direct-destructive.js +4 -0
- package/scripts/lint-no-mainthread-cartographer-walk.js +101 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/data/state-coherence-registry.json +54 -18
- package/src/scaffold/templates.ts +9 -0
- package/upgrades/1.3.491.md +37 -0
- package/upgrades/1.3.492.md +31 -0
- package/upgrades/side-effects/cartographer-sweep-eventloop-safety.md +110 -0
- package/upgrades/side-effects/guard-posture-endpoint.md +101 -0
package/dist/commands/server.js
CHANGED
|
@@ -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})`));
|
|
@@ -8289,6 +8315,30 @@ export async function startServer(options) {
|
|
|
8289
8315
|
: null;
|
|
8290
8316
|
if (cartographer)
|
|
8291
8317
|
console.log(pc.green(' Cartographer doc-tree enabled'));
|
|
8318
|
+
// fix instar#1069: build the structural index ONCE at boot, OFF the request path,
|
|
8319
|
+
// in chunks that yield the event loop — replacing the per-request lazy scaffold()
|
|
8320
|
+
// that froze /health. Fire-and-forget (boot is not blocked); P19 time ceiling so a
|
|
8321
|
+
// pathological tree can't run forever (next boot retries; routes serve not-built
|
|
8322
|
+
// meanwhile). Skipped when an index already exists.
|
|
8323
|
+
if (cartographer && !fs.existsSync(cartographer.indexFilePath())) {
|
|
8324
|
+
const scCfg = config.cartographer?.freshnessSweep;
|
|
8325
|
+
const chunkNodes = typeof scCfg?.scaffoldChunkNodes === 'number' ? scCfg.scaffoldChunkNodes : 500;
|
|
8326
|
+
const scaffoldStartedAt = Date.now();
|
|
8327
|
+
void cartographer.scaffoldChunked({
|
|
8328
|
+
chunkNodes,
|
|
8329
|
+
onYield: () => new Promise((r) => setImmediate(r)),
|
|
8330
|
+
shouldAbort: () => Date.now() - scaffoldStartedAt > 10 * 60_000, // P19: 10-min ceiling, retry next boot
|
|
8331
|
+
}).then(() => {
|
|
8332
|
+
console.log(pc.gray(' Cartographer index built (boot scaffold)'));
|
|
8333
|
+
}).catch((err) => {
|
|
8334
|
+
// @silent-fallback-ok — the boot scaffold is best-effort by design (fix
|
|
8335
|
+
// instar#1069 P19): a partial run never produces a readable index (atomic
|
|
8336
|
+
// tmp+rename), routes serve indexState:'not-built' honestly, and the next
|
|
8337
|
+
// boot retries. The yellow log line is the operator surface; failing the
|
|
8338
|
+
// boot over a map-build would invert the feature's priority.
|
|
8339
|
+
console.log(pc.yellow(` Cartographer boot scaffold incomplete (retries next boot): ${err instanceof Error ? err.message : String(err)}`));
|
|
8340
|
+
});
|
|
8341
|
+
}
|
|
8292
8342
|
// Cartographer doc-freshness sweep (spec #2). In-process poller that authors
|
|
8293
8343
|
// stale/never-authored node summaries on a LIGHT model routed OFF Claude.
|
|
8294
8344
|
// Ships dark behind freshnessSweep.enabled ONLY — the redundant egressAcknowledged
|
|
@@ -8317,6 +8367,26 @@ export async function startServer(options) {
|
|
|
8317
8367
|
const { CartographerSweepPoller } = await import('../monitoring/CartographerSweepPoller.js');
|
|
8318
8368
|
const { sampleHostPressure: _sampleHostPressure } = await import('../monitoring/HostPressureSampler.js');
|
|
8319
8369
|
const rcfg = config.monitoring?.sessionReaper;
|
|
8370
|
+
// Slice 3 (fix instar#1069): honor cartographer.freshnessSweep.framework as
|
|
8371
|
+
// the sweep's routing so BOTH the routing PROBE and the actual author call
|
|
8372
|
+
// resolve consistently (the router reads config.sessions.componentFrameworks
|
|
8373
|
+
// live). EXPLICIT-SET-ONLY: a pre-existing overrides.CartographerSweep OR an
|
|
8374
|
+
// explicitly-configured categories.job is never overridden (migration safety);
|
|
8375
|
+
// otherwise freshnessSweep.framework becomes the sweep's effective override.
|
|
8376
|
+
{
|
|
8377
|
+
const { resolveSweepFrameworkRouting } = await import('../core/CartographerSweepEngine.js');
|
|
8378
|
+
const cf = config.sessions?.componentFrameworks ?? {};
|
|
8379
|
+
const sweepFw = typeof fsCfg.framework === 'string' ? fsCfg.framework : undefined;
|
|
8380
|
+
const routed = resolveSweepFrameworkRouting(cf, sweepFw);
|
|
8381
|
+
if (routed.injectOverride && routed.framework) {
|
|
8382
|
+
// Inject so BOTH probeRouting and evaluate resolve consistently (router
|
|
8383
|
+
// reads config.sessions.componentFrameworks live). Explicit-set-only.
|
|
8384
|
+
const s = (config.sessions ??= {});
|
|
8385
|
+
const c = (s.componentFrameworks ??= {});
|
|
8386
|
+
(c.overrides ??= {}).CartographerSweep = routed.framework;
|
|
8387
|
+
}
|
|
8388
|
+
console.log(pc.gray(` Cartographer sweep routing: ${routed.framework ?? '(default)'} (source: ${routed.source})`));
|
|
8389
|
+
}
|
|
8320
8390
|
const engine = new CartographerSweepEngine({
|
|
8321
8391
|
tree: cartographer,
|
|
8322
8392
|
router: routerLike,
|
|
@@ -8339,6 +8409,15 @@ export async function startServer(options) {
|
|
|
8339
8409
|
maxDeferredPasses: num(fsCfg.maxDeferredPasses, 5),
|
|
8340
8410
|
revalidateSamplePerPass: num(fsCfg.revalidateSamplePerPass, 2),
|
|
8341
8411
|
minNodesUnderPressure: num(fsCfg.minNodesUnderPressure, 3),
|
|
8412
|
+
// fix instar#1069: off-event-loop detect knobs (worker by default).
|
|
8413
|
+
detectInWorker: fsCfg.detectInWorker !== false,
|
|
8414
|
+
detectTimeoutMs: num(fsCfg.detectTimeoutMs, 120000),
|
|
8415
|
+
detectWorkerHeapMb: num(fsCfg.detectWorkerHeapMb, 1536),
|
|
8416
|
+
maxIndexBytes: num(fsCfg.maxIndexBytes, 200 * 1024 * 1024),
|
|
8417
|
+
snapshotSampleMax: num(fsCfg.snapshotSampleMax, 500),
|
|
8418
|
+
gitMaxBuffer: num(fsCfg.gitMaxBuffer, 64 * 1024 * 1024),
|
|
8419
|
+
detectCandidateHeadroom: num(fsCfg.detectCandidateHeadroom, 4),
|
|
8420
|
+
detectGraceMs: num(fsCfg.cadenceMs, 600000) * 2,
|
|
8342
8421
|
},
|
|
8343
8422
|
stateDir: config.stateDir,
|
|
8344
8423
|
log: (m) => console.log(pc.gray(m)),
|
|
@@ -9230,6 +9309,68 @@ export async function startServer(options) {
|
|
|
9230
9309
|
lockFilePath: path.join(config.stateDir, 'lifeline.lock'),
|
|
9231
9310
|
isEnabled: () => fs.existsSync(path.join(config.stateDir, 'lifeline.lock')),
|
|
9232
9311
|
}),
|
|
9312
|
+
...createGuardPostureProbes({
|
|
9313
|
+
// Same one-read inventory GET /guards serves; null on read failure.
|
|
9314
|
+
getLocalPosture: () => {
|
|
9315
|
+
try {
|
|
9316
|
+
const snap = resolveGuardConfigSnapshot(config.projectDir);
|
|
9317
|
+
if (snap.readError)
|
|
9318
|
+
return null;
|
|
9319
|
+
return buildGuardInventory({
|
|
9320
|
+
snapshot: snap,
|
|
9321
|
+
bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
|
|
9322
|
+
registry: guardRegistry,
|
|
9323
|
+
});
|
|
9324
|
+
}
|
|
9325
|
+
catch {
|
|
9326
|
+
return null; /* @silent-fallback-ok — probe degrades to no-local-posture for this tick; the route surfaces config errors loudly */
|
|
9327
|
+
}
|
|
9328
|
+
},
|
|
9329
|
+
// Heartbeat-sourced (durable last-known for offline peers) — never a
|
|
9330
|
+
// doomed fan-out for a dark peer (spec §2.4 data-source rule).
|
|
9331
|
+
getPeerPostures: () => {
|
|
9332
|
+
try {
|
|
9333
|
+
return (machinePoolRegistry?.getCapacities() ?? [])
|
|
9334
|
+
.filter((m) => m.machineId !== (_meshSelfId ?? ''))
|
|
9335
|
+
.map((m) => ({
|
|
9336
|
+
machineId: m.machineId,
|
|
9337
|
+
nickname: m.nickname,
|
|
9338
|
+
online: m.online,
|
|
9339
|
+
posture: m.guardPosture ?? null,
|
|
9340
|
+
postureAgeMs: m.guardPostureReceivedAt ? Date.now() - Date.parse(m.guardPostureReceivedAt) : null,
|
|
9341
|
+
}));
|
|
9342
|
+
}
|
|
9343
|
+
catch {
|
|
9344
|
+
return []; /* @silent-fallback-ok — pool not wired yet (boot order): peers none this tick, next tick reads the live registry */
|
|
9345
|
+
}
|
|
9346
|
+
},
|
|
9347
|
+
// Spec §2.4 deep-read fallback: ONLY for an ONLINE peer whose
|
|
9348
|
+
// heartbeat posture block is missing/stale — a plain GET /guards
|
|
9349
|
+
// (never ?scope=pool), token attached only past the URL guard.
|
|
9350
|
+
deepReadPeer: async (machineId) => {
|
|
9351
|
+
if (!_listPoolMachines)
|
|
9352
|
+
return null; // pool not wired on this host — explicit, not incidental
|
|
9353
|
+
const m = _listPoolMachines().find((x) => x.machineId === machineId);
|
|
9354
|
+
if (!m?.lastKnownUrl)
|
|
9355
|
+
return null;
|
|
9356
|
+
const extra = config.multiMachine?.peerUrlAllowlist;
|
|
9357
|
+
if (!isPeerUrlAllowedForCredentials(m.lastKnownUrl, extra).ok)
|
|
9358
|
+
return null;
|
|
9359
|
+
const r = await fetch(`${m.lastKnownUrl}/guards`, {
|
|
9360
|
+
headers: { Authorization: `Bearer ${config.authToken}` },
|
|
9361
|
+
signal: AbortSignal.timeout(5000),
|
|
9362
|
+
});
|
|
9363
|
+
if (!r.ok)
|
|
9364
|
+
return null;
|
|
9365
|
+
return r.json();
|
|
9366
|
+
},
|
|
9367
|
+
emitAttention: async (item) => {
|
|
9368
|
+
if (!telegram)
|
|
9369
|
+
return;
|
|
9370
|
+
await telegram.createAttentionItem(item);
|
|
9371
|
+
},
|
|
9372
|
+
stateDir: config.stateDir,
|
|
9373
|
+
}),
|
|
9233
9374
|
...createPlatformProbes({
|
|
9234
9375
|
tmuxPath,
|
|
9235
9376
|
}),
|
|
@@ -10755,6 +10896,7 @@ export async function startServer(options) {
|
|
|
10755
10896
|
console.log(pc.green(' Unkillability backstop enabled (§P5 — signal-only, never auto-kills)'));
|
|
10756
10897
|
}
|
|
10757
10898
|
}
|
|
10899
|
+
guardRegistry.register('monitoring.sessionReaper.enabled', () => sessionReaper.guardStatus());
|
|
10758
10900
|
if (config.monitoring?.sessionReaper?.enabled) {
|
|
10759
10901
|
console.log(pc.green(config.monitoring.sessionReaper.dryRun === false
|
|
10760
10902
|
? ' SessionReaper enabled (idle-session reaper — LIVE)'
|
|
@@ -10936,6 +11078,9 @@ export async function startServer(options) {
|
|
|
10936
11078
|
const failoverThresholdMs = (config.multiMachine?.failoverTimeoutMinutes ?? 15) * 60_000;
|
|
10937
11079
|
const clockSkewToleranceMs = config.multiMachine?.sessionPool?.clockSkewToleranceMs ?? 300_000;
|
|
10938
11080
|
machinePoolRegistry = new poolMod.MachinePoolRegistry({
|
|
11081
|
+
// Durable last-known posture (GUARD-POSTURE-ENDPOINT-SPEC §2.3(c)) —
|
|
11082
|
+
// survives local restarts so a dark peer renders with its real age.
|
|
11083
|
+
postureStore: new GuardPostureStore(config.stateDir),
|
|
10939
11084
|
listMachines: () => poolIdMgr.getActiveMachines().map(({ machineId, entry }) => ({
|
|
10940
11085
|
machineId,
|
|
10941
11086
|
nickname: entry.nickname,
|
|
@@ -10978,6 +11123,47 @@ export async function startServer(options) {
|
|
|
10978
11123
|
return undefined; /* unknown ≠ blocked */
|
|
10979
11124
|
}
|
|
10980
11125
|
};
|
|
11126
|
+
// Self guard-posture block riding the capacity heartbeat (spec §2.3).
|
|
11127
|
+
// Computed per beat from the same one-read snapshot GET /guards uses;
|
|
11128
|
+
// a failed compute omits the block (older-peer semantics), never throws.
|
|
11129
|
+
// The resolved-config snapshot is the expensive half (defaults clone
|
|
11130
|
+
// + deep merge); cache it keyed on config.json mtime so the 30s
|
|
11131
|
+
// beat pays one cheap fs.stat instead (perf review 2026-06-12 #1).
|
|
11132
|
+
// The INVENTORY still rebuilds every beat — runtime states
|
|
11133
|
+
// (lastTickAt staleness, self-reported enabled) must stay live.
|
|
11134
|
+
let _postureComputeWarned = false;
|
|
11135
|
+
let _postureSnapCache = null;
|
|
11136
|
+
const selfGuardPosture = () => {
|
|
11137
|
+
try {
|
|
11138
|
+
let mtimeMs = -1;
|
|
11139
|
+
try {
|
|
11140
|
+
mtimeMs = fs.statSync(path.join(config.stateDir, 'config.json')).mtimeMs;
|
|
11141
|
+
}
|
|
11142
|
+
catch { /* @silent-fallback-ok — absent config file: mtime -1 still caches the defaults-only snapshot */ }
|
|
11143
|
+
if (!_postureSnapCache || _postureSnapCache.mtimeMs !== mtimeMs) {
|
|
11144
|
+
_postureSnapCache = { mtimeMs, snap: resolveGuardConfigSnapshot(config.projectDir) };
|
|
11145
|
+
}
|
|
11146
|
+
const snap = _postureSnapCache.snap;
|
|
11147
|
+
if (snap.readError)
|
|
11148
|
+
return undefined;
|
|
11149
|
+
const inv = buildGuardInventory({
|
|
11150
|
+
snapshot: snap,
|
|
11151
|
+
bootSnapshot: readGuardPostureBootSnapshot(config.stateDir),
|
|
11152
|
+
registry: guardRegistry,
|
|
11153
|
+
});
|
|
11154
|
+
return buildHeartbeatPostureBlock(inv, new Date().toISOString());
|
|
11155
|
+
}
|
|
11156
|
+
catch (err) {
|
|
11157
|
+
// @silent-fallback-ok — posture is optional on a beat (the pool
|
|
11158
|
+
// renders "unknown" honestly), and a PERSISTENT compute failure
|
|
11159
|
+
// is not invisible: the first occurrence logs below.
|
|
11160
|
+
if (!_postureComputeWarned) {
|
|
11161
|
+
_postureComputeWarned = true;
|
|
11162
|
+
console.log(pc.yellow(` [guards] heartbeat posture compute failed (beats will omit posture until it recovers): ${err instanceof Error ? err.message : String(err)}`));
|
|
11163
|
+
}
|
|
11164
|
+
return undefined;
|
|
11165
|
+
}
|
|
11166
|
+
};
|
|
10981
11167
|
const refreshPool = () => {
|
|
10982
11168
|
try {
|
|
10983
11169
|
machinePoolRegistry.recordHeartbeat({
|
|
@@ -10985,6 +11171,7 @@ export async function startServer(options) {
|
|
|
10985
11171
|
selfReportedLastSeen: new Date().toISOString(),
|
|
10986
11172
|
loadAvg: osMod.loadavg()[0],
|
|
10987
11173
|
quotaState: selfQuotaState(),
|
|
11174
|
+
guardPosture: selfGuardPosture(),
|
|
10988
11175
|
});
|
|
10989
11176
|
const hbApi = machineHeartbeat?.api;
|
|
10990
11177
|
if (hbApi) {
|
|
@@ -11427,6 +11614,14 @@ export async function startServer(options) {
|
|
|
11427
11614
|
.getActiveMachines()
|
|
11428
11615
|
.filter((m) => m.machineId !== meshSelfId && !!m.entry.lastKnownUrl)
|
|
11429
11616
|
.map((m) => ({ machineId: m.machineId, url: m.entry.lastKnownUrl }));
|
|
11617
|
+
// EVERY registered non-revoked machine — URL or not — so the /guards
|
|
11618
|
+
// pool view can account for each by name (no-known-url is a NAMED row,
|
|
11619
|
+
// never a silent omission — GUARD-POSTURE-ENDPOINT-SPEC §2.3).
|
|
11620
|
+
_listPoolMachines = () => meshIdMgr.getActiveMachines().map((m) => ({
|
|
11621
|
+
machineId: m.machineId,
|
|
11622
|
+
nickname: m.entry.nickname,
|
|
11623
|
+
lastKnownUrl: m.entry.lastKnownUrl ?? null,
|
|
11624
|
+
}));
|
|
11430
11625
|
// Pool Dashboard Streaming requesting side (§2.2): build the connector
|
|
11431
11626
|
// the WebSocketManager uses to open an upstream /pool-stream to a peer.
|
|
11432
11627
|
// connect() is synchronous (PeerStreamProxy contract) but the mint +
|
|
@@ -12203,6 +12398,9 @@ export async function startServer(options) {
|
|
|
12203
12398
|
journalAdvert,
|
|
12204
12399
|
...(cap.commitmentsAdvert ? { commitmentsAdvert: cap.commitmentsAdvert } : {}),
|
|
12205
12400
|
...(cap.quotaState ? { quotaState: cap.quotaState } : {}),
|
|
12401
|
+
// Guard posture rides the same pass-through (the A2 narrowing
|
|
12402
|
+
// lesson): dropping it here would blind the pool to peers' posture.
|
|
12403
|
+
...(cap.guardPosture ? { guardPosture: cap.guardPosture } : {}),
|
|
12206
12404
|
};
|
|
12207
12405
|
}
|
|
12208
12406
|
return null;
|
|
@@ -12496,7 +12694,7 @@ export async function startServer(options) {
|
|
|
12496
12694
|
carrier: _topicProfileCarrier,
|
|
12497
12695
|
}
|
|
12498
12696
|
: 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 });
|
|
12697
|
+
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
12698
|
// Resolve the late-bound topic-operator getter (increment 2e): routing was
|
|
12501
12699
|
// wired before the server existed; from here on inbound binds use the
|
|
12502
12700
|
// server's own store instance.
|