@warmdrift/kgauto-compiler 2.0.0-alpha.77 → 2.0.0-alpha.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  ALL_ARCHETYPES: () => ALL_ARCHETYPES,
26
26
  ARCHETYPE_FAMILY_FITS: () => ARCHETYPE_FAMILY_FITS,
27
27
  ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
28
+ BRAIN_READ_ENV_NAMES: () => BRAIN_READ_ENV_NAMES,
28
29
  COACH_CFG: () => COACH_CFG,
29
30
  CallError: () => CallError,
30
31
  DECOMPOSITION_TEMPLATES: () => DECOMPOSITION_TEMPLATES,
@@ -32,6 +33,7 @@ __export(index_exports, {
32
33
  DEFAULT_FINDINGS_ENDPOINT: () => DEFAULT_FINDINGS_ENDPOINT,
33
34
  DEFAULT_MEASURED_FAILURE_ENDPOINT: () => DEFAULT_MEASURED_FAILURE_ENDPOINT,
34
35
  DEFAULT_PROMOTIONS_ENDPOINT: () => DEFAULT_PROMOTIONS_ENDPOINT,
36
+ DELEGATE_TOOL_DEFINITION: () => DELEGATE_TOOL_DEFINITION,
35
37
  DIALECT_VERSION: () => DIALECT_VERSION,
36
38
  DISCIPLINE_GATES_V1_ALT_HEADER: () => DISCIPLINE_GATES_V1_ALT_HEADER,
37
39
  FamilyResolutionError: () => FamilyResolutionError,
@@ -43,6 +45,7 @@ __export(index_exports, {
43
45
  MEASURED_GROUNDING_MIN_N: () => MEASURED_GROUNDING_MIN_N,
44
46
  PRODUCER_OWNED_RULE_CODES: () => PRODUCER_OWNED_RULE_CODES,
45
47
  PROVIDER_ENV_KEYS: () => PROVIDER_ENV_KEYS,
48
+ ROLLBACK_SUPPRESSION_WINDOW_DAYS: () => ROLLBACK_SUPPRESSION_WINDOW_DAYS,
46
49
  RULE_DISCIPLINE_GATES_V1: () => RULE_DISCIPLINE_GATES_V1,
47
50
  RULE_DISCIPLINE_GATES_V1_STRUCTURED: () => RULE_DISCIPLINE_GATES_V1_STRUCTURED,
48
51
  RULE_SEQUENTIAL_TOOL_CLIFF: () => RULE_SEQUENTIAL_TOOL_CLIFF,
@@ -78,6 +81,7 @@ __export(index_exports, {
78
81
  configurePromotionsBrain: () => configurePromotionsBrain,
79
82
  countTokens: () => countTokens,
80
83
  createBrainForwardRoutes: () => createBrainForwardRoutes,
84
+ createDelegate: () => createDelegate,
81
85
  createKeyHealthRoute: () => createKeyHealthRoute,
82
86
  deriveFamilyFromModelId: () => deriveFamilyFromModelId,
83
87
  deriveOwnership: () => deriveOwnership,
@@ -96,17 +100,20 @@ __export(index_exports, {
96
100
  getPerAxisMetrics: () => getPerAxisMetrics,
97
101
  getProfile: () => getProfile,
98
102
  getReachabilityDiagnostic: () => getReachabilityDiagnostic,
103
+ getRecentRollback: () => getRecentRollback,
99
104
  getRecommendedPrimary: () => getRecommendedPrimary,
100
105
  getSequentialStarterChain: () => getSequentialStarterChain,
101
106
  getSequentialStarterChainWithGrounding: () => getSequentialStarterChainWithGrounding,
102
107
  getStaleExclusionFindings: () => getStaleExclusionFindings,
103
108
  getStarterChain: () => getStarterChain,
104
109
  getStarterChainWithGrounding: () => getStarterChainWithGrounding,
110
+ hasMutation: () => hasMutation,
105
111
  hashShape: () => hashShape,
106
112
  isArchetype: () => isArchetype,
107
113
  isAutoPromoteEnabledFromEnv: () => isAutoPromoteEnabledFromEnv,
108
114
  isBrainQueryActiveFor: () => isBrainQueryActiveFor,
109
115
  isBrainSync: () => isBrainSync,
116
+ isDelegateEnabledFromEnv: () => isDelegateEnabledFromEnv,
110
117
  isExclusionFindingsBrainActive: () => isExclusionFindingsBrainActive,
111
118
  isMeasuredFailureBrainActive: () => isMeasuredFailureBrainActive,
112
119
  isMeasuredFailureGateEnabledFromEnv: () => isMeasuredFailureGateEnabledFromEnv,
@@ -122,9 +129,11 @@ __export(index_exports, {
122
129
  loadChainsFromBrain: () => loadChainsFromBrain,
123
130
  loadModelsFromBrain: () => loadModelsFromBrain,
124
131
  loadPricingFromBrain: () => loadPricingFromBrain,
132
+ mapMeasuredFailureRows: () => mapMeasuredFailureRows,
125
133
  markAdvisoryResolved: () => markAdvisoryResolved,
126
134
  markExclusionFindingHandled: () => markExclusionFindingHandled,
127
135
  markPromoteReadyHandled: () => markPromoteReadyHandled,
136
+ mutationId: () => mutationId,
128
137
  parseGoldenCaptureRate: () => parseGoldenCaptureRate,
129
138
  parseJudgeVerdict: () => parseJudgeVerdict,
130
139
  peekBrainDeadLetter: () => peekBrainDeadLetter,
@@ -693,6 +702,22 @@ var PROFILES_RAW = [
693
702
  threshold: 16,
694
703
  action: "drop_to_top_relevant",
695
704
  reason: "Haiku reliability degrades above ~16 tools"
705
+ },
706
+ {
707
+ // alpha.78 — the declared `structuredOutput: 'grammar'` does NOT
708
+ // hold on long-input summarize. MEASURED (brain, playbacksam):
709
+ // 21 disambiguated `structured_output_parse_failed` fallover rows
710
+ // 2026-07-22..27, tokens_in 12,280–31,450; PB's gate counted 20/20
711
+ // in-window failures. Clean traffic p50 sits at ~9K tokens_in, so
712
+ // 12K gates the failing band without touching the working one.
713
+ // Short-input summarize carries no failure evidence and stays
714
+ // ungated — this is why it's a cliff, not an archetype-wide
715
+ // `structuredOutputHint: 'avoid'`.
716
+ metric: "input_tokens",
717
+ threshold: 12e3,
718
+ action: "quality_gate_structured",
719
+ whenIntent: "summarize",
720
+ reason: "Structured-output parse failures at 100% in-window on long-input summarize (measured on playbacksam, 2026-07-25..27; haiku only led when input size made price dominate, then failed every time)."
696
721
  }
697
722
  ],
698
723
  costInputPer1m: 1,
@@ -2939,11 +2964,18 @@ function passScoreTargets(ir, opts) {
2939
2964
  }
2940
2965
  }
2941
2966
  let qualityGatePenalty = 0;
2967
+ let structuredCliffGate;
2942
2968
  if (constraints.structuredOutput) {
2943
2969
  const schemaWeak = effectiveConventions(profile).some(
2944
2970
  (c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
2945
2971
  );
2946
2972
  if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
2973
+ if (!schemaWeak) {
2974
+ structuredCliffGate = profile.cliffs.find(
2975
+ (c) => c.action === "quality_gate_structured" && (!c.whenIntent || c.whenIntent === ir.intent.archetype) && c.metric === "input_tokens" && opts.estimatedInputTokens >= c.threshold
2976
+ );
2977
+ if (structuredCliffGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
2978
+ }
2947
2979
  }
2948
2980
  const measuredGate = opts.measuredFailureGates?.get(modelId);
2949
2981
  if (measuredGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
@@ -3008,6 +3040,16 @@ function passScoreTargets(ir, opts) {
3008
3040
  rankAfter: rank,
3009
3041
  description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' by MEASURED evidence from this app's own outcomes \u2014 ${measuredGate.nFail} of ${measuredGate.n} attempts failed on the quality axis in the trailing window (${pct(measuredGate.rate)}; 95% lower bound ${pct(measuredGate.lowerBound)} > 50%). Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. The gate is derived, not stored \u2014 it lifts on its own once the failures age out of the window.`
3010
3042
  });
3043
+ } else if (structuredCliffGate) {
3044
+ policyMutations.push({
3045
+ id: `quality-gate-structured-cliff-${modelId}`,
3046
+ source: "quality_gate",
3047
+ passName: "score_targets",
3048
+ rankDelta: -qualityGatePenalty,
3049
+ rankBefore,
3050
+ rankAfter: rank,
3051
+ description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' \u2014 declared structuredOutput + input_tokens \u2265 ${structuredCliffGate.threshold} trips a measured cliff: ${structuredCliffGate.reason} Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. Bundled knowledge \u2014 active on cold isolates with no brain.`
3052
+ });
3011
3053
  } else {
3012
3054
  policyMutations.push({
3013
3055
  id: `quality-gate-structured-${modelId}`,
@@ -3691,13 +3733,8 @@ function getArchetypePerfScore(modelId, archetype) {
3691
3733
  return { score, n, grounding };
3692
3734
  }
3693
3735
 
3694
- // src/promote-ready-brain.ts
3695
- function isRawPromoteReadyRow(x) {
3696
- if (!x || typeof x !== "object") return false;
3697
- const r = x;
3698
- return typeof r.intent_archetype === "string" && typeof r.family === "string" && typeof r.candidate_model === "string" && typeof r.current_model === "string" && typeof r.detected_at === "string";
3699
- }
3700
- function coerceNumber(v) {
3736
+ // src/measured-failure-brain.ts
3737
+ function coerceCount(v) {
3701
3738
  if (typeof v === "number") return Number.isFinite(v) ? v : null;
3702
3739
  if (typeof v === "string") {
3703
3740
  const n = Number(v);
@@ -3705,58 +3742,144 @@ function coerceNumber(v) {
3705
3742
  }
3706
3743
  return null;
3707
3744
  }
3708
- function mapRowsToFindings2(rows) {
3745
+ function isRawFailureRow(x) {
3746
+ if (!x || typeof x !== "object") return false;
3747
+ const r = x;
3748
+ return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
3749
+ }
3750
+ function mapRows(rows) {
3709
3751
  const out = [];
3710
3752
  for (const row of rows) {
3711
- if (!isRawPromoteReadyRow(row)) continue;
3712
- const sampleN = coerceNumber(row.sample_n);
3713
- const passRate = coerceNumber(row.judge_pass_rate);
3714
- const avgScore = coerceNumber(row.judge_avg_score);
3715
- if (sampleN === null || passRate === null || avgScore === null) continue;
3753
+ if (!isRawFailureRow(row)) continue;
3754
+ const n = coerceCount(row.n);
3755
+ const nFail = coerceCount(row.n_fail) ?? 0;
3756
+ if (n === null || n <= 0) continue;
3716
3757
  out.push({
3717
3758
  archetype: row.intent_archetype,
3718
- family: row.family,
3719
- candidateModel: row.candidate_model,
3720
- currentModel: row.current_model,
3721
- sampleN,
3722
- judgePassRate: passRate,
3723
- judgeAvgScore: avgScore,
3724
- costDeltaPct: coerceNumber(row.cost_delta_pct),
3725
- detectedAt: row.detected_at
3759
+ model: row.model,
3760
+ n,
3761
+ nFail
3726
3762
  });
3727
3763
  }
3728
3764
  return out;
3729
3765
  }
3766
+ var MEASURED_FAILURE_CFG = {
3767
+ /**
3768
+ * Hard minimum attempts before ANY gate may be created. Guards against
3769
+ * pathological tiny samples that the confidence bound alone would let
3770
+ * through in edge cases. At 5-for-5 the bound clears the threshold; at
3771
+ * 3-for-3 it does not, which is the behaviour we want (three failures is
3772
+ * a bad day, five in a row is a pattern).
3773
+ */
3774
+ minSample: 5,
3775
+ /**
3776
+ * Gate when we are 95% confident the model fails MORE OFTEN THAN IT
3777
+ * SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
3778
+ * a model that probably fails the majority of the time has no business
3779
+ * leading a surface, whatever its declared scores say.
3780
+ */
3781
+ lowerBoundThreshold: 0.5,
3782
+ /** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
3783
+ z: 1.96,
3784
+ /** Must match the view's window. Documented here for the advisory text. */
3785
+ windowDays: 28
3786
+ };
3787
+ function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
3788
+ if (n <= 0) return 0;
3789
+ const p = failures / n;
3790
+ const z2 = z * z;
3791
+ const denom = 1 + z2 / n;
3792
+ const centre = p + z2 / (2 * n);
3793
+ const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
3794
+ const lower2 = (centre - margin) / denom;
3795
+ return lower2 < 0 ? 0 : lower2;
3796
+ }
3797
+ function mapMeasuredFailureRows(rows) {
3798
+ return mapRows(rows);
3799
+ }
3800
+ function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
3801
+ if (!row) return void 0;
3802
+ const normalized = "nFail" in row && typeof row.n === "number" ? row : mapRows([row])[0];
3803
+ if (!normalized || normalized.n < cfg.minSample) return void 0;
3804
+ const lowerBound = wilsonLowerBound(normalized.nFail, normalized.n, cfg.z);
3805
+ return {
3806
+ gated: lowerBound > cfg.lowerBoundThreshold,
3807
+ rate: normalized.nFail / normalized.n,
3808
+ lowerBound,
3809
+ n: normalized.n,
3810
+ nFail: normalized.nFail
3811
+ };
3812
+ }
3730
3813
  var snapshots2 = /* @__PURE__ */ new Map();
3731
3814
  var runtime3;
3732
3815
  var warnedOnce2 = false;
3733
- function isPromoteReadyBrainActive() {
3816
+ var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
3817
+ function isMeasuredFailureGateEnabledFromEnv(envSource) {
3818
+ const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
3819
+ const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
3820
+ return !(raw === "0" || raw === "false");
3821
+ }
3822
+ function configureMeasuredFailureBrain(rt) {
3823
+ runtime3 = rt;
3824
+ snapshots2.clear();
3825
+ warnedOnce2 = false;
3826
+ }
3827
+ function isMeasuredFailureBrainActive() {
3734
3828
  return runtime3 !== void 0;
3735
3829
  }
3736
- function loadPromoteReadyFindings(opts) {
3830
+ function prefetchMeasuredFailure(appId) {
3737
3831
  const rt = runtime3;
3738
- if (!rt) return [];
3739
- const appId = opts.appId;
3740
- if (!appId) return [];
3832
+ if (!rt || !appId) return void 0;
3833
+ let snap = snapshots2.get(appId);
3834
+ if (!snap) {
3835
+ snap = { data: [], expiresAt: 0, refreshing: false };
3836
+ snapshots2.set(appId, snap);
3837
+ }
3838
+ if (snap.expiresAt > Date.now()) return void 0;
3839
+ const inflight = pendingRefreshes2.get(appId);
3840
+ if (inflight) return inflight;
3841
+ if (snap.refreshing) return void 0;
3842
+ snap.refreshing = true;
3843
+ void asyncRefresh3(rt, appId);
3844
+ return pendingRefreshes2.get(appId);
3845
+ }
3846
+ async function awaitMeasuredFailureReady(appId, timeoutMs) {
3847
+ if (!runtime3 || !appId) return;
3848
+ const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes2.get(appId);
3849
+ if (!(timeoutMs > 0)) return;
3850
+ if (!pending) return;
3851
+ let timer;
3852
+ try {
3853
+ await Promise.race([
3854
+ pending,
3855
+ new Promise((resolve) => {
3856
+ timer = setTimeout(resolve, timeoutMs);
3857
+ })
3858
+ ]);
3859
+ } catch {
3860
+ } finally {
3861
+ if (timer) clearTimeout(timer);
3862
+ }
3863
+ }
3864
+ function getMeasuredFailureVerdict(opts) {
3865
+ const rt = runtime3;
3866
+ if (!rt) return void 0;
3867
+ const { appId, archetype, model } = opts;
3868
+ if (!appId || !archetype || !model) return void 0;
3741
3869
  let snap = snapshots2.get(appId);
3742
3870
  if (!snap) {
3743
3871
  snap = { data: [], expiresAt: 0, refreshing: false };
3744
3872
  snapshots2.set(appId, snap);
3745
3873
  }
3746
3874
  const now = Date.now();
3747
- const stale = snap.expiresAt <= now;
3748
- if (stale && !snap.refreshing) {
3875
+ if (snap.expiresAt <= now && !snap.refreshing) {
3749
3876
  snap.refreshing = true;
3750
3877
  void asyncRefresh3(rt, appId);
3751
3878
  }
3752
- let rows = snap.data;
3753
- if (opts.archetype) {
3754
- rows = rows.filter((f) => f.archetype === opts.archetype);
3755
- }
3756
- if (opts.family) {
3757
- rows = rows.filter((f) => f.family === opts.family);
3758
- }
3759
- return rows;
3879
+ const row = snap.data.find(
3880
+ (r) => r.archetype === archetype && r.model === model
3881
+ );
3882
+ return judgeMeasuredFailure(row);
3760
3883
  }
3761
3884
  var pendingRefreshes2 = /* @__PURE__ */ new Map();
3762
3885
  async function asyncRefresh3(rt, appId) {
@@ -3780,12 +3903,11 @@ async function doRefresh3(rt, appId) {
3780
3903
  try {
3781
3904
  const res = await rt.fetchImpl(url, { method: "GET" });
3782
3905
  if (!res.ok) {
3783
- throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
3906
+ throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
3784
3907
  }
3785
3908
  const body = await res.json();
3786
3909
  if (runtime3 !== rt) return;
3787
- const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
3788
- snap.data = rows;
3910
+ snap.data = Array.isArray(body) ? mapRows(body) : [];
3789
3911
  snap.expiresAt = Date.now() + rt.ttlMs;
3790
3912
  snap.refreshing = false;
3791
3913
  } catch (err) {
@@ -3800,193 +3922,84 @@ async function doRefresh3(rt, appId) {
3800
3922
  }
3801
3923
  function defaultOnError3(err) {
3802
3924
  console.warn(
3803
- "[kgauto] promote-ready fetch failed (using empty fallback):",
3925
+ "[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
3804
3926
  err
3805
3927
  );
3806
3928
  }
3807
- function resolveFetchImpl(injected) {
3808
- return injected ?? ((...args) => globalThis.fetch(...args));
3809
- }
3810
- function normalizeEndpoint(endpoint) {
3811
- return endpoint.replace(/\/+$/, "");
3929
+ function _testResetMeasuredFailure() {
3930
+ runtime3 = void 0;
3931
+ snapshots2.clear();
3932
+ pendingRefreshes2 = /* @__PURE__ */ new Map();
3933
+ warnedOnce2 = false;
3812
3934
  }
3813
- async function markPromoteReadyHandled(opts) {
3814
- const {
3815
- appId,
3816
- archetype,
3817
- family,
3818
- resolution,
3819
- resolutionNote,
3820
- brainEndpoint,
3821
- brainJwt,
3822
- brainAnonKey,
3823
- fetch: injectedFetch
3824
- } = opts;
3825
- if (!appId) return { ok: false, reason: "app_id_required" };
3826
- if (!archetype) return { ok: false, reason: "archetype_required" };
3827
- if (!family) return { ok: false, reason: "family_required" };
3828
- if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
3829
- return { ok: false, reason: "resolution_invalid" };
3830
- }
3831
- const doFetch = resolveFetchImpl(injectedFetch);
3832
- const base = normalizeEndpoint(brainEndpoint);
3833
- const url = `${base}/rest/v1/promote_ready_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&family=eq.${encodeURIComponent(family)}&resolved_at=is.null`;
3834
- const patchBody = {
3835
- resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
3836
- resolution
3837
- };
3838
- if (resolutionNote !== void 0) {
3839
- patchBody.resolution_note = resolutionNote;
3840
- }
3841
- let res;
3842
- try {
3843
- res = await doFetch(url, {
3844
- method: "PATCH",
3845
- headers: {
3846
- Authorization: `Bearer ${brainJwt}`,
3847
- apikey: brainAnonKey,
3848
- "Content-Type": "application/json",
3849
- Accept: "application/json",
3850
- Prefer: "return=minimal"
3851
- },
3852
- body: JSON.stringify(patchBody)
3853
- });
3854
- } catch (err) {
3855
- const msg = err instanceof Error ? err.message : String(err);
3856
- return { ok: false, reason: `network_error:${msg}` };
3857
- }
3858
- if (res.status === 401 || res.status === 403) {
3859
- return { ok: false, reason: "brain_auth_misconfig" };
3860
- }
3861
- if (res.status >= 500) {
3862
- return { ok: false, reason: "brain_unavailable" };
3863
- }
3864
- if (!res.ok) {
3865
- return { ok: false, reason: `patch_failed:${res.status}` };
3866
- }
3867
- return { ok: true };
3935
+ async function _testWaitForMeasuredFailureRefresh() {
3936
+ const pending = Array.from(pendingRefreshes2.values());
3937
+ if (pending.length > 0) await Promise.all(pending);
3868
3938
  }
3869
3939
 
3870
- // src/advisor-rules/promote-ready.ts
3871
- var PROMOTE_READY_THRESHOLDS = {
3872
- minPassRate: 0.8,
3873
- minAvgScore: 4
3874
- };
3875
- function shouldFirePromoteReady(finding, resolvedPrimary) {
3876
- if (finding.currentModel !== resolvedPrimary) return false;
3877
- if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
3878
- if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
3879
- return true;
3940
+ // src/promotions-brain.ts
3941
+ function isRawPromotionRow(x) {
3942
+ if (!x || typeof x !== "object") return false;
3943
+ const r = x;
3944
+ return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
3880
3945
  }
3881
- function deriveFamilyLocal(modelId) {
3882
- if (modelId.startsWith("claude-opus-")) return "claude-opus";
3883
- if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
3884
- if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
3885
- if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
3886
- if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
3887
- if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
3888
- if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
3889
- if (modelId.startsWith("deepseek-")) return "deepseek-chat";
3890
- if (modelId.startsWith("gpt-")) return "openai-gpt";
3946
+ function coerceId(v) {
3947
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
3948
+ if (typeof v === "string") {
3949
+ const n = Number(v);
3950
+ return Number.isFinite(n) ? n : null;
3951
+ }
3891
3952
  return null;
3892
3953
  }
3893
- function advisorRulePromoteReady(ctx) {
3894
- if (!isPromoteReadyBrainActive()) return [];
3895
- if (!ctx.appId) return [];
3896
- if (!ctx.resolvedPrimary) return [];
3897
- const family = deriveFamilyLocal(ctx.resolvedPrimary);
3898
- if (!family) return [];
3899
- const findings = loadPromoteReadyFindings({
3900
- appId: ctx.appId,
3901
- archetype: ctx.archetype,
3902
- family
3903
- });
3904
- if (findings.length === 0) return [];
3905
- const qualifying = findings.filter(
3906
- (f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
3907
- );
3908
- if (qualifying.length === 0) return [];
3909
- qualifying.sort((a, b) => {
3910
- if (a.judgeAvgScore !== b.judgeAvgScore) {
3911
- return b.judgeAvgScore - a.judgeAvgScore;
3912
- }
3913
- return b.judgePassRate - a.judgePassRate;
3914
- });
3915
- const top = qualifying[0];
3916
- const pctPass = Math.round(top.judgePassRate * 100);
3917
- const score = top.judgeAvgScore.toFixed(2);
3918
- let costClause = "";
3919
- if (top.costDeltaPct !== null) {
3920
- const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
3921
- const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
3922
- costClause = `, cost ${magnitude}% ${sign}`;
3923
- }
3924
- const message = `Probe found ${top.candidateModel} produces equivalent-or-better outputs vs ${top.currentModel} on ${top.sampleN} recent ${top.archetype} prompts (pass rate ${pctPass}%, avg score ${score}/5${costClause}). Consider promoting via markPromoteReadyHandled.`;
3925
- return [
3926
- {
3927
- level: "info",
3928
- code: "promote-ready",
3929
- message,
3930
- suggestion: `Migrate ${top.archetype} traffic from ${top.currentModel} to ${top.candidateModel}, then call markPromoteReadyHandled({ appId, archetype: '${top.archetype}', family: '${top.family}', resolution: 'promoted' }) to silence this advisory.`,
3931
- // alpha.36 architectural field — not a no-ai-needed case.
3932
- recommendedArchitecture: void 0,
3933
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
3934
- }
3935
- ];
3936
- }
3937
-
3938
- // src/advisor-rules/consumer-on-stale-model.ts
3939
- function isStaleStatus(v) {
3940
- return v === "legacy" || v === "deprecated";
3941
- }
3942
- function asString(v) {
3943
- return typeof v === "string" && v.length > 0 ? v : void 0;
3944
- }
3945
- function mapRowsToFindings3(rows) {
3954
+ function mapRowsToPromotions(rows) {
3946
3955
  const out = [];
3947
- for (const raw of rows) {
3948
- if (!raw || typeof raw !== "object") continue;
3949
- const r = raw;
3950
- const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
3951
- const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
3952
- const staleProvider = asString(r.stale_provider);
3953
- const recommendedModel = asString(r.recommended_model);
3954
- const family = asString(r.family);
3955
- const message = asString(r.message);
3956
- if (!archetype || !staleModel || !recommendedModel || !family || !message) {
3957
- continue;
3958
- }
3959
- if (!isStaleStatus(r.stale_status)) continue;
3960
- const row = {
3961
- archetype,
3962
- staleModel,
3963
- staleProvider: staleProvider ?? "unknown",
3964
- staleStatus: r.stale_status,
3965
- recommendedModel,
3966
- family,
3967
- message
3968
- };
3969
- const suggestion = asString(r.suggestion);
3970
- if (suggestion) row.suggestion = suggestion;
3971
- if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
3972
- row.observationCount = r.observation_count;
3973
- }
3974
- out.push(row);
3956
+ for (const row of rows) {
3957
+ if (!isRawPromotionRow(row)) continue;
3958
+ const id = coerceId(row.id);
3959
+ if (id === null) continue;
3960
+ const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
3961
+ if (mode === null) continue;
3962
+ out.push({
3963
+ id,
3964
+ archetype: row.intent_archetype,
3965
+ mode,
3966
+ strategy: typeof row.strategy === "string" ? row.strategy : null,
3967
+ promotedModel: row.promoted_model,
3968
+ incumbentModel: row.incumbent_model,
3969
+ evalRunId: coerceId(row.eval_run_id ?? null),
3970
+ suppressQualityGate: row.suppress_quality_gate === true,
3971
+ promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : "",
3972
+ // Pre-.78 endpoints serve no status column and only active rows —
3973
+ // defaulting to 'active' is exact, not optimistic.
3974
+ status: row.status === "rolled_back" ? "rolled_back" : "active",
3975
+ ...typeof row.rolled_back_at === "string" ? { rolledBackAt: row.rolled_back_at } : {},
3976
+ ...typeof row.rollback_class === "string" ? { rollbackClass: row.rollback_class } : {}
3977
+ });
3975
3978
  }
3976
3979
  return out;
3977
3980
  }
3978
3981
  var snapshots3 = /* @__PURE__ */ new Map();
3979
3982
  var runtime4;
3980
3983
  var warnedOnce3 = false;
3981
- var pendingRefreshes3 = /* @__PURE__ */ new Map();
3982
- function isStaleModelFindingsBrainActive() {
3984
+ var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
3985
+ function isAutoPromoteEnabledFromEnv(envSource) {
3986
+ const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
3987
+ const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
3988
+ return raw === "1" || raw === "true";
3989
+ }
3990
+ function configurePromotionsBrain(rt) {
3991
+ runtime4 = rt;
3992
+ snapshots3.clear();
3993
+ warnedOnce3 = false;
3994
+ }
3995
+ function isPromotionsBrainActive() {
3983
3996
  return runtime4 !== void 0;
3984
3997
  }
3985
- function getStaleModelFindings(opts) {
3998
+ function getApplicablePromotion(opts) {
3986
3999
  const rt = runtime4;
3987
- if (!rt) return [];
4000
+ if (!rt) return void 0;
3988
4001
  const appId = opts.appId;
3989
- if (!appId) return [];
4002
+ if (!appId || !opts.archetype || !opts.mode) return void 0;
3990
4003
  let snap = snapshots3.get(appId);
3991
4004
  if (!snap) {
3992
4005
  snap = { data: [], expiresAt: 0, refreshing: false };
@@ -3998,11 +4011,31 @@ function getStaleModelFindings(opts) {
3998
4011
  snap.refreshing = true;
3999
4012
  void asyncRefresh4(rt, appId);
4000
4013
  }
4001
- if (opts.archetype) {
4002
- return snap.data.filter((f) => f.archetype === opts.archetype);
4014
+ return snap.data.find(
4015
+ (p) => p.status === "active" && p.archetype === opts.archetype && p.mode === opts.mode
4016
+ );
4017
+ }
4018
+ var ROLLBACK_SUPPRESSION_WINDOW_DAYS = 28;
4019
+ function getRecentRollback(opts) {
4020
+ const rt = runtime4;
4021
+ if (!rt) return void 0;
4022
+ if (!opts.appId || !opts.archetype || !opts.model) return void 0;
4023
+ let snap = snapshots3.get(opts.appId);
4024
+ if (!snap) {
4025
+ snap = { data: [], expiresAt: 0, refreshing: false };
4026
+ snapshots3.set(opts.appId, snap);
4003
4027
  }
4004
- return snap.data;
4028
+ const now = opts.nowMs ?? Date.now();
4029
+ if (snap.expiresAt <= now && !snap.refreshing) {
4030
+ snap.refreshing = true;
4031
+ void asyncRefresh4(rt, opts.appId);
4032
+ }
4033
+ const windowMs = (opts.windowDays ?? ROLLBACK_SUPPRESSION_WINDOW_DAYS) * 864e5;
4034
+ return snap.data.find(
4035
+ (p) => p.status === "rolled_back" && p.archetype === opts.archetype && p.promotedModel === opts.model && typeof p.rolledBackAt === "string" && now - Date.parse(p.rolledBackAt) <= windowMs
4036
+ );
4005
4037
  }
4038
+ var pendingRefreshes3 = /* @__PURE__ */ new Map();
4006
4039
  async function asyncRefresh4(rt, appId) {
4007
4040
  const promise = doRefresh4(rt, appId);
4008
4041
  pendingRefreshes3.set(appId, promise);
@@ -4015,7 +4048,7 @@ async function asyncRefresh4(rt, appId) {
4015
4048
  }
4016
4049
  }
4017
4050
  async function doRefresh4(rt, appId) {
4018
- const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
4051
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}&with_rollbacks=1`;
4019
4052
  let snap = snapshots3.get(appId);
4020
4053
  if (!snap) {
4021
4054
  snap = { data: [], expiresAt: 0, refreshing: false };
@@ -4024,11 +4057,11 @@ async function doRefresh4(rt, appId) {
4024
4057
  try {
4025
4058
  const res = await rt.fetchImpl(url, { method: "GET" });
4026
4059
  if (!res.ok) {
4027
- throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
4060
+ throw new Error(`promotions ${res.status}: ${res.statusText}`);
4028
4061
  }
4029
4062
  const body = await res.json();
4030
4063
  if (runtime4 !== rt) return;
4031
- const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
4064
+ const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
4032
4065
  snap.data = rows;
4033
4066
  snap.expiresAt = Date.now() + rt.ttlMs;
4034
4067
  snap.refreshing = false;
@@ -4044,847 +4077,905 @@ async function doRefresh4(rt, appId) {
4044
4077
  }
4045
4078
  function defaultOnError4(err) {
4046
4079
  console.warn(
4047
- "[kgauto] stale-model findings fetch failed (using empty fallback):",
4080
+ "[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
4048
4081
  err
4049
4082
  );
4050
4083
  }
4051
- var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
4052
- function advisorRuleConsumerOnStaleModel(ir) {
4053
- if (!isStaleModelFindingsBrainActive()) return [];
4054
- if (!ir.appId) return [];
4055
- const findings = getStaleModelFindings({
4056
- appId: ir.appId,
4057
- archetype: ir.intent.archetype
4058
- });
4059
- if (findings.length === 0) return [];
4060
- const ranked = [...findings].sort((a, b) => {
4061
- if (a.staleStatus !== b.staleStatus) {
4062
- return a.staleStatus === "deprecated" ? -1 : 1;
4063
- }
4064
- return a.staleModel.localeCompare(b.staleModel);
4065
- });
4066
- const top = ranked[0];
4067
- const extraCount = findings.length - 1;
4068
- const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
4069
- return [
4070
- {
4071
- level: "warn",
4072
- code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
4073
- message: `${top.message}${extraNote}`,
4074
- suggestion: top.suggestion ?? `Migrate ${top.staleModel} \u2192 ${top.recommendedModel} for archetype "${top.archetype}". The newer model is the current latest in the "${top.family}" family; the stale one is ${top.staleStatus}.`,
4075
- recommendationType: "model-swap",
4076
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4077
- }
4078
- ];
4084
+ function _testResetPromotions() {
4085
+ runtime4 = void 0;
4086
+ snapshots3.clear();
4087
+ pendingRefreshes3 = /* @__PURE__ */ new Map();
4088
+ warnedOnce3 = false;
4089
+ }
4090
+ async function _testWaitForPromotionsRefresh() {
4091
+ const pending = Array.from(pendingRefreshes3.values());
4092
+ if (pending.length > 0) await Promise.all(pending);
4079
4093
  }
4080
4094
 
4081
- // src/archetype-fits.ts
4082
- var ARCHETYPE_FAMILY_FITS = Object.freeze([
4083
- {
4084
- archetype: "plan",
4085
- betterFitFamily: "deepseek-reasoner",
4086
- reason: "Plan archetype is reasoning-shaped (multi-step chains, hypothesis-and-check, sub-goal decomposition) \u2014 exactly where reasoner-family models excel. Sonnet/Opus produce plans but at higher cost; reasoners produce equivalent-or-better plans at 7-17x lower cost at current promo pricing (deepseek-v4-pro $0.435/$0.87 per 1M promo through 2026-05-31 vs sonnet $3/$15).",
4087
- costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
4088
- },
4089
- {
4090
- archetype: "critique",
4091
- betterFitFamily: "deepseek-reasoner",
4092
- reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
4093
- costGuidance: "comparable or cheaper at current pricing"
4094
- }
4095
- ]);
4096
- function findBetterFit(archetype, currentFamily) {
4097
- for (const fit of ARCHETYPE_FAMILY_FITS) {
4098
- if (fit.archetype !== archetype) continue;
4099
- if (fit.betterFitFamily === currentFamily) return null;
4100
- return fit;
4095
+ // src/promote-ready-brain.ts
4096
+ function isRawPromoteReadyRow(x) {
4097
+ if (!x || typeof x !== "object") return false;
4098
+ const r = x;
4099
+ return typeof r.intent_archetype === "string" && typeof r.family === "string" && typeof r.candidate_model === "string" && typeof r.current_model === "string" && typeof r.detected_at === "string";
4100
+ }
4101
+ function coerceNumber(v) {
4102
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
4103
+ if (typeof v === "string") {
4104
+ const n = Number(v);
4105
+ return Number.isFinite(n) ? n : null;
4101
4106
  }
4102
4107
  return null;
4103
4108
  }
4104
-
4105
- // src/advisor-rules/cross-family-fit.ts
4106
- function familyHasCurrentActiveModel(family) {
4107
- for (const profile of allProfiles()) {
4108
- const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
4109
- if (profileFamily !== family) continue;
4110
- if (profile.status !== "current") continue;
4111
- if (profile.active === false) continue;
4112
- return true;
4109
+ function mapRowsToFindings2(rows) {
4110
+ const out = [];
4111
+ for (const row of rows) {
4112
+ if (!isRawPromoteReadyRow(row)) continue;
4113
+ const sampleN = coerceNumber(row.sample_n);
4114
+ const passRate = coerceNumber(row.judge_pass_rate);
4115
+ const avgScore = coerceNumber(row.judge_avg_score);
4116
+ if (sampleN === null || passRate === null || avgScore === null) continue;
4117
+ out.push({
4118
+ archetype: row.intent_archetype,
4119
+ family: row.family,
4120
+ candidateModel: row.candidate_model,
4121
+ currentModel: row.current_model,
4122
+ sampleN,
4123
+ judgePassRate: passRate,
4124
+ judgeAvgScore: avgScore,
4125
+ costDeltaPct: coerceNumber(row.cost_delta_pct),
4126
+ detectedAt: row.detected_at
4127
+ });
4113
4128
  }
4114
- return false;
4129
+ return out;
4115
4130
  }
4116
- function listCandidatesInFamily(family) {
4117
- const candidates = [];
4118
- for (const profile of allProfiles()) {
4119
- const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
4120
- if (profileFamily !== family) continue;
4121
- if (profile.status !== "current") continue;
4122
- if (profile.active === false) continue;
4123
- candidates.push(profile.id);
4124
- if (candidates.length >= 3) break;
4125
- }
4126
- return candidates;
4131
+ var snapshots4 = /* @__PURE__ */ new Map();
4132
+ var runtime5;
4133
+ var warnedOnce4 = false;
4134
+ function isPromoteReadyBrainActive() {
4135
+ return runtime5 !== void 0;
4127
4136
  }
4128
- function advisorRuleCrossFamilyFit(ctx) {
4129
- if (!ctx.resolvedPrimary) return [];
4130
- const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
4131
- if (!currentFamily) return [];
4132
- const fit = findBetterFit(ctx.archetype, currentFamily);
4133
- if (!fit) return [];
4134
- if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
4135
- const candidates = listCandidatesInFamily(fit.betterFitFamily);
4136
- if (candidates.length === 0) return [];
4137
- const candidateStr = candidates.join(", ");
4138
- const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
4139
- return [
4140
- {
4141
- level: "info",
4142
- code: "cross-family-fit-candidate",
4143
- ownership: "consumer-actionable",
4144
- message,
4145
- suggestion: `Swap the model literal in \`ir.models\` to one of: ${candidateStr}. Or call \`getRecommendedPrimary({ family: '${fit.betterFitFamily}', archetype: '${ctx.archetype}', fallback: { id: '${candidates[0]}', reason: 'cross-family-fit-recommendation' } })\` to let kgauto resolve to the current+active family member.`,
4146
- recommendationType: "model-swap",
4147
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4137
+ function loadPromoteReadyFindings(opts) {
4138
+ const rt = runtime5;
4139
+ if (!rt) return [];
4140
+ const appId = opts.appId;
4141
+ if (!appId) return [];
4142
+ let snap = snapshots4.get(appId);
4143
+ if (!snap) {
4144
+ snap = { data: [], expiresAt: 0, refreshing: false };
4145
+ snapshots4.set(appId, snap);
4146
+ }
4147
+ const now = Date.now();
4148
+ const stale = snap.expiresAt <= now;
4149
+ if (stale && !snap.refreshing) {
4150
+ snap.refreshing = true;
4151
+ void asyncRefresh5(rt, appId);
4152
+ }
4153
+ let rows = snap.data;
4154
+ if (opts.archetype) {
4155
+ rows = rows.filter((f) => f.archetype === opts.archetype);
4156
+ }
4157
+ if (opts.family) {
4158
+ rows = rows.filter((f) => f.family === opts.family);
4159
+ }
4160
+ return rows;
4161
+ }
4162
+ var pendingRefreshes4 = /* @__PURE__ */ new Map();
4163
+ async function asyncRefresh5(rt, appId) {
4164
+ const promise = doRefresh5(rt, appId);
4165
+ pendingRefreshes4.set(appId, promise);
4166
+ try {
4167
+ await promise;
4168
+ } finally {
4169
+ if (pendingRefreshes4.get(appId) === promise) {
4170
+ pendingRefreshes4.delete(appId);
4148
4171
  }
4149
- ];
4172
+ }
4150
4173
  }
4151
-
4152
- // src/advisor.ts
4153
- var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
4154
- var TIER_DOWN_COST_RATIO = 0.5;
4155
- var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
4156
- var PRODUCER_OWNED_RULE_CODES = Object.freeze(
4157
- /* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
4158
- );
4159
- function deriveOwnership(code, selfDeclared) {
4160
- if (selfDeclared) return selfDeclared;
4161
- return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
4174
+ async function doRefresh5(rt, appId) {
4175
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
4176
+ let snap = snapshots4.get(appId);
4177
+ if (!snap) {
4178
+ snap = { data: [], expiresAt: 0, refreshing: false };
4179
+ snapshots4.set(appId, snap);
4180
+ }
4181
+ try {
4182
+ const res = await rt.fetchImpl(url, { method: "GET" });
4183
+ if (!res.ok) {
4184
+ throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
4185
+ }
4186
+ const body = await res.json();
4187
+ if (runtime5 !== rt) return;
4188
+ const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
4189
+ snap.data = rows;
4190
+ snap.expiresAt = Date.now() + rt.ttlMs;
4191
+ snap.refreshing = false;
4192
+ } catch (err) {
4193
+ if (runtime5 !== rt) return;
4194
+ snap.refreshing = false;
4195
+ snap.expiresAt = Date.now() + rt.ttlMs;
4196
+ if (!warnedOnce4) {
4197
+ warnedOnce4 = true;
4198
+ (rt.onError ?? defaultOnError5)(err);
4199
+ }
4200
+ }
4162
4201
  }
4163
- function runAdvisor(ir, result, profile, policy, phase2) {
4164
- const out = [];
4165
- out.push(...detectCachingOff(ir, profile));
4166
- out.push(...detectSingleChunkSystem(ir, profile));
4167
- out.push(...detectToolBloat(ir, result));
4168
- out.push(...detectHistoryUncached(ir, profile));
4169
- out.push(...detectSingleModelArray(ir, policy));
4170
- if (policy?.posture !== "locked") {
4171
- out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
4172
- out.push(...detectModelStaleEvidence(ir, profile));
4173
- out.push(...detectTierDown(ir, profile, phase2));
4202
+ function defaultOnError5(err) {
4203
+ console.warn(
4204
+ "[kgauto] promote-ready fetch failed (using empty fallback):",
4205
+ err
4206
+ );
4207
+ }
4208
+ function resolveFetchImpl(injected) {
4209
+ return injected ?? ((...args) => globalThis.fetch(...args));
4210
+ }
4211
+ function normalizeEndpoint(endpoint) {
4212
+ return endpoint.replace(/\/+$/, "");
4213
+ }
4214
+ async function markPromoteReadyHandled(opts) {
4215
+ const {
4216
+ appId,
4217
+ archetype,
4218
+ family,
4219
+ resolution,
4220
+ resolutionNote,
4221
+ brainEndpoint,
4222
+ brainJwt,
4223
+ brainAnonKey,
4224
+ fetch: injectedFetch
4225
+ } = opts;
4226
+ if (!appId) return { ok: false, reason: "app_id_required" };
4227
+ if (!archetype) return { ok: false, reason: "archetype_required" };
4228
+ if (!family) return { ok: false, reason: "family_required" };
4229
+ if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
4230
+ return { ok: false, reason: "resolution_invalid" };
4174
4231
  }
4175
- if (!translatorClearedToolCallCliff(phase2)) {
4176
- out.push(...detectArchetypePerfFloorBreach(ir, profile));
4232
+ const doFetch = resolveFetchImpl(injectedFetch);
4233
+ const base = normalizeEndpoint(brainEndpoint);
4234
+ const url = `${base}/rest/v1/promote_ready_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&family=eq.${encodeURIComponent(family)}&resolved_at=is.null`;
4235
+ const patchBody = {
4236
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
4237
+ resolution
4238
+ };
4239
+ if (resolutionNote !== void 0) {
4240
+ patchBody.resolution_note = resolutionNote;
4177
4241
  }
4178
- if (policy?.posture !== "locked") {
4179
- out.push(...detectStaleExclusionCandidate(ir));
4242
+ let res;
4243
+ try {
4244
+ res = await doFetch(url, {
4245
+ method: "PATCH",
4246
+ headers: {
4247
+ Authorization: `Bearer ${brainJwt}`,
4248
+ apikey: brainAnonKey,
4249
+ "Content-Type": "application/json",
4250
+ Accept: "application/json",
4251
+ Prefer: "return=minimal"
4252
+ },
4253
+ body: JSON.stringify(patchBody)
4254
+ });
4255
+ } catch (err) {
4256
+ const msg = err instanceof Error ? err.message : String(err);
4257
+ return { ok: false, reason: `network_error:${msg}` };
4180
4258
  }
4181
- if (policy?.posture !== "locked" && ir.appId) {
4182
- out.push(
4183
- ...advisorRulePromoteReady({
4184
- appId: ir.appId,
4185
- archetype: ir.intent.archetype,
4186
- resolvedPrimary: profile.id
4187
- })
4188
- );
4189
- out.push(...advisorRuleConsumerOnStaleModel(ir));
4259
+ if (res.status === 401 || res.status === 403) {
4260
+ return { ok: false, reason: "brain_auth_misconfig" };
4190
4261
  }
4191
- if (policy?.posture !== "locked") {
4192
- out.push(
4193
- ...advisorRuleCrossFamilyFit({
4194
- archetype: ir.intent.archetype,
4195
- resolvedPrimary: profile.id
4196
- })
4197
- );
4262
+ if (res.status >= 500) {
4263
+ return { ok: false, reason: "brain_unavailable" };
4198
4264
  }
4199
- return out;
4200
- }
4201
- function translatorClearedToolCallCliff(phase2) {
4202
- const rewrites = phase2?.sectionRewritesApplied;
4203
- if (!rewrites || rewrites.length === 0) return false;
4204
- for (const rw of rewrites) {
4205
- if (rw.kind === "tool_call_contract") return true;
4265
+ if (!res.ok) {
4266
+ return { ok: false, reason: `patch_failed:${res.status}` };
4206
4267
  }
4207
- return false;
4268
+ return { ok: true };
4208
4269
  }
4209
- function detectCachingOff(ir, profile) {
4210
- if (profile.provider !== "anthropic") return [];
4211
- const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
4212
- if (totalChars < 2e3) return [];
4213
- const anyCacheable = ir.sections.some((s) => s.cacheable === true);
4214
- if (anyCacheable) return [];
4270
+
4271
+ // src/advisor-rules/promote-ready.ts
4272
+ var PROMOTE_READY_THRESHOLDS = {
4273
+ minPassRate: 0.8,
4274
+ minAvgScore: 4
4275
+ };
4276
+ function shouldFirePromoteReady(finding, resolvedPrimary) {
4277
+ if (finding.currentModel !== resolvedPrimary) return false;
4278
+ if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
4279
+ if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
4280
+ return true;
4281
+ }
4282
+ function deriveFamilyLocal(modelId) {
4283
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
4284
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
4285
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
4286
+ if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
4287
+ if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
4288
+ if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
4289
+ if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
4290
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
4291
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
4292
+ return null;
4293
+ }
4294
+ function advisorRulePromoteReady(ctx) {
4295
+ if (!isPromoteReadyBrainActive()) return [];
4296
+ if (!ctx.appId) return [];
4297
+ if (!ctx.resolvedPrimary) return [];
4298
+ const family = deriveFamilyLocal(ctx.resolvedPrimary);
4299
+ if (!family) return [];
4300
+ const findings = loadPromoteReadyFindings({
4301
+ appId: ctx.appId,
4302
+ archetype: ctx.archetype,
4303
+ family
4304
+ });
4305
+ if (findings.length === 0) return [];
4306
+ const qualifying = findings.filter(
4307
+ (f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
4308
+ );
4309
+ if (qualifying.length === 0) return [];
4310
+ qualifying.sort((a, b) => {
4311
+ if (a.judgeAvgScore !== b.judgeAvgScore) {
4312
+ return b.judgeAvgScore - a.judgeAvgScore;
4313
+ }
4314
+ return b.judgePassRate - a.judgePassRate;
4315
+ });
4316
+ const top = qualifying[0];
4317
+ const pctPass = Math.round(top.judgePassRate * 100);
4318
+ const score = top.judgeAvgScore.toFixed(2);
4319
+ let costClause = "";
4320
+ if (top.costDeltaPct !== null) {
4321
+ const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
4322
+ const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
4323
+ costClause = `, cost ${magnitude}% ${sign}`;
4324
+ }
4325
+ const message = `Probe found ${top.candidateModel} produces equivalent-or-better outputs vs ${top.currentModel} on ${top.sampleN} recent ${top.archetype} prompts (pass rate ${pctPass}%, avg score ${score}/5${costClause}). Consider promoting via markPromoteReadyHandled.`;
4215
4326
  return [
4216
4327
  {
4217
- level: "warn",
4218
- code: "caching-off-on-claude",
4219
- message: `System prompt is ${totalChars} chars on Anthropic but no PromptSection has cacheable=true. Anthropic prompt caching cuts cached-prefix input cost by ~90% on subsequent calls; without it, every turn re-pays full price for the static system context.`,
4220
- suggestion: "Mark stable system sections (role, persona, tool policy) with `cacheable: true`. The lowering pass concatenates cacheable sections into a single cache-controlled block before the dynamic ones.",
4328
+ level: "info",
4329
+ code: "promote-ready",
4330
+ message,
4331
+ suggestion: `Migrate ${top.archetype} traffic from ${top.currentModel} to ${top.candidateModel}, then call markPromoteReadyHandled({ appId, archetype: '${top.archetype}', family: '${top.family}', resolution: 'promoted' }) to silence this advisory.`,
4332
+ // alpha.36 architectural field — not a no-ai-needed case.
4333
+ recommendedArchitecture: void 0,
4221
4334
  docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4222
4335
  }
4223
4336
  ];
4224
4337
  }
4225
- function detectSingleChunkSystem(ir, profile) {
4226
- if (profile.provider !== "anthropic") return [];
4227
- if (ir.sections.length !== 1) return [];
4228
- const only = ir.sections[0];
4229
- if (!only || only.text.length <= 1e3) return [];
4230
- return [
4231
- {
4232
- level: "info",
4233
- code: "single-chunk-system",
4234
- message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
4235
- suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't.",
4236
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4237
- }
4238
- ];
4338
+
4339
+ // src/advisor-rules/consumer-on-stale-model.ts
4340
+ function isStaleStatus(v) {
4341
+ return v === "legacy" || v === "deprecated";
4239
4342
  }
4240
- function detectToolBloat(ir, result) {
4241
- const SHORT_OUTPUT = /* @__PURE__ */ new Set([
4242
- "classify",
4243
- "extract",
4244
- "summarize",
4245
- "transform",
4246
- "critique"
4247
- ]);
4248
- if (!ir.tools || ir.tools.length === 0) return [];
4249
- const toolsKept = result.diagnostics.toolsKept;
4250
- if (toolsKept <= 10) return [];
4251
- if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
4252
- return [
4253
- {
4254
- level: "warn",
4255
- code: "tool-bloat",
4256
- message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
4257
- suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
4258
- docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
4343
+ function asString(v) {
4344
+ return typeof v === "string" && v.length > 0 ? v : void 0;
4345
+ }
4346
+ function mapRowsToFindings3(rows) {
4347
+ const out = [];
4348
+ for (const raw of rows) {
4349
+ if (!raw || typeof raw !== "object") continue;
4350
+ const r = raw;
4351
+ const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
4352
+ const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
4353
+ const staleProvider = asString(r.stale_provider);
4354
+ const recommendedModel = asString(r.recommended_model);
4355
+ const family = asString(r.family);
4356
+ const message = asString(r.message);
4357
+ if (!archetype || !staleModel || !recommendedModel || !family || !message) {
4358
+ continue;
4259
4359
  }
4260
- ];
4360
+ if (!isStaleStatus(r.stale_status)) continue;
4361
+ const row = {
4362
+ archetype,
4363
+ staleModel,
4364
+ staleProvider: staleProvider ?? "unknown",
4365
+ staleStatus: r.stale_status,
4366
+ recommendedModel,
4367
+ family,
4368
+ message
4369
+ };
4370
+ const suggestion = asString(r.suggestion);
4371
+ if (suggestion) row.suggestion = suggestion;
4372
+ if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
4373
+ row.observationCount = r.observation_count;
4374
+ }
4375
+ out.push(row);
4376
+ }
4377
+ return out;
4261
4378
  }
4262
- function detectHistoryUncached(ir, profile) {
4263
- if (profile.provider !== "anthropic") return [];
4264
- if (!ir.history || ir.history.length < 2) return [];
4265
- if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
4266
- return [];
4379
+ var snapshots5 = /* @__PURE__ */ new Map();
4380
+ var runtime6;
4381
+ var warnedOnce5 = false;
4382
+ var pendingRefreshes5 = /* @__PURE__ */ new Map();
4383
+ function isStaleModelFindingsBrainActive() {
4384
+ return runtime6 !== void 0;
4385
+ }
4386
+ function getStaleModelFindings(opts) {
4387
+ const rt = runtime6;
4388
+ if (!rt) return [];
4389
+ const appId = opts.appId;
4390
+ if (!appId) return [];
4391
+ let snap = snapshots5.get(appId);
4392
+ if (!snap) {
4393
+ snap = { data: [], expiresAt: 0, refreshing: false };
4394
+ snapshots5.set(appId, snap);
4267
4395
  }
4268
- return [
4269
- {
4270
- level: "warn",
4271
- code: "history-uncached-on-claude",
4272
- message: `${ir.history.length} history messages on Anthropic with no historyCachePolicy. Every turn re-pays for the full conversation context; with caching, subsequent turns hit the cache at ~10% the input cost.`,
4273
- suggestion: "Set `historyCachePolicy: { strategy: 'all-but-latest' }` on this IR. The lowering pass marks the message immediately preceding currentTurn with cache_control; subsequent turns whose history prefix matches byte-for-byte hit the cache.",
4274
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4275
- }
4276
- ];
4396
+ const now = Date.now();
4397
+ const stale = snap.expiresAt <= now;
4398
+ if (stale && !snap.refreshing) {
4399
+ snap.refreshing = true;
4400
+ void asyncRefresh6(rt, appId);
4401
+ }
4402
+ if (opts.archetype) {
4403
+ return snap.data.filter((f) => f.archetype === opts.archetype);
4404
+ }
4405
+ return snap.data;
4277
4406
  }
4278
- function detectSingleModelArray(ir, policy) {
4279
- if (ir.models.length !== 1) return [];
4280
- if (policy?.posture === "locked") return [];
4281
- const only = ir.models[0];
4282
- return [
4283
- {
4284
- level: "warn",
4285
- code: "single-model-array",
4286
- message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. Master plan \xA71.2 closes the reliability gap with a 2-step minimum.`,
4287
- suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
4288
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
4407
+ async function asyncRefresh6(rt, appId) {
4408
+ const promise = doRefresh6(rt, appId);
4409
+ pendingRefreshes5.set(appId, promise);
4410
+ try {
4411
+ await promise;
4412
+ } finally {
4413
+ if (pendingRefreshes5.get(appId) === promise) {
4414
+ pendingRefreshes5.delete(appId);
4289
4415
  }
4290
- ];
4416
+ }
4291
4417
  }
4292
- function detectCostMismatchedArchetype(ir, profile, phase2) {
4293
- if (!phase2 || phase2.fallbackChain.length === 0) return [];
4294
- if (!phase2.profileResolver) return [];
4295
- const archetype = ir.intent.archetype;
4296
- const chosenScore = getArchetypePerfScore(profile.id, archetype);
4297
- const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
4298
- if (!chosenHasRoomToGrow) return [];
4299
- let bestAlt = null;
4300
- for (const altId of phase2.fallbackChain) {
4301
- const altProfile = phase2.profileResolver(altId);
4302
- if (!altProfile) continue;
4303
- if (altProfile.id === profile.id) continue;
4304
- const altScore = getArchetypePerfScore(altProfile.id, archetype);
4305
- if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
4306
- if (altScore.score < chosenScore.score) continue;
4307
- if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
4308
- if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
4309
- bestAlt = { id: altId, profile: altProfile, score: altScore };
4310
- }
4418
+ async function doRefresh6(rt, appId) {
4419
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
4420
+ let snap = snapshots5.get(appId);
4421
+ if (!snap) {
4422
+ snap = { data: [], expiresAt: 0, refreshing: false };
4423
+ snapshots5.set(appId, snap);
4311
4424
  }
4312
- if (!bestAlt) return [];
4313
- const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
4314
- if (tierDownWouldFire) return [];
4315
- const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
4316
- const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
4317
- return [
4318
- {
4319
- level: "warn",
4320
- code: "cost-mismatched-archetype",
4321
- message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
4322
- suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
4323
- recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
4324
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4425
+ try {
4426
+ const res = await rt.fetchImpl(url, { method: "GET" });
4427
+ if (!res.ok) {
4428
+ throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
4325
4429
  }
4326
- ];
4327
- }
4328
- function detectModelStaleEvidence(ir, profile) {
4329
- if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
4330
- const archetype = ir.intent.archetype;
4331
- const chosen = getArchetypePerfScore(profile.id, archetype);
4332
- if (chosen.grounding !== "judgment") return [];
4333
- return [
4334
- {
4335
- level: "info",
4336
- code: "model-stale-evidence",
4337
- message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}) despite brain-query mode being active. Measurement substrate is wired but the brain hasn't accumulated >=10 outcomes for this (model, archetype) tuple yet \u2014 routing decisions remain pre-measured for this slot.`,
4338
- suggestion: "Verify that `record()` is being called on every call() outcome with the appropriate `actualModel` and `mutationsApplied` fields. Once the brain accumulates n>=10 rows on this tuple, the score promotes from judgment to measured automatically (5-min SWR cache). No code change required from your side \u2014 this is the substrate signaling the gap.",
4339
- recommendationType: "prompt-fix",
4340
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4430
+ const body = await res.json();
4431
+ if (runtime6 !== rt) return;
4432
+ const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
4433
+ snap.data = rows;
4434
+ snap.expiresAt = Date.now() + rt.ttlMs;
4435
+ snap.refreshing = false;
4436
+ } catch (err) {
4437
+ if (runtime6 !== rt) return;
4438
+ snap.refreshing = false;
4439
+ snap.expiresAt = Date.now() + rt.ttlMs;
4440
+ if (!warnedOnce5) {
4441
+ warnedOnce5 = true;
4442
+ (rt.onError ?? defaultOnError6)(err);
4341
4443
  }
4342
- ];
4444
+ }
4343
4445
  }
4344
- function detectTierDown(ir, profile, phase2) {
4345
- if (!phase2 || phase2.fallbackChain.length === 0) return [];
4346
- if (!phase2.profileResolver) return [];
4347
- const archetype = ir.intent.archetype;
4348
- const chosenScore = getArchetypePerfScore(profile.id, archetype);
4349
- const chosenCost = profile.costInputPer1m;
4350
- let bestAlt = null;
4351
- for (const altId of phase2.fallbackChain) {
4352
- const altProfile = phase2.profileResolver(altId);
4353
- if (!altProfile) continue;
4354
- if (altProfile.id === profile.id) continue;
4355
- const altScore = getArchetypePerfScore(altProfile.id, archetype);
4356
- if (altScore.grounding !== "measured") continue;
4357
- if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
4358
- if (altScore.score < chosenScore.score) continue;
4359
- if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
4360
- if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
4361
- bestAlt = { id: altId, profile: altProfile, score: altScore };
4446
+ function defaultOnError6(err) {
4447
+ console.warn(
4448
+ "[kgauto] stale-model findings fetch failed (using empty fallback):",
4449
+ err
4450
+ );
4451
+ }
4452
+ var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
4453
+ function advisorRuleConsumerOnStaleModel(ir) {
4454
+ if (!isStaleModelFindingsBrainActive()) return [];
4455
+ if (!ir.appId) return [];
4456
+ const findings = getStaleModelFindings({
4457
+ appId: ir.appId,
4458
+ archetype: ir.intent.archetype
4459
+ });
4460
+ if (findings.length === 0) return [];
4461
+ const ranked = [...findings].sort((a, b) => {
4462
+ if (a.staleStatus !== b.staleStatus) {
4463
+ return a.staleStatus === "deprecated" ? -1 : 1;
4362
4464
  }
4363
- }
4364
- if (!bestAlt) return [];
4365
- const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
4465
+ return a.staleModel.localeCompare(b.staleModel);
4466
+ });
4467
+ const top = ranked[0];
4468
+ const extraCount = findings.length - 1;
4469
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
4366
4470
  return [
4367
4471
  {
4368
4472
  level: "warn",
4369
- code: "tier-down",
4370
- message: `Tier-down: target=${profile.id} (${chosenDesc}) selected for ${archetype}. Brain shows ${bestAlt.id} delivers equal-or-better quality (archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}) at $${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} per 1M vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} \u2014 a measured tier-down opportunity.`,
4371
- suggestion: `Move \`${bestAlt.id}\` to primary for this archetype. The brain has n=${bestAlt.score.n} measured outcomes backing the recommendation; this is data, not opinion. If posture='locked' is required (compliance/brand promise), set it explicitly to silence this rule.`,
4372
- recommendationType: "tier-down",
4473
+ code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
4474
+ message: `${top.message}${extraNote}`,
4475
+ suggestion: top.suggestion ?? `Migrate ${top.staleModel} \u2192 ${top.recommendedModel} for archetype "${top.archetype}". The newer model is the current latest in the "${top.family}" family; the stale one is ${top.staleStatus}.`,
4476
+ recommendationType: "model-swap",
4373
4477
  docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4374
4478
  }
4375
4479
  ];
4376
4480
  }
4377
- function detectArchetypePerfFloorBreach(ir, profile) {
4378
- const compat = getModelCompatibility(profile.id, {
4379
- archetype: ir.intent.archetype,
4380
- toolOrchestration: ir.constraints?.toolOrchestration
4381
- });
4382
- if (compat.status === "compatible") return [];
4383
- if (compat.status === "requires-adapter") {
4384
- return [
4385
- {
4386
- level: "warn",
4387
- code: "archetype-perf-floor-breach",
4388
- message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}). A known adapter would lift it: ${compat.adapter.parameter}=${compat.adapter.value}. ${compat.adapter.consequence}`,
4389
- suggestion: `Pass \`ir.constraints.${compat.adapter.parameter} = '${compat.adapter.value}'\` for this call, OR pick a model whose archetypePerf for ${ir.intent.archetype} already clears the floor (call \`getModelCompatibility(modelId, { archetype: '${ir.intent.archetype}' })\` to check). Estimated post-adapter score: ${compat.archetypePerfWithAdapter}/10.`,
4390
- recommendationType: "prompt-fix",
4391
- suggestedAdaptation: compat.adapter,
4392
- docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4393
- }
4394
- ];
4481
+
4482
+ // src/archetype-fits.ts
4483
+ var ARCHETYPE_FAMILY_FITS = Object.freeze([
4484
+ {
4485
+ archetype: "plan",
4486
+ betterFitFamily: "deepseek-reasoner",
4487
+ reason: "Plan archetype is reasoning-shaped (multi-step chains, hypothesis-and-check, sub-goal decomposition) \u2014 exactly where reasoner-family models excel. Sonnet/Opus produce plans but at higher cost; reasoners produce equivalent-or-better plans at 7-17x lower cost at current promo pricing (deepseek-v4-pro $0.435/$0.87 per 1M promo through 2026-05-31 vs sonnet $3/$15).",
4488
+ costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
4489
+ },
4490
+ {
4491
+ archetype: "critique",
4492
+ betterFitFamily: "deepseek-reasoner",
4493
+ reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
4494
+ costGuidance: "comparable or cheaper at current pricing"
4495
+ }
4496
+ ]);
4497
+ function findBetterFit(archetype, currentFamily) {
4498
+ for (const fit of ARCHETYPE_FAMILY_FITS) {
4499
+ if (fit.archetype !== archetype) continue;
4500
+ if (fit.betterFitFamily === currentFamily) return null;
4501
+ return fit;
4395
4502
  }
4503
+ return null;
4504
+ }
4505
+
4506
+ // src/advisor-rules/cross-family-fit.ts
4507
+ function familyHasCurrentActiveModel(family) {
4508
+ for (const profile of allProfiles()) {
4509
+ const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
4510
+ if (profileFamily !== family) continue;
4511
+ if (profile.status !== "current") continue;
4512
+ if (profile.active === false) continue;
4513
+ return true;
4514
+ }
4515
+ return false;
4516
+ }
4517
+ function listCandidatesInFamily(family) {
4518
+ const candidates = [];
4519
+ for (const profile of allProfiles()) {
4520
+ const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
4521
+ if (profileFamily !== family) continue;
4522
+ if (profile.status !== "current") continue;
4523
+ if (profile.active === false) continue;
4524
+ candidates.push(profile.id);
4525
+ if (candidates.length >= 3) break;
4526
+ }
4527
+ return candidates;
4528
+ }
4529
+ function advisorRuleCrossFamilyFit(ctx) {
4530
+ if (!ctx.resolvedPrimary) return [];
4531
+ const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
4532
+ if (!currentFamily) return [];
4533
+ const fit = findBetterFit(ctx.archetype, currentFamily);
4534
+ if (!fit) return [];
4535
+ if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
4536
+ const candidates = listCandidatesInFamily(fit.betterFitFamily);
4537
+ if (candidates.length === 0) return [];
4538
+ const candidateStr = candidates.join(", ");
4539
+ const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
4396
4540
  return [
4397
4541
  {
4398
- level: "critical",
4399
- code: "archetype-perf-floor-breach",
4400
- message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}) and no known adapter would lift it. ${compat.reason}`,
4401
- suggestion: `Swap to a model whose archetypePerf for ${ir.intent.archetype} clears the floor. Use \`getModelCompatibility(candidateId, { archetype: '${ir.intent.archetype}' })\` to vet candidates, or \`getDefaultFallbackChain({ archetype: '${ir.intent.archetype}', posture: 'open' })\` for a library-picked chain that respects the floor by construction.`,
4542
+ level: "info",
4543
+ code: "cross-family-fit-candidate",
4544
+ ownership: "consumer-actionable",
4545
+ message,
4546
+ suggestion: `Swap the model literal in \`ir.models\` to one of: ${candidateStr}. Or call \`getRecommendedPrimary({ family: '${fit.betterFitFamily}', archetype: '${ctx.archetype}', fallback: { id: '${candidates[0]}', reason: 'cross-family-fit-recommendation' } })\` to let kgauto resolve to the current+active family member.`,
4402
4547
  recommendationType: "model-swap",
4403
4548
  docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4404
4549
  }
4405
4550
  ];
4406
4551
  }
4407
- function detectStaleExclusionCandidate(ir) {
4408
- if (!isExclusionFindingsBrainActive()) return [];
4409
- if (!ir.appId) return [];
4410
- const findings = getStaleExclusionFindings({
4411
- appId: ir.appId,
4412
- archetype: ir.intent.archetype
4413
- });
4414
- if (findings.length === 0) return [];
4415
- const ranked = [...findings].sort((a, b) => {
4416
- const sa = a.estimatedSavingsUsd30d ?? -Infinity;
4417
- const sb = b.estimatedSavingsUsd30d ?? -Infinity;
4418
- if (sa !== sb) return sb - sa;
4419
- return confidenceRank(b.confidence) - confidenceRank(a.confidence);
4420
- });
4421
- const top = ranked[0];
4422
- const extraCount = findings.length - 1;
4423
- const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
4552
+
4553
+ // src/advisor.ts
4554
+ var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
4555
+ var TIER_DOWN_COST_RATIO = 0.5;
4556
+ var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
4557
+ var PRODUCER_OWNED_RULE_CODES = Object.freeze(
4558
+ /* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
4559
+ );
4560
+ function deriveOwnership(code, selfDeclared) {
4561
+ if (selfDeclared) return selfDeclared;
4562
+ return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
4563
+ }
4564
+ function runAdvisor(ir, result, profile, policy, phase2) {
4565
+ const out = [];
4566
+ out.push(...detectCachingOff(ir, profile));
4567
+ out.push(...detectSingleChunkSystem(ir, profile));
4568
+ out.push(...detectToolBloat(ir, result));
4569
+ out.push(...detectHistoryUncached(ir, profile));
4570
+ out.push(...detectSingleModelArray(ir, policy));
4571
+ if (policy?.posture !== "locked") {
4572
+ out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
4573
+ out.push(...detectModelStaleEvidence(ir, profile));
4574
+ out.push(...detectTierDown(ir, profile, phase2));
4575
+ }
4576
+ if (!translatorClearedToolCallCliff(phase2)) {
4577
+ out.push(...detectArchetypePerfFloorBreach(ir, profile));
4578
+ }
4579
+ if (policy?.posture !== "locked") {
4580
+ out.push(...detectStaleExclusionCandidate(ir));
4581
+ }
4582
+ if (policy?.posture !== "locked" && ir.appId) {
4583
+ out.push(
4584
+ ...advisorRulePromoteReady({
4585
+ appId: ir.appId,
4586
+ archetype: ir.intent.archetype,
4587
+ resolvedPrimary: profile.id
4588
+ })
4589
+ );
4590
+ out.push(...advisorRuleConsumerOnStaleModel(ir));
4591
+ }
4592
+ if (policy?.posture !== "locked") {
4593
+ out.push(
4594
+ ...advisorRuleCrossFamilyFit({
4595
+ archetype: ir.intent.archetype,
4596
+ resolvedPrimary: profile.id
4597
+ })
4598
+ );
4599
+ }
4600
+ return out;
4601
+ }
4602
+ function translatorClearedToolCallCliff(phase2) {
4603
+ const rewrites = phase2?.sectionRewritesApplied;
4604
+ if (!rewrites || rewrites.length === 0) return false;
4605
+ for (const rw of rewrites) {
4606
+ if (rw.kind === "tool_call_contract") return true;
4607
+ }
4608
+ return false;
4609
+ }
4610
+ function detectCachingOff(ir, profile) {
4611
+ if (profile.provider !== "anthropic") return [];
4612
+ const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
4613
+ if (totalChars < 2e3) return [];
4614
+ const anyCacheable = ir.sections.some((s) => s.cacheable === true);
4615
+ if (anyCacheable) return [];
4424
4616
  return [
4425
4617
  {
4426
- level: "info",
4427
- code: "stale-exclusion-candidate",
4428
- message: `${top.message}${extraNote}`,
4429
- suggestion: top.suggestion,
4430
- recommendationType: "tier-down",
4618
+ level: "warn",
4619
+ code: "caching-off-on-claude",
4620
+ message: `System prompt is ${totalChars} chars on Anthropic but no PromptSection has cacheable=true. Anthropic prompt caching cuts cached-prefix input cost by ~90% on subsequent calls; without it, every turn re-pays full price for the static system context.`,
4621
+ suggestion: "Mark stable system sections (role, persona, tool policy) with `cacheable: true`. The lowering pass concatenates cacheable sections into a single cache-controlled block before the dynamic ones.",
4431
4622
  docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4432
4623
  }
4433
4624
  ];
4434
4625
  }
4435
- function confidenceRank(c) {
4436
- if (c === "high") return 3;
4437
- if (c === "medium") return 2;
4438
- return 1;
4626
+ function detectSingleChunkSystem(ir, profile) {
4627
+ if (profile.provider !== "anthropic") return [];
4628
+ if (ir.sections.length !== 1) return [];
4629
+ const only = ir.sections[0];
4630
+ if (!only || only.text.length <= 1e3) return [];
4631
+ return [
4632
+ {
4633
+ level: "info",
4634
+ code: "single-chunk-system",
4635
+ message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
4636
+ suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't. NOTE: the lowering pass HOISTS cacheable sections ahead of dynamic ones on the Anthropic wire (prefix caching requires it) \u2014 if your prompt has a protected ordering (e.g. a voice/persona block that must precede boilerplate), splitting will reorder the compiled output; declining this advisory is then correct. Also: a cacheable block under ~1024 tokens gets NO cache_control marker (provider minimum), so marking small sections is inert, not harmful.",
4637
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4638
+ }
4639
+ ];
4439
4640
  }
4440
-
4441
- // src/translator.ts
4442
- var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
4443
- var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
4444
- var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
4445
- var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
4446
- var SEQUENTIAL_TOOL_PREAMBLE = "IMPORTANT: Use one tool call per response. Wait for the tool result before deciding the next tool. Do NOT batch tool calls in parallel.";
4447
- var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
4448
- var NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE = "Reasoning is internal. Output ONLY the requested content; do not emit <thinking> blocks or internal monologue as user-facing text.";
4449
- var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
4450
- var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
4451
- 1. Evidence before reasoning: cite what you observed before concluding from it.
4452
- 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4453
- 3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
4454
- 4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
4455
- 5. Label each claim: mark it observed, inferred, or assumed.
4456
- 6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
4457
- var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
4458
- 1. Evidence before reasoning: cite what you observed before concluding from it.
4459
- 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4460
- 3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
4461
- 4. Label each claim: mark it observed, inferred, or assumed.
4462
- 5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
4463
- var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
4464
- var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
4465
- 1. Evidence before reasoning: cite what you observed before concluding from it.
4466
- 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4467
- 3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
4468
- 4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
4469
- var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
4470
- 1. Evidence before reasoning: cite what you observed before concluding from it.
4471
- 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4472
- 3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
4473
- var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
4474
- "hunt",
4475
- "summarize",
4476
- "plan",
4477
- "critique",
4478
- "judge"
4479
- ]);
4480
- function matchRule(kind, profile, archetype, ctx) {
4481
- if (kind === "discipline_contract") {
4482
- if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
4483
- if (ctx.outputMode !== "text") {
4484
- return {
4485
- id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
4486
- preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
4487
- };
4641
+ function detectToolBloat(ir, result) {
4642
+ const SHORT_OUTPUT = /* @__PURE__ */ new Set([
4643
+ "classify",
4644
+ "extract",
4645
+ "summarize",
4646
+ "transform",
4647
+ "critique"
4648
+ ]);
4649
+ if (!ir.tools || ir.tools.length === 0) return [];
4650
+ const toolsKept = result.diagnostics.toolsKept;
4651
+ if (toolsKept <= 10) return [];
4652
+ if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
4653
+ return [
4654
+ {
4655
+ level: "warn",
4656
+ code: "tool-bloat",
4657
+ message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
4658
+ suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
4659
+ docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
4488
4660
  }
4489
- return {
4490
- id: RULE_DISCIPLINE_GATES_V1,
4491
- preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
4492
- };
4661
+ ];
4662
+ }
4663
+ function detectHistoryUncached(ir, profile) {
4664
+ if (profile.provider !== "anthropic") return [];
4665
+ if (!ir.history || ir.history.length < 2) return [];
4666
+ if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
4667
+ return [];
4493
4668
  }
4494
- if (kind === "tool_call_contract") {
4495
- if (!profile.archetypePerf) return null;
4496
- const archetypeScore = profile.archetypePerf[archetype];
4497
- if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
4498
- return null;
4669
+ return [
4670
+ {
4671
+ level: "warn",
4672
+ code: "history-uncached-on-claude",
4673
+ message: `${ir.history.length} history messages on Anthropic with no historyCachePolicy. Every turn re-pays for the full conversation context; with caching, subsequent turns hit the cache at ~10% the input cost.`,
4674
+ suggestion: "Set `historyCachePolicy: { strategy: 'all-but-latest' }` on this IR. The lowering pass marks the message immediately preceding currentTurn with cache_control; subsequent turns whose history prefix matches byte-for-byte hit the cache.",
4675
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4499
4676
  }
4500
- return {
4501
- id: RULE_SEQUENTIAL_TOOL_CLIFF,
4502
- preamble: SEQUENTIAL_TOOL_PREAMBLE,
4503
- wireOverrides: { parallelToolCalls: false }
4504
- };
4505
- }
4506
- if (kind === "narration_contract") {
4507
- if (profile.provider === "anthropic") {
4508
- return {
4509
- id: RULE_NARRATION_DRIFT_ANTHROPIC,
4510
- preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
4511
- };
4677
+ ];
4678
+ }
4679
+ function detectSingleModelArray(ir, policy) {
4680
+ if (ir.models.length !== 1) return [];
4681
+ if (policy?.posture === "locked") return [];
4682
+ const only = ir.models[0];
4683
+ return [
4684
+ {
4685
+ level: "warn",
4686
+ code: "single-model-array",
4687
+ message: `\`ir.models\` has length 1 (only "${only}") and posture is not 'locked'. A single-model chain has no safety net \u2014 the first 429 / 5xx / cliff hits the user as a failure. Master plan \xA71.2 closes the reliability gap with a 2-step minimum.`,
4688
+ suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
4689
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
4512
4690
  }
4513
- if (profile.provider === "deepseek") {
4514
- return {
4515
- id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
4516
- preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
4517
- };
4691
+ ];
4692
+ }
4693
+ function detectCostMismatchedArchetype(ir, profile, phase2) {
4694
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
4695
+ if (!phase2.profileResolver) return [];
4696
+ const archetype = ir.intent.archetype;
4697
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
4698
+ const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
4699
+ if (!chosenHasRoomToGrow) return [];
4700
+ let bestAlt = null;
4701
+ for (const altId of phase2.fallbackChain) {
4702
+ const altProfile = phase2.profileResolver(altId);
4703
+ if (!altProfile) continue;
4704
+ if (altProfile.id === profile.id) continue;
4705
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
4706
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
4707
+ if (altScore.score < chosenScore.score) continue;
4708
+ if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
4709
+ if (getMeasuredFailureVerdict({
4710
+ appId: ir.appId,
4711
+ archetype,
4712
+ model: altProfile.id
4713
+ })?.gated === true) {
4714
+ continue;
4715
+ }
4716
+ if (ir.constraints?.structuredOutput && effectiveConventions(altProfile).some(
4717
+ (c) => c.archetype === archetype && c.structuredOutputHint === "avoid"
4718
+ )) {
4719
+ continue;
4720
+ }
4721
+ if (getRecentRollback({
4722
+ appId: ir.appId,
4723
+ archetype,
4724
+ model: altProfile.id
4725
+ }) !== void 0) {
4726
+ continue;
4727
+ }
4728
+ if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
4729
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
4518
4730
  }
4519
- return null;
4520
4731
  }
4521
- return null;
4732
+ if (!bestAlt) return [];
4733
+ const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
4734
+ if (tierDownWouldFire) return [];
4735
+ const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
4736
+ const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
4737
+ return [
4738
+ {
4739
+ level: "warn",
4740
+ code: "cost-mismatched-archetype",
4741
+ message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
4742
+ suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
4743
+ recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
4744
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4745
+ }
4746
+ ];
4522
4747
  }
4523
- function applySectionRewrites(args) {
4524
- const { ir, profile, archetype } = args;
4525
- if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
4526
- return { rewrittenIR: ir, rewrites: [] };
4748
+ function detectModelStaleEvidence(ir, profile) {
4749
+ if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
4750
+ const archetype = ir.intent.archetype;
4751
+ const chosen = getArchetypePerfScore(profile.id, archetype);
4752
+ if (chosen.grounding !== "judgment") return [];
4753
+ return [
4754
+ {
4755
+ level: "info",
4756
+ code: "model-stale-evidence",
4757
+ message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}, cross-app 90d window) despite brain-query mode being active. Fewer than 10 outcomes back this (model, archetype) tuple across ALL consumers \u2014 routing decisions remain pre-measured for this slot.`,
4758
+ suggestion: "Verify that `record()` is being called on every call() outcome. Counts are cross-app (migration 050 view): once ANY consumers accumulate n>=10 rows on this tuple, the score promotes from judgment to measured automatically within the 5-min SWR window. (Before alpha.78 this promotion was advertised but had no implementing mechanism \u2014 n was never populated; if this advisory has been firing for weeks at n=0 despite real traffic, bump to >=alpha.78 and it will clear on its own.)",
4759
+ recommendationType: "prompt-fix",
4760
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4761
+ }
4762
+ ];
4763
+ }
4764
+ function detectTierDown(ir, profile, phase2) {
4765
+ if (!phase2 || phase2.fallbackChain.length === 0) return [];
4766
+ if (!phase2.profileResolver) return [];
4767
+ const archetype = ir.intent.archetype;
4768
+ const chosenScore = getArchetypePerfScore(profile.id, archetype);
4769
+ const chosenCost = profile.costInputPer1m;
4770
+ let bestAlt = null;
4771
+ for (const altId of phase2.fallbackChain) {
4772
+ const altProfile = phase2.profileResolver(altId);
4773
+ if (!altProfile) continue;
4774
+ if (altProfile.id === profile.id) continue;
4775
+ const altScore = getArchetypePerfScore(altProfile.id, archetype);
4776
+ if (altScore.grounding !== "measured") continue;
4777
+ if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
4778
+ if (altScore.score < chosenScore.score) continue;
4779
+ if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
4780
+ if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
4781
+ bestAlt = { id: altId, profile: altProfile, score: altScore };
4782
+ }
4527
4783
  }
4528
- const outputMode = args.outputMode ?? resolveOutputMode({
4529
- declared: ir.constraints?.outputMode,
4530
- structuredOutput: ir.constraints?.structuredOutput,
4531
- toolCount: ir.tools?.length ?? 0
4532
- });
4533
- const hasTools = (ir.tools?.length ?? 0) > 0;
4534
- const ctx = { outputMode, hasTools };
4535
- const rewrites = [];
4536
- const newSections = ir.sections.map((section) => {
4537
- if (!section.kind || section.kind === "arbitrary") return section;
4538
- const rule = matchRule(section.kind, profile, archetype, ctx);
4539
- if (!rule) return section;
4540
- const originalText = section.text;
4541
- const transformedText = `${rule.preamble}
4542
-
4543
- ${originalText}`;
4544
- rewrites.push({
4545
- sectionId: section.id,
4546
- kind: section.kind,
4547
- rule: rule.id,
4548
- originalText,
4549
- transformedText,
4550
- ...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
4551
- });
4552
- return { ...section, text: transformedText };
4784
+ if (!bestAlt) return [];
4785
+ const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
4786
+ return [
4787
+ {
4788
+ level: "warn",
4789
+ code: "tier-down",
4790
+ message: `Tier-down: target=${profile.id} (${chosenDesc}) selected for ${archetype}. Brain shows ${bestAlt.id} delivers equal-or-better quality (archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}) at $${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} per 1M vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} \u2014 a measured tier-down opportunity.`,
4791
+ suggestion: `Move \`${bestAlt.id}\` to primary for this archetype. The brain has n=${bestAlt.score.n} measured outcomes backing the recommendation; this is data, not opinion. If posture='locked' is required (compliance/brand promise), set it explicitly to silence this rule.`,
4792
+ recommendationType: "tier-down",
4793
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4794
+ }
4795
+ ];
4796
+ }
4797
+ function detectArchetypePerfFloorBreach(ir, profile) {
4798
+ const compat = getModelCompatibility(profile.id, {
4799
+ archetype: ir.intent.archetype,
4800
+ toolOrchestration: ir.constraints?.toolOrchestration
4553
4801
  });
4554
- if (rewrites.length === 0) {
4555
- return { rewrittenIR: ir, rewrites: [] };
4802
+ if (compat.status === "compatible") return [];
4803
+ if (compat.status === "requires-adapter") {
4804
+ return [
4805
+ {
4806
+ level: "warn",
4807
+ code: "archetype-perf-floor-breach",
4808
+ message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}). A known adapter would lift it: ${compat.adapter.parameter}=${compat.adapter.value}. ${compat.adapter.consequence}`,
4809
+ suggestion: `Pass \`ir.constraints.${compat.adapter.parameter} = '${compat.adapter.value}'\` for this call, OR pick a model whose archetypePerf for ${ir.intent.archetype} already clears the floor (call \`getModelCompatibility(modelId, { archetype: '${ir.intent.archetype}' })\` to check). Estimated post-adapter score: ${compat.archetypePerfWithAdapter}/10.`,
4810
+ recommendationType: "prompt-fix",
4811
+ suggestedAdaptation: compat.adapter,
4812
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4813
+ }
4814
+ ];
4556
4815
  }
4557
- const rewrittenIR = { ...ir, sections: newSections };
4558
- return { rewrittenIR, rewrites };
4816
+ return [
4817
+ {
4818
+ level: "critical",
4819
+ code: "archetype-perf-floor-breach",
4820
+ message: `${profile.id} sits below the archetype floor for ${ir.intent.archetype} (score ${compat.archetypePerf}/10, floor ${6}) and no known adapter would lift it. ${compat.reason}`,
4821
+ suggestion: `Swap to a model whose archetypePerf for ${ir.intent.archetype} clears the floor. Use \`getModelCompatibility(candidateId, { archetype: '${ir.intent.archetype}' })\` to vet candidates, or \`getDefaultFallbackChain({ archetype: '${ir.intent.archetype}', posture: 'open' })\` for a library-picked chain that respects the floor by construction.`,
4822
+ recommendationType: "model-swap",
4823
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4824
+ }
4825
+ ];
4559
4826
  }
4560
-
4561
- // src/promotions-brain.ts
4562
- function isRawPromotionRow(x) {
4563
- if (!x || typeof x !== "object") return false;
4564
- const r = x;
4565
- return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
4827
+ function detectStaleExclusionCandidate(ir) {
4828
+ if (!isExclusionFindingsBrainActive()) return [];
4829
+ if (!ir.appId) return [];
4830
+ const findings = getStaleExclusionFindings({
4831
+ appId: ir.appId,
4832
+ archetype: ir.intent.archetype
4833
+ });
4834
+ if (findings.length === 0) return [];
4835
+ const ranked = [...findings].sort((a, b) => {
4836
+ const sa = a.estimatedSavingsUsd30d ?? -Infinity;
4837
+ const sb = b.estimatedSavingsUsd30d ?? -Infinity;
4838
+ if (sa !== sb) return sb - sa;
4839
+ return confidenceRank(b.confidence) - confidenceRank(a.confidence);
4840
+ });
4841
+ const top = ranked[0];
4842
+ const extraCount = findings.length - 1;
4843
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
4844
+ return [
4845
+ {
4846
+ level: "info",
4847
+ code: "stale-exclusion-candidate",
4848
+ message: `${top.message}${extraNote}`,
4849
+ suggestion: top.suggestion,
4850
+ recommendationType: "tier-down",
4851
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
4852
+ }
4853
+ ];
4566
4854
  }
4567
- function coerceId(v) {
4568
- if (typeof v === "number") return Number.isFinite(v) ? v : null;
4569
- if (typeof v === "string") {
4570
- const n = Number(v);
4571
- return Number.isFinite(n) ? n : null;
4572
- }
4573
- return null;
4574
- }
4575
- function mapRowsToPromotions(rows) {
4576
- const out = [];
4577
- for (const row of rows) {
4578
- if (!isRawPromotionRow(row)) continue;
4579
- const id = coerceId(row.id);
4580
- if (id === null) continue;
4581
- const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
4582
- if (mode === null) continue;
4583
- out.push({
4584
- id,
4585
- archetype: row.intent_archetype,
4586
- mode,
4587
- strategy: typeof row.strategy === "string" ? row.strategy : null,
4588
- promotedModel: row.promoted_model,
4589
- incumbentModel: row.incumbent_model,
4590
- evalRunId: coerceId(row.eval_run_id ?? null),
4591
- suppressQualityGate: row.suppress_quality_gate === true,
4592
- promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : ""
4593
- });
4594
- }
4595
- return out;
4596
- }
4597
- var snapshots4 = /* @__PURE__ */ new Map();
4598
- var runtime5;
4599
- var warnedOnce4 = false;
4600
- var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
4601
- function isAutoPromoteEnabledFromEnv(envSource) {
4602
- const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
4603
- const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
4604
- return raw === "1" || raw === "true";
4605
- }
4606
- function configurePromotionsBrain(rt) {
4607
- runtime5 = rt;
4608
- snapshots4.clear();
4609
- warnedOnce4 = false;
4610
- }
4611
- function isPromotionsBrainActive() {
4612
- return runtime5 !== void 0;
4613
- }
4614
- function getApplicablePromotion(opts) {
4615
- const rt = runtime5;
4616
- if (!rt) return void 0;
4617
- const appId = opts.appId;
4618
- if (!appId || !opts.archetype || !opts.mode) return void 0;
4619
- let snap = snapshots4.get(appId);
4620
- if (!snap) {
4621
- snap = { data: [], expiresAt: 0, refreshing: false };
4622
- snapshots4.set(appId, snap);
4623
- }
4624
- const now = Date.now();
4625
- const stale = snap.expiresAt <= now;
4626
- if (stale && !snap.refreshing) {
4627
- snap.refreshing = true;
4628
- void asyncRefresh5(rt, appId);
4629
- }
4630
- return snap.data.find(
4631
- (p) => p.archetype === opts.archetype && p.mode === opts.mode
4632
- );
4633
- }
4634
- var pendingRefreshes4 = /* @__PURE__ */ new Map();
4635
- async function asyncRefresh5(rt, appId) {
4636
- const promise = doRefresh5(rt, appId);
4637
- pendingRefreshes4.set(appId, promise);
4638
- try {
4639
- await promise;
4640
- } finally {
4641
- if (pendingRefreshes4.get(appId) === promise) {
4642
- pendingRefreshes4.delete(appId);
4643
- }
4644
- }
4645
- }
4646
- async function doRefresh5(rt, appId) {
4647
- const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
4648
- let snap = snapshots4.get(appId);
4649
- if (!snap) {
4650
- snap = { data: [], expiresAt: 0, refreshing: false };
4651
- snapshots4.set(appId, snap);
4652
- }
4653
- try {
4654
- const res = await rt.fetchImpl(url, { method: "GET" });
4655
- if (!res.ok) {
4656
- throw new Error(`promotions ${res.status}: ${res.statusText}`);
4657
- }
4658
- const body = await res.json();
4659
- if (runtime5 !== rt) return;
4660
- const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
4661
- snap.data = rows;
4662
- snap.expiresAt = Date.now() + rt.ttlMs;
4663
- snap.refreshing = false;
4664
- } catch (err) {
4665
- if (runtime5 !== rt) return;
4666
- snap.refreshing = false;
4667
- snap.expiresAt = Date.now() + rt.ttlMs;
4668
- if (!warnedOnce4) {
4669
- warnedOnce4 = true;
4670
- (rt.onError ?? defaultOnError5)(err);
4671
- }
4672
- }
4673
- }
4674
- function defaultOnError5(err) {
4675
- console.warn(
4676
- "[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
4677
- err
4678
- );
4679
- }
4680
- function _testResetPromotions() {
4681
- runtime5 = void 0;
4682
- snapshots4.clear();
4683
- pendingRefreshes4 = /* @__PURE__ */ new Map();
4684
- warnedOnce4 = false;
4685
- }
4686
- async function _testWaitForPromotionsRefresh() {
4687
- const pending = Array.from(pendingRefreshes4.values());
4688
- if (pending.length > 0) await Promise.all(pending);
4855
+ function confidenceRank(c) {
4856
+ if (c === "high") return 3;
4857
+ if (c === "medium") return 2;
4858
+ return 1;
4689
4859
  }
4690
4860
 
4691
- // src/measured-failure-brain.ts
4692
- function coerceCount(v) {
4693
- if (typeof v === "number") return Number.isFinite(v) ? v : null;
4694
- if (typeof v === "string") {
4695
- const n = Number(v);
4696
- return Number.isFinite(n) ? n : null;
4697
- }
4698
- return null;
4699
- }
4700
- function isRawFailureRow(x) {
4701
- if (!x || typeof x !== "object") return false;
4702
- const r = x;
4703
- return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
4704
- }
4705
- function mapRows(rows) {
4706
- const out = [];
4707
- for (const row of rows) {
4708
- if (!isRawFailureRow(row)) continue;
4709
- const n = coerceCount(row.n);
4710
- const nFail = coerceCount(row.n_fail) ?? 0;
4711
- if (n === null || n <= 0) continue;
4712
- out.push({
4713
- archetype: row.intent_archetype,
4714
- model: row.model,
4715
- n,
4716
- nFail
4717
- });
4718
- }
4719
- return out;
4720
- }
4721
- var MEASURED_FAILURE_CFG = {
4722
- /**
4723
- * Hard minimum attempts before ANY gate may be created. Guards against
4724
- * pathological tiny samples that the confidence bound alone would let
4725
- * through in edge cases. At 5-for-5 the bound clears the threshold; at
4726
- * 3-for-3 it does not, which is the behaviour we want (three failures is
4727
- * a bad day, five in a row is a pattern).
4728
- */
4729
- minSample: 5,
4730
- /**
4731
- * Gate when we are 95% confident the model fails MORE OFTEN THAN IT
4732
- * SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
4733
- * a model that probably fails the majority of the time has no business
4734
- * leading a surface, whatever its declared scores say.
4735
- */
4736
- lowerBoundThreshold: 0.5,
4737
- /** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
4738
- z: 1.96,
4739
- /** Must match the view's window. Documented here for the advisory text. */
4740
- windowDays: 28
4741
- };
4742
- function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
4743
- if (n <= 0) return 0;
4744
- const p = failures / n;
4745
- const z2 = z * z;
4746
- const denom = 1 + z2 / n;
4747
- const centre = p + z2 / (2 * n);
4748
- const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
4749
- const lower2 = (centre - margin) / denom;
4750
- return lower2 < 0 ? 0 : lower2;
4751
- }
4752
- function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
4753
- if (!row || row.n < cfg.minSample) return void 0;
4754
- const lowerBound = wilsonLowerBound(row.nFail, row.n, cfg.z);
4755
- return {
4756
- gated: lowerBound > cfg.lowerBoundThreshold,
4757
- rate: row.nFail / row.n,
4758
- lowerBound,
4759
- n: row.n,
4760
- nFail: row.nFail
4761
- };
4762
- }
4763
- var snapshots5 = /* @__PURE__ */ new Map();
4764
- var runtime6;
4765
- var warnedOnce5 = false;
4766
- var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
4767
- function isMeasuredFailureGateEnabledFromEnv(envSource) {
4768
- const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
4769
- const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
4770
- return !(raw === "0" || raw === "false");
4771
- }
4772
- function configureMeasuredFailureBrain(rt) {
4773
- runtime6 = rt;
4774
- snapshots5.clear();
4775
- warnedOnce5 = false;
4776
- }
4777
- function isMeasuredFailureBrainActive() {
4778
- return runtime6 !== void 0;
4779
- }
4780
- function prefetchMeasuredFailure(appId) {
4781
- const rt = runtime6;
4782
- if (!rt || !appId) return void 0;
4783
- let snap = snapshots5.get(appId);
4784
- if (!snap) {
4785
- snap = { data: [], expiresAt: 0, refreshing: false };
4786
- snapshots5.set(appId, snap);
4787
- }
4788
- if (snap.expiresAt > Date.now()) return void 0;
4789
- const inflight = pendingRefreshes5.get(appId);
4790
- if (inflight) return inflight;
4791
- if (snap.refreshing) return void 0;
4792
- snap.refreshing = true;
4793
- void asyncRefresh6(rt, appId);
4794
- return pendingRefreshes5.get(appId);
4795
- }
4796
- async function awaitMeasuredFailureReady(appId, timeoutMs) {
4797
- if (!runtime6 || !appId) return;
4798
- const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes5.get(appId);
4799
- if (!(timeoutMs > 0)) return;
4800
- if (!pending) return;
4801
- let timer;
4802
- try {
4803
- await Promise.race([
4804
- pending,
4805
- new Promise((resolve) => {
4806
- timer = setTimeout(resolve, timeoutMs);
4807
- })
4808
- ]);
4809
- } catch {
4810
- } finally {
4811
- if (timer) clearTimeout(timer);
4812
- }
4813
- }
4814
- function getMeasuredFailureVerdict(opts) {
4815
- const rt = runtime6;
4816
- if (!rt) return void 0;
4817
- const { appId, archetype, model } = opts;
4818
- if (!appId || !archetype || !model) return void 0;
4819
- let snap = snapshots5.get(appId);
4820
- if (!snap) {
4821
- snap = { data: [], expiresAt: 0, refreshing: false };
4822
- snapshots5.set(appId, snap);
4823
- }
4824
- const now = Date.now();
4825
- if (snap.expiresAt <= now && !snap.refreshing) {
4826
- snap.refreshing = true;
4827
- void asyncRefresh6(rt, appId);
4828
- }
4829
- const row = snap.data.find(
4830
- (r) => r.archetype === archetype && r.model === model
4831
- );
4832
- return judgeMeasuredFailure(row);
4833
- }
4834
- var pendingRefreshes5 = /* @__PURE__ */ new Map();
4835
- async function asyncRefresh6(rt, appId) {
4836
- const promise = doRefresh6(rt, appId);
4837
- pendingRefreshes5.set(appId, promise);
4838
- try {
4839
- await promise;
4840
- } finally {
4841
- if (pendingRefreshes5.get(appId) === promise) {
4842
- pendingRefreshes5.delete(appId);
4861
+ // src/translator.ts
4862
+ var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
4863
+ var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
4864
+ var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
4865
+ var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
4866
+ var SEQUENTIAL_TOOL_PREAMBLE = "IMPORTANT: Use one tool call per response. Wait for the tool result before deciding the next tool. Do NOT batch tool calls in parallel.";
4867
+ var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
4868
+ var NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE = "Reasoning is internal. Output ONLY the requested content; do not emit <thinking> blocks or internal monologue as user-facing text.";
4869
+ var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
4870
+ var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
4871
+ 1. Evidence before reasoning: cite what you observed before concluding from it.
4872
+ 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4873
+ 3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
4874
+ 4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
4875
+ 5. Label each claim: mark it observed, inferred, or assumed.
4876
+ 6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
4877
+ var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
4878
+ 1. Evidence before reasoning: cite what you observed before concluding from it.
4879
+ 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4880
+ 3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
4881
+ 4. Label each claim: mark it observed, inferred, or assumed.
4882
+ 5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
4883
+ var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
4884
+ var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
4885
+ 1. Evidence before reasoning: cite what you observed before concluding from it.
4886
+ 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4887
+ 3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
4888
+ 4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
4889
+ var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
4890
+ 1. Evidence before reasoning: cite what you observed before concluding from it.
4891
+ 2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
4892
+ 3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
4893
+ var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
4894
+ "hunt",
4895
+ "summarize",
4896
+ "plan",
4897
+ "critique",
4898
+ "judge"
4899
+ ]);
4900
+ function matchRule(kind, profile, archetype, ctx) {
4901
+ if (kind === "discipline_contract") {
4902
+ if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
4903
+ if (ctx.outputMode !== "text") {
4904
+ return {
4905
+ id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
4906
+ preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
4907
+ };
4843
4908
  }
4909
+ return {
4910
+ id: RULE_DISCIPLINE_GATES_V1,
4911
+ preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
4912
+ };
4844
4913
  }
4845
- }
4846
- async function doRefresh6(rt, appId) {
4847
- const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
4848
- let snap = snapshots5.get(appId);
4849
- if (!snap) {
4850
- snap = { data: [], expiresAt: 0, refreshing: false };
4851
- snapshots5.set(appId, snap);
4914
+ if (kind === "tool_call_contract") {
4915
+ if (!profile.archetypePerf) return null;
4916
+ const archetypeScore = profile.archetypePerf[archetype];
4917
+ if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
4918
+ return null;
4919
+ }
4920
+ return {
4921
+ id: RULE_SEQUENTIAL_TOOL_CLIFF,
4922
+ preamble: SEQUENTIAL_TOOL_PREAMBLE,
4923
+ wireOverrides: { parallelToolCalls: false }
4924
+ };
4852
4925
  }
4853
- try {
4854
- const res = await rt.fetchImpl(url, { method: "GET" });
4855
- if (!res.ok) {
4856
- throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
4926
+ if (kind === "narration_contract") {
4927
+ if (profile.provider === "anthropic") {
4928
+ return {
4929
+ id: RULE_NARRATION_DRIFT_ANTHROPIC,
4930
+ preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
4931
+ };
4857
4932
  }
4858
- const body = await res.json();
4859
- if (runtime6 !== rt) return;
4860
- snap.data = Array.isArray(body) ? mapRows(body) : [];
4861
- snap.expiresAt = Date.now() + rt.ttlMs;
4862
- snap.refreshing = false;
4863
- } catch (err) {
4864
- if (runtime6 !== rt) return;
4865
- snap.refreshing = false;
4866
- snap.expiresAt = Date.now() + rt.ttlMs;
4867
- if (!warnedOnce5) {
4868
- warnedOnce5 = true;
4869
- (rt.onError ?? defaultOnError6)(err);
4933
+ if (profile.provider === "deepseek") {
4934
+ return {
4935
+ id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
4936
+ preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
4937
+ };
4870
4938
  }
4939
+ return null;
4871
4940
  }
4941
+ return null;
4872
4942
  }
4873
- function defaultOnError6(err) {
4874
- console.warn(
4875
- "[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
4876
- err
4877
- );
4878
- }
4879
- function _testResetMeasuredFailure() {
4880
- runtime6 = void 0;
4881
- snapshots5.clear();
4882
- pendingRefreshes5 = /* @__PURE__ */ new Map();
4883
- warnedOnce5 = false;
4884
- }
4885
- async function _testWaitForMeasuredFailureRefresh() {
4886
- const pending = Array.from(pendingRefreshes5.values());
4887
- if (pending.length > 0) await Promise.all(pending);
4943
+ function applySectionRewrites(args) {
4944
+ const { ir, profile, archetype } = args;
4945
+ if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
4946
+ return { rewrittenIR: ir, rewrites: [] };
4947
+ }
4948
+ const outputMode = args.outputMode ?? resolveOutputMode({
4949
+ declared: ir.constraints?.outputMode,
4950
+ structuredOutput: ir.constraints?.structuredOutput,
4951
+ toolCount: ir.tools?.length ?? 0
4952
+ });
4953
+ const hasTools = (ir.tools?.length ?? 0) > 0;
4954
+ const ctx = { outputMode, hasTools };
4955
+ const rewrites = [];
4956
+ const newSections = ir.sections.map((section) => {
4957
+ if (!section.kind || section.kind === "arbitrary") return section;
4958
+ const rule = matchRule(section.kind, profile, archetype, ctx);
4959
+ if (!rule) return section;
4960
+ const originalText = section.text;
4961
+ const transformedText = `${rule.preamble}
4962
+
4963
+ ${originalText}`;
4964
+ rewrites.push({
4965
+ sectionId: section.id,
4966
+ kind: section.kind,
4967
+ rule: rule.id,
4968
+ originalText,
4969
+ transformedText,
4970
+ ...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
4971
+ });
4972
+ return { ...section, text: transformedText };
4973
+ });
4974
+ if (rewrites.length === 0) {
4975
+ return { rewrittenIR: ir, rewrites: [] };
4976
+ }
4977
+ const rewrittenIR = { ...ir, sections: newSections };
4978
+ return { rewrittenIR, rewrites };
4888
4979
  }
4889
4980
 
4890
4981
  // src/compile.ts
@@ -6014,6 +6105,17 @@ function parseJsonLoose(raw) {
6014
6105
  var FAILED = /* @__PURE__ */ Symbol("parse-failed");
6015
6106
 
6016
6107
  // src/ir.ts
6108
+ function mutationId(m) {
6109
+ return typeof m === "string" ? m : m.id;
6110
+ }
6111
+ function hasMutation(list, idOrPrefix) {
6112
+ if (!Array.isArray(list)) return false;
6113
+ const prefix = idOrPrefix.endsWith("*") ? idOrPrefix.slice(0, -1) : void 0;
6114
+ return list.some((m) => {
6115
+ const id = mutationId(m);
6116
+ return prefix !== void 0 ? id.startsWith(prefix) : id === idOrPrefix;
6117
+ });
6118
+ }
6017
6119
  var CallError = class extends Error {
6018
6120
  attempts;
6019
6121
  lastErrorCode;
@@ -6136,6 +6238,17 @@ function getReachabilityDiagnostic(opts = {}) {
6136
6238
  }
6137
6239
  return out;
6138
6240
  }
6241
+ var BRAIN_READ_ENV_NAMES = {
6242
+ endpoint: "KGAUTO_V2_BRAIN_SUPABASE_URL",
6243
+ jwt: "KGAUTO_V2_BRAIN_JWT",
6244
+ anonKey: "KGAUTO_V2_BRAIN_ANON_KEY",
6245
+ /** Accepted fallbacks, in resolution order after the canonical name. */
6246
+ fallbacks: {
6247
+ endpoint: ["KGAUTO_V2_BRAIN_URL"],
6248
+ jwt: ["GLASSBOX_BRAIN_JWT"],
6249
+ anonKey: []
6250
+ }
6251
+ };
6139
6252
  function readBrainReadEnv(envSource = defaultEnv()) {
6140
6253
  const endpoint = readKeyValue(envSource.KGAUTO_V2_BRAIN_SUPABASE_URL) ?? readKeyValue(envSource.KGAUTO_V2_BRAIN_URL);
6141
6254
  const jwt = readKeyValue(envSource.KGAUTO_V2_BRAIN_JWT) ?? readKeyValue(envSource.GLASSBOX_BRAIN_JWT);
@@ -6152,12 +6265,46 @@ function isSameModelRetryEnabledFromEnv(envSource = defaultEnv()) {
6152
6265
  }
6153
6266
 
6154
6267
  // src/streaming.ts
6268
+ function classifyThrownFetchError(err) {
6269
+ const name = err?.name;
6270
+ if (name === "TimeoutError") {
6271
+ return { errorType: "retryable", errorCode: "timeout", message: String(err) };
6272
+ }
6273
+ if (name === "AbortError") {
6274
+ return { errorType: "terminal", errorCode: "aborted", message: "aborted by caller signal" };
6275
+ }
6276
+ return { errorType: "retryable", errorCode: "network_error", message: String(err) };
6277
+ }
6278
+ function createStallGuard(external, stallTimeoutMs) {
6279
+ const ctl = new AbortController();
6280
+ if (external) {
6281
+ if (external.aborted) ctl.abort(external.reason);
6282
+ else external.addEventListener("abort", () => ctl.abort(external.reason), { once: true });
6283
+ }
6284
+ let timer;
6285
+ const arm = () => {
6286
+ if (!stallTimeoutMs || stallTimeoutMs <= 0) return;
6287
+ if (timer) clearTimeout(timer);
6288
+ timer = setTimeout(() => {
6289
+ ctl.abort(
6290
+ new DOMException(`stream stalled: no bytes for ${stallTimeoutMs}ms`, "TimeoutError")
6291
+ );
6292
+ }, stallTimeoutMs);
6293
+ };
6294
+ const clear = () => {
6295
+ if (timer) clearTimeout(timer);
6296
+ timer = void 0;
6297
+ };
6298
+ return { signal: ctl.signal, arm, clear };
6299
+ }
6155
6300
  var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
6156
6301
  async function streamAnthropic(request, apiKey, opts) {
6157
6302
  const { provider: _provider, ...body } = request;
6158
6303
  const fetchFn = opts.fetchImpl ?? fetch;
6304
+ const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
6159
6305
  let res;
6160
6306
  try {
6307
+ guard.arm();
6161
6308
  res = await fetchFn(ANTHROPIC_URL, {
6162
6309
  method: "POST",
6163
6310
  headers: {
@@ -6165,12 +6312,16 @@ async function streamAnthropic(request, apiKey, opts) {
6165
6312
  "anthropic-version": "2023-06-01",
6166
6313
  "content-type": "application/json"
6167
6314
  },
6168
- body: JSON.stringify({ ...body, stream: true })
6315
+ body: JSON.stringify({ ...body, stream: true }),
6316
+ signal: guard.signal
6169
6317
  });
6170
6318
  } catch (err) {
6171
- return retryableError(0, "network_error", String(err), null);
6319
+ guard.clear();
6320
+ const c = classifyThrownFetchError(err);
6321
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6172
6322
  }
6173
6323
  if (!res.ok) {
6324
+ guard.clear();
6174
6325
  const errBody = await res.json().catch(() => ({}));
6175
6326
  return classifyHttpError(res.status, errBody);
6176
6327
  }
@@ -6233,9 +6384,13 @@ async function streamAnthropic(request, apiKey, opts) {
6233
6384
  if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
6234
6385
  return;
6235
6386
  }
6236
- });
6387
+ }, guard.arm, guard.signal);
6237
6388
  } catch (err) {
6238
- return retryableError(0, "stream_interrupted", String(err), null);
6389
+ const c = classifyThrownFetchError(err);
6390
+ const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
6391
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
6392
+ } finally {
6393
+ guard.clear();
6239
6394
  }
6240
6395
  const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
6241
6396
  id: b.id,
@@ -6262,6 +6417,7 @@ async function streamAnthropic(request, apiKey, opts) {
6262
6417
  async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
6263
6418
  const { provider: _provider, ...body } = request;
6264
6419
  const fetchFn = opts.fetchImpl ?? fetch;
6420
+ const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
6265
6421
  const reqBody = {
6266
6422
  ...body,
6267
6423
  stream: true,
@@ -6272,18 +6428,23 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
6272
6428
  };
6273
6429
  let res;
6274
6430
  try {
6431
+ guard.arm();
6275
6432
  res = await fetchFn(url, {
6276
6433
  method: "POST",
6277
6434
  headers: {
6278
6435
  authorization: `Bearer ${apiKey}`,
6279
6436
  "content-type": "application/json"
6280
6437
  },
6281
- body: JSON.stringify(reqBody)
6438
+ body: JSON.stringify(reqBody),
6439
+ signal: guard.signal
6282
6440
  });
6283
6441
  } catch (err) {
6284
- return retryableError(0, "network_error", String(err), null);
6442
+ guard.clear();
6443
+ const c = classifyThrownFetchError(err);
6444
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6285
6445
  }
6286
6446
  if (!res.ok) {
6447
+ guard.clear();
6287
6448
  const errBody = await res.json().catch(() => ({}));
6288
6449
  return classifyHttpError(res.status, errBody);
6289
6450
  }
@@ -6336,9 +6497,13 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
6336
6497
  const details = usage.prompt_tokens_details;
6337
6498
  if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
6338
6499
  }
6339
- });
6500
+ }, guard.arm, guard.signal);
6340
6501
  } catch (err) {
6341
- return retryableError(0, "stream_interrupted", String(err), null);
6502
+ const c = classifyThrownFetchError(err);
6503
+ const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
6504
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
6505
+ } finally {
6506
+ guard.clear();
6342
6507
  }
6343
6508
  const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
6344
6509
  id: b.id,
@@ -6361,15 +6526,28 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
6361
6526
  };
6362
6527
  return { ok: true, status: res.status, response };
6363
6528
  }
6364
- async function parseSSEStream(response, handler) {
6529
+ async function parseSSEStream(response, handler, onRead, signal) {
6365
6530
  const body = response.body;
6366
6531
  if (!body) throw new Error("Response has no body for SSE parse");
6367
6532
  const reader = body.getReader();
6368
6533
  const decoder = new TextDecoder("utf-8");
6369
6534
  let buffer = "";
6535
+ const abortRace = signal ? new Promise((_resolve, reject) => {
6536
+ if (signal.aborted) return reject(signal.reason);
6537
+ signal.addEventListener("abort", () => reject(signal.reason), { once: true });
6538
+ }) : void 0;
6370
6539
  for (; ; ) {
6371
- const { value, done } = await reader.read();
6540
+ let readResult;
6541
+ try {
6542
+ readResult = abortRace ? await Promise.race([reader.read(), abortRace]) : await reader.read();
6543
+ } catch (err) {
6544
+ void reader.cancel().catch(() => {
6545
+ });
6546
+ throw err;
6547
+ }
6548
+ const { value, done } = readResult;
6372
6549
  if (done) break;
6550
+ onRead?.();
6373
6551
  buffer += decoder.decode(value, { stream: true });
6374
6552
  let sep;
6375
6553
  while (sep = buffer.indexOf("\n\n"), sep !== -1) {
@@ -6480,9 +6658,6 @@ function extractErrorMessage(body) {
6480
6658
  if (typeof b.message === "string") return b.message;
6481
6659
  return void 0;
6482
6660
  }
6483
- function retryableError(status, code, message, raw) {
6484
- return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
6485
- }
6486
6661
 
6487
6662
  // src/execute.ts
6488
6663
  var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
@@ -6527,7 +6702,9 @@ async function executeAnthropic(request, opts) {
6527
6702
  if (opts.onChunk) {
6528
6703
  return streamAnthropic(request, apiKey, {
6529
6704
  onChunk: opts.onChunk,
6530
- fetchImpl: opts.fetchImpl
6705
+ fetchImpl: opts.fetchImpl,
6706
+ signal: opts.signal,
6707
+ stallTimeoutMs: opts.stallTimeoutMs
6531
6708
  });
6532
6709
  }
6533
6710
  const { provider: _provider, ...body } = request;
@@ -6542,11 +6719,13 @@ async function executeAnthropic(request, opts) {
6542
6719
  "anthropic-version": "2023-06-01",
6543
6720
  "content-type": "application/json"
6544
6721
  },
6545
- body: JSON.stringify(body)
6722
+ body: JSON.stringify(body),
6723
+ signal: opts.signal
6546
6724
  });
6547
6725
  json = await res.json().catch(() => ({}));
6548
6726
  } catch (err) {
6549
- return retryableError2(0, "network_error", String(err), null);
6727
+ const c = classifyThrownFetchError(err);
6728
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6550
6729
  }
6551
6730
  if (!res.ok) return classifyHttpError2(res.status, json);
6552
6731
  return { ok: true, status: res.status, response: normalizeAnthropic(json) };
@@ -6578,11 +6757,13 @@ async function executeGoogle(request, opts) {
6578
6757
  res = await fetchFn(url, {
6579
6758
  method: "POST",
6580
6759
  headers: { "content-type": "application/json" },
6581
- body: JSON.stringify(body)
6760
+ body: JSON.stringify(body),
6761
+ signal: opts.signal
6582
6762
  });
6583
6763
  json = await res.json().catch(() => ({}));
6584
6764
  } catch (err) {
6585
- return retryableError2(0, "network_error", String(err), null);
6765
+ const c = classifyThrownFetchError(err);
6766
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6586
6767
  }
6587
6768
  if (!res.ok) return classifyHttpError2(res.status, json);
6588
6769
  return { ok: true, status: res.status, response: normalizeGoogle(json) };
@@ -6614,7 +6795,9 @@ async function executeOpenAI(request, opts) {
6614
6795
  if (opts.onChunk) {
6615
6796
  return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
6616
6797
  onChunk: opts.onChunk,
6617
- fetchImpl: opts.fetchImpl
6798
+ fetchImpl: opts.fetchImpl,
6799
+ signal: opts.signal,
6800
+ stallTimeoutMs: opts.stallTimeoutMs
6618
6801
  });
6619
6802
  }
6620
6803
  const { provider: _provider, ...body } = request;
@@ -6625,11 +6808,13 @@ async function executeOpenAI(request, opts) {
6625
6808
  res = await fetchFn(OPENAI_URL, {
6626
6809
  method: "POST",
6627
6810
  headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
6628
- body: JSON.stringify(body)
6811
+ body: JSON.stringify(body),
6812
+ signal: opts.signal
6629
6813
  });
6630
6814
  json = await res.json().catch(() => ({}));
6631
6815
  } catch (err) {
6632
- return retryableError2(0, "network_error", String(err), null);
6816
+ const c = classifyThrownFetchError(err);
6817
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6633
6818
  }
6634
6819
  if (!res.ok) return classifyHttpError2(res.status, json);
6635
6820
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
@@ -6642,7 +6827,9 @@ async function executeDeepSeek(request, opts) {
6642
6827
  if (opts.onChunk) {
6643
6828
  return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
6644
6829
  onChunk: opts.onChunk,
6645
- fetchImpl: opts.fetchImpl
6830
+ fetchImpl: opts.fetchImpl,
6831
+ signal: opts.signal,
6832
+ stallTimeoutMs: opts.stallTimeoutMs
6646
6833
  });
6647
6834
  }
6648
6835
  const { provider: _provider, ...body } = request;
@@ -6653,11 +6840,13 @@ async function executeDeepSeek(request, opts) {
6653
6840
  res = await fetchFn(DEEPSEEK_URL, {
6654
6841
  method: "POST",
6655
6842
  headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
6656
- body: JSON.stringify(body)
6843
+ body: JSON.stringify(body),
6844
+ signal: opts.signal
6657
6845
  });
6658
6846
  json = await res.json().catch(() => ({}));
6659
6847
  } catch (err) {
6660
- return retryableError2(0, "network_error", String(err), null);
6848
+ const c = classifyThrownFetchError(err);
6849
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6661
6850
  }
6662
6851
  if (!res.ok) return classifyHttpError2(res.status, json);
6663
6852
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
@@ -6670,7 +6859,9 @@ async function executeOpenAICompatible(request, opts, spec) {
6670
6859
  if (opts.onChunk) {
6671
6860
  return streamOpenAILike(spec.url, request, apiKey, spec.provider, {
6672
6861
  onChunk: opts.onChunk,
6673
- fetchImpl: opts.fetchImpl
6862
+ fetchImpl: opts.fetchImpl,
6863
+ signal: opts.signal,
6864
+ stallTimeoutMs: opts.stallTimeoutMs
6674
6865
  });
6675
6866
  }
6676
6867
  const { provider: _provider, ...body } = request;
@@ -6681,11 +6872,13 @@ async function executeOpenAICompatible(request, opts, spec) {
6681
6872
  res = await fetchFn(spec.url, {
6682
6873
  method: "POST",
6683
6874
  headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
6684
- body: JSON.stringify(body)
6875
+ body: JSON.stringify(body),
6876
+ signal: opts.signal
6685
6877
  });
6686
6878
  json = await res.json().catch(() => ({}));
6687
6879
  } catch (err) {
6688
- return retryableError2(0, "network_error", String(err), null);
6880
+ const c = classifyThrownFetchError(err);
6881
+ return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
6689
6882
  }
6690
6883
  if (!res.ok) return classifyHttpError2(res.status, json);
6691
6884
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
@@ -6755,9 +6948,6 @@ function extractErrorMessage2(body) {
6755
6948
  function terminalError(status, code, message) {
6756
6949
  return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
6757
6950
  }
6758
- function retryableError2(status, code, message, raw) {
6759
- return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
6760
- }
6761
6951
  function tryParseJson2(s) {
6762
6952
  if (typeof s !== "string" || s.length === 0) return void 0;
6763
6953
  try {
@@ -7566,6 +7756,15 @@ async function call(ir, opts = {}) {
7566
7756
  let retriedSameModel = false;
7567
7757
  for (let i = 0; i < targetsToTry.length; i++) {
7568
7758
  const targetModel = targetsToTry[i];
7759
+ if (opts.abortSignal?.aborted) {
7760
+ attempts.push({
7761
+ model: targetModel,
7762
+ status: "terminal",
7763
+ errorCode: "aborted",
7764
+ message: "Skipped \u2014 caller abortSignal fired before this attempt started"
7765
+ });
7766
+ break;
7767
+ }
7569
7768
  const targetProfile = tryGetProfile(targetModel);
7570
7769
  const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
7571
7770
  if (targetProfile && providerFailReason && !opts.noFallback) {
@@ -7602,16 +7801,29 @@ async function call(ir, opts = {}) {
7602
7801
  );
7603
7802
  const targetSupportsStreaming = targetProfile?.streaming === true;
7604
7803
  const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
7605
- const execOpts = {
7804
+ const mkExecOpts = () => ({
7606
7805
  apiKeys: opts.apiKeys,
7607
7806
  fetchImpl: opts.fetchImpl,
7608
7807
  providerOverrides: opts.providerOverrides,
7609
- onChunk: streamingOnChunk
7610
- };
7611
- const exec = await execute(activeCompile.request, execOpts);
7808
+ onChunk: streamingOnChunk,
7809
+ signal: composeAttemptSignal(
7810
+ streamingOnChunk ? void 0 : opts.attemptTimeoutMs,
7811
+ opts.abortSignal
7812
+ ),
7813
+ stallTimeoutMs: streamingOnChunk ? opts.attemptTimeoutMs : void 0
7814
+ });
7815
+ const exec = await execute(activeCompile.request, mkExecOpts());
7612
7816
  let validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
7613
7817
  let servedByRetry = false;
7614
- if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel) {
7818
+ let retrySuppressionNote;
7819
+ if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && getMeasuredFailureVerdict({
7820
+ appId: ir.appId,
7821
+ archetype: ir.intent.archetype,
7822
+ model: targetModel
7823
+ })?.gated === true) {
7824
+ retrySuppressionNote = " [sameModelRetry suppressed: this model carries an active measured-failure gate for this archetype \u2014 retrying it would near-certainly bill a second doomed inference; walking the chain instead]";
7825
+ }
7826
+ if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
7615
7827
  retriedSameModel = true;
7616
7828
  attempts.push({
7617
7829
  model: targetModel,
@@ -7623,7 +7835,7 @@ async function call(ir, opts = {}) {
7623
7835
  safeEmit(
7624
7836
  () => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
7625
7837
  );
7626
- const retryExec = await execute(retryRequest, execOpts);
7838
+ const retryExec = await execute(retryRequest, mkExecOpts());
7627
7839
  validated = retryExec.ok ? validateStructuredContract(retryExec, ir) : retryExec;
7628
7840
  servedByRetry = true;
7629
7841
  }
@@ -7750,7 +7962,7 @@ async function call(ir, opts = {}) {
7750
7962
  model: targetModel,
7751
7963
  status: validated.errorType,
7752
7964
  errorCode: validated.errorCode,
7753
- message: validated.message,
7965
+ message: retrySuppressionNote ? validated.message + retrySuppressionNote : validated.message,
7754
7966
  ...servedByRetry ? { sameModelRetry: true } : {}
7755
7967
  });
7756
7968
  lastErr = validated;
@@ -7789,6 +8001,25 @@ async function call(ir, opts = {}) {
7789
8001
  lastErr?.errorCode
7790
8002
  );
7791
8003
  }
8004
+ function composeAttemptSignal(attemptTimeoutMs, callerSignal) {
8005
+ const signals = [];
8006
+ if (callerSignal) signals.push(callerSignal);
8007
+ if (typeof attemptTimeoutMs === "number" && attemptTimeoutMs > 0) {
8008
+ signals.push(AbortSignal.timeout(attemptTimeoutMs));
8009
+ }
8010
+ if (signals.length === 0) return void 0;
8011
+ if (signals.length === 1) return signals[0];
8012
+ if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
8013
+ const ctl = new AbortController();
8014
+ for (const s of signals) {
8015
+ if (s.aborted) {
8016
+ ctl.abort(s.reason);
8017
+ break;
8018
+ }
8019
+ s.addEventListener("abort", () => ctl.abort(s.reason), { once: true });
8020
+ }
8021
+ return ctl.signal;
8022
+ }
7792
8023
  function compileAndRegister(ir, opts) {
7793
8024
  const result = compile(ir, {
7794
8025
  policy: opts.policy,
@@ -8843,6 +9074,12 @@ async function runGoldenEval(opts) {
8843
9074
  );
8844
9075
  }
8845
9076
  }
9077
+ if (wins + ties + losses === 0) {
9078
+ notes.push(
9079
+ "zero cases judged \u2014 run row persisted, evidence advisory deliberately NOT written (a verdict on n=0 is not evidence)"
9080
+ );
9081
+ return result;
9082
+ }
8846
9083
  const latestRes = await fetchFn(
8847
9084
  rest(
8848
9085
  `compile_outcomes?app_id=eq.${encodeURIComponent(opts.appId)}&select=id&order=id.desc&limit=1`
@@ -9078,7 +9315,7 @@ function createBrainForwardRoutes(config) {
9078
9315
  }
9079
9316
 
9080
9317
  // src/version.ts
9081
- var LIBRARY_VERSION = "2.0.0-alpha.77";
9318
+ var LIBRARY_VERSION = "2.0.0-alpha.79";
9082
9319
 
9083
9320
  // src/key-health.ts
9084
9321
  var JSON_HEADERS2 = { "Content-Type": "application/json" };
@@ -9364,6 +9601,171 @@ function clamp(n) {
9364
9601
  return Math.max(0, Math.min(1, n));
9365
9602
  }
9366
9603
 
9604
+ // src/delegate.ts
9605
+ function isDelegateEnabledFromEnv(envSource) {
9606
+ const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
9607
+ const raw = (env.KGAUTO_DELEGATE ?? "").trim().toLowerCase();
9608
+ return raw === "1" || raw === "true";
9609
+ }
9610
+ var DELEGATE_TOOL_DEFINITION = {
9611
+ name: "delegate",
9612
+ description: "Delegate a self-contained sub-task to a cheaper executor model chosen by kgauto from measured evidence. YOU decide what to delegate and supply a complete input; kgauto decides which model runs it (never outside the declared pool). The call may be refused (budget exhausted, no qualified executor) \u2014 on refusal, continue and compose with what you have. Verify sub-results before composing them into your answer; do not present unverified delegated content as checked.",
9613
+ inputSchema: {
9614
+ type: "object",
9615
+ properties: {
9616
+ sub_archetype: {
9617
+ type: "string",
9618
+ enum: ALL_ARCHETYPES,
9619
+ description: "What kind of work the sub-task is (its routing archetype)."
9620
+ },
9621
+ input: {
9622
+ type: "string",
9623
+ description: "Complete, self-contained sub-task input. Include everything the executor needs \u2014 it sees nothing else."
9624
+ },
9625
+ quality_floor: {
9626
+ type: "number",
9627
+ minimum: 0,
9628
+ maximum: 10,
9629
+ description: "Optional minimum executor quality score (0-10) for this archetype. Omit to accept the evidence-ranked default."
9630
+ }
9631
+ },
9632
+ required: ["sub_archetype", "input"],
9633
+ additionalProperties: false
9634
+ }
9635
+ };
9636
+ function createDelegate(opts) {
9637
+ const { parentIr, parentHandle } = opts;
9638
+ const enabled = opts.enabled ?? isDelegateEnabledFromEnv();
9639
+ const budget = opts.callOpts?.policy?.maxCostPerTraceUsd;
9640
+ let spentUsd = 0;
9641
+ async function handler(args) {
9642
+ if (!enabled) {
9643
+ return {
9644
+ ok: false,
9645
+ reason: "delegate_not_enabled",
9646
+ detail: "Delegation is not enabled for this consumer (KGAUTO_DELEGATE / CreateDelegateOpts.enabled). Do the sub-task yourself."
9647
+ };
9648
+ }
9649
+ const archetype = args.sub_archetype;
9650
+ if (!ALL_ARCHETYPES.includes(archetype)) {
9651
+ return {
9652
+ ok: false,
9653
+ reason: "invalid_sub_archetype",
9654
+ detail: `Unknown sub_archetype '${String(args.sub_archetype)}'. Valid: ${ALL_ARCHETYPES.join(", ")}. Re-classify or do the sub-task yourself.`
9655
+ };
9656
+ }
9657
+ const pool = parentIr.models;
9658
+ const concreteIds = pool.filter((m) => typeof m === "string");
9659
+ let blockedByFloor = [];
9660
+ if (typeof args.quality_floor === "number") {
9661
+ blockedByFloor = concreteIds.filter(
9662
+ (m) => getArchetypePerfScore(m, archetype).score < args.quality_floor
9663
+ );
9664
+ if (blockedByFloor.length === pool.length) {
9665
+ const scores = concreteIds.map((m) => `${m}=${getArchetypePerfScore(m, archetype).score}`).join(", ");
9666
+ return {
9667
+ ok: false,
9668
+ reason: "no_qualified_executor",
9669
+ detail: `No model in the declared pool clears quality_floor=${args.quality_floor} for '${archetype}' (${scores}). Lower the floor or do the sub-task yourself.`
9670
+ };
9671
+ }
9672
+ }
9673
+ const subIr = {
9674
+ appId: parentIr.appId,
9675
+ intent: { name: `delegate:${archetype}`, archetype },
9676
+ sections: [{ id: "delegated-task", text: args.input }],
9677
+ currentTurn: { role: "user", content: args.input },
9678
+ models: pool
9679
+ };
9680
+ const mergedPolicy = {
9681
+ ...opts.callOpts?.policy ?? {},
9682
+ blockedModels: [
9683
+ ...opts.callOpts?.policy?.blockedModels ?? [],
9684
+ ...blockedByFloor
9685
+ ]
9686
+ };
9687
+ if (typeof budget === "number" && budget > 0) {
9688
+ let estimate = 0;
9689
+ try {
9690
+ estimate = compile(subIr, { policy: mergedPolicy }).estimatedCostUsd;
9691
+ } catch {
9692
+ estimate = 0;
9693
+ }
9694
+ if (spentUsd + estimate > budget) {
9695
+ return {
9696
+ ok: false,
9697
+ reason: "trace_budget_exhausted",
9698
+ detail: `Trace budget $${budget.toFixed(4)} would be exceeded (spent $${spentUsd.toFixed(4)} + estimated $${estimate.toFixed(4)}). Compose your answer from the sub-results you already have.`
9699
+ };
9700
+ }
9701
+ }
9702
+ let result;
9703
+ try {
9704
+ result = await call(subIr, {
9705
+ ...opts.callOpts ?? {},
9706
+ policy: mergedPolicy,
9707
+ parentHandle
9708
+ // R0 linkage — branch row, trace_id = parent
9709
+ });
9710
+ } catch (err) {
9711
+ const detail = err instanceof CallError ? `Sub-call failed after ${err.attempts.length} attempt(s): ${err.attempts.map((a) => a.errorCode).join(" \u2192 ")}.` : String(err);
9712
+ return {
9713
+ ok: false,
9714
+ reason: "call_failed",
9715
+ detail: `${detail} Compose without this sub-result or retry with different input.`
9716
+ };
9717
+ }
9718
+ const profile = tryGetProfile(result.actualModel);
9719
+ const costUsd2 = profile ? result.response.tokens.input / 1e6 * profile.costInputPer1m + result.response.tokens.output / 1e6 * profile.costOutputPer1m : 0;
9720
+ spentUsd += costUsd2;
9721
+ return {
9722
+ ok: true,
9723
+ output: result.response.structuredOutput !== null ? JSON.stringify(result.response.structuredOutput) : result.response.text,
9724
+ subHandle: result.handle,
9725
+ executorModel: result.actualModel,
9726
+ costUsd: costUsd2,
9727
+ latencyMs: result.latencyMs,
9728
+ verification: "trusted"
9729
+ };
9730
+ }
9731
+ async function reportComposition(report) {
9732
+ const env = readBrainReadEnv();
9733
+ if (!env.endpoint || !env.jwt || !env.anonKey) {
9734
+ return { ok: false, reason: `brain_read_not_configured:${env.missingEnv.join(",")}` };
9735
+ }
9736
+ try {
9737
+ const res = await fetch(
9738
+ `${env.endpoint.replace(/\/$/, "")}/rest/v1/kgauto_composition_reports`,
9739
+ {
9740
+ method: "POST",
9741
+ headers: {
9742
+ Authorization: `Bearer ${env.jwt}`,
9743
+ apikey: env.anonKey,
9744
+ "Content-Type": "application/json",
9745
+ Prefer: "return=minimal"
9746
+ },
9747
+ body: JSON.stringify({
9748
+ app_id: parentIr.appId,
9749
+ sub_handle: report.subHandle,
9750
+ disposition: report.disposition,
9751
+ ...report.note ? { note: report.note } : {}
9752
+ })
9753
+ }
9754
+ );
9755
+ if (!res.ok) return { ok: false, reason: `write_failed:${res.status}` };
9756
+ return { ok: true };
9757
+ } catch (err) {
9758
+ return { ok: false, reason: `network_error:${err instanceof Error ? err.message : String(err)}` };
9759
+ }
9760
+ }
9761
+ return {
9762
+ toolDefinition: DELEGATE_TOOL_DEFINITION,
9763
+ handler,
9764
+ reportComposition,
9765
+ traceSpendUsd: () => spentUsd
9766
+ };
9767
+ }
9768
+
9367
9769
  // src/advisories-api.ts
9368
9770
  var SEVERITY_SET = /* @__PURE__ */ new Set(["info", "warn", "critical"]);
9369
9771
  var STATUS_SET = /* @__PURE__ */ new Set(["open", "snoozed", "resolved"]);
@@ -9435,19 +9837,32 @@ function resolveFetch(injected) {
9435
9837
  function normalizeEndpoint2(endpoint) {
9436
9838
  return endpoint.replace(/\/+$/, "");
9437
9839
  }
9840
+ function resolveBrainReadTrio(opts) {
9841
+ const env = readBrainReadEnv();
9842
+ const brainEndpoint = opts.brainEndpoint ?? env.endpoint;
9843
+ const brainJwt = opts.brainJwt ?? env.jwt;
9844
+ const brainAnonKey = opts.brainAnonKey ?? env.anonKey;
9845
+ if (brainEndpoint && brainJwt && brainAnonKey) {
9846
+ return { ok: true, brainEndpoint, brainJwt, brainAnonKey };
9847
+ }
9848
+ const missingEnv = [];
9849
+ if (!brainEndpoint) missingEnv.push("KGAUTO_V2_BRAIN_SUPABASE_URL");
9850
+ if (!brainJwt) missingEnv.push("KGAUTO_V2_BRAIN_JWT");
9851
+ if (!brainAnonKey) missingEnv.push("KGAUTO_V2_BRAIN_ANON_KEY");
9852
+ return { ok: false, missingEnv };
9853
+ }
9438
9854
  async function getActionableAdvisories(opts) {
9439
- const {
9440
- appId,
9441
- severity,
9442
- status,
9443
- brainEndpoint,
9444
- brainJwt,
9445
- brainAnonKey,
9446
- fetch: injectedFetch
9447
- } = opts;
9855
+ const { appId, severity, status, fetch: injectedFetch } = opts;
9448
9856
  if (!appId) {
9449
9857
  throw new Error("getActionableAdvisories: appId is required");
9450
9858
  }
9859
+ const trio = resolveBrainReadTrio(opts);
9860
+ if (!trio.ok) {
9861
+ throw new Error(
9862
+ `getActionableAdvisories: brain-read-not-configured \u2014 missing ${trio.missingEnv.join(", ")} (pass brainEndpoint/brainJwt/brainAnonKey explicitly or set the canonical env vars)`
9863
+ );
9864
+ }
9865
+ const { brainEndpoint, brainJwt, brainAnonKey } = trio;
9451
9866
  const doFetch = resolveFetch(injectedFetch);
9452
9867
  const base = normalizeEndpoint2(brainEndpoint);
9453
9868
  const qs = new URLSearchParams();
@@ -9500,17 +9915,15 @@ async function getActionableAdvisories(opts) {
9500
9915
  return out;
9501
9916
  }
9502
9917
  async function markAdvisoryResolved(opts) {
9503
- const {
9504
- id,
9505
- resolutionNote,
9506
- brainEndpoint,
9507
- brainJwt,
9508
- brainAnonKey,
9509
- fetch: injectedFetch
9510
- } = opts;
9918
+ const { id, resolutionNote, fetch: injectedFetch } = opts;
9511
9919
  if (!id) {
9512
9920
  return { ok: false, reason: "id_required" };
9513
9921
  }
9922
+ const trio = resolveBrainReadTrio(opts);
9923
+ if (!trio.ok) {
9924
+ return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
9925
+ }
9926
+ const { brainEndpoint, brainJwt, brainAnonKey } = trio;
9514
9927
  const doFetch = resolveFetch(injectedFetch);
9515
9928
  const base = normalizeEndpoint2(brainEndpoint);
9516
9929
  const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
@@ -9595,7 +10008,7 @@ async function markAdvisoryResolved(opts) {
9595
10008
  }
9596
10009
  }
9597
10010
  if (outcomeIds.length === 0) {
9598
- return { ok: true };
10011
+ return { ok: true, firingsResolved: 0, status: "unknown" };
9599
10012
  }
9600
10013
  const inList = outcomeIds.join(",");
9601
10014
  const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
@@ -9615,8 +10028,10 @@ async function markAdvisoryResolved(opts) {
9615
10028
  apikey: brainAnonKey,
9616
10029
  "Content-Type": "application/json",
9617
10030
  Accept: "application/json",
9618
- // PostgREST default is no return; we don't need the row back.
9619
- Prefer: "return=minimal"
10031
+ // alpha.78 count what actually changed. An RLS-filtered PATCH
10032
+ // returns 2xx with zero rows; return=representation makes that
10033
+ // visible instead of success-shaped.
10034
+ Prefer: "return=representation"
9620
10035
  },
9621
10036
  body: JSON.stringify(patchBody)
9622
10037
  });
@@ -9633,7 +10048,33 @@ async function markAdvisoryResolved(opts) {
9633
10048
  if (!patchRes.ok) {
9634
10049
  return { ok: false, reason: `patch_failed:${patchRes.status}` };
9635
10050
  }
9636
- return { ok: true };
10051
+ let patchedRows = [];
10052
+ try {
10053
+ patchedRows = await patchRes.json();
10054
+ } catch {
10055
+ }
10056
+ const firingsResolved = Array.isArray(patchedRows) ? patchedRows.length : 0;
10057
+ let status = "unknown";
10058
+ try {
10059
+ const statusRes = await doFetch(
10060
+ `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=status`,
10061
+ {
10062
+ method: "GET",
10063
+ headers: {
10064
+ Authorization: `Bearer ${brainJwt}`,
10065
+ apikey: brainAnonKey,
10066
+ Accept: "application/json"
10067
+ }
10068
+ }
10069
+ );
10070
+ if (statusRes.ok) {
10071
+ const rows = await statusRes.json();
10072
+ const s = Array.isArray(rows) ? rows[0]?.status : void 0;
10073
+ if (s === "open" || s === "resolved") status = s;
10074
+ }
10075
+ } catch {
10076
+ }
10077
+ return { ok: true, firingsResolved, status };
9637
10078
  }
9638
10079
  async function markExclusionFindingHandled(opts) {
9639
10080
  const {
@@ -9642,12 +10083,14 @@ async function markExclusionFindingHandled(opts) {
9642
10083
  excludedModel,
9643
10084
  resolution,
9644
10085
  resolutionNote,
9645
- brainEndpoint,
9646
- brainJwt,
9647
- brainAnonKey,
9648
10086
  fetch: injectedFetch
9649
10087
  } = opts;
9650
10088
  if (!appId) return { ok: false, reason: "app_id_required" };
10089
+ const trio = resolveBrainReadTrio(opts);
10090
+ if (!trio.ok) {
10091
+ return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
10092
+ }
10093
+ const { brainEndpoint, brainJwt, brainAnonKey } = trio;
9651
10094
  if (!archetype) return { ok: false, reason: "archetype_required" };
9652
10095
  if (!excludedModel) {
9653
10096
  return { ok: false, reason: "excluded_model_required" };
@@ -9914,6 +10357,7 @@ function compile2(ir, opts) {
9914
10357
  ALL_ARCHETYPES,
9915
10358
  ARCHETYPE_FAMILY_FITS,
9916
10359
  ARCHETYPE_FLOOR_DEFAULT,
10360
+ BRAIN_READ_ENV_NAMES,
9917
10361
  COACH_CFG,
9918
10362
  CallError,
9919
10363
  DECOMPOSITION_TEMPLATES,
@@ -9921,6 +10365,7 @@ function compile2(ir, opts) {
9921
10365
  DEFAULT_FINDINGS_ENDPOINT,
9922
10366
  DEFAULT_MEASURED_FAILURE_ENDPOINT,
9923
10367
  DEFAULT_PROMOTIONS_ENDPOINT,
10368
+ DELEGATE_TOOL_DEFINITION,
9924
10369
  DIALECT_VERSION,
9925
10370
  DISCIPLINE_GATES_V1_ALT_HEADER,
9926
10371
  FamilyResolutionError,
@@ -9932,6 +10377,7 @@ function compile2(ir, opts) {
9932
10377
  MEASURED_GROUNDING_MIN_N,
9933
10378
  PRODUCER_OWNED_RULE_CODES,
9934
10379
  PROVIDER_ENV_KEYS,
10380
+ ROLLBACK_SUPPRESSION_WINDOW_DAYS,
9935
10381
  RULE_DISCIPLINE_GATES_V1,
9936
10382
  RULE_DISCIPLINE_GATES_V1_STRUCTURED,
9937
10383
  RULE_SEQUENTIAL_TOOL_CLIFF,
@@ -9967,6 +10413,7 @@ function compile2(ir, opts) {
9967
10413
  configurePromotionsBrain,
9968
10414
  countTokens,
9969
10415
  createBrainForwardRoutes,
10416
+ createDelegate,
9970
10417
  createKeyHealthRoute,
9971
10418
  deriveFamilyFromModelId,
9972
10419
  deriveOwnership,
@@ -9985,17 +10432,20 @@ function compile2(ir, opts) {
9985
10432
  getPerAxisMetrics,
9986
10433
  getProfile,
9987
10434
  getReachabilityDiagnostic,
10435
+ getRecentRollback,
9988
10436
  getRecommendedPrimary,
9989
10437
  getSequentialStarterChain,
9990
10438
  getSequentialStarterChainWithGrounding,
9991
10439
  getStaleExclusionFindings,
9992
10440
  getStarterChain,
9993
10441
  getStarterChainWithGrounding,
10442
+ hasMutation,
9994
10443
  hashShape,
9995
10444
  isArchetype,
9996
10445
  isAutoPromoteEnabledFromEnv,
9997
10446
  isBrainQueryActiveFor,
9998
10447
  isBrainSync,
10448
+ isDelegateEnabledFromEnv,
9999
10449
  isExclusionFindingsBrainActive,
10000
10450
  isMeasuredFailureBrainActive,
10001
10451
  isMeasuredFailureGateEnabledFromEnv,
@@ -10011,9 +10461,11 @@ function compile2(ir, opts) {
10011
10461
  loadChainsFromBrain,
10012
10462
  loadModelsFromBrain,
10013
10463
  loadPricingFromBrain,
10464
+ mapMeasuredFailureRows,
10014
10465
  markAdvisoryResolved,
10015
10466
  markExclusionFindingHandled,
10016
10467
  markPromoteReadyHandled,
10468
+ mutationId,
10017
10469
  parseGoldenCaptureRate,
10018
10470
  parseJudgeVerdict,
10019
10471
  peekBrainDeadLetter,