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.
Files changed (83) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +498 -3
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +69 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/BackupManager.d.ts +10 -0
  8. package/dist/core/BackupManager.d.ts.map +1 -1
  9. package/dist/core/BackupManager.js +16 -0
  10. package/dist/core/BackupManager.js.map +1 -1
  11. package/dist/core/BoundedJsonlAudit.d.ts +37 -0
  12. package/dist/core/BoundedJsonlAudit.d.ts.map +1 -0
  13. package/dist/core/BoundedJsonlAudit.js +87 -0
  14. package/dist/core/BoundedJsonlAudit.js.map +1 -0
  15. package/dist/core/JudgmentProvenanceLog.d.ts +144 -0
  16. package/dist/core/JudgmentProvenanceLog.d.ts.map +1 -0
  17. package/dist/core/JudgmentProvenanceLog.js +301 -0
  18. package/dist/core/JudgmentProvenanceLog.js.map +1 -0
  19. package/dist/core/MachineIdentity.d.ts.map +1 -1
  20. package/dist/core/MachineIdentity.js +2 -0
  21. package/dist/core/MachineIdentity.js.map +1 -1
  22. package/dist/core/OwnerDarkLadder.d.ts +108 -0
  23. package/dist/core/OwnerDarkLadder.d.ts.map +1 -0
  24. package/dist/core/OwnerDarkLadder.js +229 -0
  25. package/dist/core/OwnerDarkLadder.js.map +1 -0
  26. package/dist/core/PostUpdateMigrator.d.ts +10 -0
  27. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  28. package/dist/core/PostUpdateMigrator.js +106 -0
  29. package/dist/core/PostUpdateMigrator.js.map +1 -1
  30. package/dist/core/SpawnAdmission.d.ts +218 -0
  31. package/dist/core/SpawnAdmission.d.ts.map +1 -0
  32. package/dist/core/SpawnAdmission.js +440 -0
  33. package/dist/core/SpawnAdmission.js.map +1 -0
  34. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  35. package/dist/core/devGatedFeatures.js +24 -0
  36. package/dist/core/devGatedFeatures.js.map +1 -1
  37. package/dist/core/machineCoherenceManifest.d.ts.map +1 -1
  38. package/dist/core/machineCoherenceManifest.js +30 -0
  39. package/dist/core/machineCoherenceManifest.js.map +1 -1
  40. package/dist/core/types.d.ts +87 -0
  41. package/dist/core/types.d.ts.map +1 -1
  42. package/dist/core/types.js.map +1 -1
  43. package/dist/monitoring/DuplicateSessionReconciler.d.ts +225 -0
  44. package/dist/monitoring/DuplicateSessionReconciler.d.ts.map +1 -0
  45. package/dist/monitoring/DuplicateSessionReconciler.js +544 -0
  46. package/dist/monitoring/DuplicateSessionReconciler.js.map +1 -0
  47. package/dist/monitoring/FailureAnalyzer.d.ts.map +1 -1
  48. package/dist/monitoring/FailureAnalyzer.js +40 -1
  49. package/dist/monitoring/FailureAnalyzer.js.map +1 -1
  50. package/dist/monitoring/FailureLedger.d.ts +9 -0
  51. package/dist/monitoring/FailureLedger.d.ts.map +1 -1
  52. package/dist/monitoring/FailureLedger.js +21 -4
  53. package/dist/monitoring/FailureLedger.js.map +1 -1
  54. package/dist/scaffold/templates.d.ts.map +1 -1
  55. package/dist/scaffold/templates.js +2 -1
  56. package/dist/scaffold/templates.js.map +1 -1
  57. package/dist/server/AgentServer.d.ts +5 -0
  58. package/dist/server/AgentServer.d.ts.map +1 -1
  59. package/dist/server/AgentServer.js +4 -0
  60. package/dist/server/AgentServer.js.map +1 -1
  61. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  62. package/dist/server/CapabilityIndex.js +9 -0
  63. package/dist/server/CapabilityIndex.js.map +1 -1
  64. package/dist/server/fileRoutes.d.ts +16 -0
  65. package/dist/server/fileRoutes.d.ts.map +1 -1
  66. package/dist/server/fileRoutes.js +46 -0
  67. package/dist/server/fileRoutes.js.map +1 -1
  68. package/dist/server/routes.d.ts +7 -0
  69. package/dist/server/routes.d.ts.map +1 -1
  70. package/dist/server/routes.js +99 -0
  71. package/dist/server/routes.js.map +1 -1
  72. package/dist/testing/selfActionRegistry.d.ts.map +1 -1
  73. package/dist/testing/selfActionRegistry.js +89 -0
  74. package/dist/testing/selfActionRegistry.js.map +1 -1
  75. package/package.json +1 -1
  76. package/scripts/lib/self-action-detect.mjs +2 -0
  77. package/skills/instar-dev/templates/side-effects-artifact.md +8 -0
  78. package/skills/spec-converge/SKILL.md +1 -0
  79. package/skills/spec-converge/scripts/write-convergence-tag.mjs +71 -0
  80. package/src/data/builtin-manifest.json +65 -65
  81. package/src/scaffold/templates.ts +2 -1
  82. package/upgrades/1.3.820.md +25 -0
  83. 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;AAgmDD;;;;;;;;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,CAonBN;AAgmBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA63mBtE;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;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"}
@@ -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;