instar 1.3.729 → 1.3.730

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 (65) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +174 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/ModelSwapService.d.ts +13 -0
  5. package/dist/core/ModelSwapService.d.ts.map +1 -1
  6. package/dist/core/ModelSwapService.js +11 -0
  7. package/dist/core/ModelSwapService.js.map +1 -1
  8. package/dist/core/ModelTierEscalation.d.ts +10 -0
  9. package/dist/core/ModelTierEscalation.d.ts.map +1 -1
  10. package/dist/core/ModelTierEscalation.js +4 -0
  11. package/dist/core/ModelTierEscalation.js.map +1 -1
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +18 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/ProactiveSwapMonitor.d.ts +74 -20
  16. package/dist/core/ProactiveSwapMonitor.d.ts.map +1 -1
  17. package/dist/core/ProactiveSwapMonitor.js +349 -28
  18. package/dist/core/ProactiveSwapMonitor.js.map +1 -1
  19. package/dist/core/QuotaAwareScheduler.d.ts +70 -3
  20. package/dist/core/QuotaAwareScheduler.d.ts.map +1 -1
  21. package/dist/core/QuotaAwareScheduler.js +100 -16
  22. package/dist/core/QuotaAwareScheduler.js.map +1 -1
  23. package/dist/core/SessionManager.d.ts +28 -0
  24. package/dist/core/SessionManager.d.ts.map +1 -1
  25. package/dist/core/SessionManager.js +102 -0
  26. package/dist/core/SessionManager.js.map +1 -1
  27. package/dist/core/SessionRefresh.d.ts +116 -1
  28. package/dist/core/SessionRefresh.d.ts.map +1 -1
  29. package/dist/core/SessionRefresh.js +280 -17
  30. package/dist/core/SessionRefresh.js.map +1 -1
  31. package/dist/core/SwapAntiThrash.d.ts +329 -0
  32. package/dist/core/SwapAntiThrash.d.ts.map +1 -0
  33. package/dist/core/SwapAntiThrash.js +1025 -0
  34. package/dist/core/SwapAntiThrash.js.map +1 -0
  35. package/dist/core/SwapLedger.d.ts +168 -0
  36. package/dist/core/SwapLedger.d.ts.map +1 -0
  37. package/dist/core/SwapLedger.js +275 -0
  38. package/dist/core/SwapLedger.js.map +1 -0
  39. package/dist/core/SwapWorkGate.d.ts +100 -0
  40. package/dist/core/SwapWorkGate.d.ts.map +1 -0
  41. package/dist/core/SwapWorkGate.js +175 -0
  42. package/dist/core/SwapWorkGate.js.map +1 -0
  43. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  44. package/dist/core/devGatedFeatures.js +6 -0
  45. package/dist/core/devGatedFeatures.js.map +1 -1
  46. package/dist/core/types.d.ts +63 -1
  47. package/dist/core/types.d.ts.map +1 -1
  48. package/dist/core/types.js.map +1 -1
  49. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  50. package/dist/monitoring/guardManifest.js +40 -1
  51. package/dist/monitoring/guardManifest.js.map +1 -1
  52. package/dist/scaffold/templates.d.ts.map +1 -1
  53. package/dist/scaffold/templates.js +1 -0
  54. package/dist/scaffold/templates.js.map +1 -1
  55. package/dist/server/AgentServer.d.ts.map +1 -1
  56. package/dist/server/AgentServer.js +19 -0
  57. package/dist/server/AgentServer.js.map +1 -1
  58. package/dist/server/routes.d.ts.map +1 -1
  59. package/dist/server/routes.js +36 -3
  60. package/dist/server/routes.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/data/builtin-manifest.json +65 -65
  63. package/src/scaffold/templates.ts +1 -0
  64. package/upgrades/1.3.730.md +112 -0
  65. package/upgrades/side-effects/swap-continuity-antithrash.md +118 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+CH,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,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;AAi/CD,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,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+hkBtE;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;AAgDH,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,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;AAu/CD,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,CAijBN;AA2lBD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2skBtE;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"}
