instar 1.3.587 → 1.3.589

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 (70) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +237 -9
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/LiveTestArtifactStore.d.ts +129 -0
  5. package/dist/core/LiveTestArtifactStore.d.ts.map +1 -0
  6. package/dist/core/LiveTestArtifactStore.js +184 -0
  7. package/dist/core/LiveTestArtifactStore.js.map +1 -0
  8. package/dist/core/LiveTestGate.d.ts +54 -0
  9. package/dist/core/LiveTestGate.d.ts.map +1 -0
  10. package/dist/core/LiveTestGate.js +124 -0
  11. package/dist/core/LiveTestGate.js.map +1 -0
  12. package/dist/core/LiveTestHarness.d.ts +99 -0
  13. package/dist/core/LiveTestHarness.d.ts.map +1 -0
  14. package/dist/core/LiveTestHarness.js +102 -0
  15. package/dist/core/LiveTestHarness.js.map +1 -0
  16. package/dist/core/LocalSessionOwnershipStore.d.ts +56 -0
  17. package/dist/core/LocalSessionOwnershipStore.d.ts.map +1 -0
  18. package/dist/core/LocalSessionOwnershipStore.js +130 -0
  19. package/dist/core/LocalSessionOwnershipStore.js.map +1 -0
  20. package/dist/core/OwnershipApplier.d.ts +78 -0
  21. package/dist/core/OwnershipApplier.d.ts.map +1 -0
  22. package/dist/core/OwnershipApplier.js +101 -0
  23. package/dist/core/OwnershipApplier.js.map +1 -0
  24. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  25. package/dist/core/PostUpdateMigrator.js +9 -0
  26. package/dist/core/PostUpdateMigrator.js.map +1 -1
  27. package/dist/core/WorkEvidence.d.ts +17 -0
  28. package/dist/core/WorkEvidence.d.ts.map +1 -1
  29. package/dist/core/WorkEvidence.js +17 -0
  30. package/dist/core/WorkEvidence.js.map +1 -1
  31. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  32. package/dist/core/devGatedFeatures.js +12 -0
  33. package/dist/core/devGatedFeatures.js.map +1 -1
  34. package/dist/core/gapBCommitmentEvidence.d.ts +143 -0
  35. package/dist/core/gapBCommitmentEvidence.d.ts.map +1 -0
  36. package/dist/core/gapBCommitmentEvidence.js +138 -0
  37. package/dist/core/gapBCommitmentEvidence.js.map +1 -0
  38. package/dist/core/types.d.ts +21 -0
  39. package/dist/core/types.d.ts.map +1 -1
  40. package/dist/core/types.js.map +1 -1
  41. package/dist/monitoring/CommitmentTracker.d.ts +12 -0
  42. package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
  43. package/dist/monitoring/CommitmentTracker.js +14 -0
  44. package/dist/monitoring/CommitmentTracker.js.map +1 -1
  45. package/dist/monitoring/ReapLog.d.ts +11 -0
  46. package/dist/monitoring/ReapLog.d.ts.map +1 -1
  47. package/dist/monitoring/ReapLog.js +1 -0
  48. package/dist/monitoring/ReapLog.js.map +1 -1
  49. package/dist/monitoring/ResumeQueueDrainer.d.ts +15 -0
  50. package/dist/monitoring/ResumeQueueDrainer.d.ts.map +1 -1
  51. package/dist/monitoring/ResumeQueueDrainer.js +12 -1
  52. package/dist/monitoring/ResumeQueueDrainer.js.map +1 -1
  53. package/dist/scaffold/templates.d.ts.map +1 -1
  54. package/dist/scaffold/templates.js +2 -0
  55. package/dist/scaffold/templates.js.map +1 -1
  56. package/dist/server/AgentServer.d.ts +3 -0
  57. package/dist/server/AgentServer.d.ts.map +1 -1
  58. package/dist/server/AgentServer.js +2 -0
  59. package/dist/server/AgentServer.js.map +1 -1
  60. package/dist/server/routes.d.ts +4 -0
  61. package/dist/server/routes.d.ts.map +1 -1
  62. package/dist/server/routes.js +58 -0
  63. package/dist/server/routes.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/data/builtin-manifest.json +64 -64
  66. package/src/scaffold/templates.ts +2 -0
  67. package/upgrades/1.3.588.md +27 -0
  68. package/upgrades/1.3.589.md +68 -0
  69. package/upgrades/side-effects/autonomous-registration-guarantee.md +51 -0
  70. package/upgrades/side-effects/live-user-channel-proof-standard.md +54 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAg4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA04ctE;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;AAmCH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAS3D,OAAO,EAAE,eAAe,EAAiC,MAAM,iCAAiC,CAAC;AAuBjG,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAkH7D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsBtD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC1C,OAAO,CAUT;AAyID,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAg4CD,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,eAAe,EACzB,cAAc,EAAE,cAAc,EAC9B,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,WAAW,EACzB,WAAW,CAAC,EAAE,WAAW,EACzB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EAGvE,UAAU,CAAC,EAAE,MAAM,OAAO,8BAA8B,EAAE,WAAW,GAAG,IAAI,EAK5E,qBAAqB,CAAC,EAAE,MAAM,OAAO,gCAAgC,EAAE,kBAAkB,GAAG,IAAI,EAKhG,mBAAmB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GACpD,IAAI,CA8eN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkndtE;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"}
