instar 1.3.819 → 1.3.820
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 +498 -3
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +69 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/BackupManager.d.ts +10 -0
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +16 -0
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/BoundedJsonlAudit.d.ts +37 -0
- package/dist/core/BoundedJsonlAudit.d.ts.map +1 -0
- package/dist/core/BoundedJsonlAudit.js +87 -0
- package/dist/core/BoundedJsonlAudit.js.map +1 -0
- package/dist/core/JudgmentProvenanceLog.d.ts +144 -0
- package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -0
- package/dist/core/JudgmentProvenanceLog.js +301 -0
- package/dist/core/JudgmentProvenanceLog.js.map +1 -0
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +2 -0
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/OwnerDarkLadder.d.ts +108 -0
- package/dist/core/OwnerDarkLadder.d.ts.map +1 -0
- package/dist/core/OwnerDarkLadder.js +229 -0
- package/dist/core/OwnerDarkLadder.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +10 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +106 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SpawnAdmission.d.ts +218 -0
- package/dist/core/SpawnAdmission.d.ts.map +1 -0
- package/dist/core/SpawnAdmission.js +440 -0
- package/dist/core/SpawnAdmission.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +24 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
- package/dist/core/machineCoherenceManifest.js +30 -0
- package/dist/core/machineCoherenceManifest.js.map +1 -1
- package/dist/core/types.d.ts +87 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/DuplicateSessionReconciler.d.ts +225 -0
- package/dist/monitoring/DuplicateSessionReconciler.d.ts.map +1 -0
- package/dist/monitoring/DuplicateSessionReconciler.js +544 -0
- package/dist/monitoring/DuplicateSessionReconciler.js.map +1 -0
- package/dist/monitoring/FailureAnalyzer.d.ts.map +1 -1
- package/dist/monitoring/FailureAnalyzer.js +40 -1
- package/dist/monitoring/FailureAnalyzer.js.map +1 -1
- package/dist/monitoring/FailureLedger.d.ts +9 -0
- package/dist/monitoring/FailureLedger.d.ts.map +1 -1
- package/dist/monitoring/FailureLedger.js +21 -4
- package/dist/monitoring/FailureLedger.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +5 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +4 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +9 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/fileRoutes.d.ts +16 -0
- package/dist/server/fileRoutes.d.ts.map +1 -1
- package/dist/server/fileRoutes.js +46 -0
- package/dist/server/fileRoutes.js.map +1 -1
- package/dist/server/routes.d.ts +7 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +99 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/testing/selfActionRegistry.d.ts.map +1 -1
- package/dist/testing/selfActionRegistry.js +89 -0
- package/dist/testing/selfActionRegistry.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lib/self-action-detect.mjs +2 -0
- package/skills/instar-dev/templates/side-effects-artifact.md +8 -0
- package/skills/spec-converge/SKILL.md +1 -0
- package/skills/spec-converge/scripts/write-convergence-tag.mjs +71 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.820.md +25 -0
- package/upgrades/side-effects/ownership-gated-spawn-increment-1.md +166 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,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;AAwDH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAU3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAyBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AA6H7D,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;AAkoDD;;;;;;;;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,CAgsBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAswnBtE;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
|
@@ -742,6 +742,38 @@ let _ownershipReconciler = null;
|
|
|
742
742
|
// GET /pool/stale-owner-release route + the reconciler dep can reach it. Null
|
|
743
743
|
// until the pool block constructs it (single-machine / dark → the route 503s).
|
|
744
744
|
let _staleOwnerEngine = null;
|
|
745
|
+
// Ownership-gated spawn seam (ownership-gated-spawn-and-judgment-within-floors
|
|
746
|
+
// §3.1 Layer A) + its owner-dark ladder + the judgment-provenance log — hoisted
|
|
747
|
+
// so the inbound handlers (registered earlier in this function) and the status
|
|
748
|
+
// routes can reach them. Null until the mesh block constructs them
|
|
749
|
+
// (single-machine / pool-dark → the seam's own null-guard/short-circuit keeps
|
|
750
|
+
// dispatch byte-identical; the provenance route 503s).
|
|
751
|
+
let _spawnAdmission = null;
|
|
752
|
+
let _ownerDarkLadder = null;
|
|
753
|
+
let _judgmentProvenance = null;
|
|
754
|
+
let _duplicateReconciler = null;
|
|
755
|
+
/**
|
|
756
|
+
* Increment-1 dry-run soak (ownership-gated-spawn §3.3): when the seam
|
|
757
|
+
* OBSERVES a would-refuse on a DARK owner (the spawn still proceeds —
|
|
758
|
+
* observe mode changes nothing), the ladder journals the would-notice.
|
|
759
|
+
* That journal is the soak evidence the eventual enforce flip requires;
|
|
760
|
+
* without this consult the ladder would sit dark through the whole
|
|
761
|
+
* observe stage and the flip would arrive with zero ladder data.
|
|
762
|
+
* mode 'dry-run' NEVER sends — it only journals + opens the episode.
|
|
763
|
+
*/
|
|
764
|
+
function _ladderDryRunConsult(d, sessionKey, topicId) {
|
|
765
|
+
if (!_ownerDarkLadder || !d.allow || !d.wouldBlock)
|
|
766
|
+
return;
|
|
767
|
+
if (d.ownership?.kind !== 'other-dark')
|
|
768
|
+
return;
|
|
769
|
+
void _ownerDarkLadder.handleOwnerDark({
|
|
770
|
+
sessionKey,
|
|
771
|
+
topicId,
|
|
772
|
+
ownerMachineId: d.ownership?.owner ?? 'unknown',
|
|
773
|
+
mode: 'dry-run',
|
|
774
|
+
custodyLive: false,
|
|
775
|
+
}).catch(() => { });
|
|
776
|
+
}
|
|
745
777
|
// U4.4 lease hand-back — the F4 reconciler, hoisted so the lease pull tick hook
|
|
746
778
|
// (constructed early) and the /pool surfaces (constructed late) can reach it.
|
|
747
779
|
let _leaseHandbackReconciler = null;
|
|
@@ -2246,6 +2278,11 @@ getHubClassifierDeps) {
|
|
|
2246
2278
|
console.warn(`[session-pool] nickname relocation error for topic ${topicId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2247
2279
|
}
|
|
2248
2280
|
}
|
|
2281
|
+
// SpawnAdmission TOCTOU guard (ownership-gated-spawn §3.1 item 2): stash the
|
|
2282
|
+
// router's verdict for THIS message so the seam at the spawn callsites below
|
|
2283
|
+
// CONSUMES it instead of re-resolving — the admission decision and the
|
|
2284
|
+
// routing decision cannot disagree mid-dispatch.
|
|
2285
|
+
let _admissionVerdict;
|
|
2249
2286
|
if (_sessionRouter && _sessionPoolStage() !== 'dark') {
|
|
2250
2287
|
// Ordering gate (Durable Inbound Message Queue §2.3): a live message for
|
|
2251
2288
|
// a session with queued custody enqueues BEHIND the existing entries —
|
|
@@ -2283,6 +2320,7 @@ getHubClassifierDeps) {
|
|
|
2283
2320
|
// box (the recognizer logs its pin, but route()'s actual placement/forward
|
|
2284
2321
|
// decision was invisible; that hid the bug below from the first live test).
|
|
2285
2322
|
console.log(`[session-pool] route topic ${topicId} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
2323
|
+
_admissionVerdict = { messageId: String(msg.id), action: outcome.action, acked: outcome.acked };
|
|
2286
2324
|
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
2287
2325
|
// `rejected` (the owner re-validated the sender and refused) is NOT a
|
|
2288
2326
|
// successful forward: TELL the user via the ORIGINATING adapter and
|
|
@@ -2346,6 +2384,69 @@ getHubClassifierDeps) {
|
|
|
2346
2384
|
console.warn(`[session-pool] route error for topic ${topicId} — falling back to local dispatch: ${err instanceof Error ? err.message : String(err)}`);
|
|
2347
2385
|
}
|
|
2348
2386
|
}
|
|
2387
|
+
// ── SpawnAdmission — the binding-verdict seam (ownership-gated-spawn §3.1,
|
|
2388
|
+
// Layer A / the Ownership-Gated Side Effects standard). Consulted before
|
|
2389
|
+
// EVERY local session-creating action below. Increment-1 posture: dryRun
|
|
2390
|
+
// (observe-only — allow is always true; would-block verdicts are journaled).
|
|
2391
|
+
// Enforce mode (dryRun:false + durable custody live, §3.1 item 6) refuses a
|
|
2392
|
+
// non-owner spawn: durable-queue custody where available, the rung-3 honest
|
|
2393
|
+
// notice for a dark owner, and a loud fail-open for the residual edge —
|
|
2394
|
+
// never a bootleg local copy, never silence.
|
|
2395
|
+
const admitLocalSpawn = (callsite) => {
|
|
2396
|
+
if (!_spawnAdmission)
|
|
2397
|
+
return true;
|
|
2398
|
+
try {
|
|
2399
|
+
const d = _spawnAdmission.admit({
|
|
2400
|
+
sessionKey: String(topicId),
|
|
2401
|
+
callsite,
|
|
2402
|
+
routerVerdict: _admissionVerdict,
|
|
2403
|
+
});
|
|
2404
|
+
if (d.allow) {
|
|
2405
|
+
_ladderDryRunConsult(d, String(topicId), topicId);
|
|
2406
|
+
return true;
|
|
2407
|
+
}
|
|
2408
|
+
console.log(`[spawn-admission] topic ${topicId} ${callsite} REFUSED (row=${d.row}) — ${d.reason}`);
|
|
2409
|
+
// Custody first: the durable queue is the forward-compatible floor
|
|
2410
|
+
// (enforce mode structurally implies the queue is live, §3.1 item 6).
|
|
2411
|
+
let custody = _admissionVerdict?.acked === true;
|
|
2412
|
+
if (!custody && _inboundQueue) {
|
|
2413
|
+
try {
|
|
2414
|
+
const q = _inboundQueue.enqueueLive({
|
|
2415
|
+
sessionKey: String(topicId),
|
|
2416
|
+
messageId: String(msg.id),
|
|
2417
|
+
payload: text,
|
|
2418
|
+
senderEnvelope: { userId: telegramUserId || undefined, firstName: pipeline.sender.firstName },
|
|
2419
|
+
topicMetadata: _pinPlacementMetadata ? _pinPlacementMetadata(String(topicId)) : _topicPinStore?.asTopicMetadata(String(topicId)),
|
|
2420
|
+
}, 'spawn-admission-refusal');
|
|
2421
|
+
custody = q.result === 'queued' || q.result === 'already-queued';
|
|
2422
|
+
}
|
|
2423
|
+
catch { /* @silent-fallback-ok: custody is best-effort — the ladder notice below states custody honestly either way (queue-live vs resend wording). */ }
|
|
2424
|
+
}
|
|
2425
|
+
if (d.refusalAction === 'forward' && !custody) {
|
|
2426
|
+
// Owner ALIVE but the router fell through AND custody refused —
|
|
2427
|
+
// refusing here would be silent loss. Reachability wins (the §0
|
|
2428
|
+
// argued-exemption direction): allow the spawn, loudly.
|
|
2429
|
+
console.warn(`[spawn-admission] topic ${topicId} owner-alive refusal without custody — failing OPEN to local spawn (reachability wins)`);
|
|
2430
|
+
return true;
|
|
2431
|
+
}
|
|
2432
|
+
if ((d.refusalAction === 'owner-dark-ladder' || d.refusalAction === 'rung3-notice') && _ownerDarkLadder) {
|
|
2433
|
+
void _ownerDarkLadder.handleOwnerDark({
|
|
2434
|
+
sessionKey: String(topicId),
|
|
2435
|
+
topicId,
|
|
2436
|
+
ownerMachineId: d.ownership?.owner ?? 'unknown',
|
|
2437
|
+
mode: 'enforce',
|
|
2438
|
+
custodyLive: custody,
|
|
2439
|
+
}).catch(() => { });
|
|
2440
|
+
}
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
catch (err) {
|
|
2444
|
+
// The seam must never break dispatch — an admission ERROR fails open
|
|
2445
|
+
// to today's behavior (the row-e direction, §3.1).
|
|
2446
|
+
console.warn(`[spawn-admission] admit error (fail-open): ${err instanceof Error ? err.message : String(err)}`);
|
|
2447
|
+
return true;
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2349
2450
|
if (targetSession) {
|
|
2350
2451
|
// Session is mapped — check if it's alive, inject or respawn
|
|
2351
2452
|
if (sessionManager.isSessionAlive(targetSession)) {
|
|
@@ -2386,6 +2487,8 @@ getHubClassifierDeps) {
|
|
|
2386
2487
|
// Context exhaustion: respawn FRESH (no --resume) — the old conversation
|
|
2387
2488
|
// is too large to continue. The respawn path will load telegram thread
|
|
2388
2489
|
// history as context, giving the new session continuity.
|
|
2490
|
+
if (!admitLocalSpawn('telegram-respawn-context-exhausted'))
|
|
2491
|
+
return;
|
|
2389
2492
|
if (spawningTopics.has(topicId)) {
|
|
2390
2493
|
console.log(`[telegram→session] Spawn already in progress for topic ${topicId} — skipping duplicate respawn`);
|
|
2391
2494
|
return;
|
|
@@ -2405,6 +2508,8 @@ getHubClassifierDeps) {
|
|
|
2405
2508
|
});
|
|
2406
2509
|
}
|
|
2407
2510
|
else if (!isQuotaDeath) {
|
|
2511
|
+
if (!admitLocalSpawn('telegram-respawn-dead'))
|
|
2512
|
+
return;
|
|
2408
2513
|
// Guard: skip respawn if one is already in progress for this topic.
|
|
2409
2514
|
// Prevents the infinite respawn loop: dead session + rapid messages → each
|
|
2410
2515
|
// message triggers a new respawn → multiple concurrent spawns → chaos.
|
|
@@ -2461,6 +2566,11 @@ getHubClassifierDeps) {
|
|
|
2461
2566
|
}
|
|
2462
2567
|
return;
|
|
2463
2568
|
}
|
|
2569
|
+
// The binding-verdict seam (ownership-gated-spawn §3.1) — the cold-spawn
|
|
2570
|
+
// callsite is THE incident's fall-through (2026-07-10: verdict computed,
|
|
2571
|
+
// then discarded 6ms later by this path's legacy reflex).
|
|
2572
|
+
if (!admitLocalSpawn('telegram-cold-spawn'))
|
|
2573
|
+
return;
|
|
2464
2574
|
const _spawnTokC = spawningTopics.add(topicId);
|
|
2465
2575
|
// Resolve topic name — try in-memory, then active probe, then fallback
|
|
2466
2576
|
let spawnName = storedTopicName;
|
|
@@ -7408,7 +7518,11 @@ export async function startServer(options) {
|
|
|
7408
7518
|
// mesh bridge replays when a Slack message was forwarded to this machine
|
|
7409
7519
|
// because it owns the conversation. Sharing one function is "Structure >
|
|
7410
7520
|
// Willpower": the forwarded path and the live path can never drift.
|
|
7411
|
-
const slackInboundDispatch = async (message
|
|
7521
|
+
const slackInboundDispatch = async (message,
|
|
7522
|
+
// SpawnAdmission TOCTOU guard (ownership-gated-spawn §3.1 item 2): the
|
|
7523
|
+
// pool checkpoint below passes its router verdict through so the seam
|
|
7524
|
+
// CONSUMES it rather than re-resolving.
|
|
7525
|
+
admissionVerdict) => {
|
|
7412
7526
|
const channelId = message.channel.identifier;
|
|
7413
7527
|
const isDM = message.metadata?.isDM;
|
|
7414
7528
|
const senderName = message.metadata?.senderName || 'User';
|
|
@@ -7590,6 +7704,56 @@ export async function startServer(options) {
|
|
|
7590
7704
|
// A thread session NEVER folds into the DM lifeline — it is its own
|
|
7591
7705
|
// isolated work session (DMs don't carry thread_ts anyway).
|
|
7592
7706
|
const targetSession = (isDM && !isThreadSession) ? 'lifeline' : undefined;
|
|
7707
|
+
// ── SpawnAdmission (ownership-gated-spawn §3.1) — the Slack inbound
|
|
7708
|
+
// spawn callsite. Refusal floor: durable-queue custody where live;
|
|
7709
|
+
// Slack has no telegram-topic notice surface, so the ladder records
|
|
7710
|
+
// the episode journal-only (topicId null); no custody + owner-alive
|
|
7711
|
+
// residual fails OPEN (reachability wins, loudly).
|
|
7712
|
+
if (_spawnAdmission) {
|
|
7713
|
+
try {
|
|
7714
|
+
const d = _spawnAdmission.admit({
|
|
7715
|
+
sessionKey: routingKey,
|
|
7716
|
+
callsite: 'slack-inbound-spawn',
|
|
7717
|
+
routerVerdict: admissionVerdict,
|
|
7718
|
+
});
|
|
7719
|
+
_ladderDryRunConsult(d, routingKey, null);
|
|
7720
|
+
if (!d.allow) {
|
|
7721
|
+
console.log(`[spawn-admission] slack key ${routingKey} spawn REFUSED (row=${d.row}) — ${d.reason}`);
|
|
7722
|
+
let custody = admissionVerdict?.acked === true;
|
|
7723
|
+
if (!custody && _inboundQueue) {
|
|
7724
|
+
try {
|
|
7725
|
+
const q = _inboundQueue.enqueueLive({
|
|
7726
|
+
sessionKey: routingKey,
|
|
7727
|
+
messageId: String(message.id),
|
|
7728
|
+
payload: message.content,
|
|
7729
|
+
senderEnvelope: null,
|
|
7730
|
+
topicMetadata: undefined,
|
|
7731
|
+
}, 'spawn-admission-refusal');
|
|
7732
|
+
custody = q.result === 'queued' || q.result === 'already-queued';
|
|
7733
|
+
}
|
|
7734
|
+
catch { /* @silent-fallback-ok: custody is best-effort — the Slack refusal path stays honest about custody in its journal row. */ }
|
|
7735
|
+
}
|
|
7736
|
+
if (!custody && d.refusalAction === 'forward') {
|
|
7737
|
+
console.warn(`[spawn-admission] slack key ${routingKey} owner-alive refusal without custody — failing OPEN to local spawn (reachability wins)`);
|
|
7738
|
+
}
|
|
7739
|
+
else {
|
|
7740
|
+
if (_ownerDarkLadder && (d.refusalAction === 'owner-dark-ladder' || d.refusalAction === 'rung3-notice')) {
|
|
7741
|
+
void _ownerDarkLadder.handleOwnerDark({
|
|
7742
|
+
sessionKey: routingKey,
|
|
7743
|
+
topicId: null,
|
|
7744
|
+
ownerMachineId: d.ownership?.owner ?? 'unknown',
|
|
7745
|
+
mode: 'enforce',
|
|
7746
|
+
custodyLive: custody,
|
|
7747
|
+
}).catch(() => { });
|
|
7748
|
+
}
|
|
7749
|
+
return;
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7753
|
+
catch (err) {
|
|
7754
|
+
console.warn(`[spawn-admission] slack admit error (fail-open): ${err instanceof Error ? err.message : String(err)}`);
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7593
7757
|
try {
|
|
7594
7758
|
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, targetSession, {
|
|
7595
7759
|
resumeSessionId,
|
|
@@ -7686,6 +7850,9 @@ export async function startServer(options) {
|
|
|
7686
7850
|
// where a Slack channel pinned/transferred to a peer machine still injected
|
|
7687
7851
|
// the next message into the already-running LOCAL session (Telegram's
|
|
7688
7852
|
// inbound path already followed the transfer; Slack's never did).
|
|
7853
|
+
// SpawnAdmission TOCTOU guard (ownership-gated-spawn §3.1 item 2):
|
|
7854
|
+
// stash the router verdict so the dispatch's seam consumes it.
|
|
7855
|
+
let _slackAdmissionVerdict;
|
|
7689
7856
|
if (_sessionRouter && _sessionPoolStage() !== 'dark') {
|
|
7690
7857
|
try {
|
|
7691
7858
|
const outcome = await _sessionRouter.route({
|
|
@@ -7703,6 +7870,7 @@ export async function startServer(options) {
|
|
|
7703
7870
|
channel: { platform: 'slack', workspaceId: _slackAdapter?.getWorkspaceId(), channelId },
|
|
7704
7871
|
});
|
|
7705
7872
|
console.log(`[session-pool] slack route key=${routingKey} → action=${outcome.action} owner=${outcome.owner ?? '?'} self=${_meshSelfId ?? '?'} acked=${outcome.acked}`);
|
|
7873
|
+
_slackAdmissionVerdict = { messageId: String(message.id), action: outcome.action, acked: outcome.acked };
|
|
7706
7874
|
// silent-loss-refusal-conservation §2.A/§2.C — a first-class terminal
|
|
7707
7875
|
// `rejected` is NOT a successful forward: reply in-thread on the
|
|
7708
7876
|
// originating Slack channel and terminal-return WITHOUT local dispatch,
|
|
@@ -7746,7 +7914,7 @@ export async function startServer(options) {
|
|
|
7746
7914
|
console.warn(`[session-pool] slack route error for key ${routingKey} — falling back to local dispatch: ${err instanceof Error ? err.message : String(err)}`);
|
|
7747
7915
|
}
|
|
7748
7916
|
}
|
|
7749
|
-
await slackInboundDispatch(message);
|
|
7917
|
+
await slackInboundDispatch(message, _slackAdmissionVerdict);
|
|
7750
7918
|
});
|
|
7751
7919
|
await slackAdapter.start();
|
|
7752
7920
|
_slackAdapter = slackAdapter;
|
|
@@ -10076,6 +10244,23 @@ export async function startServer(options) {
|
|
|
10076
10244
|
const contextData = lines.join('\n');
|
|
10077
10245
|
fs.writeFileSync(ctxPath, contextData);
|
|
10078
10246
|
const bootstrapMessage = `[slack:${slackApiChannel}] ${contextData}`;
|
|
10247
|
+
// SpawnAdmission (ownership-gated-spawn §3.1) — the Slack recovery
|
|
10248
|
+
// spawn callsite (fresh ownership resolution; no router verdict on
|
|
10249
|
+
// this path). Enforce-mode refusal: the conversation belongs to
|
|
10250
|
+
// another machine — recovery there is that machine's job; skip.
|
|
10251
|
+
if (_spawnAdmission) {
|
|
10252
|
+
try {
|
|
10253
|
+
const d = _spawnAdmission.admit({ sessionKey: slackChId, callsite: 'slack-recovery-spawn' });
|
|
10254
|
+
_ladderDryRunConsult(d, slackChId, null);
|
|
10255
|
+
if (!d.allow) {
|
|
10256
|
+
console.log(`[spawn-admission] slack recovery spawn for ${slackChId} REFUSED (row=${d.row}) — ${d.reason}`);
|
|
10257
|
+
return;
|
|
10258
|
+
}
|
|
10259
|
+
}
|
|
10260
|
+
catch (err) {
|
|
10261
|
+
console.warn(`[spawn-admission] slack recovery admit error (fail-open): ${err instanceof Error ? err.message : String(err)}`);
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10079
10264
|
try {
|
|
10080
10265
|
// Spawn with the RAW channel (+ thread_ts) but register on the routing key.
|
|
10081
10266
|
const newSessionName = await sessionManager.spawnInteractiveSession(bootstrapMessage, undefined, { slackChannelId: slackApiChannel, slackThreadTs: slackReplyThread });
|
|
@@ -20584,6 +20769,316 @@ export async function startServer(options) {
|
|
|
20584
20769
|
catch (err) {
|
|
20585
20770
|
console.error(`[inbound-queue] engine construction failed (queue stays OFF): ${err instanceof Error ? err.message : String(err)}`);
|
|
20586
20771
|
}
|
|
20772
|
+
// ── Ownership-gated spawn seam (ownership-gated-spawn §3.1, Layer A) ──
|
|
20773
|
+
// Constructed whenever the mesh block runs; the seam short-circuits
|
|
20774
|
+
// itself on pool stage 'dark' / missing mesh identity (byte-identical
|
|
20775
|
+
// dispatch, zero writes), so construction here is safe on every pool.
|
|
20776
|
+
try {
|
|
20777
|
+
const spawnAdmMod = await import('../core/SpawnAdmission.js');
|
|
20778
|
+
const ladderMod = await import('../core/OwnerDarkLadder.js');
|
|
20779
|
+
const jplMod = await import('../core/JudgmentProvenanceLog.js');
|
|
20780
|
+
const audMod = await import('../core/BoundedJsonlAudit.js');
|
|
20781
|
+
const ogsCfg = config.multiMachine?.sessionPool?.ownershipGatedSpawn ?? {};
|
|
20782
|
+
const provCfg = config.provenance ?? {};
|
|
20783
|
+
_judgmentProvenance = new jplMod.JudgmentProvenanceLog({
|
|
20784
|
+
dir: path.join(config.stateDir, 'state', 'judgment-provenance'),
|
|
20785
|
+
retentionDays: provCfg.retentionDays,
|
|
20786
|
+
sampling: provCfg.deterministicSampling,
|
|
20787
|
+
log: (m) => console.log(pc.dim(` ${m}`)),
|
|
20788
|
+
});
|
|
20789
|
+
const ownerDarkAudit = new audMod.BoundedJsonlAudit({
|
|
20790
|
+
file: path.join(config.stateDir, 'logs', 'owner-dark-ladder.jsonl'),
|
|
20791
|
+
log: (m) => console.log(pc.dim(` ${m}`)),
|
|
20792
|
+
});
|
|
20793
|
+
// Same liveness expression the router uses (§3.1: `other-dark`
|
|
20794
|
+
// derives from the EXISTING isMachineAlive input — one authority).
|
|
20795
|
+
const seamIsAlive = (m) => m === meshSelfId ||
|
|
20796
|
+
((machinePoolRegistry?.getCapacity(m)?.online ?? false) && !ownerSuspectBreaker.isSuspect(m));
|
|
20797
|
+
_ownerDarkLadder = new ladderMod.OwnerDarkLadder(config.ownerDarkLadder, {
|
|
20798
|
+
isMachineAlive: seamIsAlive,
|
|
20799
|
+
// The deterministic G1 path (telegram.sendToTopic — never the LLM
|
|
20800
|
+
// tone gate), with the DECLARED owner-dark exception to speaker
|
|
20801
|
+
// election (§3.3 rung 3; election rule 1 is owner-liveness-blind
|
|
20802
|
+
// and would structurally silence the notice).
|
|
20803
|
+
sendNotice: async (noticeTopicId, noticeText) => {
|
|
20804
|
+
if (!telegram)
|
|
20805
|
+
return false;
|
|
20806
|
+
try {
|
|
20807
|
+
await telegram.sendToTopic(noticeTopicId, noticeText);
|
|
20808
|
+
return true;
|
|
20809
|
+
}
|
|
20810
|
+
catch {
|
|
20811
|
+
// @silent-fallback-ok: a failed send returns false — the ladder
|
|
20812
|
+
// records notice-send-failed in its journal (the loud path).
|
|
20813
|
+
return false;
|
|
20814
|
+
}
|
|
20815
|
+
},
|
|
20816
|
+
// Topic-scoped suppression (split-brain guard): a recent
|
|
20817
|
+
// owner-dark notice visible in the topic's send history — from
|
|
20818
|
+
// ANY machine — suppresses a second voice.
|
|
20819
|
+
topicHistoryHasRecentNotice: (noticeTopicId) => {
|
|
20820
|
+
try {
|
|
20821
|
+
return (telegram?.getTopicHistory(noticeTopicId, 10) ?? [])
|
|
20822
|
+
.some((e) => !e.fromUser && typeof e.text === 'string' && e.text.includes('temporarily unreachable'));
|
|
20823
|
+
}
|
|
20824
|
+
catch {
|
|
20825
|
+
// @silent-fallback-ok: history unreadable → no suppression signal;
|
|
20826
|
+
// the ladder's episode-dedupe + cooldown layers still bound the send.
|
|
20827
|
+
return false;
|
|
20828
|
+
}
|
|
20829
|
+
},
|
|
20830
|
+
journal: ownerDarkAudit,
|
|
20831
|
+
log: (m) => console.log(pc.dim(` ${m}`)),
|
|
20832
|
+
});
|
|
20833
|
+
_spawnAdmission = new spawnAdmMod.SpawnAdmission({
|
|
20834
|
+
enabled: resolveDevAgentGate(ogsCfg.enabled, config),
|
|
20835
|
+
dryRun: ogsCfg.dryRun !== false,
|
|
20836
|
+
}, {
|
|
20837
|
+
selfMachineId: () => _meshSelfId,
|
|
20838
|
+
poolStage: () => _sessionPoolStage(),
|
|
20839
|
+
readOwnership: (sk) => {
|
|
20840
|
+
const r = ownReg.read(sk);
|
|
20841
|
+
if (!r)
|
|
20842
|
+
return null;
|
|
20843
|
+
return { owner: ownReg.ownerOf(sk), epoch: r.ownershipEpoch, status: r.status ?? null };
|
|
20844
|
+
},
|
|
20845
|
+
isMachineAlive: seamIsAlive,
|
|
20846
|
+
durableCustodyLive: () => !!_inboundQueue,
|
|
20847
|
+
journal: (row) => ownerDarkAudit.append(row),
|
|
20848
|
+
raiseAttention: (item) => {
|
|
20849
|
+
void telegram
|
|
20850
|
+
?.createAttentionItem({
|
|
20851
|
+
id: item.id,
|
|
20852
|
+
title: item.title,
|
|
20853
|
+
summary: item.body,
|
|
20854
|
+
category: 'system',
|
|
20855
|
+
priority: item.priority === 'high' ? 'HIGH' : 'NORMAL',
|
|
20856
|
+
sourceContext: 'spawn-admission',
|
|
20857
|
+
})
|
|
20858
|
+
.catch(() => { });
|
|
20859
|
+
},
|
|
20860
|
+
provenance: (row) => {
|
|
20861
|
+
_judgmentProvenance?.recordDecision(row);
|
|
20862
|
+
},
|
|
20863
|
+
log: (m) => console.log(pc.dim(` [spawn-admission] ${m}`)),
|
|
20864
|
+
});
|
|
20865
|
+
console.log(pc.dim(` [spawn-admission] seam constructed (mode: ${_spawnAdmission.effectiveMode()})`));
|
|
20866
|
+
}
|
|
20867
|
+
catch (err) {
|
|
20868
|
+
console.error(`[spawn-admission] construction failed (seam stays off — legacy spawn behavior): ${err instanceof Error ? err.message : String(err)}`);
|
|
20869
|
+
}
|
|
20870
|
+
// ── Duplicate-session reconciler (ownership-gated-spawn §3.2, Layer B) ──
|
|
20871
|
+
// Detection + evidence ladder + would-converge journaling run on the
|
|
20872
|
+
// serving-lease holder; the substrate gate (§3.2.0) refuses to arm on
|
|
20873
|
+
// the fleet-default in-memory ownership store regardless of flags,
|
|
20874
|
+
// and dryRun (the Increment-1 canary) lands no CAS.
|
|
20875
|
+
try {
|
|
20876
|
+
const durMod = await import('../monitoring/DuplicateSessionReconciler.js');
|
|
20877
|
+
const audMod2 = await import('../core/BoundedJsonlAudit.js');
|
|
20878
|
+
const dupCfgRaw = (config.multiMachine?.sessionPool?.duplicateReconciler ?? {});
|
|
20879
|
+
const dupAudit = new audMod2.BoundedJsonlAudit({
|
|
20880
|
+
file: path.join(config.stateDir, 'logs', 'duplicate-reconciler.jsonl'),
|
|
20881
|
+
log: (m) => console.log(pc.dim(` ${m}`)),
|
|
20882
|
+
});
|
|
20883
|
+
const selfBase = `http://127.0.0.1:${config.port}`;
|
|
20884
|
+
const meshAuthHeaders = { Authorization: `Bearer ${config.authToken ?? ''}` };
|
|
20885
|
+
const fetchJson = async (url, timeoutMs) => {
|
|
20886
|
+
const r = await fetch(url, { headers: meshAuthHeaders, signal: AbortSignal.timeout(timeoutMs) });
|
|
20887
|
+
if (!r.ok)
|
|
20888
|
+
throw new Error(`HTTP ${r.status}`);
|
|
20889
|
+
return r.json();
|
|
20890
|
+
};
|
|
20891
|
+
const dupCfgResolved = {
|
|
20892
|
+
enabled: resolveDevAgentGate(dupCfgRaw.enabled, config),
|
|
20893
|
+
dryRun: dupCfgRaw.dryRun !== false,
|
|
20894
|
+
reconcilerTickMs: Math.max(5_000, dupCfgRaw.reconcilerTickMs ?? 60_000),
|
|
20895
|
+
maxReconcilesPerTick: dupCfgRaw.maxReconcilesPerTick ?? 3,
|
|
20896
|
+
maxConvergenceWritesPerTick: dupCfgRaw.maxConvergenceWritesPerTick ?? 5,
|
|
20897
|
+
echoConfirmTicks: dupCfgRaw.echoConfirmTicks ?? 4,
|
|
20898
|
+
breakerThreshold: dupCfgRaw.breakerThreshold ?? 3,
|
|
20899
|
+
breakerWindowMs: dupCfgRaw.breakerWindowMs ?? 86_400_000,
|
|
20900
|
+
};
|
|
20901
|
+
_duplicateReconciler = new durMod.DuplicateSessionReconciler(dupCfgResolved, {
|
|
20902
|
+
selfMachineId: () => _meshSelfId,
|
|
20903
|
+
holdsLease: () => (leaseCoordinatorRef ? leaseCoordinatorRef.holdsLease() : true),
|
|
20904
|
+
// §3.2.0: durable + replicated ownership substrate (the same two
|
|
20905
|
+
// activation signals the store selection above resolved).
|
|
20906
|
+
substrateReady: () => durableOwnershipOn && _replicationOn
|
|
20907
|
+
? { ready: true }
|
|
20908
|
+
: {
|
|
20909
|
+
ready: false,
|
|
20910
|
+
reason: durableOwnershipOn
|
|
20911
|
+
? 'placement replication not enabled (peer views unresolvable)'
|
|
20912
|
+
: 'in-memory ownership store (fleet default — records wiped on restart)',
|
|
20913
|
+
},
|
|
20914
|
+
errorEpisodeOpen: () => _spawnAdmission?.isErrorEpisodeOpen() ?? false,
|
|
20915
|
+
topicHasAuthorityInMotion: (sk) => {
|
|
20916
|
+
try {
|
|
20917
|
+
const r = ownReg.read(sk);
|
|
20918
|
+
return r?.status === 'transferring' || r?.status === 'placing';
|
|
20919
|
+
}
|
|
20920
|
+
catch {
|
|
20921
|
+
// @silent-fallback-ok: unreadable → treat as in-motion, the
|
|
20922
|
+
// reconciler DEFERS the topic entirely (the safe direction).
|
|
20923
|
+
return true;
|
|
20924
|
+
}
|
|
20925
|
+
},
|
|
20926
|
+
// Lease-holder bounded fan-out at tick cadence (§3.2.1 — the
|
|
20927
|
+
// declared fallback while the WS4.4(f) poll-cache is dev-gated):
|
|
20928
|
+
// the pool sessions view already computes duplicateTopics from a
|
|
20929
|
+
// bounded per-peer fetch; one detector, one implementation.
|
|
20930
|
+
discoverCandidates: async () => {
|
|
20931
|
+
const j = (await fetchJson(`${selfBase}/sessions?scope=pool`, 15_000));
|
|
20932
|
+
const dups = j.pool?.duplicateTopics ?? [];
|
|
20933
|
+
return {
|
|
20934
|
+
candidates: dups.map((d) => ({
|
|
20935
|
+
key: `${d.platform}:${String(d.platformId)}`,
|
|
20936
|
+
platform: d.platform,
|
|
20937
|
+
platformId: String(d.platformId),
|
|
20938
|
+
machines: d.machineIds.map((m) => ({ machineId: m, sessions: d.sessions })),
|
|
20939
|
+
})),
|
|
20940
|
+
};
|
|
20941
|
+
},
|
|
20942
|
+
// Fresh direct probe (5s budget) — never acts on a cached row.
|
|
20943
|
+
probeLiveCopy: async (machineId, key) => {
|
|
20944
|
+
try {
|
|
20945
|
+
const sep = key.indexOf(':');
|
|
20946
|
+
const platform = key.slice(0, sep);
|
|
20947
|
+
const pid = key.slice(sep + 1);
|
|
20948
|
+
const base = machineId === _meshSelfId
|
|
20949
|
+
? selfBase
|
|
20950
|
+
: (_resolvePeerUrls?.() ?? []).find((p) => p.machineId === machineId)?.url;
|
|
20951
|
+
if (!base)
|
|
20952
|
+
return { ok: false, live: false };
|
|
20953
|
+
const list = (await fetchJson(`${base}/sessions`, 5_000));
|
|
20954
|
+
const live = (Array.isArray(list) ? list : []).some((s) => String(s.platform) === platform && String(s.platformId) === pid);
|
|
20955
|
+
return { ok: true, live };
|
|
20956
|
+
}
|
|
20957
|
+
catch {
|
|
20958
|
+
// @silent-fallback-ok: ok:false is the DEFERRAL signal — the
|
|
20959
|
+
// reconciler journals probe-failed-deferred and burns an
|
|
20960
|
+
// attempt; a failed probe is never acted on as evidence.
|
|
20961
|
+
return { ok: false, live: false };
|
|
20962
|
+
}
|
|
20963
|
+
},
|
|
20964
|
+
readPin: (sk) => {
|
|
20965
|
+
try {
|
|
20966
|
+
const tp = _pinPlacementMetadata ? _pinPlacementMetadata(sk) : _topicPinStore?.asTopicMetadata(sk);
|
|
20967
|
+
// Quarantined pins never surface through the fold reads (U4.1).
|
|
20968
|
+
return tp ? { pinned: !!tp.pinned, preferredMachine: tp.preferredMachine ?? null, quarantined: false } : null;
|
|
20969
|
+
}
|
|
20970
|
+
catch {
|
|
20971
|
+
// @silent-fallback-ok: no pin evidence → the ladder falls to
|
|
20972
|
+
// rule 2/3 or ESCALATES — absence of evidence never converges.
|
|
20973
|
+
return null;
|
|
20974
|
+
}
|
|
20975
|
+
},
|
|
20976
|
+
readOwnershipViews: (sk) => {
|
|
20977
|
+
try {
|
|
20978
|
+
const r = ownReg.read(sk);
|
|
20979
|
+
if (!r)
|
|
20980
|
+
return [];
|
|
20981
|
+
// The local view is journal-materialized (OwnershipApplier) —
|
|
20982
|
+
// epoch fast-forward means it already carries the highest
|
|
20983
|
+
// epoch this machine has SEEN; peers' own views are consulted
|
|
20984
|
+
// at echo time via /pool/ownership-view.
|
|
20985
|
+
return [{ machineId: _meshSelfId ?? 'self', owner: ownReg.ownerOf(sk), epoch: r.ownershipEpoch, admissible: true }];
|
|
20986
|
+
}
|
|
20987
|
+
catch {
|
|
20988
|
+
// @silent-fallback-ok: no admissible view → rule 2 yields no
|
|
20989
|
+
// winner and the verdict ESCALATES (no-admissible-evidence).
|
|
20990
|
+
return [];
|
|
20991
|
+
}
|
|
20992
|
+
},
|
|
20993
|
+
// §3.2.2 rule 3: run registration read from each machine's own
|
|
20994
|
+
// authenticated surface — the fetch succeeding IS the live probe.
|
|
20995
|
+
liveRunHosts: async (sk) => {
|
|
20996
|
+
const out = [];
|
|
20997
|
+
const targets = [
|
|
20998
|
+
{ machineId: _meshSelfId ?? 'self', url: selfBase },
|
|
20999
|
+
...(_resolvePeerUrls?.() ?? []),
|
|
21000
|
+
];
|
|
21001
|
+
for (const t of targets) {
|
|
21002
|
+
try {
|
|
21003
|
+
const j = (await fetchJson(`${t.url}/autonomous/sessions`, 5_000));
|
|
21004
|
+
const run = (j.sessions ?? []).find((s) => s.active && s.topic === sk);
|
|
21005
|
+
if (run) {
|
|
21006
|
+
out.push({
|
|
21007
|
+
machineId: t.machineId,
|
|
21008
|
+
registeredAt: run.startedAt ? Date.parse(run.startedAt) : Date.now(),
|
|
21009
|
+
confirmed: true,
|
|
21010
|
+
});
|
|
21011
|
+
}
|
|
21012
|
+
}
|
|
21013
|
+
catch {
|
|
21014
|
+
/* @silent-fallback-ok: a dark peer's run is UNCONFIRMED — it never corroborates (§3.2.2). */
|
|
21015
|
+
}
|
|
21016
|
+
}
|
|
21017
|
+
return out;
|
|
21018
|
+
},
|
|
21019
|
+
// The fenced convergence CAS (§3.2.3 item 1): `claim` by the
|
|
21020
|
+
// intended owner — the FSM's fenced epoch increment + journal
|
|
21021
|
+
// fast-forward carry the repair to every peer's view.
|
|
21022
|
+
casConverge: (sk, owner) => {
|
|
21023
|
+
try {
|
|
21024
|
+
const prevOwner = ownReg.read(sk)?.ownerMachineId;
|
|
21025
|
+
const r = ownReg.cas({ type: 'claim', machineId: owner }, { sessionKey: sk, sender: _meshSelfId ?? 'dup-reconciler', nonce: `dup-reconcile:${sk}:${Date.now().toString(36)}` });
|
|
21026
|
+
// COHERENCE-JOURNAL-SPEC §3.3 pairing: the journal emit IS the
|
|
21027
|
+
// replication path that carries the repair to every peer's
|
|
21028
|
+
// materialized view — without it the peer-echo verify (§3.2.3
|
|
21029
|
+
// item 2) could never observe the converged owner.
|
|
21030
|
+
emitPlacement(sk, r, 'reconcile', prevOwner);
|
|
21031
|
+
return r.ok ? { ok: true } : { ok: false, reason: String(r.reason ?? 'cas-refused') };
|
|
21032
|
+
}
|
|
21033
|
+
catch (err) {
|
|
21034
|
+
// @silent-fallback-ok: ok:false carries the reason UP — the
|
|
21035
|
+
// reconciler ESCALATES a refused/errored CAS (never retries
|
|
21036
|
+
// blindly), so this is the loud path, not a swallow.
|
|
21037
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
21038
|
+
}
|
|
21039
|
+
},
|
|
21040
|
+
// Peer-echo (§3.2.3 item 2): the peer's OWN registry view via the
|
|
21041
|
+
// proxy-free /pool/ownership-view read.
|
|
21042
|
+
peerEchoObserved: async (sk, owner, machineId) => {
|
|
21043
|
+
const base = (_resolvePeerUrls?.() ?? []).find((p) => p.machineId === machineId)?.url;
|
|
21044
|
+
if (!base)
|
|
21045
|
+
return false;
|
|
21046
|
+
const j = (await fetchJson(`${base}/pool/ownership-view?key=${encodeURIComponent(sk)}`, 5_000));
|
|
21047
|
+
return j.owner === owner;
|
|
21048
|
+
},
|
|
21049
|
+
// §3.2.3 item 3: the converged RECORD is the arming mechanism —
|
|
21050
|
+
// each non-owner machine's own closeout sweeper fires off its
|
|
21051
|
+
// (journal-materialized) topicOwnerElsewhere read. Journal intent.
|
|
21052
|
+
armCloseout: (sk, owner) => {
|
|
21053
|
+
dupAudit.append({ ts: new Date().toISOString(), kind: 'closeout-armed-via-record', key: sk, owner });
|
|
21054
|
+
},
|
|
21055
|
+
raiseAttention: (item) => {
|
|
21056
|
+
void telegram
|
|
21057
|
+
?.createAttentionItem({
|
|
21058
|
+
id: item.id,
|
|
21059
|
+
title: item.title,
|
|
21060
|
+
summary: item.body,
|
|
21061
|
+
category: 'system',
|
|
21062
|
+
priority: item.priority === 'high' ? 'HIGH' : 'NORMAL',
|
|
21063
|
+
sourceContext: 'duplicate-reconciler',
|
|
21064
|
+
})
|
|
21065
|
+
.catch(() => { });
|
|
21066
|
+
},
|
|
21067
|
+
journal: dupAudit,
|
|
21068
|
+
provenance: (row) => {
|
|
21069
|
+
_judgmentProvenance?.recordDecision(row);
|
|
21070
|
+
},
|
|
21071
|
+
log: (m) => console.log(pc.dim(` [dup-reconciler] ${m}`)),
|
|
21072
|
+
});
|
|
21073
|
+
const dupTimer = setInterval(() => {
|
|
21074
|
+
void _duplicateReconciler?.tick();
|
|
21075
|
+
}, dupCfgResolved.reconcilerTickMs);
|
|
21076
|
+
dupTimer.unref?.();
|
|
21077
|
+
console.log(pc.dim(` [dup-reconciler] constructed (enabled=${dupCfgResolved.enabled}, dryRun=${dupCfgResolved.dryRun}, tick=${dupCfgResolved.reconcilerTickMs}ms)`));
|
|
21078
|
+
}
|
|
21079
|
+
catch (err) {
|
|
21080
|
+
console.error(`[dup-reconciler] construction failed (reconciler stays off): ${err instanceof Error ? err.message : String(err)}`);
|
|
21081
|
+
}
|
|
20587
21082
|
// ── B (HTTP presence transport): pull each peer's self-capacity over
|
|
20588
21083
|
// the signed /mesh/rpc channel and record it into the pool registry.
|
|
20589
21084
|
// This is the credential-less presence path — a standby that cannot
|
|
@@ -21537,7 +22032,7 @@ export async function startServer(options) {
|
|
|
21537
22032
|
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, 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) {
|
|
21538
22033
|
peers = (_listPoolMachines?.() ?? []).filter((m) => m.machineId !== _meshSelfId && !!m.lastKnownUrl).map((m) => ({ machineId: m.machineId, nickname: m.nickname ?? m.machineId, url: m.lastKnownUrl }));
|
|
21539
22034
|
} if (peers.length === 0)
|
|
21540
|
-
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, meshRpcDispatcher, workingSetPullCoordinator, workingSetArtifactManager, orchestratorPoller, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, topicPinSkewQuarantine: _topicPinSkewQuarantine ?? undefined, topicPinFoldView: _topicPinFoldView ?? undefined, ownershipReconciler: _ownershipReconciler ?? undefined, staleOwnerEngine: _staleOwnerEngine ?? 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
|
|
22035
|
+
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, meshRpcDispatcher, workingSetPullCoordinator, workingSetArtifactManager, orchestratorPoller, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, 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
|
|
21541
22036
|
// wired before the server existed; from here on inbound binds use the
|
|
21542
22037
|
// server's own store instance.
|
|
21543
22038
|
_agentServerRef = server;
|