@@ -26,6 +26,7 @@ import { SpawningTopicsRegistry } from '../core/SpawningTopicsRegistry.js';
26
26
  import { TopicReachabilityVerifier } from '../monitoring/TopicReachabilityVerifier.js';
27
27
  import { SingleInstanceLock, installReleaseHandlers } from '../core/SingleInstanceLock.js';
28
28
  import { resolveDevAgentGate, resolveStateSyncStores } from '../core/devAgentGate.js';
29
+ import { resolveAntiThrashKnobs, readingValidity } from '../core/SwapAntiThrash.js';
29
30
  import { shouldReleaseOnComplete, planClaimOnSpawn, ownershipNonce } from '../core/ownershipFollowsLiveWork.js';
30
31
  import { PrHandLease } from '../core/PrHandLease.js';
31
32
  import { claimSuspensionExcludesPin } from '../core/TopicClaimAnnotationStore.js';
@@ -760,6 +761,12 @@ let _quotaAwareScheduler = null;
760
761
  // Subscription & Auth Standard P1.3 — pre-limit proactive swap monitor. Null
761
762
  // until wired (requires the scheduler; gated behind subscriptionPool.proactiveSwap).
762
763
  let _proactiveSwapMonitor = null;
764
+ // swap-continuity-antithrash §3/§4 — the anti-thrash spine. The ledger + engine
765
+ // load and hydrate UNCONDITIONALLY at boot (R4-L3: reactive rows append
766
+ // regardless of antiThrash.enabled, so the index is warm at a mid-run flag
767
+ // flip). Null only when construction itself failed (non-fatal).
768
+ let _swapAntiThrashEngine = null;
769
+ let _swapWorkGate = null;
763
770
  async function spawnSessionForTopic(sessionManager, telegram, sessionName, topicId, latestMessage, topicMemory, userProfile, precomputedContext,
764
771
  /** Subscription & Auth Standard P1.3 (additive): launch under this account's
765
772
  * config home + record its id (the quota-aware account-swap mechanism).
@@ -14962,6 +14969,94 @@ export async function startServer(options) {
14962
14969
  divergenceChecker = undefined;
14963
14970
  }
14964
14971
  }
14972
+ // ── Swap-continuity anti-thrash spine (swap-continuity-antithrash §3/§4) ──
14973
+ // SwapLedger + SwapAntiThrashEngine + SwapWorkGate. Constructed and
14974
+ // hydrated UNCONDITIONALLY (R4-L3): the ledger loads at every boot
14975
+ // regardless of antiThrash.enabled so the in-memory index (dwell /
14976
+ // reversal / breaker / frequency) is warm the moment the flag flips on
14977
+ // mid-run — the §7.1 half-enable trap closed for this key. All antiThrash
14978
+ // knobs are read LIVE per tick via the getter (the config object is
14979
+ // shared + mutated in place by config PATCHes).
14980
+ const antiThrashKnobsGetter = () => resolveAntiThrashKnobs(config.subscriptionPool?.proactiveSwap?.antiThrash, {
14981
+ thresholdPct: config.subscriptionPool?.proactiveSwap?.thresholdPct,
14982
+ tickMs: config.subscriptionPool?.proactiveSwap?.tickMs,
14983
+ });
14984
+ // swapContinuity (Piece 2, the work gate): the `enabled` KEY IS OMITTED
14985
+ // from shipped config on purpose — omission routes it through the
14986
+ // dev-agent gate (live on a dev agent, dark on the fleet; the #1001
14987
+ // anti-mechanism). `enabled` is RESTART-REQUIRED (§7.1 — the gate wiring
14988
+ // into SessionRefresh is constructor-injected); dryRun + numeric knobs
14989
+ // are read live per evaluation via the getters below.
14990
+ const swapContinuityEnabled = resolveDevAgentGate(config.subscriptionPool?.swapContinuity?.enabled, config);
14991
+ const swapContinuityKnobs = () => {
14992
+ const c = config.subscriptionPool?.swapContinuity;
14993
+ return {
14994
+ enabled: swapContinuityEnabled,
14995
+ dryRun: c?.dryRun ?? true,
14996
+ deferralCeilingMs: c?.deferralCeilingMs ?? 1_800_000,
14997
+ reactiveGraceMs: c?.reactiveGraceMs ?? 120_000,
14998
+ recheckMs: c?.recheckMs ?? 10_000,
14999
+ };
15000
+ };
15001
+ try {
15002
+ const { SwapLedger } = await import('../core/SwapLedger.js');
15003
+ const { SwapAntiThrashEngine, retentionBoundMs, crossKnobWarnings } = await import('../core/SwapAntiThrash.js');
15004
+ const { SwapWorkGate } = await import('../core/SwapWorkGate.js');
15005
+ const swapLedger = new SwapLedger({
15006
+ filePath: path.join(config.stateDir, 'state', 'swap-ledger.jsonl'),
15007
+ windowMs: () => retentionBoundMs(antiThrashKnobsGetter()),
15008
+ // Counts supplied at outage-resume time (the ledger-lost refusals the
15009
+ // writer itself could not record — §3.5/I5).
15010
+ outageRefusalCount: () => _swapAntiThrashEngine?.ledgerLostRefusalCount() ?? 0,
15011
+ machineId: _meshSelfId ?? 'local',
15012
+ logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15013
+ });
15014
+ _swapAntiThrashEngine = new SwapAntiThrashEngine({
15015
+ ledger: swapLedger,
15016
+ getKnobs: antiThrashKnobsGetter,
15017
+ machineId: _meshSelfId ?? 'local',
15018
+ // P17: episode-deduped attention (the engine dedupes on episodeId;
15019
+ // the id passed here is the stable per-episode dedupe key).
15020
+ raiseAttention: (id, title, body) => {
15021
+ void telegram
15022
+ ?.createAttentionItem({
15023
+ id,
15024
+ title,
15025
+ summary: body,
15026
+ category: 'subscription-pool',
15027
+ priority: 'HIGH',
15028
+ sourceContext: 'subscription-pool:swap-antithrash',
15029
+ })
15030
+ .catch(() => {
15031
+ /* @silent-fallback-ok: attention is best-effort; the ledger row is the durable trace */
15032
+ });
15033
+ },
15034
+ logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15035
+ });
15036
+ _swapAntiThrashEngine.hydrate();
15037
+ // Cross-knob coherence warnings (§7, R4-L2 — warn-only, once at boot).
15038
+ // 15 min = the QuotaPoller's default cadence (pollIntervalMs ?? 15*60_000).
15039
+ for (const w of crossKnobWarnings(antiThrashKnobsGetter(), 15 * 60_000))
15040
+ console.warn(w);
15041
+ _swapWorkGate = new SwapWorkGate({
15042
+ checkSessionWorkState: (tmux) => sessionManager.checkSessionWorkState(tmux),
15043
+ getClaudeSessionId: (tmux) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === tmux)?.claudeSessionId ?? null,
15044
+ hasActiveSubagents: (id) => subagentTracker.hasActiveSubagents(id),
15045
+ getActiveSubagents: (id) => subagentTracker.getActiveSubagents(id).map((r) => ({
15046
+ agentType: r.agentType,
15047
+ startedAt: r.startedAt,
15048
+ ...(r.transcriptPath ? { transcriptPath: r.transcriptPath } : {}),
15049
+ })),
15050
+ });
15051
+ }
15052
+ catch (err) {
15053
+ // @silent-fallback-ok: cascade-isolation — a spine construction failure
15054
+ // must never 503 the server; behavior degrades to pre-brake semantics
15055
+ // (the legacy path) and re-attempts next boot.
15056
+ console.warn('[instar] swap-continuity anti-thrash spine init failed (non-fatal):', err);
15057
+ _swapAntiThrashEngine = null;
15058
+ _swapWorkGate = null;
15059
+ }
14965
15060
  // SessionRefresh — agent-initiated session respawn. Requires a Telegram