@@ -24,7 +24,8 @@ import { CodexResumeMap } from '../core/CodexResumeMap.js';
24
24
  import { paneIdleWithEmptyInput } from '../core/ModelSwapService.js';
25
25
  import { escalatedModelIds, normalizeTierEscalationConfig } from '../core/ModelTierEscalation.js';
26
26
  import { activeAutonomousJobs, autonomousRunRemainingForTopic } from '../core/AutonomousSessions.js';
27
- import { AGE_LIMIT_ACTIVE_RUN_REASON } from '../core/WorkEvidence.js';
27
+ import { AGE_LIMIT_ACTIVE_RUN_REASON, COMMITMENT_ACTIVE_RUN_REASON } from '../core/WorkEvidence.js';
28
+ import { gapBEligibleForTopic, recentUserMessageFromHistory, resolveGapBInjectionGate, decideGapBInjection } from '../core/gapBCommitmentEvidence.js';
28
29
  import { TopicProfileTransferCarrier, createTopicProfilePullHandler } from '../core/TopicProfileTransferCarrier.js';
29
30
  import { closeAllSqlite } from '../core/SqliteRegistry.js';
30
31
  import { SessionManager } from '../core/SessionManager.js';
@@ -6377,6 +6378,56 @@ export async function startServer(options) {
6377
6378
  const rqCfg = config.monitoring?.resumeQueue ?? {};
6378
6379
  let resumeQueue = null;
6379
6380
  let resumeDrainer = null;
6381
+ // GAP-B Part A surface (spec: autonomous-registration-guarantee.md) — the
6382
+ // aggregated-attention chokepoint reference, hoisted to the handler's scope.
6383
+ // Assigned inside the `if (rqCfg.enabled)` block (where raiseResumeAggregated
6384
+ // is built); null otherwise ⇒ Part A is a silent no-op (never blocks).
6385
+ let gapBRaiseAggregated = null;
6386
+ // GAP-B Part B (the commitment-evidence backstop, ships DARK). The gate is
6387
+ // armed ONLY by an explicit `monitoring.resumeQueue.commitmentEvidence.enabled
6388
+ // === true` (omitted ⇒ OFF on fleet AND dev — the containment: no injection ⇒
6389
+ // no revival ⇒ the 2026-06-13 loop is structurally impossible while dark).
6390
+ const gapBCfg = rqCfg.commitmentEvidence ?? {};
6391
+ const gapBGate = resolveGapBInjectionGate(gapBCfg);
6392
+ const gapBFreshWindowMs = gapBCfg.freshCommitmentWindowMs ?? 6 * 60 * 60_000; // D1: 6h
6393
+ // D8 window — reuse SessionReaper's staleCommitmentWindowMinutes (480/8h,
6394
+ // ConfigDefaults) so the GAP-B recent-user-message corroboration uses the SAME
6395
+ // horizon as ReapGuard's commitment-coupled KEEP (no new ConfigDefaults entry).
6396
+ const gapBStaleWindowMs = (config.monitoring?.sessionReaper?.staleCommitmentWindowMinutes ?? 480) * 60_000;
6397
+ // Part D (spec: autonomous-registration-guarantee.md) — the SHARED inbound-
6398
+ // user-message recency predicate. ONE definition, used by BOTH the GAP-B
6399
+ // eligibility helper (D8) below AND the ReapGuard/SessionReaper deps further
6400
+ // down, so a KEEP decision and the revival eligibility are computed from the
6401
+ // identical truth and CANNOT disagree (the loop the agreement prevents).
6402
+ // Store: TelegramAdapter.getTopicHistory (sync in-memory LogEntry tail cache —
6403
+ // NOT the Threadline A2A MessageStore). Inbound user = LogEntry.fromUser ===
6404
+ // true; freshness = its `timestamp` within `withinMs`. Fail-open (D7): a throw
6405
+ // ⇒ false (no KEEP, no inject) — never endangers the kill path.
6406
+ const recentUserMessageShared = (topicId, withinMs) => {
6407
+ try {
6408
+ if (!telegram)
6409
+ return false;
6410
+ // 50-entry tail is within getTopicHistory's TAIL_CACHE_LIMIT fast path
6411
+ // (sync cache read, no file scan for live topics).
6412
+ return recentUserMessageFromHistory(telegram.getTopicHistory(topicId, 50), withinMs);
6413
+ }
6414
+ catch {
6415
+ // @silent-fallback-ok — Part D D7: fail toward today's behavior.
6416
+ return false;
6417
+ }
6418
+ };
6419
+ // GAP-B Part B + D9 — the SHARED qualifying-commitment + D8 agreement verdict,
6420
+ // ONE closure used by BOTH the reaped-session injection (enqueue time) AND the
6421
+ // drainer's drain-time re-validation (`commitmentStillActiveForTopic`), so the
6422
+ // two cannot disagree on "what a qualifying open commitment means". Delegates
6423
+ // to the pure, unit-tested predicate; reads THIS machine's local tracker.
6424
+ // Fail-open (D7): handled inside gapBEligibleForTopic (a throw ⇒ false).
6425
+ const gapBCommitmentQualifies = (topicId) => gapBEligibleForTopic(topicId, commitmentTracker, {
6426
+ ownMachineId: cjOwnMachineId,
6427
+ freshCommitmentWindowMs: gapBFreshWindowMs,
6428
+ staleCommitmentWindowMs: gapBStaleWindowMs,
6429
+ recentUserMessage: recentUserMessageShared,
6430
+ });
6380
6431
  // Operator-stop record for the drainer's R2.6 validation (in-memory map +
6381
6432
  // the durable autonomous-emergency-stop flag file's mtime as global stop).
6382
6433
  const operatorStopsByTopic = new Map();
@@ -6430,6 +6481,10 @@ export async function startServer(options) {
6430
6481
  }
6431
6482
  catch { /* never endanger the caller */ }
6432
6483
  };
