@warmdrift/kgauto-compiler 2.0.0-alpha.83 → 2.0.0-alpha.86

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
@@ -27,6 +27,8 @@ __export(index_exports, {
27
27
  ALT_STRATEGY_IDS: () => ALT_STRATEGY_IDS,
28
28
  ARCHETYPE_FAMILY_FITS: () => ARCHETYPE_FAMILY_FITS,
29
29
  ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
30
+ BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE: () => BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE,
31
+ BLOCKED_MODEL_NOT_IN_ROSTER_CODE: () => BLOCKED_MODEL_NOT_IN_ROSTER_CODE,
30
32
  BRAIN_READ_ENV_NAMES: () => BRAIN_READ_ENV_NAMES,
31
33
  BURST_SPAN_MS: () => BURST_SPAN_MS,
32
34
  COACH_CFG: () => COACH_CFG,
@@ -70,6 +72,7 @@ __export(index_exports, {
70
72
  applySectionRewrites: () => applySectionRewrites,
71
73
  attachCacheControlToStreamTextInput: () => attachCacheControlToStreamTextInput,
72
74
  awaitMeasuredFailureReady: () => awaitMeasuredFailureReady,
75
+ awaitPromotionsReady: () => awaitPromotionsReady,
73
76
  brainHealth: () => brainHealth,
74
77
  bucketContext: () => bucketContext,
75
78
  bucketHistory: () => bucketHistory,
@@ -156,6 +159,7 @@ __export(index_exports, {
156
159
  peekBrainDeadLetter: () => peekBrainDeadLetter,
157
160
  planDecomposition: () => planDecomposition,
158
161
  prefetchMeasuredFailure: () => prefetchMeasuredFailure,
162
+ prefetchPromotions: () => prefetchPromotions,
159
163
  probeShadow: () => probeShadow,
160
164
  profileToRow: () => profileToRow,
161
165
  profilesByProvider: () => profilesByProvider,
@@ -168,6 +172,7 @@ __export(index_exports, {
168
172
  resetTokenizer: () => resetTokenizer,
169
173
  resolveConventionsForProfile: () => resolveConventionsForProfile,
170
174
  resolveGoldenCaptureRate: () => resolveGoldenCaptureRate,
175
+ resolveModelAlias: () => resolveModelAlias,
171
176
  resolvePricingAt: () => resolvePricingAt,
172
177
  resolveProviderKey: () => resolveProviderKey,
173
178
  rowToAdvisory: () => rowToAdvisory,
@@ -969,9 +974,21 @@ var PROFILES_RAW = [
969
974
  },
970
975
  {
971
976
  id: "gemini-2.5-pro",
972
- verifiedAgainstDocs: "2026-05-08",
977
+ verifiedAgainstDocs: "2026-08-08",
973
978
  provider: "google",
974
- status: "current",
979
+ // alpha.86 — AVAILABILITY demotion, not a provider-lifecycle mirror.
980
+ // Google's docs still list 2.5-pro as Stable, but the serving layer
981
+ // disagrees per account cohort: tt-intel measured 4/4 refusals
982
+ // ("no longer available to new users", 2026-08-02, their bench), and
983
+ // kgauto's own key 429s on a FreeTier per-day quota (probed
984
+ // 2026-08-08). A chain anchor that hard-fails for whole account
985
+ // cohorts is not an anchor; `legacy` removes it from cost-ordered
986
+ // chains and new selection while keeping the profile resolvable for
987
+ // consumers who pass it explicitly. Re-promotion is one status flip
988
+ // if Google restores availability. The registry cannot yet express
989
+ // account-cohort availability as data — see interfaces/kgauto.md
990
+ // ## Requested 2026-08-02.
991
+ status: "legacy",
975
992
  maxContextTokens: 1048576,
976
993
  maxOutputTokens: 65535,
977
994
  maxTools: 128,
@@ -1001,7 +1018,7 @@ var PROFILES_RAW = [
1001
1018
  }
1002
1019
  ],
1003
1020
  strengths: ["reasoning", "1m_context", "structured_output", "tool_use"],
1004
- weaknesses: ["pricing_above_200k"],
1021
+ weaknesses: ["pricing_above_200k", "availability_restricted_by_account_cohort"],
1005
1022
  // Master plan §3.3 anchor: tier-2 cross-provider in almost every chain.
1006
1023
  // Sits on the frontier at perf-9 — close to Sonnet but cheaper input.
1007
1024
  archetypePerf: {
@@ -2024,6 +2041,9 @@ function _setProfileBrainHook(hook) {
2024
2041
  function canonicalId(id) {
2025
2042
  return brainHook.resolveAlias?.(id) ?? ALIASES[id] ?? id;
2026
2043
  }
2044
+ function resolveModelAlias(id) {
2045
+ return canonicalId(id);
2046
+ }
2027
2047
  var PROFILE_INDEX = new Map(
2028
2048
  PROFILES_RAW.map((p) => [p.id, p])
2029
2049
  );
@@ -2523,6 +2543,17 @@ function resolveFamilyEntry(family, ctx) {
2523
2543
  return winner.id;
2524
2544
  }
2525
2545
 
2546
+ // src/policy-match.ts
2547
+ function canonicalPolicySet(ids) {
2548
+ const set = /* @__PURE__ */ new Set();
2549
+ for (const id of ids ?? []) set.add(resolveModelAlias(id));
2550
+ return set;
2551
+ }
2552
+ function policySetHas(set, modelId) {
2553
+ if (set.size === 0) return false;
2554
+ return set.has(resolveModelAlias(modelId));
2555
+ }
2556
+
2526
2557
  // src/tokenizer.ts
2527
2558
  var tokenizerImpl = defaultCharBasedCounter;
2528
2559
  function defaultCharBasedCounter(text) {
@@ -2831,9 +2862,12 @@ function passApplyCliffs(ir, profile, estimatedInputTokens) {
2831
2862
  if (!triggered) continue;
2832
2863
  switch (cliff.action) {
2833
2864
  case "drop_to_top_relevant": {
2834
- const targetCount = Math.min(
2835
- Math.floor(cliff.threshold * 0.75),
2836
- Math.max(1, Math.floor((nextIR.tools?.length ?? 0) / 2))
2865
+ const targetCount = Math.max(
2866
+ 1,
2867
+ Math.min(
2868
+ Math.floor(cliff.threshold * 0.75),
2869
+ Math.floor((nextIR.tools?.length ?? 0) / 2)
2870
+ )
2837
2871
  );
2838
2872
  if (nextIR.tools && nextIR.tools.length > targetCount) {
2839
2873
  const intent = nextIR.intent.archetype;
@@ -2918,8 +2952,8 @@ function effectiveConventions(profile) {
2918
2952
  function passScoreTargets(ir, opts) {
2919
2953
  const constraints = ir.constraints ?? {};
2920
2954
  const policy = opts.policy ?? {};
2921
- const blockedSet = new Set(policy.blockedModels ?? []);
2922
- const preferredSet = new Set(policy.preferredModels ?? []);
2955
+ const blockedSet = canonicalPolicySet(policy.blockedModels);
2956
+ const preferredSet = canonicalPolicySet(policy.preferredModels);
2923
2957
  const scores = [];
2924
2958
  const policyMutations = [];
2925
2959
  const rawPromotion = opts.promotion;
@@ -2942,7 +2976,7 @@ function passScoreTargets(ir, opts) {
2942
2976
  continue;
2943
2977
  }
2944
2978
  const reasons = [];
2945
- if (blockedSet.has(modelId)) {
2979
+ if (policySetHas(blockedSet, modelId)) {
2946
2980
  reasons.push(`blocked_by_policy (consumer gated this model \u2014 see CompilePolicy.blockedModels)`);
2947
2981
  }
2948
2982
  if (opts.estimatedInputTokens > profile.maxContextTokens * 0.9) {
@@ -2972,7 +3006,7 @@ function passScoreTargets(ir, opts) {
2972
3006
  const qualityScore = Math.max(0, baseQuality - qualityPenalty);
2973
3007
  const callerOrderBoost = (modelIds.length - modelIds.indexOf(modelId)) * 0.1;
2974
3008
  const costPenalty = estimatedCostUsd * 5;
2975
- const preferredBoost = preferredSet.has(modelId) ? 0.5 : 0;
3009
+ const preferredBoost = policySetHas(preferredSet, modelId) ? 0.5 : 0;
2976
3010
  let latencyPenalty = 0;
2977
3011
  const maxLatencyMs = constraints.maxLatencyMs;
2978
3012
  if (typeof maxLatencyMs === "number" && maxLatencyMs > 0) {
@@ -3029,7 +3063,7 @@ function passScoreTargets(ir, opts) {
3029
3063
  description: `Model ${modelId} excluded \u2014 estimated cost $${estimatedCostUsd.toFixed(4)} exceeds policy ceiling $${policy.maxCostPerCallUsd.toFixed(4)}`
3030
3064
  });
3031
3065
  }
3032
- if (preferredSet.has(modelId) && reasons.length === 0) {
3066
+ if (policySetHas(preferredSet, modelId) && reasons.length === 0) {
3033
3067
  policyMutations.push({
3034
3068
  id: `policy-preferred-${modelId}`,
3035
3069
  source: "compile_policy",
@@ -3911,8 +3945,14 @@ var STARTER_CHAINS_GROUNDED = {
3911
3945
  critique: [
3912
3946
  { id: "claude-opus-4-7", grounding: "judgment", reason: "Highest reasoning bar, no degradation tier \u2014 engineer pick, awaiting measured backing" },
3913
3947
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Same-provider walk-down from Opus on 429" },
3914
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor in similar quality bracket" },
3915
- { id: "gpt-5.5", grounding: "judgment", reason: "alpha.16: third-provider frontier-tier floor (archetypePerf=9)" }
3948
+ // alpha.86: gemini-2.5-pro dropped availability-restricted (tt-intel
3949
+ // measured 4/4 cohort refusal 2026-08-02; kgauto key free-tier-quota
3950
+ // 429 2026-08-08). No current Google model clears the critique floor
3951
+ // (3.5-flash critique=5; 3.1-pro-preview scores 10 but is preview —
3952
+ // default chains carry promoted models only). deepseek-v4-pro takes the
3953
+ // third-provider tail instead.
3954
+ { id: "gpt-5.5", grounding: "judgment", reason: "alpha.16: third-provider frontier-tier floor (archetypePerf=9)" },
3955
+ { id: "deepseek-v4-pro", grounding: "judgment", reason: "alpha.86: third-provider tail replacing availability-restricted gemini-2.5-pro \u2014 critique=6 clears the floor exactly; last-resort when Anthropic AND OpenAI are down, not a quality peer of the tiers above" }
3916
3956
  ],
3917
3957
  // alpha.62 (eval spine) — pairwise output comparison. Same reasoning-floor
3918
3958
  // posture as critique (a verdict from a weak judge is worse than no
@@ -3921,14 +3961,16 @@ var STARTER_CHAINS_GROUNDED = {
3921
3961
  judge: [
3922
3962
  { id: "claude-opus-4-8", grounding: "judgment", reason: "Highest reasoning bar for pairwise verdicts \u2014 current opus (s64 roster)" },
3923
3963
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Same-provider walk-down on 429" },
3924
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor in similar quality bracket" },
3925
- { id: "gpt-5.5", grounding: "judgment", reason: "Third-provider frontier-tier floor" }
3964
+ // alpha.86: gemini-2.5-pro dropped (availability-restricted; see critique
3965
+ // note). gpt-5.5 remains the cross-provider frontier floor for verdicts.
3966
+ { id: "gpt-5.5", grounding: "judgment", reason: "Third-provider frontier-tier floor" },
3967
+ { id: "deepseek-v4-pro", grounding: "judgment", reason: "alpha.86: third-provider tail replacing availability-restricted gemini-2.5-pro \u2014 native structured output per the judge contract; last-resort tier" }
3926
3968
  ],
3927
3969
  // Reasoning matters — Sonnet primary; walk UP to Opus on 429.
3928
3970
  plan: [
3929
3971
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Reasoning + cost balance \u2014 engineer pick" },
3930
3972
  { id: "claude-opus-4-7", grounding: "judgment", reason: 'Same-provider walk-UP on 429 (rare exception to "always cheaper")' },
3931
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor" },
3973
+ { id: "gemini-3.5-flash", grounding: "judgment", reason: "alpha.86: cross-provider anchor \u2014 replaces availability-restricted gemini-2.5-pro (tt-intel 4/4 cohort refusal 2026-08-02; probed serving 200 on kgauto key 2026-08-08)" },
3932
3974
  { id: "deepseek-v4-pro", grounding: "judgment", reason: "Tier 3 cost floor \u2014 no brain evidence yet" },
3933
3975
  { id: "gpt-5.4", grounding: "judgment", reason: "alpha.33: third-provider tail (archetypePerf=7) \u2014 closes openai-in-default-fallback-chains" }
3934
3976
  ],
@@ -3936,7 +3978,7 @@ var STARTER_CHAINS_GROUNDED = {
3936
3978
  generate: [
3937
3979
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Quality + cost match \u2014 engineer pick" },
3938
3980
  { id: "claude-haiku-4-5", grounding: "judgment", reason: "Same-provider step-down" },
3939
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor" },
3981
+ { id: "gemini-3.5-flash", grounding: "judgment", reason: "alpha.86: cross-provider anchor \u2014 replaces availability-restricted gemini-2.5-pro (tt-intel 4/4 cohort refusal 2026-08-02; probed serving 200 on kgauto key 2026-08-08)" },
3940
3982
  { id: "gpt-5.4-mini", grounding: "judgment", reason: "alpha.16: third-provider tail (archetypePerf=7) \u2014 closes mono-Anthropic gap" }
3941
3983
  ],
3942
3984
  // ask::sonnet — STARTER_CHAINS calls this "Quality + cost match" but
@@ -3945,7 +3987,7 @@ var STARTER_CHAINS_GROUNDED = {
3945
3987
  ask: [
3946
3988
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Quality + cost match \u2014 engineer pick. NOTE: tt-intel s78 prod showed 27% empty rate; placement awaits measurement validation" },
3947
3989
  { id: "claude-haiku-4-5", grounding: "judgment", reason: "Same-provider step-down" },
3948
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor" },
3990
+ { id: "gemini-3.5-flash", grounding: "judgment", reason: "alpha.86: cross-provider anchor \u2014 replaces availability-restricted gemini-2.5-pro (tt-intel 4/4 cohort refusal 2026-08-02; probed serving 200 on kgauto key 2026-08-08)" },
3949
3991
  { id: "gpt-5.4-mini", grounding: "judgment", reason: "alpha.16: third-provider tail (archetypePerf=7)" }
3950
3992
  ],
3951
3993
  // Structured-output archetype — Flash skipped (alpha.8 MAX_TOKENS cliff,
@@ -3953,14 +3995,14 @@ var STARTER_CHAINS_GROUNDED = {
3953
3995
  extract: [
3954
3996
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Reliable structured-output anchor \u2014 engineer pick" },
3955
3997
  { id: "claude-haiku-4-5", grounding: "judgment", reason: "Same-provider step-down with native structured output" },
3956
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor with structured-output support" },
3998
+ { id: "gemini-3.5-flash", grounding: "judgment", reason: "alpha.86: cross-provider anchor with native structured output \u2014 replaces availability-restricted gemini-2.5-pro" },
3957
3999
  { id: "gpt-5.4", grounding: "capability-fact", reason: "alpha.16: third-provider floor \u2014 native structured-output capability (archetypePerf=8)" }
3958
4000
  ],
3959
4001
  // Forgiving archetype — Sonnet primary but Flash safely floors it.
3960
4002
  transform: [
3961
4003
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Quality anchor \u2014 engineer pick" },
3962
4004
  { id: "claude-haiku-4-5", grounding: "judgment", reason: "Same-provider step-down" },
3963
- { id: "gemini-2.5-pro", grounding: "judgment", reason: "Cross-provider anchor" },
4005
+ { id: "gemini-3.5-flash", grounding: "judgment", reason: "alpha.86: cross-provider anchor \u2014 replaces availability-restricted gemini-2.5-pro (tt-intel 4/4 cohort refusal 2026-08-02; probed serving 200 on kgauto key 2026-08-08)" },
3964
4006
  { id: "gemini-2.5-flash", grounding: "judgment", reason: "Cost floor \u2014 forgiving archetype tolerates Flash" },
3965
4007
  { id: "gpt-5.4-mini", grounding: "judgment", reason: "alpha.33: third-provider tail (archetypePerf=7) \u2014 closes openai-in-default-fallback-chains" }
3966
4008
  ],
@@ -3968,7 +4010,7 @@ var STARTER_CHAINS_GROUNDED = {
3968
4010
  // (capability-fact: Flash 15-75 parallel calls/step vs DeepSeek 7-8).
3969
4011
  hunt: [
3970
4012
  { id: "gemini-2.5-flash", grounding: "capability-fact", reason: "L-040 parallel-tool throughput champion (15-75 calls/step)" },
3971
- { id: "gemini-2.5-pro", grounding: "capability-fact", reason: "Cross-provider tier 1 with strong parallel-tool support" },
4013
+ { id: "gemini-3.5-flash", grounding: "capability-fact", reason: "alpha.86: parallel-tool agentic tier (hunt archetypePerf 9, L-040 family) \u2014 replaces availability-restricted gemini-2.5-pro" },
3972
4014
  { id: "claude-sonnet-4-6", grounding: "judgment", reason: "Quality safety net for blocked-Flash case" },
3973
4015
  { id: "claude-haiku-4-5", grounding: "judgment", reason: "Reduced tool budget \u2014 cliff at 16 fires" },
3974
4016
  { id: "gpt-5.4-mini", grounding: "judgment", reason: "alpha.33: third-provider tail \u2014 OpenAI parallel-tool capable archetype" }
@@ -4016,9 +4058,9 @@ var STARTER_CHAINS_BY_MODE_GROUNDED = {
4016
4058
  reason: "Cross-provider safety net \u2014 Sonnet handles sequential agentic loops cleanly"
4017
4059
  },
4018
4060
  {
4019
- id: "gemini-2.5-pro",
4061
+ id: "gemini-3.5-flash",
4020
4062
  grounding: "judgment",
4021
- reason: "Third-provider tail when no DeepSeek key reachable"
4063
+ reason: "alpha.86: third-provider tail when no DeepSeek key reachable \u2014 replaces availability-restricted gemini-2.5-pro"
4022
4064
  }
4023
4065
  ]
4024
4066
  }
@@ -4071,8 +4113,8 @@ function getDefaultFallbackChain(opts) {
4071
4113
  chain = [...starter];
4072
4114
  }
4073
4115
  if (policy?.blockedModels && policy.blockedModels.length > 0) {
4074
- const blocked = new Set(policy.blockedModels);
4075
- chain = chain.filter((id) => !blocked.has(id));
4116
+ const blocked = canonicalPolicySet(policy.blockedModels);
4117
+ chain = chain.filter((id) => !policySetHas(blocked, id));
4076
4118
  }
4077
4119
  const seen = /* @__PURE__ */ new Set();
4078
4120
  const deduped = [];
@@ -4175,8 +4217,8 @@ function getDefaultFallbackChainWithGrounding(opts) {
4175
4217
  chain = [...starter];
4176
4218
  }
4177
4219
  if (policy?.blockedModels && policy.blockedModels.length > 0) {
4178
- const blocked = new Set(policy.blockedModels);
4179
- chain = chain.filter((e) => !blocked.has(e.id));
4220
+ const blocked = canonicalPolicySet(policy.blockedModels);
4221
+ chain = chain.filter((e) => !policySetHas(blocked, e.id));
4180
4222
  }
4181
4223
  const seen = /* @__PURE__ */ new Set();
4182
4224
  const deduped = [];
@@ -4584,6 +4626,40 @@ function defaultOnError4(err) {
4584
4626
  err
4585
4627
  );
4586
4628
  }
4629
+ function prefetchPromotions(appId) {
4630
+ const rt = runtime4;
4631
+ if (!rt || !appId) return void 0;
4632
+ let snap = snapshots3.get(appId);
4633
+ if (!snap) {
4634
+ snap = { data: [], expiresAt: 0, refreshing: false };
4635
+ snapshots3.set(appId, snap);
4636
+ }
4637
+ if (snap.expiresAt > Date.now()) return void 0;
4638
+ const inflight = pendingRefreshes3.get(appId);
4639
+ if (inflight) return inflight;
4640
+ if (snap.refreshing) return void 0;
4641
+ snap.refreshing = true;
4642
+ void asyncRefresh4(rt, appId);
4643
+ return pendingRefreshes3.get(appId);
4644
+ }
4645
+ async function awaitPromotionsReady(appId, timeoutMs) {
4646
+ if (!runtime4 || !appId) return;
4647
+ const pending = prefetchPromotions(appId) ?? pendingRefreshes3.get(appId);
4648
+ if (!(timeoutMs > 0)) return;
4649
+ if (!pending) return;
4650
+ let timer;
4651
+ try {
4652
+ await Promise.race([
4653
+ pending,
4654
+ new Promise((resolve) => {
4655
+ timer = setTimeout(resolve, timeoutMs);
4656
+ })
4657
+ ]);
4658
+ } catch {
4659
+ } finally {
4660
+ if (timer) clearTimeout(timer);
4661
+ }
4662
+ }
4587
4663
  function _testResetPromotions() {
4588
4664
  runtime4 = void 0;
4589
4665
  snapshots3.clear();
@@ -5053,6 +5129,92 @@ function advisorRuleCrossFamilyFit(ctx) {
5053
5129
  ];
5054
5130
  }
5055
5131
 
5132
+ // src/advisor-rules/blocked-model-drift.ts
5133
+ var BLOCKED_MODEL_NOT_IN_ROSTER_CODE = "blocked-model-not-in-roster";
5134
+ var BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE = "blocked-model-family-sibling-served";
5135
+ var DOCS_URL = "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories";
5136
+ function familyOf2(modelId, profile) {
5137
+ return profile?.family ?? deriveFamilyFromModelId(modelId);
5138
+ }
5139
+ function isOnTheWayOut(status) {
5140
+ return status === "legacy" || status === "deprecated";
5141
+ }
5142
+ function advisorRuleBlockedModelDrift(ctx) {
5143
+ const blocked = ctx.policy?.blockedModels;
5144
+ if (!blocked || blocked.length === 0) return [];
5145
+ const resolve = ctx.resolveProfile ?? tryGetProfile;
5146
+ const out = [];
5147
+ const entries = [...new Set(blocked)].sort();
5148
+ const selectedProfile = resolve(ctx.selectedModelId);
5149
+ const selectedFamily = familyOf2(ctx.selectedModelId, selectedProfile);
5150
+ const orphans = entries.filter((e) => resolve(e) === void 0);
5151
+ if (orphans.length > 0) {
5152
+ const list = orphans.map((o) => `\`${o}\``).join(", ");
5153
+ const plural = orphans.length === 1 ? "entry" : "entries";
5154
+ const verb = orphans.length === 1 ? "matches" : "match";
5155
+ out.push({
5156
+ level: "warn",
5157
+ code: BLOCKED_MODEL_NOT_IN_ROSTER_CODE,
5158
+ message: `CompilePolicy.blockedModels ${plural} ${list} ${verb} no model in the current roster, so ${orphans.length === 1 ? "it is" : "they are"} inert \u2014 \`blockedModels\` is matched by exact model id, and nothing kgauto can select carries ${orphans.length === 1 ? "that id" : "those ids"}. The block will never fire.`,
5159
+ suggestion: `Check for a typo, or for an id that was retired from the roster since the block was written. This is how a block goes quiet without an error: tt-intel (2026-07-29) carried \`KGAUTO_BLOCKED_MODELS="claude-sonnet-4-6"\` across a roster retarget and the block stopped covering the traffic they believed it covered. Resolve the intended model id against the live roster \u2014 \`getRecommendedPrimary({ family, fallback })\` returns the id the family currently resolves to \u2014 and block that id, or drop the entry if it is no longer needed.`,
5160
+ docsUrl: DOCS_URL
5161
+ });
5162
+ }
5163
+ if (selectedFamily !== null) {
5164
+ const siblings = entries.filter((e) => {
5165
+ if (resolveModelAlias(e) === resolveModelAlias(ctx.selectedModelId)) return false;
5166
+ const p = resolve(e);
5167
+ if (familyOf2(e, p) !== selectedFamily) return false;
5168
+ if (p && selectedProfile && p.provider !== selectedProfile.provider) {
5169
+ return false;
5170
+ }
5171
+ return true;
5172
+ });
5173
+ if (siblings.length > 0) {
5174
+ const list = siblings.map((s) => `\`${s}\``).join(", ");
5175
+ const plural = siblings.length === 1 ? "" : "s";
5176
+ const retargetShaped = siblings.some((s) => {
5177
+ const p = resolve(s);
5178
+ return isOnTheWayOut(p?.status) && selectedProfile?.status === "current";
5179
+ });
5180
+ const retargetNote = retargetShaped ? ` The blocked id${plural} ${siblings.length === 1 ? "is" : "are"} legacy/deprecated while \`${ctx.selectedModelId}\` is current \u2014 that is the signature of a roster lifecycle move rather than a deliberate per-generation block, so this is more likely to be drift than intent.` : "";
5181
+ out.push({
5182
+ level: "warn",
5183
+ code: BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE,
5184
+ message: `\`${ctx.selectedModelId}\` was selected for this call. It is in the same family (\`${selectedFamily}\`) as blocked entr${siblings.length === 1 ? "y" : "ies"} ${list}, but its exact id differs \u2014 and \`CompilePolicy.blockedModels\` matches by exact id, so the block does not cover it.${retargetNote}`,
5185
+ suggestion: `Two readings, and kgauto cannot tell them apart: (1) intentional \u2014 you meant to gate that specific id and \`${ctx.selectedModelId}\` is fine, in which case nothing needs doing and you can filter this code; (2) drift \u2014 you meant to gate the family, and a roster change moved traffic to a sibling your block never named. This happened to tt-intel: when the \`claude-sonnet\` family primary retargeted from \`claude-sonnet-4-6\` to \`claude-sonnet-5\`, their literal-id gate stopped matching the family's routed traffic (their fix was local family resolution). If you meant the family, add \`${ctx.selectedModelId}\` to \`blockedModels\` \u2014 kgauto deliberately does NOT widen exact-id blocks into family globs, because that would silently change what every existing block covers.`,
5186
+ docsUrl: DOCS_URL
5187
+ });
5188
+ }
5189
+ }
5190
+ return out;
5191
+ }
5192
+
5193
+ // src/advisor-rules/preferred-blocked-overlap.ts
5194
+ var PREFERRED_MODEL_BLOCKED_CODE = "preferred-model-blocked";
5195
+ var DOCS_URL2 = "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories";
5196
+ function advisorRulePreferredBlockedOverlap(ctx) {
5197
+ const preferred = ctx.policy?.preferredModels;
5198
+ const blocked = ctx.policy?.blockedModels;
5199
+ if (!preferred?.length || !blocked?.length) return [];
5200
+ const blockedSet = canonicalPolicySet(blocked);
5201
+ const deadPins = [...new Set(preferred)].filter((p) => policySetHas(blockedSet, p)).sort();
5202
+ if (deadPins.length === 0) return [];
5203
+ const list = deadPins.map((p) => `\`${p}\``).join(", ");
5204
+ const one = deadPins.length === 1;
5205
+ const preferredSet = canonicalPolicySet(preferred);
5206
+ const servedIsPinned = policySetHas(preferredSet, ctx.selectedModelId);
5207
+ return [
5208
+ {
5209
+ level: "warn",
5210
+ code: PREFERRED_MODEL_BLOCKED_CODE,
5211
+ message: `CompilePolicy.preferredModels entr${one ? "y" : "ies"} ${list} ${one ? "is" : "are"} also in \`blockedModels\` (matched canonically, aliases included). A blocked model is hard-rejected before preference boosts apply, so ${one ? "this pin" : "these pins"} can never serve. ` + (servedIsPinned ? `This call was served by \`${ctx.selectedModelId}\`, which is itself a live pin \u2014 the dead entr${one ? "y is" : "ies are"} latent, not currently rerouting traffic.` : `Substitution is your steady state: this call landed on \`${ctx.selectedModelId}\`, which you did not pin.`),
5212
+ suggestion: `Two readings, and kgauto cannot tell them apart: (1) intentional \u2014 your spend gate deliberately outranks the pin, in which case nothing needs doing and you can filter this code; (2) misconfiguration \u2014 the pin and the block were written at different times and the overlap is an accident. tt-intel hit reading (2) on 2026-08-01: a summarize site pinned \`claude-sonnet\` while \`KGAUTO_BLOCKED_MODELS\` carried the same family, and every call silently substituted a reasoning model whose reasoning burn exceeded the site's \`maxOutputTokens\` \u2014 100% empty payloads under HTTP 200. If the block should win, remove the pin so the policy says what it does. If the pin should win, remove ${one ? "the blocking entry" : "the blocking entries"} or re-scope the block to the exact ids you mean. Check what the substitute costs at your input shape while you are here \u2014 a spend gate that lands traffic on a pricier model than the one it blocked is a cost inversion, not a saving.`,
5213
+ docsUrl: DOCS_URL2
5214
+ }
5215
+ ];
5216
+ }
5217
+
5056
5218
  // src/advisor.ts
5057
5219
  var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
5058
5220
  var TIER_DOWN_COST_RATIO = 0.5;
@@ -5071,6 +5233,18 @@ function runAdvisor(ir, result, profile, policy, phase2) {
5071
5233
  out.push(...detectToolBloat(ir, result));
5072
5234
  out.push(...detectHistoryUncached(ir, profile));
5073
5235
  out.push(...detectSingleModelArray(ir, policy));
5236
+ out.push(
5237
+ ...advisorRuleBlockedModelDrift({
5238
+ policy,
5239
+ selectedModelId: profile.id
5240
+ })
5241
+ );
5242
+ out.push(
5243
+ ...advisorRulePreferredBlockedOverlap({
5244
+ policy,
5245
+ selectedModelId: profile.id
5246
+ })
5247
+ );
5074
5248
  if (policy?.posture !== "locked") {
5075
5249
  out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
5076
5250
  out.push(...detectModelStaleEvidence(ir, profile));
@@ -5182,18 +5356,27 @@ function detectHistoryUncached(ir, profile) {
5182
5356
  function detectSingleModelArray(ir, policy) {
5183
5357
  if (ir.models.length !== 1) return [];
5184
5358
  if (policy?.posture === "locked") return [];
5185
- const only = ir.models[0];
5359
+ const entry = ir.models[0];
5360
+ const only = typeof entry === "string" ? entry : `family:${entry.family}`;
5361
+ const blocked = canonicalPolicySet(policy?.blockedModels);
5362
+ let alternatives = [];
5363
+ try {
5364
+ alternatives = getDefaultFallbackChain({
5365
+ archetype: ir.intent.archetype,
5366
+ primary: only,
5367
+ posture: "preferred",
5368
+ policy
5369
+ }).filter((id) => resolveModelAlias(id) !== resolveModelAlias(only)).filter((id) => !policySetHas(blocked, id)).filter((id) => getModelCompatibility(id, { archetype: ir.intent.archetype }).status !== "reject");
5370
+ } catch {
5371
+ }
5372
+ const hasAlternative = alternatives.length > 0;
5373
+ const remedy = "Widen the chain AND pin your primary: `compile({ ...ir, models: getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' }) }, { policy: { preferredModels: ['" + only + "'] } })`. The chain is a CANDIDATE SET \u2014 compile() scores it and does not honour its order, so without the `preferredModels` pin the extra entries can retarget your primary (and with it your cost profile). With the pin, `" + only + "` stays primary and the added entries serve only as the safety net. Note `posture` has no effect once `primary` is passed. If you gate models by spend, re-verify `policy.blockedModels` against the widened set before shipping. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.";
5186
5374
  return [
5187
5375
  {
5188
- level: "warn",
5376
+ level: hasAlternative ? "critical" : "warn",
5189
5377
  code: "single-model-array",
5190
- 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.`,
5191
- // alpha.80: `posture: 'preferred'` keeps the consumer's current model at
5192
- // position 0 and is therefore cost-neutral; `'open'` re-picks the
5193
- // primary and is NOT. This rule is about reliability, not cost, so both
5194
- // stay on offer — but the cost consequence of the second is now stated,
5195
- // since a reliability fix should not silently become a repricing.
5196
- suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain \u2014 this keeps `" + only + "` as your primary and only adds fallbacks, so your cost profile is unchanged. `getDefaultFallbackChain({ archetype, posture: 'open' })` instead lets the library pick the PRIMARY, ordered by archetype performance rather than cost \u2014 check what it returns before adopting it. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
5378
+ message: hasAlternative ? `\`ir.models\` has length 1 (only "${only}") for archetype "${ir.intent.archetype}" 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 \u2014 and a compatible, non-blocked alternative exists in the roster today (${alternatives[0]}), so this is critical: the missing safety net is adoptable now.` : `\`ir.models\` has length 1 (only "${only}") for archetype "${ir.intent.archetype}" 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. No compatible non-blocked alternative is visible in the roster for this archetype, so this stays a warning.`,
5379
+ suggestion: remedy,
5197
5380
  docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
5198
5381
  }
5199
5382
  ];
@@ -5747,6 +5930,15 @@ function compile(ir, opts = {}) {
5747
5930
  sectionRewritesApplied
5748
5931
  }
5749
5932
  );
5933
+ if (ir["policy"] !== void 0 && opts.policy === void 0) {
5934
+ rawAdvisories.push({
5935
+ level: "critical",
5936
+ code: "policy-in-ir-ignored",
5937
+ message: "The IR passed to compile() carries a `policy` field. `policy` belongs in the SECOND argument \u2014 `compile(ir, { policy })` \u2014 and inside the IR it is an unknown field that is completely ignored. If that policy names blockedModels, no block is being enforced on this call.",
5938
+ suggestion: "Move it: `compile(ir, { policy: { ... } })`. If you also configure policy correctly elsewhere on this path, remove the IR copy so the next reader is not misled.",
5939
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#public-api"
5940
+ });
5941
+ }
5750
5942
  const advisories = rawAdvisories.map((a) => ({
5751
5943
  ...a,
5752
5944
  kgautoRequestId: handle,
@@ -5865,7 +6057,7 @@ function validateFinalFit(ir, profile, tokens) {
5865
6057
  }
5866
6058
 
5867
6059
  // src/version.ts
5868
- var LIBRARY_VERSION = "2.0.0-alpha.83";
6060
+ var LIBRARY_VERSION = "2.0.0-alpha.86";
5869
6061
 
5870
6062
  // src/pricing-brain.ts
5871
6063
  function isPricingRow(x) {
@@ -5976,6 +6168,12 @@ function configureBrain(config) {
5976
6168
  fetchImpl: config.fetchImpl ?? fetch,
5977
6169
  onError: config.onError
5978
6170
  });
6171
+ if (config.appId) {
6172
+ try {
6173
+ void prefetchPromotions(config.appId);
6174
+ } catch {
6175
+ }
6176
+ }
5979
6177
  } else {
5980
6178
  configurePromotionsBrain(void 0);
5981
6179
  }
@@ -6347,6 +6545,7 @@ function buildPayload(input, reg) {
6347
6545
  latency_ms: input.latencyMs,
6348
6546
  success: input.success,
6349
6547
  empty_response: input.emptyResponse ?? input.tokensOut === 0,
6548
+ source: input.source,
6350
6549
  error_type: input.errorType,
6351
6550
  tools_called: input.toolsCalled,
6352
6551
  oracle_score: input.oracleScore?.score,
@@ -7746,7 +7945,11 @@ async function call(ir, opts = {}) {
7746
7945
  )
7747
7946
  })
7748
7947
  );
7749
- await awaitMeasuredFailureReady(ir.appId, resolveGateWarmupMs(opts));
7948
+ const warmupMs = resolveGateWarmupMs(opts);
7949
+ await Promise.all([
7950
+ awaitMeasuredFailureReady(ir.appId, warmupMs),
7951
+ awaitPromotionsReady(ir.appId, warmupMs)
7952
+ ]);
7750
7953
  const initial = compileAndRegister(ir, opts);
7751
7954
  safeEmit(
7752
7955
  () => emitCompileDone(traceId, ir.appId, {
@@ -7786,6 +7989,7 @@ async function call(ir, opts = {}) {
7786
7989
  const latencyMs2 = Date.now() - start;
7787
7990
  await record({
7788
7991
  handle: initial.handle,
7992
+ source: opts.source,
7789
7993
  tokensIn: 0,
7790
7994
  tokensOut: 0,
7791
7995
  latencyMs: latencyMs2,
@@ -7820,11 +8024,11 @@ async function call(ir, opts = {}) {
7820
8024
  }
7821
8025
  let policyBlockedFiltered;
7822
8026
  if (opts.policy?.blockedModels && opts.policy.blockedModels.length > 0) {
7823
- const blocked = new Set(opts.policy.blockedModels);
8027
+ const blocked = canonicalPolicySet(opts.policy.blockedModels);
7824
8028
  const filtered = [];
7825
8029
  const dropped = [];
7826
8030
  for (const t of targetsToTry) {
7827
- if (blocked.has(t)) {
8031
+ if (policySetHas(blocked, t)) {
7828
8032
  dropped.push(t);
7829
8033
  } else {
7830
8034
  filtered.push(t);
@@ -7838,6 +8042,7 @@ async function call(ir, opts = {}) {
7838
8042
  const latencyMs2 = Date.now() - start;
7839
8043
  await record({
7840
8044
  handle: initial.handle,
8045
+ source: opts.source,
7841
8046
  tokensIn: 0,
7842
8047
  tokensOut: 0,
7843
8048
  latencyMs: latencyMs2,
@@ -7864,10 +8069,18 @@ async function call(ir, opts = {}) {
7864
8069
  const failedProviders = /* @__PURE__ */ new Map();
7865
8070
  const sameModelRetryEnabled = opts.sameModelRetry ?? isSameModelRetryEnabledFromEnv();
7866
8071
  let retriedSameModel = false;
8072
+ const pushAttempt = (attempt) => {
8073
+ attempts.push(attempt);
8074
+ if (attempt.status === "success") return;
8075
+ try {
8076
+ opts.onFailedAttempt?.(attempt);
8077
+ } catch {
8078
+ }
8079
+ };
7867
8080
  for (let i = 0; i < targetsToTry.length; i++) {
7868
8081
  const targetModel = targetsToTry[i];
7869
8082
  if (opts.abortSignal?.aborted) {
7870
- attempts.push({
8083
+ pushAttempt({
7871
8084
  model: targetModel,
7872
8085
  status: "terminal",
7873
8086
  errorCode: "aborted",
@@ -7878,7 +8091,7 @@ async function call(ir, opts = {}) {
7878
8091
  const targetProfile = tryGetProfile(targetModel);
7879
8092
  const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
7880
8093
  if (targetProfile && providerFailReason && !opts.noFallback) {
7881
- attempts.push({
8094
+ pushAttempt({
7882
8095
  model: targetModel,
7883
8096
  status: "terminal",
7884
8097
  errorCode: `${providerFailReason}_inferred`,
@@ -7897,7 +8110,7 @@ async function call(ir, opts = {}) {
7897
8110
  opts
7898
8111
  );
7899
8112
  } catch (err) {
7900
- attempts.push({
8113
+ pushAttempt({
7901
8114
  model: targetModel,
7902
8115
  status: "terminal",
7903
8116
  errorCode: "compile_error",
@@ -7935,7 +8148,7 @@ async function call(ir, opts = {}) {
7935
8148
  }
7936
8149
  if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
7937
8150
  retriedSameModel = true;
7938
- attempts.push({
8151
+ pushAttempt({
7939
8152
  model: targetModel,
7940
8153
  status: validated.errorType,
7941
8154
  errorCode: validated.errorCode,
@@ -7950,7 +8163,7 @@ async function call(ir, opts = {}) {
7950
8163
  servedByRetry = true;
7951
8164
  }
7952
8165
  if (validated.ok) {
7953
- attempts.push({
8166
+ pushAttempt({
7954
8167
  model: targetModel,
7955
8168
  status: "success",
7956
8169
  ...servedByRetry ? { sameModelRetry: true } : {}
@@ -7971,6 +8184,7 @@ async function call(ir, opts = {}) {
7971
8184
  const goldenShapeKey = goldenRate > 0 ? peekRegisteredShapeKey(initial.handle) : void 0;
7972
8185
  await record({
7973
8186
  handle: initial.handle,
8187
+ source: opts.source,
7974
8188
  tokensIn: validated.response.tokens.input,
7975
8189
  tokensOut: validated.response.tokens.output,
7976
8190
  latencyMs: latencyMs2,
@@ -8068,7 +8282,7 @@ async function call(ir, opts = {}) {
8068
8282
  advisories: activeCompile.advisories
8069
8283
  };
8070
8284
  }
8071
- attempts.push({
8285
+ pushAttempt({
8072
8286
  model: targetModel,
8073
8287
  status: validated.errorType,
8074
8288
  errorCode: validated.errorCode,
@@ -8093,6 +8307,7 @@ async function call(ir, opts = {}) {
8093
8307
  const latencyMs = Date.now() - start;
8094
8308
  await record({
8095
8309
  handle: initial.handle,
8310
+ source: opts.source,
8096
8311
  tokensIn: lastErr?.tokens?.input ?? 0,
8097
8312
  tokensOut: lastErr?.tokens?.output ?? 0,
8098
8313
  latencyMs,
@@ -10588,6 +10803,8 @@ function compile2(ir, opts) {
10588
10803
  ALT_STRATEGY_IDS,
10589
10804
  ARCHETYPE_FAMILY_FITS,
10590
10805
  ARCHETYPE_FLOOR_DEFAULT,
10806
+ BLOCKED_MODEL_FAMILY_SIBLING_SERVED_CODE,
10807
+ BLOCKED_MODEL_NOT_IN_ROSTER_CODE,
10591
10808
  BRAIN_READ_ENV_NAMES,
10592
10809
  BURST_SPAN_MS,
10593
10810
  COACH_CFG,
@@ -10631,6 +10848,7 @@ function compile2(ir, opts) {
10631
10848
  applySectionRewrites,
10632
10849
  attachCacheControlToStreamTextInput,
10633
10850
  awaitMeasuredFailureReady,
10851
+ awaitPromotionsReady,
10634
10852
  brainHealth,
10635
10853
  bucketContext,
10636
10854
  bucketHistory,
@@ -10717,6 +10935,7 @@ function compile2(ir, opts) {
10717
10935
  peekBrainDeadLetter,
10718
10936
  planDecomposition,
10719
10937
  prefetchMeasuredFailure,
10938
+ prefetchPromotions,
10720
10939
  probeShadow,
10721
10940
  profileToRow,
10722
10941
  profilesByProvider,
@@ -10729,6 +10948,7 @@ function compile2(ir, opts) {
10729
10948
  resetTokenizer,
10730
10949
  resolveConventionsForProfile,
10731
10950
  resolveGoldenCaptureRate,
10951
+ resolveModelAlias,
10732
10952
  resolvePricingAt,
10733
10953
  resolveProviderKey,
10734
10954
  rowToAdvisory,