@warmdrift/kgauto-compiler 2.0.0-alpha.27 → 2.0.0-alpha.29

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
@@ -20,14 +20,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ ABSOLUTE_FLOOR: () => ABSOLUTE_FLOOR,
23
24
  ALIASES: () => ALIASES,
24
25
  ALL_ARCHETYPES: () => ALL_ARCHETYPES,
26
+ ARCHETYPE_FLOOR_DEFAULT: () => ARCHETYPE_FLOOR_DEFAULT,
25
27
  CallError: () => CallError,
26
28
  DIALECT_VERSION: () => DIALECT_VERSION,
27
29
  INTENT_ARCHETYPES: () => INTENT_ARCHETYPES,
28
30
  MEASURED_GROUNDING_MIN_N: () => MEASURED_GROUNDING_MIN_N,
29
31
  PROVIDER_ENV_KEYS: () => PROVIDER_ENV_KEYS,
32
+ RULE_SEQUENTIAL_TOOL_CLIFF: () => RULE_SEQUENTIAL_TOOL_CLIFF,
33
+ TRANSLATOR_FLOOR: () => TRANSLATOR_FLOOR,
30
34
  allProfiles: () => allProfiles,
35
+ applySectionRewrites: () => applySectionRewrites,
31
36
  bucketContext: () => bucketContext,
32
37
  bucketHistory: () => bucketHistory,
33
38
  bucketToolCount: () => bucketToolCount,
