@warmdrift/kgauto-compiler 2.0.0-alpha.39 → 2.0.0-alpha.41

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.mjs CHANGED
@@ -342,7 +342,8 @@ function passScoreTargets(ir, opts) {
342
342
  const preferredSet = new Set(policy.preferredModels ?? []);
343
343
  const scores = [];
344
344
  const policyMutations = [];
345
- for (const modelId of ir.models) {
345
+ const modelIds = ir.models.filter((m) => typeof m === "string");
346
+ for (const modelId of modelIds) {
346
347
  let profile;
347
348
  try {
348
349
  profile = opts.profilesById(modelId);
@@ -386,7 +387,7 @@ function passScoreTargets(ir, opts) {
386
387
  }
387
388
  const baseQuality = profile.strengths.includes("reasoning") ? 0.85 : profile.strengths.includes("quality") ? 0.8 : 0.6;
388
389
  const qualityScore = Math.max(0, baseQuality - qualityPenalty);
389
- const callerOrderBoost = (ir.models.length - ir.models.indexOf(modelId)) * 0.1;
390
+ const callerOrderBoost = (modelIds.length - modelIds.indexOf(modelId)) * 0.1;
390
391
  const costPenalty = estimatedCostUsd * 5;
391
392
  const preferredBoost = preferredSet.has(modelId) ? 0.5 : 0;
392
393
  const rank = qualityScore + callerOrderBoost - costPenalty - reasons.length * 10 + preferredBoost;
@@ -944,6 +945,393 @@ function defaultOnError(err) {
944
945
  );
945
946
  }
946
947
 
948
+ // src/promote-ready-brain.ts
949
+ function isRawPromoteReadyRow(x) {
950
+ if (!x || typeof x !== "object") return false;
951
+ const r = x;
952
+ 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";
953
+ }
954
+ function coerceNumber(v) {
955
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
956
+ if (typeof v === "string") {
957
+ const n = Number(v);
958
+ return Number.isFinite(n) ? n : null;
959
+ }
960
+ return null;
961
+ }
962
+ function mapRowsToFindings2(rows) {
963
+ const out = [];
964
+ for (const row of rows) {
965
+ if (!isRawPromoteReadyRow(row)) continue;
966
+ const sampleN = coerceNumber(row.sample_n);
967
+ const passRate = coerceNumber(row.judge_pass_rate);
968
+ const avgScore = coerceNumber(row.judge_avg_score);
969
+ if (sampleN === null || passRate === null || avgScore === null) continue;
970
+ out.push({
971
+ archetype: row.intent_archetype,
972
+ family: row.family,
973
+ candidateModel: row.candidate_model,
974
+ currentModel: row.current_model,
975
+ sampleN,
976
+ judgePassRate: passRate,
977
+ judgeAvgScore: avgScore,
978
+ costDeltaPct: coerceNumber(row.cost_delta_pct),
979
+ detectedAt: row.detected_at
980
+ });
981
+ }
982
+ return out;
983
+ }
984
+ var snapshots2 = /* @__PURE__ */ new Map();
985
+ var runtime2;
986
+ var warnedOnce2 = false;
987
+ function isPromoteReadyBrainActive() {
988
+ return runtime2 !== void 0;
989
+ }
990
+ function loadPromoteReadyFindings(opts) {
991
+ const rt = runtime2;
992
+ if (!rt) return [];
993
+ const appId = opts.appId;
994
+ if (!appId) return [];
995
+ let snap = snapshots2.get(appId);
996
+ if (!snap) {
997
+ snap = { data: [], expiresAt: 0, refreshing: false };
998
+ snapshots2.set(appId, snap);
999
+ }
1000
+ const now = Date.now();
1001
+ const stale = snap.expiresAt <= now;
1002
+ if (stale && !snap.refreshing) {
1003
+ snap.refreshing = true;
1004
+ void asyncRefresh2(rt, appId);
1005
+ }
1006
+ let rows = snap.data;
1007
+ if (opts.archetype) {
1008
+ rows = rows.filter((f) => f.archetype === opts.archetype);
1009
+ }
1010
+ if (opts.family) {
1011
+ rows = rows.filter((f) => f.family === opts.family);
1012
+ }
1013
+ return rows;
1014
+ }
1015
+ var pendingRefreshes2 = /* @__PURE__ */ new Map();
1016
+ async function asyncRefresh2(rt, appId) {
1017
+ const promise = doRefresh2(rt, appId);
1018
+ pendingRefreshes2.set(appId, promise);
1019
+ try {
1020
+ await promise;
1021
+ } finally {
1022
+ if (pendingRefreshes2.get(appId) === promise) {
1023
+ pendingRefreshes2.delete(appId);
1024
+ }
1025
+ }
1026
+ }
1027
+ async function doRefresh2(rt, appId) {
1028
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1029
+ let snap = snapshots2.get(appId);
1030
+ if (!snap) {
1031
+ snap = { data: [], expiresAt: 0, refreshing: false };
1032
+ snapshots2.set(appId, snap);
1033
+ }
1034
+ try {
1035
+ const res = await rt.fetchImpl(url, { method: "GET" });
1036
+ if (!res.ok) {
1037
+ throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
1038
+ }
1039
+ const body = await res.json();
1040
+ if (runtime2 !== rt) return;
1041
+ const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
1042
+ snap.data = rows;
1043
+ snap.expiresAt = Date.now() + rt.ttlMs;
1044
+ snap.refreshing = false;
1045
+ } catch (err) {
1046
+ if (runtime2 !== rt) return;
1047
+ snap.refreshing = false;
1048
+ snap.expiresAt = Date.now() + rt.ttlMs;
1049
+ if (!warnedOnce2) {
1050
+ warnedOnce2 = true;
1051
+ (rt.onError ?? defaultOnError2)(err);
1052
+ }
1053
+ }
1054
+ }
1055
+ function defaultOnError2(err) {
1056
+ console.warn(
1057
+ "[kgauto] promote-ready fetch failed (using empty fallback):",
1058
+ err
1059
+ );
1060
+ }
1061
+ function resolveFetchImpl(injected) {
1062
+ return injected ?? ((...args) => globalThis.fetch(...args));
1063
+ }
1064
+ function normalizeEndpoint(endpoint) {
1065
+ return endpoint.replace(/\/+$/, "");
1066
+ }
1067
+ async function markPromoteReadyHandled(opts) {
1068
+ const {
1069
+ appId,
1070
+ archetype,
1071
+ family,
1072
+ resolution,
1073
+ resolutionNote,
1074
+ brainEndpoint,
1075
+ brainJwt,
1076
+ brainAnonKey,
1077
+ fetch: injectedFetch
1078
+ } = opts;
1079
+ if (!appId) return { ok: false, reason: "app_id_required" };
1080
+ if (!archetype) return { ok: false, reason: "archetype_required" };
1081
+ if (!family) return { ok: false, reason: "family_required" };
1082
+ if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
1083
+ return { ok: false, reason: "resolution_invalid" };
1084
+ }
1085
+ const doFetch = resolveFetchImpl(injectedFetch);
1086
+ const base = normalizeEndpoint(brainEndpoint);
1087
+ 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`;
1088
+ const patchBody = {
1089
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
1090
+ resolution
1091
+ };
1092
+ if (resolutionNote !== void 0) {
1093
+ patchBody.resolution_note = resolutionNote;
1094
+ }
1095
+ let res;
1096
+ try {
1097
+ res = await doFetch(url, {
1098
+ method: "PATCH",
1099
+ headers: {
1100
+ Authorization: `Bearer ${brainJwt}`,
1101
+ apikey: brainAnonKey,
1102
+ "Content-Type": "application/json",
1103
+ Accept: "application/json",
1104
+ Prefer: "return=minimal"
1105
+ },
1106
+ body: JSON.stringify(patchBody)
1107
+ });
1108
+ } catch (err) {
1109
+ const msg = err instanceof Error ? err.message : String(err);
1110
+ return { ok: false, reason: `network_error:${msg}` };
1111
+ }
1112
+ if (res.status === 401 || res.status === 403) {
1113
+ return { ok: false, reason: "brain_auth_misconfig" };
1114
+ }
1115
+ if (res.status >= 500) {
1116
+ return { ok: false, reason: "brain_unavailable" };
1117
+ }
1118
+ if (!res.ok) {
1119
+ return { ok: false, reason: `patch_failed:${res.status}` };
1120
+ }
1121
+ return { ok: true };
1122
+ }
1123
+
1124
+ // src/advisor-rules/promote-ready.ts
1125
+ var PROMOTE_READY_THRESHOLDS = {
1126
+ minPassRate: 0.8,
1127
+ minAvgScore: 4
1128
+ };
1129
+ function shouldFirePromoteReady(finding, resolvedPrimary) {
1130
+ if (finding.currentModel !== resolvedPrimary) return false;
1131
+ if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
1132
+ if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
1133
+ return true;
1134
+ }
1135
+ function deriveFamilyLocal(modelId) {
1136
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
1137
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
1138
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
1139
+ if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
1140
+ if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
1141
+ if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
1142
+ if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
1143
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
1144
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
1145
+ return null;
1146
+ }
1147
+ function advisorRulePromoteReady(ctx) {
1148
+ if (!isPromoteReadyBrainActive()) return [];
1149
+ if (!ctx.appId) return [];
1150
+ if (!ctx.resolvedPrimary) return [];
1151
+ const family = deriveFamilyLocal(ctx.resolvedPrimary);
1152
+ if (!family) return [];
1153
+ const findings = loadPromoteReadyFindings({
1154
+ appId: ctx.appId,
1155
+ archetype: ctx.archetype,
1156
+ family
1157
+ });
1158
+ if (findings.length === 0) return [];
1159
+ const qualifying = findings.filter(
1160
+ (f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
1161
+ );
1162
+ if (qualifying.length === 0) return [];
1163
+ qualifying.sort((a, b) => {
1164
+ if (a.judgeAvgScore !== b.judgeAvgScore) {
1165
+ return b.judgeAvgScore - a.judgeAvgScore;
1166
+ }
1167
+ return b.judgePassRate - a.judgePassRate;
1168
+ });
1169
+ const top = qualifying[0];
1170
+ const pctPass = Math.round(top.judgePassRate * 100);
1171
+ const score = top.judgeAvgScore.toFixed(2);
1172
+ let costClause = "";
1173
+ if (top.costDeltaPct !== null) {
1174
+ const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
1175
+ const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
1176
+ costClause = `, cost ${magnitude}% ${sign}`;
1177
+ }
1178
+ 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.`;
1179
+ return [
1180
+ {
1181
+ level: "info",
1182
+ code: "promote-ready",
1183
+ message,
1184
+ 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.`,
1185
+ // alpha.36 architectural field — not a no-ai-needed case.
1186
+ recommendedArchitecture: void 0,
1187
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1188
+ }
1189
+ ];
1190
+ }
1191
+
1192
+ // src/advisor-rules/consumer-on-stale-model.ts
1193
+ function isStaleStatus(v) {
1194
+ return v === "legacy" || v === "deprecated";
1195
+ }
1196
+ function asString(v) {
1197
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1198
+ }
1199
+ function mapRowsToFindings3(rows) {
1200
+ const out = [];
1201
+ for (const raw of rows) {
1202
+ if (!raw || typeof raw !== "object") continue;
1203
+ const r = raw;
1204
+ const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
1205
+ const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
1206
+ const staleProvider = asString(r.stale_provider);
1207
+ const recommendedModel = asString(r.recommended_model);
1208
+ const family = asString(r.family);
1209
+ const message = asString(r.message);
1210
+ if (!archetype || !staleModel || !recommendedModel || !family || !message) {
1211
+ continue;
1212
+ }
1213
+ if (!isStaleStatus(r.stale_status)) continue;
1214
+ const row = {
1215
+ archetype,
1216
+ staleModel,
1217
+ staleProvider: staleProvider ?? "unknown",
1218
+ staleStatus: r.stale_status,
1219
+ recommendedModel,
1220
+ family,
1221
+ message
1222
+ };
1223
+ const suggestion = asString(r.suggestion);
1224
+ if (suggestion) row.suggestion = suggestion;
1225
+ if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
1226
+ row.observationCount = r.observation_count;
1227
+ }
1228
+ out.push(row);
1229
+ }
1230
+ return out;
1231
+ }
1232
+ var snapshots3 = /* @__PURE__ */ new Map();
1233
+ var runtime3;
1234
+ var warnedOnce3 = false;
1235
+ var pendingRefreshes3 = /* @__PURE__ */ new Map();
1236
+ function isStaleModelFindingsBrainActive() {
1237
+ return runtime3 !== void 0;
1238
+ }
1239
+ function getStaleModelFindings(opts) {
1240
+ const rt = runtime3;
1241
+ if (!rt) return [];
1242
+ const appId = opts.appId;
1243
+ if (!appId) return [];
1244
+ let snap = snapshots3.get(appId);
1245
+ if (!snap) {
1246
+ snap = { data: [], expiresAt: 0, refreshing: false };
1247
+ snapshots3.set(appId, snap);
1248
+ }
1249
+ const now = Date.now();
1250
+ const stale = snap.expiresAt <= now;
1251
+ if (stale && !snap.refreshing) {
1252
+ snap.refreshing = true;
1253
+ void asyncRefresh3(rt, appId);
1254
+ }
1255
+ if (opts.archetype) {
1256
+ return snap.data.filter((f) => f.archetype === opts.archetype);
1257
+ }
1258
+ return snap.data;
1259
+ }
1260
+ async function asyncRefresh3(rt, appId) {
1261
+ const promise = doRefresh3(rt, appId);
1262
+ pendingRefreshes3.set(appId, promise);
1263
+ try {
1264
+ await promise;
1265
+ } finally {
1266
+ if (pendingRefreshes3.get(appId) === promise) {
1267
+ pendingRefreshes3.delete(appId);
1268
+ }
1269
+ }
1270
+ }
1271
+ async function doRefresh3(rt, appId) {
1272
+ const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
1273
+ let snap = snapshots3.get(appId);
1274
+ if (!snap) {
1275
+ snap = { data: [], expiresAt: 0, refreshing: false };
1276
+ snapshots3.set(appId, snap);
1277
+ }
1278
+ try {
1279
+ const res = await rt.fetchImpl(url, { method: "GET" });
1280
+ if (!res.ok) {
1281
+ throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
1282
+ }
1283
+ const body = await res.json();
1284
+ if (runtime3 !== rt) return;
1285
+ const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
1286
+ snap.data = rows;
1287
+ snap.expiresAt = Date.now() + rt.ttlMs;
1288
+ snap.refreshing = false;
1289
+ } catch (err) {
1290
+ if (runtime3 !== rt) return;
1291
+ snap.refreshing = false;
1292
+ snap.expiresAt = Date.now() + rt.ttlMs;
1293
+ if (!warnedOnce3) {
1294
+ warnedOnce3 = true;
1295
+ (rt.onError ?? defaultOnError3)(err);
1296
+ }
1297
+ }
1298
+ }
1299
+ function defaultOnError3(err) {
1300
+ console.warn(
1301
+ "[kgauto] stale-model findings fetch failed (using empty fallback):",
1302
+ err
1303
+ );
1304
+ }
1305
+ var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
1306
+ function advisorRuleConsumerOnStaleModel(ir) {
1307
+ if (!isStaleModelFindingsBrainActive()) return [];
1308
+ if (!ir.appId) return [];
1309
+ const findings = getStaleModelFindings({
1310
+ appId: ir.appId,
1311
+ archetype: ir.intent.archetype
1312
+ });
1313
+ if (findings.length === 0) return [];
1314
+ const ranked = [...findings].sort((a, b) => {
1315
+ if (a.staleStatus !== b.staleStatus) {
1316
+ return a.staleStatus === "deprecated" ? -1 : 1;
1317
+ }
1318
+ return a.staleModel.localeCompare(b.staleModel);
1319
+ });
1320
+ const top = ranked[0];
1321
+ const extraCount = findings.length - 1;
1322
+ const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
1323
+ return [
1324
+ {
1325
+ level: "warn",
1326
+ code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
1327
+ message: `${top.message}${extraNote}`,
1328
+ 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}.`,
1329
+ recommendationType: "model-swap",
1330
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
1331
+ }
1332
+ ];
1333
+ }
1334
+
947
1335
  // src/advisor.ts
948
1336
  var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
949
1337
  var TIER_DOWN_COST_RATIO = 0.5;
@@ -966,6 +1354,16 @@ function runAdvisor(ir, result, profile, policy, phase2) {
966
1354
  if (policy?.posture !== "locked") {
967
1355
  out.push(...detectStaleExclusionCandidate(ir));
968
1356
  }
1357
+ if (policy?.posture !== "locked" && ir.appId) {
1358
+ out.push(
1359
+ ...advisorRulePromoteReady({
1360
+ appId: ir.appId,
1361
+ archetype: ir.intent.archetype,
1362
+ resolvedPrimary: profile.id
1363
+ })
1364
+ );
1365
+ out.push(...advisorRuleConsumerOnStaleModel(ir));
1366
+ }
969
1367
  return out;
970
1368
  }
971
1369
  function translatorClearedToolCallCliff(phase2) {
@@ -1277,6 +1675,261 @@ ${originalText}`;
1277
1675
  return { rewrittenIR, rewrites };
1278
1676
  }
1279
1677
 
1678
+ // src/models-brain.ts
1679
+ function isModelRow(x) {
1680
+ if (!x || typeof x !== "object") return false;
1681
+ const r = x;
1682
+ return typeof r.model_id === "string" && typeof r.provider === "string";
1683
+ }
1684
+ function isAliasRow(x) {
1685
+ if (!x || typeof x !== "object") return false;
1686
+ const r = x;
1687
+ return typeof r.alias_id === "string" && typeof r.canonical_id === "string";
1688
+ }
1689
+ function rowToProfile(row) {
1690
+ try {
1691
+ if (row.cliffs !== void 0 && row.cliffs !== null && !Array.isArray(row.cliffs)) {
1692
+ return null;
1693
+ }
1694
+ if (row.recovery !== void 0 && row.recovery !== null && !Array.isArray(row.recovery)) {
1695
+ return null;
1696
+ }
1697
+ if (row.lowering !== void 0 && row.lowering !== null && (typeof row.lowering !== "object" || Array.isArray(row.lowering))) {
1698
+ return null;
1699
+ }
1700
+ return {
1701
+ id: row.model_id,
1702
+ provider: row.provider,
1703
+ status: row.status ?? "current",
1704
+ maxContextTokens: row.max_context_tokens ?? 0,
1705
+ maxOutputTokens: row.max_output_tokens ?? 0,
1706
+ maxTools: row.max_tools ?? 0,
1707
+ parallelToolCalls: row.parallel_tool_calls ?? false,
1708
+ structuredOutput: row.structured_output ?? "none",
1709
+ systemPromptMode: row.system_prompt_mode ?? "inline",
1710
+ streaming: row.streaming ?? true,
1711
+ cliffs: row.cliffs ?? [],
1712
+ costInputPer1m: row.cost_input_per_1m ?? 0,
1713
+ costOutputPer1m: row.cost_output_per_1m ?? 0,
1714
+ lowering: row.lowering ?? { system: { mode: "inline" }, cache: { strategy: "unsupported" } },
1715
+ recovery: row.recovery ?? [],
1716
+ strengths: row.strengths ?? [],
1717
+ weaknesses: row.weaknesses ?? [],
1718
+ notes: row.notes ?? void 0,
1719
+ verifiedAgainstDocs: row.verified_against_docs ?? void 0,
1720
+ archetypePerf: row.archetype_perf ?? void 0,
1721
+ // alpha.41 — family-resolution fields. `family` may be null pre-
1722
+ // migration 024; runtime falls back to deriveFamilyFromModelId at
1723
+ // resolution time (see family-resolution.ts, G1).
1724
+ family: row.family ?? void 0,
1725
+ versionAdded: row.version_added ?? void 0,
1726
+ active: row.active ?? void 0
1727
+ };
1728
+ } catch {
1729
+ return null;
1730
+ }
1731
+ }
1732
+ function profileToRow(profile, opts = {}) {
1733
+ const row = {
1734
+ model_id: profile.id,
1735
+ provider: profile.provider,
1736
+ status: profile.status,
1737
+ max_context_tokens: profile.maxContextTokens,
1738
+ max_output_tokens: profile.maxOutputTokens,
1739
+ max_tools: profile.maxTools,
1740
+ parallel_tool_calls: profile.parallelToolCalls,
1741
+ structured_output: profile.structuredOutput,
1742
+ system_prompt_mode: profile.systemPromptMode,
1743
+ streaming: profile.streaming,
1744
+ cliffs: profile.cliffs,
1745
+ cost_input_per_1m: profile.costInputPer1m,
1746
+ cost_output_per_1m: profile.costOutputPer1m,
1747
+ lowering: profile.lowering,
1748
+ recovery: profile.recovery,
1749
+ strengths: profile.strengths,
1750
+ weaknesses: profile.weaknesses,
1751
+ notes: profile.notes ?? null,
1752
+ archetype_perf: profile.archetypePerf ?? null,
1753
+ active: opts.active ?? profile.active ?? true,
1754
+ // alpha.41 — round-trip family + version_added when present on profile.
1755
+ // version_added is operator-controlled via opts; profile-side value is
1756
+ // used only when opts didn't override.
1757
+ family: profile.family ?? null
1758
+ };
1759
+ if (opts.verifiedAgainstDocs !== void 0) {
1760
+ row.verified_against_docs = opts.verifiedAgainstDocs;
1761
+ } else if (profile.verifiedAgainstDocs !== void 0) {
1762
+ const v = profile.verifiedAgainstDocs;
1763
+ row.verified_against_docs = /^\d{4}-\d{2}-\d{2}/.test(v) ? v : null;
1764
+ }
1765
+ if (opts.versionAdded !== void 0) row.version_added = opts.versionAdded;
1766
+ else if (profile.versionAdded !== void 0) row.version_added = profile.versionAdded;
1767
+ if (opts.versionRemoved !== void 0) row.version_removed = opts.versionRemoved;
1768
+ return row;
1769
+ }
1770
+ function mapRowsToModels(rows) {
1771
+ const out = /* @__PURE__ */ new Map();
1772
+ for (const row of rows) {
1773
+ if (!isModelRow(row)) continue;
1774
+ const profile = rowToProfile(row);
1775
+ if (profile) out.set(profile.id, profile);
1776
+ }
1777
+ return out;
1778
+ }
1779
+ function mapRowsToAliases(rows) {
1780
+ const out = {};
1781
+ for (const row of rows) {
1782
+ if (!isAliasRow(row)) continue;
1783
+ out[row.alias_id] = row.canonical_id;
1784
+ }
1785
+ return out;
1786
+ }
1787
+ function bundledModels() {
1788
+ return new Map(allProfilesRaw().map((p) => [p.id, p]));
1789
+ }
1790
+ function bundledAliases() {
1791
+ return { ...ALIASES };
1792
+ }
1793
+ var loadModelsFromBrain = createBrainQueryCache({
1794
+ table: "kgauto_models",
1795
+ mapRows: mapRowsToModels,
1796
+ bundledFallback: bundledModels
1797
+ });
1798
+ var loadAliasesFromBrain = createBrainQueryCache({
1799
+ table: "kgauto_aliases",
1800
+ mapRows: mapRowsToAliases,
1801
+ bundledFallback: bundledAliases
1802
+ });
1803
+ _setProfileBrainHook({
1804
+ getProfile: (canonical) => loadModelsFromBrain().get(canonical),
1805
+ resolveAlias: (id) => loadAliasesFromBrain()[id]
1806
+ });
1807
+
1808
+ // src/family-resolution.ts
1809
+ var FamilyResolutionError = class extends Error {
1810
+ family;
1811
+ cause;
1812
+ constructor(family, cause) {
1813
+ super(
1814
+ `Family "${family}" did not resolve to any current+active model. ${cause}. Pass a literal model id in ir.models, or call getRecommendedPrimary({ family, fallback }) at IR-construction time.`
1815
+ );
1816
+ this.name = "FamilyResolutionError";
1817
+ this.family = family;
1818
+ this.cause = cause;
1819
+ }
1820
+ };
1821
+ function deriveFamilyFromModelId(modelId) {
1822
+ if (typeof modelId !== "string" || modelId.length === 0) return null;
1823
+ if (modelId.startsWith("claude-opus-")) return "claude-opus";
1824
+ if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
1825
+ if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
1826
+ if (modelId.startsWith("gemini-") && modelId.includes("flash-lite")) {
1827
+ return "gemini-flash-lite";
1828
+ }
1829
+ if (modelId.startsWith("gemini-") && modelId.includes("flash")) {
1830
+ return "gemini-flash";
1831
+ }
1832
+ if (modelId.startsWith("gemini-") && modelId.includes("pro")) {
1833
+ return "gemini-pro";
1834
+ }
1835
+ if (modelId.startsWith("deepseek-") && modelId.includes("pro")) {
1836
+ return "deepseek-reasoner";
1837
+ }
1838
+ if (modelId.startsWith("deepseek-")) return "deepseek-chat";
1839
+ if (modelId.startsWith("gpt-")) return "openai-gpt";
1840
+ return null;
1841
+ }
1842
+ function familyOf(profile) {
1843
+ return profile.family ?? deriveFamilyFromModelId(profile.id);
1844
+ }
1845
+ function archetypePerfFor(profile, archetype) {
1846
+ if (!archetype) return 0;
1847
+ const score = profile.archetypePerf?.[archetype];
1848
+ return typeof score === "number" ? score : 5;
1849
+ }
1850
+ function selectCandidates(registry, family, archetype, appId) {
1851
+ const out = [];
1852
+ const excluded = /* @__PURE__ */ new Set();
1853
+ if (appId && archetype) {
1854
+ const findings = getStaleExclusionFindings({ appId, archetype });
1855
+ for (const f of findings) {
1856
+ if (f.verdict === "stay-excluded") excluded.add(f.excludedModel);
1857
+ }
1858
+ }
1859
+ for (const profile of registry.values()) {
1860
+ if (familyOf(profile) !== family) continue;
1861
+ if (profile.status !== "current") continue;
1862
+ if (profile.active === false) continue;
1863
+ if (archetype) {
1864
+ if (archetypePerfFor(profile, archetype) < ARCHETYPE_FLOOR_DEFAULT) continue;
1865
+ }
1866
+ if (excluded.has(profile.id)) continue;
1867
+ out.push(profile);
1868
+ }
1869
+ return out;
1870
+ }
1871
+ function sortCandidates(candidates, archetype) {
1872
+ const sorted = [...candidates];
1873
+ sorted.sort((a, b) => {
1874
+ const perfA = archetypePerfFor(a, archetype);
1875
+ const perfB = archetypePerfFor(b, archetype);
1876
+ if (perfA !== perfB) return perfB - perfA;
1877
+ const vA = a.versionAdded ?? "";
1878
+ const vB = b.versionAdded ?? "";
1879
+ if (vA !== vB) return vA < vB ? 1 : -1;
1880
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
1881
+ });
1882
+ return sorted;
1883
+ }
1884
+ function getRecommendedPrimary(opts) {
1885
+ if (opts.posture === "locked") return opts.fallback;
1886
+ const registry = loadModelsFromBrain();
1887
+ const candidates = selectCandidates(
1888
+ registry,
1889
+ opts.family,
1890
+ opts.archetype,
1891
+ opts.appId
1892
+ );
1893
+ if (candidates.length === 0) return opts.fallback;
1894
+ const sorted = sortCandidates(candidates, opts.archetype);
1895
+ return sorted[0]?.id ?? opts.fallback;
1896
+ }
1897
+ function resolveFamilyEntry(family, ctx) {
1898
+ if (typeof family !== "string" || family.length === 0) {
1899
+ throw new FamilyResolutionError(
1900
+ String(family),
1901
+ "Family tag must be a non-empty string"
1902
+ );
1903
+ }
1904
+ const registry = loadModelsFromBrain();
1905
+ const candidates = selectCandidates(
1906
+ registry,
1907
+ family,
1908
+ ctx.archetype,
1909
+ ctx.appId
1910
+ );
1911
+ if (candidates.length === 0) {
1912
+ let anyInFamily = false;
1913
+ for (const profile of registry.values()) {
1914
+ if (familyOf(profile) === family) {
1915
+ anyInFamily = true;
1916
+ break;
1917
+ }
1918
+ }
1919
+ const cause = anyInFamily ? `Family has registered models but none are current+active${ctx.archetype ? ` at archetype-perf >= ${ARCHETYPE_FLOOR_DEFAULT} for "${ctx.archetype}"` : ""}${ctx.appId ? ` and not excluded for app "${ctx.appId}"` : ""}` : `No models in brain registry carry family="${family}". Check the taxonomy table in family-resolution.ts or migration 024`;
1920
+ throw new FamilyResolutionError(family, cause);
1921
+ }
1922
+ const sorted = sortCandidates(candidates, ctx.archetype);
1923
+ const winner = sorted[0];
1924
+ if (!winner) {
1925
+ throw new FamilyResolutionError(
1926
+ family,
1927
+ "Candidates non-empty but sort returned undefined \u2014 internal invariant violation"
1928
+ );
1929
+ }
1930
+ return winner.id;
1931
+ }
1932
+
1280
1933
  // src/compile.ts
1281
1934
  var counter = 0;
1282
1935
  function makeHandle() {
@@ -1286,6 +1939,7 @@ function makeHandle() {
1286
1939
  function compile(ir, opts = {}) {
1287
1940
  const resolver = opts.profileResolver ?? getProfile;
1288
1941
  validateIR(ir);
1942
+ ir = resolveModelEntries(ir);
1289
1943
  const sliced = passSlice(ir);
1290
1944
  const deduped = passDedupe(sliced.value);
1291
1945
  const toolFiltered = passToolRelevance(deduped.value, {
@@ -1472,6 +2126,29 @@ function validateIR(ir) {
1472
2126
  throw new Error("compile(): ir.sections must be an array");
1473
2127
  }
1474
2128
  }
2129
+ function resolveModelEntries(ir) {
2130
+ const out = [];
2131
+ const seen = /* @__PURE__ */ new Set();
2132
+ for (const entry of ir.models) {
2133
+ let id;
2134
+ if (typeof entry === "string") {
2135
+ id = entry;
2136
+ } else if (entry && typeof entry === "object" && "family" in entry) {
2137
+ id = resolveFamilyEntry(entry.family, {
2138
+ archetype: ir.intent.archetype,
2139
+ appId: ir.appId
2140
+ });
2141
+ } else {
2142
+ throw new Error(
2143
+ `compile(): ir.models entry must be a string or { family: string }; got ${JSON.stringify(entry)}`
2144
+ );
2145
+ }
2146
+ if (seen.has(id)) continue;
2147
+ seen.add(id);
2148
+ out.push(id);
2149
+ }
2150
+ return { ...ir, models: out };
2151
+ }
1475
2152
  function pickTarget(ir, scores) {
1476
2153
  if (ir.constraints?.forceModel) {
1477
2154
  const forced = scores.find((s) => s.modelId === ir.constraints.forceModel);
@@ -1698,7 +2375,7 @@ async function record(input) {
1698
2375
  }
1699
2376
  outcomeId = await tryExtractOutcomeId(res);
1700
2377
  } catch (err) {
1701
- (config.onError ?? defaultOnError2)(err);
2378
+ (config.onError ?? defaultOnError4)(err);
1702
2379
  return;
1703
2380
  }
1704
2381
  const advisories = input.advisories ?? reg?.advisoriesFromCompile;
@@ -1720,7 +2397,7 @@ async function record(input) {
1720
2397
  throw new Error(`brain advisories ${res.status}: ${text}`);
1721
2398
  }
1722
2399
  } catch (err) {
1723
- (config.onError ?? defaultOnError2)(err);
2400
+ (config.onError ?? defaultOnError4)(err);
1724
2401
  }
1725
2402
  };
1726
2403
  if (config.sync) {
@@ -1729,7 +2406,7 @@ async function record(input) {
1729
2406
  void send();
1730
2407
  }
1731
2408
  }
1732
- function defaultOnError2(err) {
2409
+ function defaultOnError4(err) {
1733
2410
  console.warn("[kgauto] brain record failed:", err);
1734
2411
  }
1735
2412
  function buildPayload(input, reg) {
@@ -1861,12 +2538,12 @@ async function recordOutcome(input) {
1861
2538
  if (!res.ok) {
1862
2539
  const text = await res.text().catch(() => "<no body>");
1863
2540
  const err = new Error(`brain ${res.status}: ${text}`);
1864
- (config.onError ?? defaultOnError2)(err);
2541
+ (config.onError ?? defaultOnError4)(err);
1865
2542
  return { ok: false, reason: "persistence_failed" };
1866
2543
  }
1867
2544
  return { ok: true };
1868
2545
  } catch (err) {
1869
- (config.onError ?? defaultOnError2)(err);
2546
+ (config.onError ?? defaultOnError4)(err);
1870
2547
  return { ok: false, reason: "persistence_failed" };
1871
2548
  }
1872
2549
  };
@@ -2421,7 +3098,13 @@ async function call(ir, opts = {}) {
2421
3098
  () => emitCompileStart(traceId, ir.appId, {
2422
3099
  appId: ir.appId,
2423
3100
  archetype: ir.intent.archetype,
2424
- models: ir.models
3101
+ // alpha.41: ir.models is ChainModelEntry[] (string | { family }).
3102
+ // Glass-Box renderer carries string ids only; surface family entries
3103
+ // as their family tag prefixed so the panel still shows them in the
3104
+ // pre-resolution log.
3105
+ models: ir.models.map(
3106
+ (m) => typeof m === "string" ? m : `family:${m.family}`
3107
+ )
2425
3108
  })
2426
3109
  );
2427
3110
  const initial = compileAndRegister(ir, opts);
@@ -2887,7 +3570,7 @@ var RESOLUTION_SOURCE_SET = /* @__PURE__ */ new Set([
2887
3570
  "consumer-marked",
2888
3571
  "declined"
2889
3572
  ]);
2890
- function asString(v) {
3573
+ function asString2(v) {
2891
3574
  return typeof v === "string" && v.length > 0 ? v : void 0;
2892
3575
  }
2893
3576
  function asSeverity(v) {
@@ -2909,10 +3592,10 @@ function asResolutionSource(v) {
2909
3592
  return void 0;
2910
3593
  }
2911
3594
  function rowToAdvisory(row) {
2912
- const archetype = asString(row.applies_to_archetype);
2913
- const model = asString(row.applies_to_model);
2914
- const docsLink = asString(row.docs_url);
2915
- const suggestion = asString(row.suggestion);
3595
+ const archetype = asString2(row.applies_to_archetype);
3596
+ const model = asString2(row.applies_to_model);
3597
+ const docsLink = asString2(row.docs_url);
3598
+ const suggestion = asString2(row.suggestion);
2916
3599
  let suggestedFix = null;
2917
3600
  if (docsLink || suggestion) {
2918
3601
  suggestedFix = { type: "manual" };
@@ -2936,18 +3619,18 @@ function rowToAdvisory(row) {
2936
3619
  // reserved — alpha.30+
2937
3620
  status: asStatus(row.status)
2938
3621
  };
2939
- const resolvedAt = asString(row.resolved_at);
3622
+ const resolvedAt = asString2(row.resolved_at);
2940
3623
  if (resolvedAt) out.resolvedAt = resolvedAt;
2941
3624
  const resolutionSource = asResolutionSource(row.resolution_source);
2942
3625
  if (resolutionSource) out.resolutionSource = resolutionSource;
2943
- const resolutionNote = asString(row.resolution_note);
3626
+ const resolutionNote = asString2(row.resolution_note);
2944
3627
  if (resolutionNote) out.resolutionNote = resolutionNote;
2945
3628
  return out;
2946
3629
  }
2947
3630
  function resolveFetch(injected) {
2948
3631
  return injected ?? ((...args) => globalThis.fetch(...args));
2949
3632
  }
2950
- function normalizeEndpoint(endpoint) {
3633
+ function normalizeEndpoint2(endpoint) {
2951
3634
  return endpoint.replace(/\/+$/, "");
2952
3635
  }
2953
3636
  async function getActionableAdvisories(opts) {
@@ -2964,7 +3647,7 @@ async function getActionableAdvisories(opts) {
2964
3647
  throw new Error("getActionableAdvisories: appId is required");
2965
3648
  }
2966
3649
  const doFetch = resolveFetch(injectedFetch);
2967
- const base = normalizeEndpoint(brainEndpoint);
3650
+ const base = normalizeEndpoint2(brainEndpoint);
2968
3651
  const qs = new URLSearchParams();
2969
3652
  qs.set("app_id", `eq.${appId}`);
2970
3653
  if (severity) qs.set("severity", `eq.${severity}`);
@@ -3027,7 +3710,7 @@ async function markAdvisoryResolved(opts) {
3027
3710
  return { ok: false, reason: "id_required" };
3028
3711
  }
3029
3712
  const doFetch = resolveFetch(injectedFetch);
3030
- const base = normalizeEndpoint(brainEndpoint);
3713
+ const base = normalizeEndpoint2(brainEndpoint);
3031
3714
  const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
3032
3715
  let lookupRes;
3033
3716
  try {
@@ -3171,7 +3854,7 @@ async function markExclusionFindingHandled(opts) {
3171
3854
  return { ok: false, reason: "resolution_invalid" };
3172
3855
  }
3173
3856
  const doFetch = resolveFetch(injectedFetch);
3174
- const base = normalizeEndpoint(brainEndpoint);
3857
+ const base = normalizeEndpoint2(brainEndpoint);
3175
3858
  const url = `${base}/rest/v1/exclusion_findings?app_id=eq.${encodeURIComponent(appId)}&intent_archetype=eq.${encodeURIComponent(archetype)}&excluded_model=eq.${encodeURIComponent(excludedModel)}&resolved_at=is.null`;
3176
3859
  const patchBody = {
3177
3860
  resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3211,125 +3894,6 @@ async function markExclusionFindingHandled(opts) {
3211
3894
  return { ok: true };
3212
3895
  }
3213
3896
 
3214
- // src/models-brain.ts
3215
- function isModelRow(x) {
3216
- if (!x || typeof x !== "object") return false;
3217
- const r = x;
3218
- return typeof r.model_id === "string" && typeof r.provider === "string";
3219
- }
3220
- function isAliasRow(x) {
3221
- if (!x || typeof x !== "object") return false;
3222
- const r = x;
3223
- return typeof r.alias_id === "string" && typeof r.canonical_id === "string";
3224
- }
3225
- function rowToProfile(row) {
3226
- try {
3227
- if (row.cliffs !== void 0 && row.cliffs !== null && !Array.isArray(row.cliffs)) {
3228
- return null;
3229
- }
3230
- if (row.recovery !== void 0 && row.recovery !== null && !Array.isArray(row.recovery)) {
3231
- return null;
3232
- }
3233
- if (row.lowering !== void 0 && row.lowering !== null && (typeof row.lowering !== "object" || Array.isArray(row.lowering))) {
3234
- return null;
3235
- }
3236
- return {
3237
- id: row.model_id,
3238
- provider: row.provider,
3239
- status: row.status ?? "current",
3240
- maxContextTokens: row.max_context_tokens ?? 0,
3241
- maxOutputTokens: row.max_output_tokens ?? 0,
3242
- maxTools: row.max_tools ?? 0,
3243
- parallelToolCalls: row.parallel_tool_calls ?? false,
3244
- structuredOutput: row.structured_output ?? "none",
3245
- systemPromptMode: row.system_prompt_mode ?? "inline",
3246
- streaming: row.streaming ?? true,
3247
- cliffs: row.cliffs ?? [],
3248
- costInputPer1m: row.cost_input_per_1m ?? 0,
3249
- costOutputPer1m: row.cost_output_per_1m ?? 0,
3250
- lowering: row.lowering ?? { system: { mode: "inline" }, cache: { strategy: "unsupported" } },
3251
- recovery: row.recovery ?? [],
3252
- strengths: row.strengths ?? [],
3253
- weaknesses: row.weaknesses ?? [],
3254
- notes: row.notes ?? void 0,
3255
- verifiedAgainstDocs: row.verified_against_docs ?? void 0,
3256
- archetypePerf: row.archetype_perf ?? void 0
3257
- };
3258
- } catch {
3259
- return null;
3260
- }
3261
- }
3262
- function profileToRow(profile, opts = {}) {
3263
- const row = {
3264
- model_id: profile.id,
3265
- provider: profile.provider,
3266
- status: profile.status,
3267
- max_context_tokens: profile.maxContextTokens,
3268
- max_output_tokens: profile.maxOutputTokens,
3269
- max_tools: profile.maxTools,
3270
- parallel_tool_calls: profile.parallelToolCalls,
3271
- structured_output: profile.structuredOutput,
3272
- system_prompt_mode: profile.systemPromptMode,
3273
- streaming: profile.streaming,
3274
- cliffs: profile.cliffs,
3275
- cost_input_per_1m: profile.costInputPer1m,
3276
- cost_output_per_1m: profile.costOutputPer1m,
3277
- lowering: profile.lowering,
3278
- recovery: profile.recovery,
3279
- strengths: profile.strengths,
3280
- weaknesses: profile.weaknesses,
3281
- notes: profile.notes ?? null,
3282
- archetype_perf: profile.archetypePerf ?? null,
3283
- active: opts.active ?? true
3284
- };
3285
- if (opts.verifiedAgainstDocs !== void 0) {
3286
- row.verified_against_docs = opts.verifiedAgainstDocs;
3287
- } else if (profile.verifiedAgainstDocs !== void 0) {
3288
- const v = profile.verifiedAgainstDocs;
3289
- row.verified_against_docs = /^\d{4}-\d{2}-\d{2}/.test(v) ? v : null;
3290
- }
3291
- if (opts.versionAdded !== void 0) row.version_added = opts.versionAdded;
3292
- if (opts.versionRemoved !== void 0) row.version_removed = opts.versionRemoved;
3293
- return row;
3294
- }
3295
- function mapRowsToModels(rows) {
3296
- const out = /* @__PURE__ */ new Map();
3297
- for (const row of rows) {
3298
- if (!isModelRow(row)) continue;
3299
- const profile = rowToProfile(row);
3300
- if (profile) out.set(profile.id, profile);
3301
- }
3302
- return out;
3303
- }
3304
- function mapRowsToAliases(rows) {
3305
- const out = {};
3306
- for (const row of rows) {
3307
- if (!isAliasRow(row)) continue;
3308
- out[row.alias_id] = row.canonical_id;
3309
- }
3310
- return out;
3311
- }
3312
- function bundledModels() {
3313
- return new Map(allProfilesRaw().map((p) => [p.id, p]));
3314
- }
3315
- function bundledAliases() {
3316
- return { ...ALIASES };
3317
- }
3318
- var loadModelsFromBrain = createBrainQueryCache({
3319
- table: "kgauto_models",
3320
- mapRows: mapRowsToModels,
3321
- bundledFallback: bundledModels
3322
- });
3323
- var loadAliasesFromBrain = createBrainQueryCache({
3324
- table: "kgauto_aliases",
3325
- mapRows: mapRowsToAliases,
3326
- bundledFallback: bundledAliases
3327
- });
3328
- _setProfileBrainHook({
3329
- getProfile: (canonical) => loadModelsFromBrain().get(canonical),
3330
- resolveAlias: (id) => loadAliasesFromBrain()[id]
3331
- });
3332
-
3333
3897
  // src/index.ts
3334
3898
  function compile2(ir, opts) {
3335
3899
  const result = compile(ir, opts);
@@ -3344,6 +3908,7 @@ export {
3344
3908
  CallError,
3345
3909
  DEFAULT_FINDINGS_ENDPOINT,
3346
3910
  DIALECT_VERSION,
3911
+ FamilyResolutionError,
3347
3912
  INTENT_ARCHETYPES,
3348
3913
  MEASURED_GROUNDING_MIN_N,
3349
3914
  PROVIDER_ENV_KEYS,
@@ -3361,6 +3926,7 @@ export {
3361
3926
  compile2 as compile,
3362
3927
  configureBrain,
3363
3928
  countTokens,
3929
+ deriveFamilyFromModelId,
3364
3930
  execute,
3365
3931
  getActionableAdvisories,
3366
3932
  getAllStarterChains,
@@ -3372,6 +3938,7 @@ export {
3372
3938
  getPerAxisMetrics,
3373
3939
  getProfile,
3374
3940
  getReachabilityDiagnostic,
3941
+ getRecommendedPrimary,
3375
3942
  getSequentialStarterChain,
3376
3943
  getSequentialStarterChainWithGrounding,
3377
3944
  getStaleExclusionFindings,
@@ -3392,6 +3959,7 @@ export {
3392
3959
  loadPricingFromBrain,
3393
3960
  markAdvisoryResolved,
3394
3961
  markExclusionFindingHandled,
3962
+ markPromoteReadyHandled,
3395
3963
  profileToRow,
3396
3964
  profilesByProvider,
3397
3965
  record,