6484
+ // GAP-B Part A: route the unregistered-autonomous surface through the SAME
6485
+ // aggregated chokepoint (P17 — the bound lives at the creation chokepoint so
6486
+ // a unique key can't dodge it). Hoist the reference to the handler's scope.
6487
+ gapBRaiseAggregated = raiseResumeAggregated;
6433
6488
  resumeQueue = new ResumeQueue({
6434
6489
  stateDir: path.join(_projectDir, '.instar'),
6435
6490
  audit: auditResumeQueue,
@@ -6535,6 +6590,13 @@ export async function startServer(options) {
6535
6590
  // admitted the entry). A throw resolves to NOT-finished inside the drainer's
6536
6591
  // safeBool (SAFE side — the revival still passes the other reality gates).
6537
6592
  autonomousRunFinished: (topicId) => autonomousRunRemainingForTopic(config.stateDir, topicId) == null,
6593
+ // GAP-B D9: the parallel drain-time re-check for a COMMITMENT-admitted
6594
+ // entry. Re-runs the SAME qualifying + D8 agreement check used at
6595
+ // enqueue; `true` ⇒ still active (spawn proceeds), `false` ⇒ the
6596
+ // commitment closed or user-activity lapsed since enqueue (the drainer
6597
+ // invalidates `commitment-no-longer-active`, never a spawn). A throw
6598
+ // resolves SAFE (still-active) inside the drainer's safeBool.
6599
+ commitmentStillActiveForTopic: (topicId) => gapBCommitmentQualifies(topicId),
6538
6600
  jobCheck: (slug, queuedAtIso) => {
6539
6601
  if (!scheduler)
6540
6602
  return { ok: false, why: 'scheduler-unavailable' };
@@ -6700,6 +6762,46 @@ export async function startServer(options) {
6700
6762
  }
6701
6763
  }
6702
6764
  sessionManager.on('sessionReaped', (e) => {
6765
+ // ── GAP-B Part B (spec: autonomous-registration-guarantee.md) — the
6766
+ // commitment-evidence BACKSTOP for an UNregistered-but-working autonomous
6767
+ // run. Computed ONCE here (cheap, fail-open) so its verdict feeds BOTH the
6768
+ // reap-log `evidenceSource` tag and the enqueue-time injection below.
6769
+ // Pinned to the SAME branch the state-file source uses: an `age-limit`
6770
+ // reap whose per-topic state file is ABSENT (a registered run is handled
6771
+ // by the AGE_LIMIT_ACTIVE_RUN_REASON path; this is the gap it can't see).
6772
+ // Ships DARK (`gapBGate.armed`) — when off it is a strict no-op. ──
6773
+ let gapBDecision = { fired: false, inject: false };
6774
+ try {
6775
+ if (gapBGate.armed && e.reason === 'age-limit') {
6776
+ // Only resolve topic + the cost-bearing eligibility on the armed
6777
+ // age-limit branch (every other reap pays zero added cost).
6778
+ const rawTopicG = telegram?.getTopicForSession(e.session.tmuxSession);
6779
+ const topicG = rawTopicG == null ? null : Number.isFinite(Number(rawTopicG)) ? Number(rawTopicG) : null;
6780
+ if (topicG != null) {
6781
+ gapBDecision = decideGapBInjection({
6782
+ gate: gapBGate,
6783
+ reason: e.reason,
6784
+ // State-file PRESENT ⇒ the registered-run path covers it upstream;
6785
+ // GAP-B fires only when ABSENT.
6786
+ stateFilePresent: autonomousRunRemainingForTopic(config.stateDir, topicG) != null,
6787
+ // D1/D2 qualifying set + D8 recent-user-message agreement (shared with
6788
+ // the drainer's drain-time re-check so the two cannot disagree).
6789
+ eligible: gapBCommitmentQualifies(topicG),
6790
+ });
6791
+ }
6792
+ }
6793
+ }
6794
+ catch (err) {
6795
+ // @silent-fallback-ok — D7 fail-open: any read throwing ⇒ no injection,
6796
+ // the reap proceeds exactly as today. Never endangers the kill path.
6797
+ gapBDecision = { fired: false, inject: false };
6798
+ console.warn('[gap-b] commitment-evidence probe raised (non-fatal):', err);
6799
+ }
6800
+ const gapBCommitmentFired = gapBDecision.fired;
6801
+ // dryRun (default when armed): the verdict is logged but the candidate is
6802
+ // NOT tagged — the dark-soak proving KEEP/eligibility agree before evidence
6803
+ // actually flows. `gapBInject` is the LIVE-injection switch.
6804
+ const gapBInject = gapBDecision.inject;
6703
6805
  reapLog.recordReaped({
6704
6806
  session: e.session.name,
6705
6807
  tmuxSession: e.session.tmuxSession,
@@ -6717,7 +6819,24 @@ export async function startServer(options) {
6717
6819
  // chokepoint; the reap-log row is the boot-reconciliation source of truth.
6718
6820
  ...(e.midWork !== undefined ? { midWork: e.midWork } : {}),
6719
6821
  ...(e.workEvidence && e.workEvidence.length > 0 ? { workEvidence: e.workEvidence } : {}),
6822
+ // GAP-B D3: tag the source only when the LIVE injection fired (dryRun
6823
+ // logs but does not tag — the row must reflect what actually drove
6824
+ // eligibility). No PII: the field carries only the source token.
6825
+ ...(gapBInject ? { evidenceSource: 'commitment' } : {}),
6720
6826
  });
6827
+ if (gapBCommitmentFired) {
6828
+ // Part A (observe-only): surface the unregistered-but-committed run through
6829
+ // the SAME aggregated chokepoint (P17), deduped per topic. Never blocks.
6830
+ // Fires on the VERDICT (even in dryRun) — the surface exists to make the
6831
+ // invisible visible so the run gets registered, independent of injection.
6832
+ try {
6833
+ const rawTopicA = telegram?.getTopicForSession(e.session.tmuxSession);
6834
+ const topicA = rawTopicA == null ? null : Number.isFinite(Number(rawTopicA)) ? Number(rawTopicA) : null;
6835
+ gapBRaiseAggregated?.(`unregistered-autonomous:${topicA ?? '?'}`, `An autonomous-flavored run on topic ${topicA ?? '?'} is proceeding UNREGISTERED (no per-topic state file) — surfaced from a fresh open commitment + recent user activity. Register it so its survival doesn't depend on a commitment existing.`);
6836
+ }
6837
+ catch { /* @silent-fallback-ok — Part A never endangers the reap path */ }
6838
+ console.log(`[gap-b] commitment-evidence ${gapBInject ? 'INJECTED' : 'would inject (dryRun)'} for ${e.session.name} (unregistered autonomous run)`);
6839
+ }
6721
6840
  // Enqueue hook (reap-notify R2.2): every terminal autonomous reap is
6722
6841
  // OFFERED to the resume queue; eligibility (evidence classes, job
6723
6842
  // opt-in, operator exclusion, resurrection cap) is decided inside.
@@ -6750,6 +6869,19 @@ export async function startServer(options) {
6750
6869
  candidateReason = AGE_LIMIT_ACTIVE_RUN_REASON;
6751
6870
  candidateWorkEvidence = [...candidateWorkEvidence, 'build-or-autonomous-active'];
6752
6871
  }
6872
+ else if (gapBInject) {
6873
+ // GAP-B Part B (the committed-unregistered-run backstop, LIVE only when
6874
+ // armed + NOT dryRun — see the verdict computed at the top of the
6875
+ // handler). Mutually exclusive with the branch above by construction:
6876
+ // that requires the per-topic state file PRESENT, gapBCommitmentFired
6877
+ // requires it ABSENT. Tag the DISTINCT reason so the drainer routes
6878
+ // this candidate to its own drain-time re-check (commitment liveness,
6879
+ // not the absent state file). The strong signal then flows through the
6880
+ // already-shipped evidenceEligible gate → ResumeQueue → drainer,
6881
+ // inheriting the resurrection cap (≤2/24h/topic — P19 bound).
6882
+ candidateReason = COMMITMENT_ACTIVE_RUN_REASON;
6883
+ candidateWorkEvidence = [...candidateWorkEvidence, 'build-or-autonomous-active'];
6884
+ }
6753
6885
  resumeQueue.considerEnqueue({
6754
6886
  sessionName: e.session.name,
6755
6887
  tmuxSession: e.session.tmuxSession,
@@ -12064,6 +12196,43 @@ export async function startServer(options) {
12064
12196
  completionEvaluator = new CompletionEvaluator({ intelligence: sharedIntelligence });
12065
12197
  console.log(pc.green(' Completion evaluator: active (independent /goal-style judge)'));
12066
12198
  }
12199
+ // ── LiveTestGate (live-user-channel-proof spec §4) ──────────────────────
12200
+ // The completion-gate teeth: refuse a "done" verdict for a USER-FACING feature
12201
+ // without a verified live-test artifact. DEV-GATED + dry-run default — the gate
12202
+ // COMPUTES the veto and logs it but never overrides a verdict until promoted to
12203
+ // warn/veto. Same machine signs + verifies (dev dogfood); cross-machine verify
12204
+ // (resolve a peer's harness pubkey) is a tracked follow-on.
12205
+ let liveTestGate;
12206
+ let liveTestGateMode = 'dry-run';
12207
+ try {
12208
+ const ltgEnabled = resolveDevAgentGate(config.monitoring?.liveTestGate?.enabled, config);
12209
+ const idMgrForLtg = coordinator?.managers?.identityManager;
12210
+ if (ltgEnabled && idMgrForLtg && coordinator?.identity?.machineId) {
12211
+ const { LiveTestArtifactStore } = await import('../core/LiveTestArtifactStore.js');
12212
+ const { LiveTestGate } = await import('../core/LiveTestGate.js');
12213
+ const nodeCrypto = await import('node:crypto');
12214
+ const signingKeyPem = idMgrForLtg.loadSigningKey();
12215
+ const publicKeyPem = nodeCrypto.createPublicKey({ key: signingKeyPem }).export({ type: 'spki', format: 'pem' }).toString();
12216
+ const machineId = coordinator.identity.machineId;
12217
+ const ltStore = new LiveTestArtifactStore({
12218
+ stateDir: config.stateDir,
12219
+ machineId,
12220
+ signerFingerprint: machineId,
12221
+ sign: (d) => signEd25519(d, signingKeyPem),
12222
+ verify: (d, s) => verifyEd25519(d, s, publicKeyPem),
12223
+ logger: (m) => console.log(pc.dim(` ${m}`)),
12224
+ });
12225
+ liveTestGate = new LiveTestGate(ltStore);
12226
+ const m = config.monitoring?.liveTestGate?.mode;
12227
+ liveTestGateMode = m === 'warn' || m === 'veto' ? m : 'dry-run';
12228
+ console.log(pc.dim(` [live-test-gate] active (dev-gate, mode=${liveTestGateMode}) — completion veto §4`));
12229
+ }
12230
+ }
12231
+ catch (err) {
12232
+ // @silent-fallback-ok — gate not wired → no veto (today's exact completion
12233
+ // behavior). Logged; never blocks server boot.
12234
+ console.log(pc.dim(` [live-test-gate] not wired: ${err instanceof Error ? err.message : String(err)}`));
12235
+ }
12067
12236
  // ── CollaborationRedriveEngine ────────────────────────────────────
12068
12237
  // Proactively re-engage a COUNTERPART that has gone silent on an open
12069
12238
  // threadline-reply commitment. Bounded peer nudges with a durable,
@@ -12711,12 +12880,17 @@ export async function startServer(options) {
12711
12880
  hasPendingInjection: (s) => sessionManager.getPendingInjection(s) != null,
12712
12881
  isRelayLeaseActive: (id) => sessionManager.isRelayLeaseActive(id),
12713
12882
  topicBinding: _resolveTopic,
12714
- // Gate I is a v1 stub (returns false): active conversation is already
12715
- // covered by the relay-lease + pending-injection gates and by render
12716
- // stasis (a session being talked to is not render-static for the full
12717
- // hysteresis+threshold window). Promoting to a real message-recency
12718
- // query is a tracked tuning follow-up.
12719
- recentUserMessage: () => false,
12883
+ // Part D (spec: autonomous-registration-guarantee.md) promoted from the
12884
+ // v1 stub (`() => false`) to the SHARED real inbound-user-message recency
12885
+ // predicate (`recentUserMessageShared`, defined once near the resume-queue
12886
+ // wiring). This dep feeds FIVE live sites at once: ReapGuard's standalone
12887
+ // ~30min KEEP @137, its commitment-coupled 8h KEEP @149, the two terminate-
12888
+ // path mirrors @221/@239, and SessionReaper's staleIdle inversion @489. By
12889
+ // sharing the SAME predicate the GAP-B eligibility uses (D8), the KEEP
12890
+ // decisions and the revival eligibility are computed from identical truth
12891
+ // and cannot disagree (the 2026-06-13 loop the agreement prevents). All five
12892
+ // un-stub in the SAFE (keep-more) direction; fail-open (D7) on a throw.
12893
+ recentUserMessage: recentUserMessageShared,
12720
12894
  activeCommitmentForTopic: (topicId) => {
12721
12895
  try {
12722
12896
  return commitmentTracker.getActive().some(c => c.topicId === topicId);
@@ -13801,8 +13975,30 @@ export async function startServer(options) {
13801
13975
  // is constructed further down (working-set wiring); until then the floor
13802
13976
  // reads 0, which preserves pre-fix behavior for the boot window.
13803
13977
  let ownershipEpochFloor = null;
13978
+ // ── Transfer fix (spec docs/specs/live-user-channel-proof-standard.md §7.2) ──
13979
+ // The in-memory store was the bug: a transfer wrote the target's ownership
13980
+ // into the SOURCE's process Map → vanished on restart, never crossed machines.
13981
+ // Behind the DEV-AGENT DARK GATE (live on a dev agent / dark on the fleet),
13982
+ // swap in the DURABLE per-session store; the OwnershipApplier (wired below)
13983
+ // materializes ownership on the target from the REPLICATED placement journal,
13984
+ // so a seat genuinely moves. Off → InMemory (today's exact behavior).
13985
+ const durableOwnershipOn = resolveDevAgentGate(config.multiMachine?.durableOwnership?.enabled, config);
13986
+ let durableOwnershipStore = null;
13987
+ let ownershipStore;
13988
+ if (durableOwnershipOn) {
13989
+ const { LocalSessionOwnershipStore } = await import('../core/LocalSessionOwnershipStore.js');
13990
+ durableOwnershipStore = new LocalSessionOwnershipStore({
13991
+ dir: path.join(config.stateDir, 'ownership', 'local'),
13992
+ logger: (m) => console.log(pc.dim(` ${m}`)),
13993
+ });
13994
+ ownershipStore = durableOwnershipStore;
13995
+ console.log(pc.dim(' [ownership] durable LocalSessionOwnershipStore active (dev-gate live) — transfer-fix §7.2'));
13996
+ }
13997
+ else {
13998
+ ownershipStore = new ownMod.InMemorySessionOwnershipStore();
13999
+ }
13804
14000
  sessionOwnershipRegistry = new ownMod.SessionOwnershipRegistry({
13805
- store: new ownMod.InMemorySessionOwnershipStore(),
14001
+ store: ownershipStore,
13806
14002
  seenNonce: (k) => seenOwnNonces.has(k),
13807
14003
  recordNonce: (k) => seenOwnNonces.add(k),
13808
14004
  epochFloorOf: (sk) => ownershipEpochFloor?.(sk) ?? 0,
@@ -13913,6 +14109,38 @@ export async function startServer(options) {
13913
14109
  }, tickMs);
13914
14110
  ws13Timer.unref?.();
13915
14111
  }
14112
+ // ── Transfer fix §7.2: OwnershipApplier ─────────────────────────────────
14113
+ // Materialize durable local ownership from the REPLICATED placement journal
14114
+ // (peers/<machineId>.topic-placement.jsonl) so the machine a topic moved TO
14115
+ // resolves the right owner on the next message — the step the in-memory store
14116
+ // never had. Off the hot path: ticked on an interval + once at boot (to adopt
14117
+ // a transfer that landed while we were down). Only when the durable store is
14118
+ // active (dev-gate); InMemory has no cross-machine replication to apply.
14119
+ if (durableOwnershipStore && _meshSelfId) {
14120
+ const { OwnershipApplier } = await import('../core/OwnershipApplier.js');
14121
+ const { CoherenceJournalReader } = await import('../core/CoherenceJournalReader.js');
14122
+ const applier = new OwnershipApplier({
14123
+ reader: new CoherenceJournalReader({ stateDir: config.stateDir }),
14124
+ store: durableOwnershipStore,
14125
+ selfMachineId: _meshSelfId,
14126
+ logger: (m) => console.log(pc.dim(` ${m}`)),
14127
+ });
14128
+ try {
14129
+ applier.tick();
14130
+ }
14131
+ catch { /* @silent-fallback-ok — boot tick best-effort; the interval converges */ }
14132
+ const applierTimer = setInterval(() => {
14133
+ try {
14134
+ const r = applier.tick();
14135
+ if (r.materialized)
14136
+ console.log(pc.dim(` [OwnershipApplier] materialized ${r.materialized}/${r.examined} topic(s) from replicated placements`));
14137
+ }
14138
+ catch (err) {
14139
+ console.error('[OwnershipApplier] tick failed:', err instanceof Error ? err.message : String(err));
14140
+ }
14141
+ }, 15000);
14142
+ applierTimer.unref?.();
14143
+ }
13916
14144
  // Coherence journal §3.3: the emit is a thin wrapper at every CAS call
13917
14145
  // site — `reason` is caller knowledge (cas() is a storage primitive and
13918
14146
  // cannot know WHY). A mesh-applied action records the coarse reason;
@@ -16038,7 +16266,7 @@ export async function startServer(options) {
16038
16266
  carrier: _topicProfileCarrier,
16039
16267
  }
16040
16268
  : null;
16041
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, 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, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, 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, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
16269
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, cartographer: cartographer ?? undefined, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, subscriptionPool, quotaPoller, quotaAwareScheduler: _quotaAwareScheduler ?? undefined, proactiveSwapMonitor: _proactiveSwapMonitor ?? undefined, inUseAccountResolver, enrollmentWizard, 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, localSigningKeyPem, leaseTransport, onLeasePullRequest: () => leaseCoordinatorRef?.currentLease() ?? null, liveTailReceiver, handoffWireTransport, onHandoffBegin, onHandoffInitiate: handoffInitiate, handoffInProgress: handoffSentinelInProgress, messageLedger, currentInboundByTopic, replyMarkerTransport, onReplyMarker: messageLedger ? (marker) => { const m = marker; messageLedger.applyRemoteReplyMarker(m.dedupeKey, { platform: m.platform, replyIdempotencyKey: m.replyIdempotencyKey, epoch: m.epoch, topic: m.topic ?? null }); } : undefined, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, conversationStore, threadLog, threadMessageRecorder, warrantsReplyGate, collaborationSurfacer, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, a2aDeliveryTracker: a2aDeliveryTracker ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, completionEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, machinePoolRegistry, getInboundQueue: () => _inboundQueue, meshRpcDispatcher, workingSetPullCoordinator, commitmentReplicaStore, preferenceReplicaStore, replicatedRecordEmitter, conflictStore, rollbackUnmerge, droppedOriginRegistry, preferencesUnionReader, forwardCommitmentMutate, sessionOwnershipRegistry, topicPinStore: _topicPinStore ?? undefined, streamTicketStore: _streamTicketStore ?? undefined, poolStreamAllowRemoteInput: config.dashboard?.poolStream?.allowRemoteInput ?? false, poolStreamConnector: _poolStreamConnector ?? undefined, secretSync: _secretSyncHandle ?? undefined, meshSelfId: _meshSelfId ?? undefined, resolveRouterUrl: _resolveRouterUrl ?? undefined, resolvePeerUrls: _resolvePeerUrls ?? undefined, guardRegistry, listPoolMachines: _listPoolMachines ?? undefined, 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, orphanedWorkSentinel, mcpProcessReaper, geminiLoopRunner, sleepController, agentActivityState, reapLog, resumeQueue, resumeDrainer, operatorStopRecorder: recordOperatorStop, sleepWakeDetector, unjustifiedStopGate, stopGateDb, stopNotifier, liveTestGate, liveTestGateMode }); // Resolve the late-bound topic-operator getter (increment 2e): routing was
16042
16270
  // wired before the server existed; from here on inbound binds use the
16043
16271
  // server's own store instance.
16044
16272
  _agentServerRef = server;