@@ -38,11 +43,13 @@ __export(index_exports, {
38
43
  configureBrain: () => configureBrain,
39
44
  countTokens: () => countTokens,
40
45
  execute: () => execute,
46
+ getActionableAdvisories: () => getActionableAdvisories,
41
47
  getAllStarterChains: () => getAllStarterChains,
42
48
  getAllStarterChainsWithGrounding: () => getAllStarterChainsWithGrounding,
43
49
  getArchetypePerfScore: () => getArchetypePerfScore,
44
50
  getDefaultFallbackChain: () => getDefaultFallbackChain,
45
51
  getDefaultFallbackChainWithGrounding: () => getDefaultFallbackChainWithGrounding,
52
+ getModelCompatibility: () => getModelCompatibility,
46
53
  getPerAxisMetrics: () => getPerAxisMetrics,
47
54
  getProfile: () => getProfile,
48
55
  getReachabilityDiagnostic: () => getReachabilityDiagnostic,
@@ -62,6 +69,7 @@ __export(index_exports, {
62
69
  loadChainsFromBrain: () => loadChainsFromBrain,
63
70
  loadModelsFromBrain: () => loadModelsFromBrain,
64
71
  loadPricingFromBrain: () => loadPricingFromBrain,
72
+ markAdvisoryResolved: () => markAdvisoryResolved,
65
73
  profileToRow: () => profileToRow,
66
74
  profilesByProvider: () => profilesByProvider,
67
75
  record: () => record,
@@ -583,6 +591,7 @@ function lowerAnthropic(ir, profile, hints) {
583
591
  const historyCacheableTokens = markIndex >= 0 ? sumHistoryTokens(history, markIndex) : 0;
584
592
  const totalCacheableTokens = cacheableTokens + historyCacheableTokens;
585
593
  const cacheSavings = totalCacheableTokens / 1e6 * profile.costInputPer1m * (1 - (profile.lowering.cache.discount ?? 0.1));
594
+ const toolChoice = hints.wireOverrides?.parallelToolCalls === false && tools && tools.length > 0 ? { type: "auto", disable_parallel_tool_use: true } : void 0;
586
595
  return {
587
596
  request: {
588
597
  provider: "anthropic",
@@ -594,7 +603,8 @@ function lowerAnthropic(ir, profile, hints) {
594
603
  // floor surprised every consumer once (PB-Cairn contract-gaps brief, Gap 3).
595
604
  // Profile is the single source of truth; consumers wanting a tighter
596
605
  // budget can pass providerOverrides.anthropic.max_tokens explicitly.
597
- max_tokens: hints.forceTerseOutput ? 200 : profile.maxOutputTokens
606
+ max_tokens: hints.forceTerseOutput ? 200 : profile.maxOutputTokens,
607
+ tool_choice: toolChoice
598
608
  },
599
609
  diagnostics: {
600
610
  cacheableTokens,
@@ -788,6 +798,7 @@ function lowerOpenAI(ir, profile, hints) {
788
798
  const history = (ir.history ?? []).filter((m) => m.role !== "system");
789
799
  const histMarkIndex = resolveHistoryMarkIndex(history.length, ir.historyCachePolicy);
790
800
  const historyCacheableTokens = histMarkIndex >= 0 ? sumHistoryTokens(history, histMarkIndex) : 0;
801
+ const openaiParallelToolCalls = hints.wireOverrides?.parallelToolCalls === false && ir.tools && ir.tools.length > 0 ? false : void 0;
791
802
  return {
792
803
  request: {
793
804
  provider: "openai",
@@ -795,7 +806,8 @@ function lowerOpenAI(ir, profile, hints) {
795
806
  messages,
796
807
  tools: ir.tools && ir.tools.length > 0 ? toOpenAITools(ir.tools) : void 0,
797
808
  response_format: ir.constraints?.structuredOutput ? { type: "json_object" } : void 0,
798
- reasoning_effort: hints.forceTerseOutput ? "low" : void 0
809
+ reasoning_effort: hints.forceTerseOutput ? "low" : void 0,
810
+ parallel_tool_calls: openaiParallelToolCalls
799
811
  },
800
812
  diagnostics: {
801
813
  cacheableTokens: 0,
@@ -2307,6 +2319,86 @@ function getArchetypePerfScore(modelId, archetype) {
2307
2319
  return { score, n, grounding };
2308
2320
  }
2309
2321
 
2322
+ // src/compatibility.ts
2323
+ var ARCHETYPE_FLOOR_DEFAULT = 6;
2324
+ var ABSOLUTE_FLOOR = 4;
2325
+ function rawArchetypePerf(profile, archetype) {
2326
+ return profile.archetypePerf?.[archetype] ?? 5;
2327
+ }
2328
+ function hasSequentialToolCliffForHunt(profile) {
2329
+ if (profile.parallelToolCalls !== false) return false;
2330
+ const huntScore = profile.archetypePerf?.hunt ?? 5;
2331
+ return huntScore < ARCHETYPE_FLOOR_DEFAULT;
2332
+ }
2333
+ function adapterForCliff(profile, archetype) {
2334
+ if (archetype === "hunt" && hasSequentialToolCliffForHunt(profile)) {
2335
+ const otherScores = [];
2336
+ if (profile.archetypePerf) {
2337
+ for (const [k, v] of Object.entries(profile.archetypePerf)) {
2338
+ if (k === "hunt") continue;
2339
+ if (typeof v === "number") otherScores.push(v);
2340
+ }
2341
+ }
2342
+ const sorted = [...otherScores].sort((a, b) => a - b);
2343
+ const median = sorted.length === 0 ? ARCHETYPE_FLOOR_DEFAULT + 1 : sorted[Math.floor(sorted.length / 2)] ?? ARCHETYPE_FLOOR_DEFAULT + 1;
2344
+ const estimated = Math.max(ARCHETYPE_FLOOR_DEFAULT + 1, median);
2345
+ return {
2346
+ adapter: {
2347
+ parameter: "toolOrchestration",
2348
+ value: "sequential",
2349
+ consequence: "Tool calls run one at a time instead of in parallel \u2014 slower per step but reliable for this model."
2350
+ },
2351
+ estimatedScoreWithAdapter: estimated
2352
+ };
2353
+ }
2354
+ return void 0;
2355
+ }
2356
+ function archetypeDescriptor(archetype) {
2357
+ return archetype;
2358
+ }
2359
+ function getModelCompatibility(modelId, intent) {
2360
+ const profile = tryGetProfile(modelId);
2361
+ if (!profile) {
2362
+ return {
2363
+ status: "reject",
2364
+ reason: `Model "${modelId}" is not registered with kgauto \u2014 no compatibility data available.`,
2365
+ archetypePerf: 0
2366
+ };
2367
+ }
2368
+ const { archetype, toolOrchestration } = intent;
2369
+ const rawScore = rawArchetypePerf(profile, archetype);
2370
+ const descriptor = archetypeDescriptor(archetype);
2371
+ const adapterMatch = adapterForCliff(profile, archetype);
2372
+ if (toolOrchestration === "sequential" && adapterMatch && adapterMatch.adapter.parameter === "toolOrchestration" && adapterMatch.adapter.value === "sequential") {
2373
+ return {
2374
+ status: "compatible",
2375
+ reason: `Suited for ${descriptor} with sequential tool calls.`,
2376
+ archetypePerf: rawScore
2377
+ };
2378
+ }
2379
+ if (rawScore >= ARCHETYPE_FLOOR_DEFAULT) {
2380
+ return {
2381
+ status: "compatible",
2382
+ reason: `Suited for ${descriptor}.`,
2383
+ archetypePerf: rawScore
2384
+ };
2385
+ }
2386
+ if (adapterMatch) {
2387
+ return {
2388
+ status: "requires-adapter",
2389
+ reason: `Best with ${adapterMatch.adapter.value} ${adapterMatch.adapter.parameter === "toolOrchestration" ? "tool calls" : adapterMatch.adapter.parameter} for ${descriptor} \u2014 slower but works.`,
2390
+ archetypePerf: rawScore,
2391
+ archetypePerfWithAdapter: adapterMatch.estimatedScoreWithAdapter,
2392
+ adapter: adapterMatch.adapter
2393
+ };
2394
+ }
2395
+ return {
2396
+ status: "reject",
2397
+ reason: `Not suited for ${descriptor} \u2014 would underperform significantly.`,
2398
+ archetypePerf: rawScore
2399
+ };
2400
+ }
2401
+
2310
2402
  // src/advisor.ts
2311
2403
  var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
2312
2404
  var TIER_DOWN_COST_RATIO = 0.5;
@@ -2323,8 +2415,19 @@ function runAdvisor(ir, result, profile, policy, phase2) {
2323
2415
  out.push(...detectModelStaleEvidence(ir, profile));
2324
2416
  out.push(...detectTierDown(ir, profile, phase2));
2325
2417
  }
2418
+ if (!translatorClearedToolCallCliff(phase2)) {
2419
+ out.push(...detectArchetypePerfFloorBreach(ir, profile));
2420
+ }
2326
2421
  return out;
2327
2422
  }
2423
+ function translatorClearedToolCallCliff(phase2) {
2424
+ const rewrites = phase2?.sectionRewritesApplied;
2425
+ if (!rewrites || rewrites.length === 0) return false;
2426
+ for (const rw of rewrites) {
2427
+ if (rw.kind === "tool_call_contract") return true;
2428
+ }
2429
+ return false;
2430
+ }
2328
2431
  function detectCachingOff(ir, profile) {
2329
2432
  if (profile.provider !== "anthropic") return [];
2330
2433
  const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
@@ -2493,6 +2596,77 @@ function detectTierDown(ir, profile, phase2) {
2493
2596
  }
2494
2597
  ];
2495
2598
  }
2599
+ function detectArchetypePerfFloorBreach(ir, profile) {
2600
+ const compat = getModelCompatibility(profile.id, {
2601
+ archetype: ir.intent.archetype,
2602
+ toolOrchestration: ir.constraints?.toolOrchestration
2603
+ });
2604
+ if (compat.status === "compatible") return [];
2605
+ if (compat.status === "requires-adapter") {
2606
+ return [
2607
+ {
2608
+ level: "warn",
2609
+ code: "archetype-perf-floor-breach",
2610
+ 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}`,
2611
+ 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.`,
2612
+ recommendationType: "prompt-fix",
2613
+ suggestedAdaptation: compat.adapter,
2614
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2615
+ }
2616
+ ];
2617
+ }
2618
+ return [
2619
+ {
2620
+ level: "critical",
2621
+ code: "archetype-perf-floor-breach",
2622
+ 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}`,
2623
+ 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.`,
2624
+ recommendationType: "model-swap",
2625
+ docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
2626
+ }
2627
+ ];
2628
+ }
2629
+
2630
+ // src/translator.ts
2631
+ var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
2632
+ var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
2633
+ 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.";
2634
+ function applySectionRewrites(args) {
2635
+ const { ir, profile, archetype } = args;
2636
+ if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
2637
+ return { rewrittenIR: ir, rewrites: [] };
2638
+ }
2639
+ if (!profile.archetypePerf) {
2640
+ return { rewrittenIR: ir, rewrites: [] };
2641
+ }
2642
+ const archetypeScore = profile.archetypePerf[archetype];
2643
+ const cliffFires = typeof archetypeScore === "number" && archetypeScore < TRANSLATOR_FLOOR;
2644
+ if (!cliffFires) {
2645
+ return { rewrittenIR: ir, rewrites: [] };
2646
+ }
2647
+ const rewrites = [];
2648
+ const newSections = ir.sections.map((section) => {
2649
+ if (section.kind !== "tool_call_contract") return section;
2650
+ const originalText = section.text;
2651
+ const transformedText = `${SEQUENTIAL_TOOL_PREAMBLE}
2652
+
2653
+ ${originalText}`;
2654
+ rewrites.push({
2655
+ sectionId: section.id,
2656
+ kind: "tool_call_contract",
2657
+ rule: RULE_SEQUENTIAL_TOOL_CLIFF,
2658
+ originalText,
2659
+ transformedText,
2660
+ wireOverrides: { parallelToolCalls: false }
2661
+ });
2662
+ return { ...section, text: transformedText };
2663
+ });
2664
+ if (rewrites.length === 0) {
2665
+ return { rewrittenIR: ir, rewrites: [] };
2666
+ }
2667
+ const rewrittenIR = { ...ir, sections: newSections };
2668
+ return { rewrittenIR, rewrites };
2669
+ }
2496
2670
 
2497
2671
  // src/compile.ts
2498
2672
  var counter = 0;
@@ -2537,9 +2711,33 @@ function compile(ir, opts = {}) {
2537
2711
  const cliffs = passApplyCliffs(workingIR, profile, inputTokens);
2538
2712
  workingIR = cliffs.value.ir;
2539
2713
  accumulatedMutations.push(...cliffs.mutations);
2714
+ const translated = applySectionRewrites({
2715
+ ir: workingIR,
2716
+ profile,
2717
+ archetype: ir.intent.archetype
2718
+ });
2719
+ workingIR = translated.rewrittenIR;
2720
+ const sectionRewritesApplied = translated.rewrites;
2721
+ let wireOverrides;
2722
+ for (const rw of sectionRewritesApplied) {
2723
+ if (!rw.wireOverrides) continue;
2724
+ if (!wireOverrides) wireOverrides = {};
2725
+ if (rw.wireOverrides.parallelToolCalls !== void 0) {
2726
+ wireOverrides.parallelToolCalls = rw.wireOverrides.parallelToolCalls;
2727
+ }
2728
+ }
2729
+ for (const rw of sectionRewritesApplied) {
2730
+ accumulatedMutations.push({
2731
+ id: `translator:${rw.rule}:${rw.sectionId}`,
2732
+ source: "translator",
2733
+ passName: "translator",
2734
+ description: `Rewrote section "${rw.sectionId}" (kind=${rw.kind}) via rule "${rw.rule}".`
2735
+ });
2736
+ }
2540
2737
  const lowered = lower(workingIR, profile, {
2541
2738
  forceThinkingZero: cliffs.value.loweringHints.forceThinkingZero,
2542
- forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput
2739
+ forceTerseOutput: cliffs.value.loweringHints.forceTerseOutput,
2740
+ wireOverrides
2543
2741
  });
2544
2742
  validateFinalFit(workingIR, profile, inputTokens);
2545
2743
  const handle = makeHandle();
@@ -2587,7 +2785,13 @@ function compile(ir, opts = {}) {
2587
2785
  opts.policy,
2588
2786
  {
2589
2787
  fallbackChain,
2590
- profileResolver: phase2ProfileResolver
2788
+ profileResolver: phase2ProfileResolver,
2789
+ // alpha.29 — feed translator rewrites to the advisor so the
2790
+ // `archetype-perf-floor-breach` rule can suppress when the translator
2791
+ // already cleared the cliff for the same archetype. Without this,
2792
+ // both the rewrite AND the advisory fire — noisy, and the advisory
2793
+ // would mislead consumers into thinking the cliff is unaddressed.
2794
+ sectionRewritesApplied
2591
2795
  }
2592
2796
  );
2593
2797
  return {
@@ -2600,7 +2804,9 @@ function compile(ir, opts = {}) {
2600
2804
  mutationsApplied: accumulatedMutations,
2601
2805
  fallbackChain,
2602
2806
  advisories,
2603
- diagnostics
2807
+ diagnostics,
2808
+ sectionRewritesApplied,
2809
+ wireOverrides
2604
2810
  };
2605
2811
  }
2606
2812
  function validateIR(ir) {
@@ -2753,6 +2959,9 @@ function registerCompile(appId, archetype, ir, result) {
2753
2959
  tokens
2754
2960
  );
2755
2961
  const shapeKey = `${shape.contextBucket}-${shape.toolCountBucket}-${shape.historyDepth}-${shape.outputMode}`;
2962
+ const toolsCount = result.diagnostics.toolsKept;
2963
+ const historyDepth = Array.isArray(ir.history) ? ir.history.length : 0;
2964
+ const systemPromptChars = estimateSystemPromptChars(ir.sections);
2756
2965
  compileRegistry.set(result.handle, {
2757
2966
  appId,
2758
2967
  archetype,
@@ -2766,9 +2975,28 @@ function registerCompile(appId, archetype, ir, result) {
2766
2975
  historyCacheableTokens: result.diagnostics.historyCacheableTokens,
2767
2976
  historyTokensTotal: result.diagnostics.historyTokensTotal,
2768
2977
  // alpha.20 E3: capture consumer's declared mode for the brain payload.
2769
- toolOrchestration: result.diagnostics.toolOrchestration
2978
+ toolOrchestration: result.diagnostics.toolOrchestration,
2979
+ // alpha.28: shape fields for Glass-Box renderer.
2980
+ toolsCount,
2981
+ historyDepth,
2982
+ systemPromptChars,
2983
+ // alpha.29: translator activity — persisted on the brain row so
2984
+ // cross-app aggregates can answer "Sonnet narration rule fired N times,
2985
+ // outcome quality lifted to M."
2986
+ sectionRewritesApplied: result.sectionRewritesApplied
2770
2987
  });
2771
2988
  }
2989
+ function estimateSystemPromptChars(sections) {
2990
+ if (!Array.isArray(sections) || sections.length === 0) return void 0;
2991
+ let total = 0;
2992
+ for (const s of sections) {
2993
+ if (s && typeof s === "object") {
2994
+ const content = s.content;
2995
+ if (typeof content === "string") total += content.length;
2996
+ }
2997
+ }
2998
+ return total > 0 ? total : void 0;
2999
+ }
2772
3000
  async function record(input) {
2773
3001
  const reg = compileRegistry.get(input.handle);
2774
3002
  if (reg) compileRegistry.delete(input.handle);
@@ -2846,6 +3074,8 @@ function buildPayload(input, reg) {
2846
3074
  const mutationsApplied = input.mutationsApplied ?? reg?.mutationsApplied ?? [];
2847
3075
  const costModel = actual;
2848
3076
  const costUsdActual = costModel ? computeCostUsd(costModel, input.tokensIn, input.tokensOut) : void 0;
3077
+ const fellOverFrom = input.fellOverFrom ?? requested;
3078
+ const fallbackReason = fellOverFrom ? input.fallbackReason : void 0;
2849
3079
  return {
2850
3080
  handle: input.handle,
2851
3081
  app_id: reg?.appId,
@@ -2880,7 +3110,20 @@ function buildPayload(input, reg) {
2880
3110
  // the brain can measure per-mode model perf separately (DeepSeek in
2881
3111
  // sequential vs parallel mode is two different stories — L-040).
2882
3112
  // Null when consumer hadn't adopted the constraint yet.
2883
- tool_orchestration: reg?.toolOrchestration ?? null
3113
+ tool_orchestration: reg?.toolOrchestration ?? null,
3114
+ // alpha.28 — Glass-Box renderer substrate (migration 018). All optional;
3115
+ // omitted-undefined PostgREST inserts store NULL → renderer renders "—".
3116
+ finish_reason: input.finishReason,
3117
+ total_ms: input.totalMs ?? input.latencyMs,
3118
+ tools_count: input.toolsCount ?? reg?.toolsCount,
3119
+ history_depth: input.historyDepth ?? reg?.historyDepth,
3120
+ system_prompt_chars: input.systemPromptChars ?? reg?.systemPromptChars,
3121
+ fell_over_from: fellOverFrom,
3122
+ fallback_reason: fallbackReason,
3123
+ // alpha.29 — translator activity (migration 019). Send NULL when no
3124
+ // rewrites fired so the brain's "did the translator do anything?"
3125
+ // queries can use `IS NOT NULL` cleanly.
3126
+ section_rewrites_applied: reg?.sectionRewritesApplied && reg.sectionRewritesApplied.length > 0 ? reg.sectionRewritesApplied : null
2884
3127
  };
2885
3128
  }
2886
3129
  function computeCostUsd(modelId, tokensIn, tokensOut) {
@@ -4078,6 +4321,8 @@ async function call(ir, opts = {}) {
4078
4321
  latencyMs: latencyMs2
4079
4322
  })
4080
4323
  );
4324
+ const fellOver = targetModel !== initial.target;
4325
+ const fallbackReason = fellOver ? normalizeFallbackReason(attempts) : void 0;
4081
4326
  await record({
4082
4327
  handle: initial.handle,
4083
4328
  tokensIn: validated.response.tokens.input,
@@ -4091,10 +4336,18 @@ async function call(ir, opts = {}) {
4091
4336
  promptPreview: extractPromptPreview(ir),
4092
4337
  responsePreview: validated.response.text.slice(0, 200),
4093
4338
  cacheReadInputTokens: validated.response.tokens.cached,
4094
- cacheCreationInputTokens: validated.response.tokens.cacheCreated
4339
+ cacheCreationInputTokens: validated.response.tokens.cacheCreated,
4340
+ // alpha.28 — Glass-Box renderer substrate (migration 018). call()
4341
+ // owns the lifecycle so it has direct visibility into finishReason
4342
+ // (from the normalized provider response), totalMs (mirrors latencyMs
4343
+ // for non-streaming; future streaming variant may diverge), and the
4344
+ // fell-over-from / fallback-reason pair (already computed above for
4345
+ // the CallResult return shape).
4346
+ finishReason: validated.response.finishReason,
4347
+ totalMs: latencyMs2,
4348
+ fellOverFrom: fellOver ? initial.target : void 0,
4349
+ fallbackReason
4095
4350
  });
4096
- const fellOver = targetModel !== initial.target;
4097
- const fallbackReason = fellOver ? normalizeFallbackReason(attempts) : void 0;
4098
4351
  if (fellOver) {
4099
4352
  const firstFailed = attempts.find((a) => a.status !== "success");
4100
4353
  if (firstFailed) {
@@ -4325,6 +4578,278 @@ function clamp(n) {
4325
4578
  return Math.max(0, Math.min(1, n));
4326
4579
  }
4327
4580
 
4581
+ // src/advisories-api.ts
4582
+ var SEVERITY_SET = /* @__PURE__ */ new Set(["info", "warn", "critical"]);
4583
+ var STATUS_SET = /* @__PURE__ */ new Set(["open", "snoozed", "resolved"]);
4584
+ var RESOLUTION_SOURCE_SET = /* @__PURE__ */ new Set([
4585
+ "auto",
4586
+ "consumer-marked",
4587
+ "declined"
4588
+ ]);
4589
+ function asString(v) {
4590
+ return typeof v === "string" && v.length > 0 ? v : void 0;
4591
+ }
4592
+ function asSeverity(v) {
4593
+ if (typeof v === "string" && SEVERITY_SET.has(v)) {
4594
+ return v;
4595
+ }
4596
+ return "info";
4597
+ }
4598
+ function asStatus(v) {
4599
+ if (typeof v === "string" && STATUS_SET.has(v)) {
4600
+ return v;
4601
+ }
4602
+ return "open";
4603
+ }
4604
+ function asResolutionSource(v) {
4605
+ if (typeof v === "string" && RESOLUTION_SOURCE_SET.has(v)) {
4606
+ return v;
4607
+ }
4608
+ return void 0;
4609
+ }
4610
+ function rowToAdvisory(row) {
4611
+ const archetype = asString(row.applies_to_archetype);
4612
+ const model = asString(row.applies_to_model);
4613
+ const docsLink = asString(row.docs_url);
4614
+ const suggestion = asString(row.suggestion);
4615
+ let suggestedFix = null;
4616
+ if (docsLink || suggestion) {
4617
+ suggestedFix = { type: "manual" };
4618
+ if (docsLink) suggestedFix.docsLink = docsLink;
4619
+ if (suggestion) suggestedFix.before = suggestion;
4620
+ }
4621
+ const out = {
4622
+ id: typeof row.id === "string" ? row.id : "",
4623
+ rule: typeof row.rule === "string" ? row.rule : "",
4624
+ severity: asSeverity(row.severity),
4625
+ openedAt: typeof row.opened_at === "string" ? row.opened_at : "",
4626
+ lastObservedAt: typeof row.last_observed_at === "string" ? row.last_observed_at : "",
4627
+ observationCount: typeof row.observation_count === "number" ? row.observation_count : 0,
4628
+ appliesTo: {
4629
+ ...archetype ? { archetype } : {},
4630
+ ...model ? { model } : {}
4631
+ },
4632
+ message: typeof row.message === "string" ? row.message : "",
4633
+ suggestedFix,
4634
+ autoApplicable: false,
4635
+ // reserved — alpha.30+
4636
+ status: asStatus(row.status)
4637
+ };
4638
+ const resolvedAt = asString(row.resolved_at);
4639
+ if (resolvedAt) out.resolvedAt = resolvedAt;
4640
+ const resolutionSource = asResolutionSource(row.resolution_source);
4641
+ if (resolutionSource) out.resolutionSource = resolutionSource;
4642
+ const resolutionNote = asString(row.resolution_note);
4643
+ if (resolutionNote) out.resolutionNote = resolutionNote;
4644
+ return out;
4645
+ }
4646
+ function resolveFetch(injected) {
4647
+ return injected ?? ((...args) => globalThis.fetch(...args));
4648
+ }
4649
+ function normalizeEndpoint(endpoint) {
4650
+ return endpoint.replace(/\/+$/, "");
4651
+ }
4652
+ async function getActionableAdvisories(opts) {
4653
+ const {
4654
+ appId,
4655
+ severity,
4656
+ status,
4657
+ brainEndpoint,
4658
+ brainJwt,
4659
+ brainAnonKey,
4660
+ fetch: injectedFetch
4661
+ } = opts;
4662
+ if (!appId) {
4663
+ throw new Error("getActionableAdvisories: appId is required");
4664
+ }
4665
+ const doFetch = resolveFetch(injectedFetch);
4666
+ const base = normalizeEndpoint(brainEndpoint);
4667
+ const qs = new URLSearchParams();
4668
+ qs.set("app_id", `eq.${appId}`);
4669
+ if (severity) qs.set("severity", `eq.${severity}`);
4670
+ const effectiveStatus = status ?? "open";
4671
+ if (effectiveStatus !== "all") {
4672
+ qs.set("status", `eq.${effectiveStatus}`);
4673
+ }
4674
+ qs.set("order", "last_observed_at.desc");
4675
+ const url = `${base}/rest/v1/actionable_advisories_v?${qs.toString()}`;
4676
+ let res;
4677
+ try {
4678
+ res = await doFetch(url, {
4679
+ method: "GET",
4680
+ headers: {
4681
+ Authorization: `Bearer ${brainJwt}`,
4682
+ apikey: brainAnonKey,
4683
+ Accept: "application/json"
4684
+ }
4685
+ });
4686
+ } catch (err) {
4687
+ const msg = err instanceof Error ? err.message : String(err);
4688
+ throw new Error(`getActionableAdvisories: network error: ${msg}`);
4689
+ }
4690
+ if (res.status === 401 || res.status === 403) {
4691
+ throw new Error("getActionableAdvisories: brain auth misconfig");
4692
+ }
4693
+ if (res.status >= 500) {
4694
+ throw new Error(`getActionableAdvisories: brain unavailable (${res.status})`);
4695
+ }
4696
+ if (!res.ok) {
4697
+ throw new Error(`getActionableAdvisories: bad request (${res.status})`);
4698
+ }
4699
+ let rows;
4700
+ try {
4701
+ rows = await res.json();
4702
+ } catch {
4703
+ throw new Error("getActionableAdvisories: malformed brain response");
4704
+ }
4705
+ if (!Array.isArray(rows)) {
4706
+ throw new Error("getActionableAdvisories: expected array from brain");
4707
+ }
4708
+ const out = [];
4709
+ for (const raw of rows) {
4710
+ if (raw && typeof raw === "object") {
4711
+ out.push(rowToAdvisory(raw));
4712
+ }
4713
+ }
4714
+ return out;
4715
+ }
4716
+ async function markAdvisoryResolved(opts) {
4717
+ const {
4718
+ id,
4719
+ resolutionNote,
4720
+ brainEndpoint,
4721
+ brainJwt,
4722
+ brainAnonKey,
4723
+ fetch: injectedFetch
4724
+ } = opts;
4725
+ if (!id) {
4726
+ return { ok: false, reason: "id_required" };
4727
+ }
4728
+ const doFetch = resolveFetch(injectedFetch);
4729
+ const base = normalizeEndpoint(brainEndpoint);
4730
+ const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
4731
+ let lookupRes;
4732
+ try {
4733
+ lookupRes = await doFetch(lookupUrl, {
4734
+ method: "GET",
4735
+ headers: {
4736
+ Authorization: `Bearer ${brainJwt}`,
4737
+ apikey: brainAnonKey,
4738
+ Accept: "application/json"
4739
+ }
4740
+ });
4741
+ } catch (err) {
4742
+ const msg = err instanceof Error ? err.message : String(err);
4743
+ return { ok: false, reason: `network_error:${msg}` };
4744
+ }
4745
+ if (lookupRes.status === 401 || lookupRes.status === 403) {
4746
+ return { ok: false, reason: "brain_auth_misconfig" };
4747
+ }
4748
+ if (lookupRes.status >= 500) {
4749
+ return { ok: false, reason: "brain_unavailable" };
4750
+ }
4751
+ if (!lookupRes.ok) {
4752
+ return { ok: false, reason: `brain_lookup_failed:${lookupRes.status}` };
4753
+ }
4754
+ let lookupRows;
4755
+ try {
4756
+ lookupRows = await lookupRes.json();
4757
+ } catch {
4758
+ return { ok: false, reason: "brain_lookup_malformed" };
4759
+ }
4760
+ if (!Array.isArray(lookupRows) || lookupRows.length === 0) {
4761
+ return { ok: false, reason: "advisory_not_found" };
4762
+ }
4763
+ const tuple = lookupRows[0];
4764
+ const appId = typeof tuple.app_id === "string" ? tuple.app_id : "";
4765
+ const code = typeof tuple.rule === "string" ? tuple.rule : "";
4766
+ if (!appId || !code) {
4767
+ return { ok: false, reason: "advisory_tuple_invalid" };
4768
+ }
4769
+ const outcomesUrl = `${base}/rest/v1/compile_outcomes?app_id=eq.${encodeURIComponent(appId)}&select=id`;
4770
+ let outcomesRes;
4771
+ try {
4772
+ outcomesRes = await doFetch(outcomesUrl, {
4773
+ method: "GET",
4774
+ headers: {
4775
+ Authorization: `Bearer ${brainJwt}`,
4776
+ apikey: brainAnonKey,
4777
+ Accept: "application/json"
4778
+ }
4779
+ });
4780
+ } catch (err) {
4781
+ const msg = err instanceof Error ? err.message : String(err);
4782
+ return { ok: false, reason: `network_error:${msg}` };
4783
+ }
4784
+ if (outcomesRes.status === 401 || outcomesRes.status === 403) {
4785
+ return { ok: false, reason: "brain_auth_misconfig" };
4786
+ }
4787
+ if (outcomesRes.status >= 500) {
4788
+ return { ok: false, reason: "brain_unavailable" };
4789
+ }
4790
+ if (!outcomesRes.ok) {
4791
+ return { ok: false, reason: `brain_lookup_failed:${outcomesRes.status}` };
4792
+ }
4793
+ let outcomeRows;
4794
+ try {
4795
+ outcomeRows = await outcomesRes.json();
4796
+ } catch {
4797
+ return { ok: false, reason: "brain_lookup_malformed" };
4798
+ }
4799
+ if (!Array.isArray(outcomeRows)) {
4800
+ return { ok: false, reason: "brain_lookup_malformed" };
4801
+ }
4802
+ const outcomeIds = [];
4803
+ for (const row of outcomeRows) {
4804
+ if (row && typeof row === "object") {
4805
+ const idVal = row.id;
4806
+ if (typeof idVal === "number" && Number.isFinite(idVal)) {
4807
+ outcomeIds.push(idVal);
4808
+ }
4809
+ }
4810
+ }
4811
+ if (outcomeIds.length === 0) {
4812
+ return { ok: true };
4813
+ }
4814
+ const inList = outcomeIds.join(",");
4815
+ const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
4816
+ const patchBody = {
4817
+ resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
4818
+ resolution_source: "consumer-marked"
4819
+ };
4820
+ if (resolutionNote !== void 0) {
4821
+ patchBody.resolution_note = resolutionNote;
4822
+ }
4823
+ let patchRes;
4824
+ try {
4825
+ patchRes = await doFetch(patchUrl, {
4826
+ method: "PATCH",
4827
+ headers: {
4828
+ Authorization: `Bearer ${brainJwt}`,
4829
+ apikey: brainAnonKey,
4830
+ "Content-Type": "application/json",
4831
+ Accept: "application/json",
4832
+ // PostgREST default is no return; we don't need the row back.
4833
+ Prefer: "return=minimal"
4834
+ },
4835
+ body: JSON.stringify(patchBody)
4836
+ });
4837
+ } catch (err) {
4838
+ const msg = err instanceof Error ? err.message : String(err);
4839
+ return { ok: false, reason: `network_error:${msg}` };
4840
+ }
4841
+ if (patchRes.status === 401 || patchRes.status === 403) {
4842
+ return { ok: false, reason: "brain_auth_misconfig" };
4843
+ }
4844
+ if (patchRes.status >= 500) {
4845
+ return { ok: false, reason: "brain_unavailable" };
4846
+ }
4847
+ if (!patchRes.ok) {
4848
+ return { ok: false, reason: `patch_failed:${patchRes.status}` };
4849
+ }
4850
+ return { ok: true };
4851
+ }
4852
+
4328
4853
  // src/models-brain.ts
4329
4854
  function isModelRow(x) {
4330
4855
  if (!x || typeof x !== "object") return false;
@@ -4452,14 +4977,19 @@ function compile2(ir, opts) {
4452
4977
  }
4453
4978
  // Annotate the CommonJS export names for ESM import in node:
4454
4979
  0 && (module.exports = {
4980
+ ABSOLUTE_FLOOR,
4455
4981
  ALIASES,
4456
4982
  ALL_ARCHETYPES,
4983
+ ARCHETYPE_FLOOR_DEFAULT,
4457
4984
  CallError,
4458
4985
  DIALECT_VERSION,
4459
4986
  INTENT_ARCHETYPES,
4460
4987
  MEASURED_GROUNDING_MIN_N,
4461
4988
  PROVIDER_ENV_KEYS,
4989
+ RULE_SEQUENTIAL_TOOL_CLIFF,
4990
+ TRANSLATOR_FLOOR,
4462
4991
  allProfiles,
4992
+ applySectionRewrites,
4463
4993
  bucketContext,
4464
4994
  bucketHistory,
4465
4995
  bucketToolCount,
@@ -4470,11 +5000,13 @@ function compile2(ir, opts) {
4470
5000
  configureBrain,
4471
5001
  countTokens,
4472
5002
  execute,
5003
+ getActionableAdvisories,
4473
5004
  getAllStarterChains,
4474
5005
  getAllStarterChainsWithGrounding,
4475
5006
  getArchetypePerfScore,
4476
5007
  getDefaultFallbackChain,
4477
5008
  getDefaultFallbackChainWithGrounding,
5009
+ getModelCompatibility,
4478
5010
  getPerAxisMetrics,
4479
5011
  getProfile,
4480
5012
  getReachabilityDiagnostic,
@@ -4494,6 +5026,7 @@ function compile2(ir, opts) {
4494
5026
  loadChainsFromBrain,
4495
5027
  loadModelsFromBrain,
4496
5028
  loadPricingFromBrain,
5029
+ markAdvisoryResolved,
4497
5030
  profileToRow,
4498
5031
  profilesByProvider,
4499
5032
  record,