14966
15061
  // adapter (v1 scope: Telegram-bound sessions only). The respawner closure
14967
15062
  // captures `topicMemory` by reference, so even if topicMemory is wired up
@@ -14979,6 +15074,43 @@ export async function startServer(options) {
14979
15074
  state,
14980
15075
  telegram: telegramRef,
14981
15076
  topicResumeMap: _topicResumeMap,
15077
+ // swap-continuity-antithrash §4.2 — the in-flight work gate, bound at
15078
+ // THIS primitive (the funnel every session-killing mutation flows
15079
+ // through). Constructor-injected on purpose: `swapContinuity.enabled`
15080
+ // is restart-required (§7.1); dryRun + numeric knobs read live via
15081
+ // getKnobs. Null when the feature is dark on this install ⇒
15082
+ // byte-for-byte today's behavior (unconditional kill).
15083
+ workGateCtx: swapContinuityEnabled && _swapWorkGate
15084
+ ? {
15085
+ probe: (tmux) => _swapWorkGate.probe(tmux),
15086
+ getKnobs: () => {
15087
+ const k = swapContinuityKnobs();
15088
+ return { enabled: k.enabled, dryRun: k.dryRun, reactiveGraceMs: k.reactiveGraceMs, recheckMs: k.recheckMs };
15089
+ },
15090
+ recordProceeded: (args) => _swapAntiThrashEngine?.recordProceeded(args),
15091
+ recordInteractiveRefusal: (args) => _swapAntiThrashEngine?.recordInteractiveRefusal(args),
15092
+ // §4.3(2) — Q4: v1 reads the in-memory exactly-once map (set on
15093
+ // inbound, cleared on reply); 'unknown' = the map/ledger is
15094
+ // unavailable (post-restart, or exactly-once ingress dark).
15095
+ // `messageLedger`/`currentInboundByTopic` are declared later in
15096
+ // this boot function and captured by reference — every call
15097
+ // happens post-boot, so the binding is initialized by then.
15098
+ resolveUnansweredInbound: (topicId) => {
15099
+ if (!messageLedger || !currentInboundByTopic)
15100
+ return 'unknown';
15101
+ const dedupeKey = currentInboundByTopic.get(String(topicId));
15102
+ if (!dedupeKey)
15103
+ return 'none';
15104
+ const entry = messageLedger.get(dedupeKey);
15105
+ if (!entry || entry.inputSnapshot === null)
15106
+ return 'unknown';
15107
+ const from = entry.senderEnvelope?.username ??
15108
+ entry.senderEnvelope?.firstName ??
15109
+ (entry.senderEnvelope?.userId !== undefined ? String(entry.senderEnvelope.userId) : 'unknown sender');
15110
+ return { body: entry.inputSnapshot, from, at: entry.receivedAt };
15111
+ },
15112
+ }
15113
+ : null,
14982
15114
  // §10.5 Slack binding — SlackAdapter satisfies SlackRefreshBinding
14983
15115
  // structurally (getChannelForSession / removeChannelResume /
14984
15116
  // resolveChannelForSessionFromDisk). Null ⇒ Telegram-only, unchanged.
@@ -15042,6 +15174,30 @@ export async function startServer(options) {
15042
15174
  _quotaAwareScheduler = new QuotaAwareScheduler({
15043
15175
  listAccounts: () => subscriptionPool.list(),
15044
15176
  softThresholdPct: config.subscriptionPool?.swapSoftThresholdPct,
15177
+ // swap-continuity-antithrash — the anti-thrash observation + execute-time
15178
+ // revalidation hooks. Reactive decision behavior is untouched (I6):
15179
+ // these only OBSERVE reactive swaps (dwell clock-start, hop alerts,
15180
+ // failed rows) and revalidate PROACTIVE explicit-target calls (§3.3).
15181
+ antiThrash: _swapAntiThrashEngine
15182
+ ? {
15183
+ readingValid: (acct, nowMs) => readingValidity(acct, nowMs, antiThrashKnobsGetter().quotaFreshnessMs).valid,
15184
+ getKnobs: () => {
15185
+ const k = antiThrashKnobsGetter();
15186
+ return { thresholdPct: k.thresholdPct, targetHeadroomPct: k.targetHeadroomPct, minImprovementPct: k.minImprovementPct };
15187
+ },
15188
+ resolveCurrentAccountId: (sessionName) => state.listSessions({ status: 'running' }).find((s) => s.tmuxSession === sessionName)?.subscriptionAccountId ?? null,
15189
+ onReactiveExecuted: (a) => _swapAntiThrashEngine?.recordReactiveExecuted(a),
15190
+ onReactiveFailed: (a) => _swapAntiThrashEngine?.recordExecFailure({
15191
+ session: a.session,
15192
+ from: a.from,
15193
+ ...(a.to ? { to: a.to } : {}),
15194
+ kind: 'reactive',
15195
+ errorClass: a.errorClass,
15196
+ nowMs: a.nowMs,
15197
+ }),
15198
+ onReactiveRateCapRefusal: (s, n) => _swapAntiThrashEngine?.noteReactiveRateCapRefusal(s, n),
15199
+ }
15200
+ : undefined,
15045
15201
  refreshFn: async (o) => {
15046
15202
  if (!_sessionRefresh)
15047
15203
  return false;
@@ -15050,8 +15206,13 @@ export async function startServer(options) {
15050
15206
  reason: o.reason,
15051
15207
  configHome: o.configHome,
15052
15208
  accountId: o.accountId,
15209
+ // I11: callerClass set ONLY server-internally — the scheduler is
15210
+ // the one caller that knows its lane (proactive funnel vs reactive).
15211
+ callerClass: o.callerClass,
15053
15212
  });
15054
- return r.ok;
15213
+ // Object form: surfaces the structured refusal code so the
15214
+ // anti-thrash observation layer can classify failures (§3.1/§3.6).
15215
+ return r.ok ? { ok: true } : { ok: false, code: r.code };
15055
15216
  },
15056
15217
  onNoAlternate: (sessionName, exhaustedAccountId) => {
15057
15218
  void telegramRef.createAttentionItem({
@@ -15133,6 +15294,18 @@ export async function startServer(options) {
15133
15294
  maxSwapsPerCycle: proactiveCfg.maxSwapsPerCycle,
15134
15295
  cooldownMs: proactiveCfg.cooldownMs,
15135
15296
  tickMs: proactiveCfg.tickMs,
15297
+ // swap-continuity-antithrash §3 — the brake engine at the proactive
15298
+ // DECISION chokepoint. Knobs read LIVE per tick (§7.1). dryRun keeps
15299
+ // the legacy decision path byte-identical while the engine logs
15300
+ // would-refuse shadow rows (the rung-2 soak).
15301
+ antiThrash: _swapAntiThrashEngine
15302
+ ? { engine: _swapAntiThrashEngine, getKnobs: antiThrashKnobsGetter }
15303
+ : undefined,
15304
+ // §4 — the in-flight work gate (proactive arm: defer, ceiling-drop).
15305
+ // The monitor owns the deferral lifecycle; the gate is stateless.
15306
+ workGate: _swapWorkGate
15307
+ ? { probe: (s) => _swapWorkGate.probe(s), getContinuity: swapContinuityKnobs }
15308
+ : undefined,
15136
15309
  logger: { log: (m) => console.log(m), warn: (m) => console.warn(m) },
15137
15310
  });
15138
15311
  _proactiveSwapMonitor.start();