@warmdrift/kgauto-compiler 2.0.0-alpha.77 → 2.0.0-alpha.78
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/{chunk-46U2NVOL.mjs → chunk-FT2FN6ZP.mjs} +13 -1
- package/dist/{chunk-GB7VJQ6C.mjs → chunk-QVD2QWST.mjs} +1 -1
- package/dist/{chunk-N36LE3MK.mjs → chunk-VVRDFE6T.mjs} +16 -0
- package/dist/glassbox/index.d.mts +3 -3
- package/dist/glassbox/index.d.ts +3 -3
- package/dist/glassbox-routes/format.d.mts +2 -2
- package/dist/glassbox-routes/format.d.ts +2 -2
- package/dist/glassbox-routes/index.d.mts +4 -4
- package/dist/glassbox-routes/index.d.ts +4 -4
- package/dist/glassbox-routes/index.js +16 -0
- package/dist/glassbox-routes/index.mjs +2 -2
- package/dist/glassbox-routes/react/index.d.mts +2 -2
- package/dist/glassbox-routes/react/index.d.ts +2 -2
- package/dist/index.d.mts +151 -24
- package/dist/index.d.ts +151 -24
- package/dist/index.js +1312 -1031
- package/dist/index.mjs +1282 -1033
- package/dist/{ir-CRV1_taF.d.mts → ir-BFwWhj2s.d.mts} +51 -1
- package/dist/{ir-C02EIElQ.d.ts → ir-DZKS1tI7.d.ts} +51 -1
- package/dist/key-health.js +1 -1
- package/dist/key-health.mjs +1 -1
- package/dist/profiles.d.mts +12 -2
- package/dist/profiles.d.ts +12 -2
- package/dist/profiles.js +16 -0
- package/dist/profiles.mjs +1 -1
- package/dist/{types-C5BX9bzi.d.ts → types-B4kz3Vs0.d.ts} +1 -1
- package/dist/{types-CkRvjxrI.d.ts → types-D_fLt_Xv.d.ts} +1 -1
- package/dist/{types-Caq-hOIj.d.mts → types-DpcAMmk-.d.mts} +1 -1
- package/dist/{types-3WdGMqin.d.mts → types-hjzSWxtv.d.mts} +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -16,10 +16,11 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
LIBRARY_VERSION,
|
|
18
18
|
createKeyHealthRoute
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-QVD2QWST.mjs";
|
|
20
20
|
import {
|
|
21
21
|
ABSOLUTE_FLOOR,
|
|
22
22
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
23
|
+
BRAIN_READ_ENV_NAMES,
|
|
23
24
|
PROVIDER_ENV_KEYS,
|
|
24
25
|
configureBrainQuery,
|
|
25
26
|
createBrainQueryCache,
|
|
@@ -42,7 +43,7 @@ import {
|
|
|
42
43
|
loadChainsFromBrain,
|
|
43
44
|
readBrainReadEnv,
|
|
44
45
|
resolveProviderKey
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-FT2FN6ZP.mjs";
|
|
46
47
|
import {
|
|
47
48
|
ALIASES,
|
|
48
49
|
LATENCY_TIER_MS,
|
|
@@ -53,7 +54,7 @@ import {
|
|
|
53
54
|
latencyTierOf,
|
|
54
55
|
profilesByProvider,
|
|
55
56
|
tryGetProfile
|
|
56
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-VVRDFE6T.mjs";
|
|
57
58
|
import {
|
|
58
59
|
emitAdvisoryFired,
|
|
59
60
|
emitCompileDone,
|
|
@@ -820,11 +821,18 @@ function passScoreTargets(ir, opts) {
|
|
|
820
821
|
}
|
|
821
822
|
}
|
|
822
823
|
let qualityGatePenalty = 0;
|
|
824
|
+
let structuredCliffGate;
|
|
823
825
|
if (constraints.structuredOutput) {
|
|
824
826
|
const schemaWeak = effectiveConventions(profile).some(
|
|
825
827
|
(c) => c.archetype === ir.intent.archetype && c.structuredOutputHint === "avoid"
|
|
826
828
|
);
|
|
827
829
|
if (schemaWeak) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
830
|
+
if (!schemaWeak) {
|
|
831
|
+
structuredCliffGate = profile.cliffs.find(
|
|
832
|
+
(c) => c.action === "quality_gate_structured" && (!c.whenIntent || c.whenIntent === ir.intent.archetype) && c.metric === "input_tokens" && opts.estimatedInputTokens >= c.threshold
|
|
833
|
+
);
|
|
834
|
+
if (structuredCliffGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
835
|
+
}
|
|
828
836
|
}
|
|
829
837
|
const measuredGate = opts.measuredFailureGates?.get(modelId);
|
|
830
838
|
if (measuredGate) qualityGatePenalty = QUALITY_GATE_PENALTY;
|
|
@@ -889,6 +897,16 @@ function passScoreTargets(ir, opts) {
|
|
|
889
897
|
rankAfter: rank,
|
|
890
898
|
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' by MEASURED evidence from this app's own outcomes \u2014 ${measuredGate.nFail} of ${measuredGate.n} attempts failed on the quality axis in the trailing window (${pct(measuredGate.rate)}; 95% lower bound ${pct(measuredGate.lowerBound)} > 50%). Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. The gate is derived, not stored \u2014 it lifts on its own once the failures age out of the window.`
|
|
891
899
|
});
|
|
900
|
+
} else if (structuredCliffGate) {
|
|
901
|
+
policyMutations.push({
|
|
902
|
+
id: `quality-gate-structured-cliff-${modelId}`,
|
|
903
|
+
source: "quality_gate",
|
|
904
|
+
passName: "score_targets",
|
|
905
|
+
rankDelta: -qualityGatePenalty,
|
|
906
|
+
rankBefore,
|
|
907
|
+
rankAfter: rank,
|
|
908
|
+
description: `Model ${modelId} gated below the quality floor for archetype '${ir.intent.archetype}' \u2014 declared structuredOutput + input_tokens \u2265 ${structuredCliffGate.threshold} trips a measured cliff: ${structuredCliffGate.reason} Rank ${rankBefore.toFixed(2)} \u2192 ${rank.toFixed(2)} (\u2212${qualityGatePenalty.toFixed(2)}). Down-ranked out of leadership; retained as graceful fallback only. Bundled knowledge \u2014 active on cold isolates with no brain.`
|
|
909
|
+
});
|
|
892
910
|
} else {
|
|
893
911
|
policyMutations.push({
|
|
894
912
|
id: `quality-gate-structured-${modelId}`,
|
|
@@ -1572,13 +1590,8 @@ function getArchetypePerfScore(modelId, archetype) {
|
|
|
1572
1590
|
return { score, n, grounding };
|
|
1573
1591
|
}
|
|
1574
1592
|
|
|
1575
|
-
// src/
|
|
1576
|
-
function
|
|
1577
|
-
if (!x || typeof x !== "object") return false;
|
|
1578
|
-
const r = x;
|
|
1579
|
-
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";
|
|
1580
|
-
}
|
|
1581
|
-
function coerceNumber(v) {
|
|
1593
|
+
// src/measured-failure-brain.ts
|
|
1594
|
+
function coerceCount(v) {
|
|
1582
1595
|
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1583
1596
|
if (typeof v === "string") {
|
|
1584
1597
|
const n = Number(v);
|
|
@@ -1586,58 +1599,144 @@ function coerceNumber(v) {
|
|
|
1586
1599
|
}
|
|
1587
1600
|
return null;
|
|
1588
1601
|
}
|
|
1589
|
-
function
|
|
1602
|
+
function isRawFailureRow(x) {
|
|
1603
|
+
if (!x || typeof x !== "object") return false;
|
|
1604
|
+
const r = x;
|
|
1605
|
+
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
1606
|
+
}
|
|
1607
|
+
function mapRows(rows) {
|
|
1590
1608
|
const out = [];
|
|
1591
1609
|
for (const row of rows) {
|
|
1592
|
-
if (!
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
1610
|
+
if (!isRawFailureRow(row)) continue;
|
|
1611
|
+
const n = coerceCount(row.n);
|
|
1612
|
+
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
1613
|
+
if (n === null || n <= 0) continue;
|
|
1597
1614
|
out.push({
|
|
1598
1615
|
archetype: row.intent_archetype,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
sampleN,
|
|
1603
|
-
judgePassRate: passRate,
|
|
1604
|
-
judgeAvgScore: avgScore,
|
|
1605
|
-
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
1606
|
-
detectedAt: row.detected_at
|
|
1616
|
+
model: row.model,
|
|
1617
|
+
n,
|
|
1618
|
+
nFail
|
|
1607
1619
|
});
|
|
1608
1620
|
}
|
|
1609
1621
|
return out;
|
|
1610
1622
|
}
|
|
1623
|
+
var MEASURED_FAILURE_CFG = {
|
|
1624
|
+
/**
|
|
1625
|
+
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
1626
|
+
* pathological tiny samples that the confidence bound alone would let
|
|
1627
|
+
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
1628
|
+
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
1629
|
+
* a bad day, five in a row is a pattern).
|
|
1630
|
+
*/
|
|
1631
|
+
minSample: 5,
|
|
1632
|
+
/**
|
|
1633
|
+
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
1634
|
+
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
1635
|
+
* a model that probably fails the majority of the time has no business
|
|
1636
|
+
* leading a surface, whatever its declared scores say.
|
|
1637
|
+
*/
|
|
1638
|
+
lowerBoundThreshold: 0.5,
|
|
1639
|
+
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
1640
|
+
z: 1.96,
|
|
1641
|
+
/** Must match the view's window. Documented here for the advisory text. */
|
|
1642
|
+
windowDays: 28
|
|
1643
|
+
};
|
|
1644
|
+
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
1645
|
+
if (n <= 0) return 0;
|
|
1646
|
+
const p = failures / n;
|
|
1647
|
+
const z2 = z * z;
|
|
1648
|
+
const denom = 1 + z2 / n;
|
|
1649
|
+
const centre = p + z2 / (2 * n);
|
|
1650
|
+
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
1651
|
+
const lower2 = (centre - margin) / denom;
|
|
1652
|
+
return lower2 < 0 ? 0 : lower2;
|
|
1653
|
+
}
|
|
1654
|
+
function mapMeasuredFailureRows(rows) {
|
|
1655
|
+
return mapRows(rows);
|
|
1656
|
+
}
|
|
1657
|
+
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
1658
|
+
if (!row) return void 0;
|
|
1659
|
+
const normalized = "nFail" in row && typeof row.n === "number" ? row : mapRows([row])[0];
|
|
1660
|
+
if (!normalized || normalized.n < cfg.minSample) return void 0;
|
|
1661
|
+
const lowerBound = wilsonLowerBound(normalized.nFail, normalized.n, cfg.z);
|
|
1662
|
+
return {
|
|
1663
|
+
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
1664
|
+
rate: normalized.nFail / normalized.n,
|
|
1665
|
+
lowerBound,
|
|
1666
|
+
n: normalized.n,
|
|
1667
|
+
nFail: normalized.nFail
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1611
1670
|
var snapshots2 = /* @__PURE__ */ new Map();
|
|
1612
1671
|
var runtime2;
|
|
1613
1672
|
var warnedOnce2 = false;
|
|
1614
|
-
|
|
1673
|
+
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
1674
|
+
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
1675
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
1676
|
+
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
1677
|
+
return !(raw === "0" || raw === "false");
|
|
1678
|
+
}
|
|
1679
|
+
function configureMeasuredFailureBrain(rt) {
|
|
1680
|
+
runtime2 = rt;
|
|
1681
|
+
snapshots2.clear();
|
|
1682
|
+
warnedOnce2 = false;
|
|
1683
|
+
}
|
|
1684
|
+
function isMeasuredFailureBrainActive() {
|
|
1615
1685
|
return runtime2 !== void 0;
|
|
1616
1686
|
}
|
|
1617
|
-
function
|
|
1687
|
+
function prefetchMeasuredFailure(appId) {
|
|
1618
1688
|
const rt = runtime2;
|
|
1619
|
-
if (!rt) return
|
|
1620
|
-
|
|
1621
|
-
if (!
|
|
1689
|
+
if (!rt || !appId) return void 0;
|
|
1690
|
+
let snap = snapshots2.get(appId);
|
|
1691
|
+
if (!snap) {
|
|
1692
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1693
|
+
snapshots2.set(appId, snap);
|
|
1694
|
+
}
|
|
1695
|
+
if (snap.expiresAt > Date.now()) return void 0;
|
|
1696
|
+
const inflight = pendingRefreshes2.get(appId);
|
|
1697
|
+
if (inflight) return inflight;
|
|
1698
|
+
if (snap.refreshing) return void 0;
|
|
1699
|
+
snap.refreshing = true;
|
|
1700
|
+
void asyncRefresh2(rt, appId);
|
|
1701
|
+
return pendingRefreshes2.get(appId);
|
|
1702
|
+
}
|
|
1703
|
+
async function awaitMeasuredFailureReady(appId, timeoutMs) {
|
|
1704
|
+
if (!runtime2 || !appId) return;
|
|
1705
|
+
const pending = prefetchMeasuredFailure(appId) ?? pendingRefreshes2.get(appId);
|
|
1706
|
+
if (!(timeoutMs > 0)) return;
|
|
1707
|
+
if (!pending) return;
|
|
1708
|
+
let timer;
|
|
1709
|
+
try {
|
|
1710
|
+
await Promise.race([
|
|
1711
|
+
pending,
|
|
1712
|
+
new Promise((resolve) => {
|
|
1713
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
1714
|
+
})
|
|
1715
|
+
]);
|
|
1716
|
+
} catch {
|
|
1717
|
+
} finally {
|
|
1718
|
+
if (timer) clearTimeout(timer);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function getMeasuredFailureVerdict(opts) {
|
|
1722
|
+
const rt = runtime2;
|
|
1723
|
+
if (!rt) return void 0;
|
|
1724
|
+
const { appId, archetype, model } = opts;
|
|
1725
|
+
if (!appId || !archetype || !model) return void 0;
|
|
1622
1726
|
let snap = snapshots2.get(appId);
|
|
1623
1727
|
if (!snap) {
|
|
1624
1728
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1625
1729
|
snapshots2.set(appId, snap);
|
|
1626
1730
|
}
|
|
1627
1731
|
const now = Date.now();
|
|
1628
|
-
|
|
1629
|
-
if (stale && !snap.refreshing) {
|
|
1732
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
1630
1733
|
snap.refreshing = true;
|
|
1631
1734
|
void asyncRefresh2(rt, appId);
|
|
1632
1735
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (opts.family) {
|
|
1638
|
-
rows = rows.filter((f) => f.family === opts.family);
|
|
1639
|
-
}
|
|
1640
|
-
return rows;
|
|
1736
|
+
const row = snap.data.find(
|
|
1737
|
+
(r) => r.archetype === archetype && r.model === model
|
|
1738
|
+
);
|
|
1739
|
+
return judgeMeasuredFailure(row);
|
|
1641
1740
|
}
|
|
1642
1741
|
var pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
1643
1742
|
async function asyncRefresh2(rt, appId) {
|
|
@@ -1661,12 +1760,11 @@ async function doRefresh2(rt, appId) {
|
|
|
1661
1760
|
try {
|
|
1662
1761
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
1663
1762
|
if (!res.ok) {
|
|
1664
|
-
throw new Error(`
|
|
1763
|
+
throw new Error(`measured-failure ${res.status}: ${res.statusText}`);
|
|
1665
1764
|
}
|
|
1666
1765
|
const body = await res.json();
|
|
1667
1766
|
if (runtime2 !== rt) return;
|
|
1668
|
-
|
|
1669
|
-
snap.data = rows;
|
|
1767
|
+
snap.data = Array.isArray(body) ? mapRows(body) : [];
|
|
1670
1768
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
1671
1769
|
snap.refreshing = false;
|
|
1672
1770
|
} catch (err) {
|
|
@@ -1681,193 +1779,84 @@ async function doRefresh2(rt, appId) {
|
|
|
1681
1779
|
}
|
|
1682
1780
|
function defaultOnError2(err) {
|
|
1683
1781
|
console.warn(
|
|
1684
|
-
"[kgauto]
|
|
1782
|
+
"[kgauto] measured-failure fetch failed (gate inactive until next refresh):",
|
|
1685
1783
|
err
|
|
1686
1784
|
);
|
|
1687
1785
|
}
|
|
1688
|
-
function
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1786
|
+
function _testResetMeasuredFailure() {
|
|
1787
|
+
runtime2 = void 0;
|
|
1788
|
+
snapshots2.clear();
|
|
1789
|
+
pendingRefreshes2 = /* @__PURE__ */ new Map();
|
|
1790
|
+
warnedOnce2 = false;
|
|
1693
1791
|
}
|
|
1694
|
-
async function
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
archetype,
|
|
1698
|
-
family,
|
|
1699
|
-
resolution,
|
|
1700
|
-
resolutionNote,
|
|
1701
|
-
brainEndpoint,
|
|
1702
|
-
brainJwt,
|
|
1703
|
-
brainAnonKey,
|
|
1704
|
-
fetch: injectedFetch
|
|
1705
|
-
} = opts;
|
|
1706
|
-
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
1707
|
-
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
1708
|
-
if (!family) return { ok: false, reason: "family_required" };
|
|
1709
|
-
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
1710
|
-
return { ok: false, reason: "resolution_invalid" };
|
|
1711
|
-
}
|
|
1712
|
-
const doFetch = resolveFetchImpl(injectedFetch);
|
|
1713
|
-
const base = normalizeEndpoint(brainEndpoint);
|
|
1714
|
-
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`;
|
|
1715
|
-
const patchBody = {
|
|
1716
|
-
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1717
|
-
resolution
|
|
1718
|
-
};
|
|
1719
|
-
if (resolutionNote !== void 0) {
|
|
1720
|
-
patchBody.resolution_note = resolutionNote;
|
|
1721
|
-
}
|
|
1722
|
-
let res;
|
|
1723
|
-
try {
|
|
1724
|
-
res = await doFetch(url, {
|
|
1725
|
-
method: "PATCH",
|
|
1726
|
-
headers: {
|
|
1727
|
-
Authorization: `Bearer ${brainJwt}`,
|
|
1728
|
-
apikey: brainAnonKey,
|
|
1729
|
-
"Content-Type": "application/json",
|
|
1730
|
-
Accept: "application/json",
|
|
1731
|
-
Prefer: "return=minimal"
|
|
1732
|
-
},
|
|
1733
|
-
body: JSON.stringify(patchBody)
|
|
1734
|
-
});
|
|
1735
|
-
} catch (err) {
|
|
1736
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1737
|
-
return { ok: false, reason: `network_error:${msg}` };
|
|
1738
|
-
}
|
|
1739
|
-
if (res.status === 401 || res.status === 403) {
|
|
1740
|
-
return { ok: false, reason: "brain_auth_misconfig" };
|
|
1741
|
-
}
|
|
1742
|
-
if (res.status >= 500) {
|
|
1743
|
-
return { ok: false, reason: "brain_unavailable" };
|
|
1744
|
-
}
|
|
1745
|
-
if (!res.ok) {
|
|
1746
|
-
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
1747
|
-
}
|
|
1748
|
-
return { ok: true };
|
|
1792
|
+
async function _testWaitForMeasuredFailureRefresh() {
|
|
1793
|
+
const pending = Array.from(pendingRefreshes2.values());
|
|
1794
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
1749
1795
|
}
|
|
1750
1796
|
|
|
1751
|
-
// src/
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
1757
|
-
if (finding.currentModel !== resolvedPrimary) return false;
|
|
1758
|
-
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
1759
|
-
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
1760
|
-
return true;
|
|
1797
|
+
// src/promotions-brain.ts
|
|
1798
|
+
function isRawPromotionRow(x) {
|
|
1799
|
+
if (!x || typeof x !== "object") return false;
|
|
1800
|
+
const r = x;
|
|
1801
|
+
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
1761
1802
|
}
|
|
1762
|
-
function
|
|
1763
|
-
if (
|
|
1764
|
-
if (
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
1769
|
-
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
1770
|
-
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
1771
|
-
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
1803
|
+
function coerceId(v) {
|
|
1804
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1805
|
+
if (typeof v === "string") {
|
|
1806
|
+
const n = Number(v);
|
|
1807
|
+
return Number.isFinite(n) ? n : null;
|
|
1808
|
+
}
|
|
1772
1809
|
return null;
|
|
1773
1810
|
}
|
|
1774
|
-
function
|
|
1775
|
-
if (!isPromoteReadyBrainActive()) return [];
|
|
1776
|
-
if (!ctx.appId) return [];
|
|
1777
|
-
if (!ctx.resolvedPrimary) return [];
|
|
1778
|
-
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
1779
|
-
if (!family) return [];
|
|
1780
|
-
const findings = loadPromoteReadyFindings({
|
|
1781
|
-
appId: ctx.appId,
|
|
1782
|
-
archetype: ctx.archetype,
|
|
1783
|
-
family
|
|
1784
|
-
});
|
|
1785
|
-
if (findings.length === 0) return [];
|
|
1786
|
-
const qualifying = findings.filter(
|
|
1787
|
-
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
1788
|
-
);
|
|
1789
|
-
if (qualifying.length === 0) return [];
|
|
1790
|
-
qualifying.sort((a, b) => {
|
|
1791
|
-
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
1792
|
-
return b.judgeAvgScore - a.judgeAvgScore;
|
|
1793
|
-
}
|
|
1794
|
-
return b.judgePassRate - a.judgePassRate;
|
|
1795
|
-
});
|
|
1796
|
-
const top = qualifying[0];
|
|
1797
|
-
const pctPass = Math.round(top.judgePassRate * 100);
|
|
1798
|
-
const score = top.judgeAvgScore.toFixed(2);
|
|
1799
|
-
let costClause = "";
|
|
1800
|
-
if (top.costDeltaPct !== null) {
|
|
1801
|
-
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
1802
|
-
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
1803
|
-
costClause = `, cost ${magnitude}% ${sign}`;
|
|
1804
|
-
}
|
|
1805
|
-
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.`;
|
|
1806
|
-
return [
|
|
1807
|
-
{
|
|
1808
|
-
level: "info",
|
|
1809
|
-
code: "promote-ready",
|
|
1810
|
-
message,
|
|
1811
|
-
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.`,
|
|
1812
|
-
// alpha.36 architectural field — not a no-ai-needed case.
|
|
1813
|
-
recommendedArchitecture: void 0,
|
|
1814
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
1815
|
-
}
|
|
1816
|
-
];
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// src/advisor-rules/consumer-on-stale-model.ts
|
|
1820
|
-
function isStaleStatus(v) {
|
|
1821
|
-
return v === "legacy" || v === "deprecated";
|
|
1822
|
-
}
|
|
1823
|
-
function asString(v) {
|
|
1824
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
1825
|
-
}
|
|
1826
|
-
function mapRowsToFindings3(rows) {
|
|
1811
|
+
function mapRowsToPromotions(rows) {
|
|
1827
1812
|
const out = [];
|
|
1828
|
-
for (const
|
|
1829
|
-
if (!
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
const
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
};
|
|
1850
|
-
const suggestion = asString(r.suggestion);
|
|
1851
|
-
if (suggestion) row.suggestion = suggestion;
|
|
1852
|
-
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
1853
|
-
row.observationCount = r.observation_count;
|
|
1854
|
-
}
|
|
1855
|
-
out.push(row);
|
|
1813
|
+
for (const row of rows) {
|
|
1814
|
+
if (!isRawPromotionRow(row)) continue;
|
|
1815
|
+
const id = coerceId(row.id);
|
|
1816
|
+
if (id === null) continue;
|
|
1817
|
+
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
1818
|
+
if (mode === null) continue;
|
|
1819
|
+
out.push({
|
|
1820
|
+
id,
|
|
1821
|
+
archetype: row.intent_archetype,
|
|
1822
|
+
mode,
|
|
1823
|
+
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
1824
|
+
promotedModel: row.promoted_model,
|
|
1825
|
+
incumbentModel: row.incumbent_model,
|
|
1826
|
+
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
1827
|
+
suppressQualityGate: row.suppress_quality_gate === true,
|
|
1828
|
+
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : "",
|
|
1829
|
+
// Pre-.78 endpoints serve no status column and only active rows —
|
|
1830
|
+
// defaulting to 'active' is exact, not optimistic.
|
|
1831
|
+
status: row.status === "rolled_back" ? "rolled_back" : "active",
|
|
1832
|
+
...typeof row.rolled_back_at === "string" ? { rolledBackAt: row.rolled_back_at } : {},
|
|
1833
|
+
...typeof row.rollback_class === "string" ? { rollbackClass: row.rollback_class } : {}
|
|
1834
|
+
});
|
|
1856
1835
|
}
|
|
1857
1836
|
return out;
|
|
1858
1837
|
}
|
|
1859
1838
|
var snapshots3 = /* @__PURE__ */ new Map();
|
|
1860
1839
|
var runtime3;
|
|
1861
1840
|
var warnedOnce3 = false;
|
|
1862
|
-
var
|
|
1863
|
-
function
|
|
1841
|
+
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
1842
|
+
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
1843
|
+
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
1844
|
+
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
1845
|
+
return raw === "1" || raw === "true";
|
|
1846
|
+
}
|
|
1847
|
+
function configurePromotionsBrain(rt) {
|
|
1848
|
+
runtime3 = rt;
|
|
1849
|
+
snapshots3.clear();
|
|
1850
|
+
warnedOnce3 = false;
|
|
1851
|
+
}
|
|
1852
|
+
function isPromotionsBrainActive() {
|
|
1864
1853
|
return runtime3 !== void 0;
|
|
1865
1854
|
}
|
|
1866
|
-
function
|
|
1855
|
+
function getApplicablePromotion(opts) {
|
|
1867
1856
|
const rt = runtime3;
|
|
1868
|
-
if (!rt) return
|
|
1857
|
+
if (!rt) return void 0;
|
|
1869
1858
|
const appId = opts.appId;
|
|
1870
|
-
if (!appId) return
|
|
1859
|
+
if (!appId || !opts.archetype || !opts.mode) return void 0;
|
|
1871
1860
|
let snap = snapshots3.get(appId);
|
|
1872
1861
|
if (!snap) {
|
|
1873
1862
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -1879,11 +1868,31 @@ function getStaleModelFindings(opts) {
|
|
|
1879
1868
|
snap.refreshing = true;
|
|
1880
1869
|
void asyncRefresh3(rt, appId);
|
|
1881
1870
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1871
|
+
return snap.data.find(
|
|
1872
|
+
(p) => p.status === "active" && p.archetype === opts.archetype && p.mode === opts.mode
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
var ROLLBACK_SUPPRESSION_WINDOW_DAYS = 28;
|
|
1876
|
+
function getRecentRollback(opts) {
|
|
1877
|
+
const rt = runtime3;
|
|
1878
|
+
if (!rt) return void 0;
|
|
1879
|
+
if (!opts.appId || !opts.archetype || !opts.model) return void 0;
|
|
1880
|
+
let snap = snapshots3.get(opts.appId);
|
|
1881
|
+
if (!snap) {
|
|
1882
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
1883
|
+
snapshots3.set(opts.appId, snap);
|
|
1884
1884
|
}
|
|
1885
|
-
|
|
1885
|
+
const now = opts.nowMs ?? Date.now();
|
|
1886
|
+
if (snap.expiresAt <= now && !snap.refreshing) {
|
|
1887
|
+
snap.refreshing = true;
|
|
1888
|
+
void asyncRefresh3(rt, opts.appId);
|
|
1889
|
+
}
|
|
1890
|
+
const windowMs = (opts.windowDays ?? ROLLBACK_SUPPRESSION_WINDOW_DAYS) * 864e5;
|
|
1891
|
+
return snap.data.find(
|
|
1892
|
+
(p) => p.status === "rolled_back" && p.archetype === opts.archetype && p.promotedModel === opts.model && typeof p.rolledBackAt === "string" && now - Date.parse(p.rolledBackAt) <= windowMs
|
|
1893
|
+
);
|
|
1886
1894
|
}
|
|
1895
|
+
var pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
1887
1896
|
async function asyncRefresh3(rt, appId) {
|
|
1888
1897
|
const promise = doRefresh3(rt, appId);
|
|
1889
1898
|
pendingRefreshes3.set(appId, promise);
|
|
@@ -1896,7 +1905,7 @@ async function asyncRefresh3(rt, appId) {
|
|
|
1896
1905
|
}
|
|
1897
1906
|
}
|
|
1898
1907
|
async function doRefresh3(rt, appId) {
|
|
1899
|
-
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
1908
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}&with_rollbacks=1`;
|
|
1900
1909
|
let snap = snapshots3.get(appId);
|
|
1901
1910
|
if (!snap) {
|
|
1902
1911
|
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
@@ -1905,11 +1914,11 @@ async function doRefresh3(rt, appId) {
|
|
|
1905
1914
|
try {
|
|
1906
1915
|
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
1907
1916
|
if (!res.ok) {
|
|
1908
|
-
throw new Error(`
|
|
1917
|
+
throw new Error(`promotions ${res.status}: ${res.statusText}`);
|
|
1909
1918
|
}
|
|
1910
1919
|
const body = await res.json();
|
|
1911
1920
|
if (runtime3 !== rt) return;
|
|
1912
|
-
const rows = Array.isArray(body) ?
|
|
1921
|
+
const rows = Array.isArray(body) ? mapRowsToPromotions(body) : [];
|
|
1913
1922
|
snap.data = rows;
|
|
1914
1923
|
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
1915
1924
|
snap.refreshing = false;
|
|
@@ -1925,847 +1934,905 @@ async function doRefresh3(rt, appId) {
|
|
|
1925
1934
|
}
|
|
1926
1935
|
function defaultOnError3(err) {
|
|
1927
1936
|
console.warn(
|
|
1928
|
-
"[kgauto]
|
|
1937
|
+
"[kgauto] promotions fetch failed (promotion boost inactive until next refresh):",
|
|
1929
1938
|
err
|
|
1930
1939
|
);
|
|
1931
1940
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
if (
|
|
1941
|
-
const ranked = [...findings].sort((a, b) => {
|
|
1942
|
-
if (a.staleStatus !== b.staleStatus) {
|
|
1943
|
-
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
1944
|
-
}
|
|
1945
|
-
return a.staleModel.localeCompare(b.staleModel);
|
|
1946
|
-
});
|
|
1947
|
-
const top = ranked[0];
|
|
1948
|
-
const extraCount = findings.length - 1;
|
|
1949
|
-
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
1950
|
-
return [
|
|
1951
|
-
{
|
|
1952
|
-
level: "warn",
|
|
1953
|
-
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
1954
|
-
message: `${top.message}${extraNote}`,
|
|
1955
|
-
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}.`,
|
|
1956
|
-
recommendationType: "model-swap",
|
|
1957
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
1958
|
-
}
|
|
1959
|
-
];
|
|
1941
|
+
function _testResetPromotions() {
|
|
1942
|
+
runtime3 = void 0;
|
|
1943
|
+
snapshots3.clear();
|
|
1944
|
+
pendingRefreshes3 = /* @__PURE__ */ new Map();
|
|
1945
|
+
warnedOnce3 = false;
|
|
1946
|
+
}
|
|
1947
|
+
async function _testWaitForPromotionsRefresh() {
|
|
1948
|
+
const pending = Array.from(pendingRefreshes3.values());
|
|
1949
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
1960
1950
|
}
|
|
1961
1951
|
|
|
1962
|
-
// src/
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
{
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
|
|
1974
|
-
costGuidance: "comparable or cheaper at current pricing"
|
|
1975
|
-
}
|
|
1976
|
-
]);
|
|
1977
|
-
function findBetterFit(archetype, currentFamily) {
|
|
1978
|
-
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
1979
|
-
if (fit.archetype !== archetype) continue;
|
|
1980
|
-
if (fit.betterFitFamily === currentFamily) return null;
|
|
1981
|
-
return fit;
|
|
1952
|
+
// src/promote-ready-brain.ts
|
|
1953
|
+
function isRawPromoteReadyRow(x) {
|
|
1954
|
+
if (!x || typeof x !== "object") return false;
|
|
1955
|
+
const r = x;
|
|
1956
|
+
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";
|
|
1957
|
+
}
|
|
1958
|
+
function coerceNumber(v) {
|
|
1959
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1960
|
+
if (typeof v === "string") {
|
|
1961
|
+
const n = Number(v);
|
|
1962
|
+
return Number.isFinite(n) ? n : null;
|
|
1982
1963
|
}
|
|
1983
1964
|
return null;
|
|
1984
1965
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
const
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
1966
|
+
function mapRowsToFindings2(rows) {
|
|
1967
|
+
const out = [];
|
|
1968
|
+
for (const row of rows) {
|
|
1969
|
+
if (!isRawPromoteReadyRow(row)) continue;
|
|
1970
|
+
const sampleN = coerceNumber(row.sample_n);
|
|
1971
|
+
const passRate = coerceNumber(row.judge_pass_rate);
|
|
1972
|
+
const avgScore = coerceNumber(row.judge_avg_score);
|
|
1973
|
+
if (sampleN === null || passRate === null || avgScore === null) continue;
|
|
1974
|
+
out.push({
|
|
1975
|
+
archetype: row.intent_archetype,
|
|
1976
|
+
family: row.family,
|
|
1977
|
+
candidateModel: row.candidate_model,
|
|
1978
|
+
currentModel: row.current_model,
|
|
1979
|
+
sampleN,
|
|
1980
|
+
judgePassRate: passRate,
|
|
1981
|
+
judgeAvgScore: avgScore,
|
|
1982
|
+
costDeltaPct: coerceNumber(row.cost_delta_pct),
|
|
1983
|
+
detectedAt: row.detected_at
|
|
1984
|
+
});
|
|
1994
1985
|
}
|
|
1995
|
-
return
|
|
1986
|
+
return out;
|
|
1996
1987
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
if (profile.status !== "current") continue;
|
|
2003
|
-
if (profile.active === false) continue;
|
|
2004
|
-
candidates.push(profile.id);
|
|
2005
|
-
if (candidates.length >= 3) break;
|
|
2006
|
-
}
|
|
2007
|
-
return candidates;
|
|
1988
|
+
var snapshots4 = /* @__PURE__ */ new Map();
|
|
1989
|
+
var runtime4;
|
|
1990
|
+
var warnedOnce4 = false;
|
|
1991
|
+
function isPromoteReadyBrainActive() {
|
|
1992
|
+
return runtime4 !== void 0;
|
|
2008
1993
|
}
|
|
2009
|
-
function
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
if (!
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
1994
|
+
function loadPromoteReadyFindings(opts) {
|
|
1995
|
+
const rt = runtime4;
|
|
1996
|
+
if (!rt) return [];
|
|
1997
|
+
const appId = opts.appId;
|
|
1998
|
+
if (!appId) return [];
|
|
1999
|
+
let snap = snapshots4.get(appId);
|
|
2000
|
+
if (!snap) {
|
|
2001
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2002
|
+
snapshots4.set(appId, snap);
|
|
2003
|
+
}
|
|
2004
|
+
const now = Date.now();
|
|
2005
|
+
const stale = snap.expiresAt <= now;
|
|
2006
|
+
if (stale && !snap.refreshing) {
|
|
2007
|
+
snap.refreshing = true;
|
|
2008
|
+
void asyncRefresh4(rt, appId);
|
|
2009
|
+
}
|
|
2010
|
+
let rows = snap.data;
|
|
2011
|
+
if (opts.archetype) {
|
|
2012
|
+
rows = rows.filter((f) => f.archetype === opts.archetype);
|
|
2013
|
+
}
|
|
2014
|
+
if (opts.family) {
|
|
2015
|
+
rows = rows.filter((f) => f.family === opts.family);
|
|
2016
|
+
}
|
|
2017
|
+
return rows;
|
|
2018
|
+
}
|
|
2019
|
+
var pendingRefreshes4 = /* @__PURE__ */ new Map();
|
|
2020
|
+
async function asyncRefresh4(rt, appId) {
|
|
2021
|
+
const promise = doRefresh4(rt, appId);
|
|
2022
|
+
pendingRefreshes4.set(appId, promise);
|
|
2023
|
+
try {
|
|
2024
|
+
await promise;
|
|
2025
|
+
} finally {
|
|
2026
|
+
if (pendingRefreshes4.get(appId) === promise) {
|
|
2027
|
+
pendingRefreshes4.delete(appId);
|
|
2029
2028
|
}
|
|
2030
|
-
|
|
2029
|
+
}
|
|
2031
2030
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2031
|
+
async function doRefresh4(rt, appId) {
|
|
2032
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
2033
|
+
let snap = snapshots4.get(appId);
|
|
2034
|
+
if (!snap) {
|
|
2035
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2036
|
+
snapshots4.set(appId, snap);
|
|
2037
|
+
}
|
|
2038
|
+
try {
|
|
2039
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
2040
|
+
if (!res.ok) {
|
|
2041
|
+
throw new Error(`promote-ready ${res.status}: ${res.statusText}`);
|
|
2042
|
+
}
|
|
2043
|
+
const body = await res.json();
|
|
2044
|
+
if (runtime4 !== rt) return;
|
|
2045
|
+
const rows = Array.isArray(body) ? mapRowsToFindings2(body) : [];
|
|
2046
|
+
snap.data = rows;
|
|
2047
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2048
|
+
snap.refreshing = false;
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
if (runtime4 !== rt) return;
|
|
2051
|
+
snap.refreshing = false;
|
|
2052
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2053
|
+
if (!warnedOnce4) {
|
|
2054
|
+
warnedOnce4 = true;
|
|
2055
|
+
(rt.onError ?? defaultOnError4)(err);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2043
2058
|
}
|
|
2044
|
-
function
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2059
|
+
function defaultOnError4(err) {
|
|
2060
|
+
console.warn(
|
|
2061
|
+
"[kgauto] promote-ready fetch failed (using empty fallback):",
|
|
2062
|
+
err
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
function resolveFetchImpl(injected) {
|
|
2066
|
+
return injected ?? ((...args) => globalThis.fetch(...args));
|
|
2067
|
+
}
|
|
2068
|
+
function normalizeEndpoint(endpoint) {
|
|
2069
|
+
return endpoint.replace(/\/+$/, "");
|
|
2070
|
+
}
|
|
2071
|
+
async function markPromoteReadyHandled(opts) {
|
|
2072
|
+
const {
|
|
2073
|
+
appId,
|
|
2074
|
+
archetype,
|
|
2075
|
+
family,
|
|
2076
|
+
resolution,
|
|
2077
|
+
resolutionNote,
|
|
2078
|
+
brainEndpoint,
|
|
2079
|
+
brainJwt,
|
|
2080
|
+
brainAnonKey,
|
|
2081
|
+
fetch: injectedFetch
|
|
2082
|
+
} = opts;
|
|
2083
|
+
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
2084
|
+
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
2085
|
+
if (!family) return { ok: false, reason: "family_required" };
|
|
2086
|
+
if (resolution !== "promoted" && resolution !== "declined" && resolution !== "still-evaluating") {
|
|
2087
|
+
return { ok: false, reason: "resolution_invalid" };
|
|
2055
2088
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2089
|
+
const doFetch = resolveFetchImpl(injectedFetch);
|
|
2090
|
+
const base = normalizeEndpoint(brainEndpoint);
|
|
2091
|
+
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`;
|
|
2092
|
+
const patchBody = {
|
|
2093
|
+
resolved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2094
|
+
resolution
|
|
2095
|
+
};
|
|
2096
|
+
if (resolutionNote !== void 0) {
|
|
2097
|
+
patchBody.resolution_note = resolutionNote;
|
|
2058
2098
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2099
|
+
let res;
|
|
2100
|
+
try {
|
|
2101
|
+
res = await doFetch(url, {
|
|
2102
|
+
method: "PATCH",
|
|
2103
|
+
headers: {
|
|
2104
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
2105
|
+
apikey: brainAnonKey,
|
|
2106
|
+
"Content-Type": "application/json",
|
|
2107
|
+
Accept: "application/json",
|
|
2108
|
+
Prefer: "return=minimal"
|
|
2109
|
+
},
|
|
2110
|
+
body: JSON.stringify(patchBody)
|
|
2111
|
+
});
|
|
2112
|
+
} catch (err) {
|
|
2113
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2114
|
+
return { ok: false, reason: `network_error:${msg}` };
|
|
2061
2115
|
}
|
|
2062
|
-
if (
|
|
2063
|
-
|
|
2064
|
-
...advisorRulePromoteReady({
|
|
2065
|
-
appId: ir.appId,
|
|
2066
|
-
archetype: ir.intent.archetype,
|
|
2067
|
-
resolvedPrimary: profile.id
|
|
2068
|
-
})
|
|
2069
|
-
);
|
|
2070
|
-
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
2116
|
+
if (res.status === 401 || res.status === 403) {
|
|
2117
|
+
return { ok: false, reason: "brain_auth_misconfig" };
|
|
2071
2118
|
}
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2074
|
-
...advisorRuleCrossFamilyFit({
|
|
2075
|
-
archetype: ir.intent.archetype,
|
|
2076
|
-
resolvedPrimary: profile.id
|
|
2077
|
-
})
|
|
2078
|
-
);
|
|
2119
|
+
if (res.status >= 500) {
|
|
2120
|
+
return { ok: false, reason: "brain_unavailable" };
|
|
2079
2121
|
}
|
|
2080
|
-
|
|
2081
|
-
}
|
|
2082
|
-
function translatorClearedToolCallCliff(phase2) {
|
|
2083
|
-
const rewrites = phase2?.sectionRewritesApplied;
|
|
2084
|
-
if (!rewrites || rewrites.length === 0) return false;
|
|
2085
|
-
for (const rw of rewrites) {
|
|
2086
|
-
if (rw.kind === "tool_call_contract") return true;
|
|
2122
|
+
if (!res.ok) {
|
|
2123
|
+
return { ok: false, reason: `patch_failed:${res.status}` };
|
|
2087
2124
|
}
|
|
2088
|
-
return
|
|
2125
|
+
return { ok: true };
|
|
2089
2126
|
}
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2127
|
+
|
|
2128
|
+
// src/advisor-rules/promote-ready.ts
|
|
2129
|
+
var PROMOTE_READY_THRESHOLDS = {
|
|
2130
|
+
minPassRate: 0.8,
|
|
2131
|
+
minAvgScore: 4
|
|
2132
|
+
};
|
|
2133
|
+
function shouldFirePromoteReady(finding, resolvedPrimary) {
|
|
2134
|
+
if (finding.currentModel !== resolvedPrimary) return false;
|
|
2135
|
+
if (finding.judgePassRate < PROMOTE_READY_THRESHOLDS.minPassRate) return false;
|
|
2136
|
+
if (finding.judgeAvgScore < PROMOTE_READY_THRESHOLDS.minAvgScore) return false;
|
|
2137
|
+
return true;
|
|
2138
|
+
}
|
|
2139
|
+
function deriveFamilyLocal(modelId) {
|
|
2140
|
+
if (modelId.startsWith("claude-opus-")) return "claude-opus";
|
|
2141
|
+
if (modelId.startsWith("claude-sonnet-")) return "claude-sonnet";
|
|
2142
|
+
if (modelId.startsWith("claude-haiku-")) return "claude-haiku";
|
|
2143
|
+
if (/^gemini-.*-flash-lite/.test(modelId)) return "gemini-flash-lite";
|
|
2144
|
+
if (/^gemini-.*-flash/.test(modelId)) return "gemini-flash";
|
|
2145
|
+
if (/^gemini-.*-pro/.test(modelId)) return "gemini-pro";
|
|
2146
|
+
if (/^deepseek-.*-pro/.test(modelId)) return "deepseek-reasoner";
|
|
2147
|
+
if (modelId.startsWith("deepseek-")) return "deepseek-chat";
|
|
2148
|
+
if (modelId.startsWith("gpt-")) return "openai-gpt";
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
function advisorRulePromoteReady(ctx) {
|
|
2152
|
+
if (!isPromoteReadyBrainActive()) return [];
|
|
2153
|
+
if (!ctx.appId) return [];
|
|
2154
|
+
if (!ctx.resolvedPrimary) return [];
|
|
2155
|
+
const family = deriveFamilyLocal(ctx.resolvedPrimary);
|
|
2156
|
+
if (!family) return [];
|
|
2157
|
+
const findings = loadPromoteReadyFindings({
|
|
2158
|
+
appId: ctx.appId,
|
|
2159
|
+
archetype: ctx.archetype,
|
|
2160
|
+
family
|
|
2161
|
+
});
|
|
2162
|
+
if (findings.length === 0) return [];
|
|
2163
|
+
const qualifying = findings.filter(
|
|
2164
|
+
(f) => shouldFirePromoteReady(f, ctx.resolvedPrimary)
|
|
2165
|
+
);
|
|
2166
|
+
if (qualifying.length === 0) return [];
|
|
2167
|
+
qualifying.sort((a, b) => {
|
|
2168
|
+
if (a.judgeAvgScore !== b.judgeAvgScore) {
|
|
2169
|
+
return b.judgeAvgScore - a.judgeAvgScore;
|
|
2170
|
+
}
|
|
2171
|
+
return b.judgePassRate - a.judgePassRate;
|
|
2172
|
+
});
|
|
2173
|
+
const top = qualifying[0];
|
|
2174
|
+
const pctPass = Math.round(top.judgePassRate * 100);
|
|
2175
|
+
const score = top.judgeAvgScore.toFixed(2);
|
|
2176
|
+
let costClause = "";
|
|
2177
|
+
if (top.costDeltaPct !== null) {
|
|
2178
|
+
const sign = top.costDeltaPct < 0 ? "cheaper" : "more expensive";
|
|
2179
|
+
const magnitude = Math.abs(top.costDeltaPct * 100).toFixed(1);
|
|
2180
|
+
costClause = `, cost ${magnitude}% ${sign}`;
|
|
2181
|
+
}
|
|
2182
|
+
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.`;
|
|
2096
2183
|
return [
|
|
2097
2184
|
{
|
|
2098
|
-
level: "
|
|
2099
|
-
code: "
|
|
2100
|
-
message
|
|
2101
|
-
suggestion:
|
|
2185
|
+
level: "info",
|
|
2186
|
+
code: "promote-ready",
|
|
2187
|
+
message,
|
|
2188
|
+
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.`,
|
|
2189
|
+
// alpha.36 architectural field — not a no-ai-needed case.
|
|
2190
|
+
recommendedArchitecture: void 0,
|
|
2102
2191
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2103
2192
|
}
|
|
2104
2193
|
];
|
|
2105
2194
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
if (!only || only.text.length <= 1e3) return [];
|
|
2111
|
-
return [
|
|
2112
|
-
{
|
|
2113
|
-
level: "info",
|
|
2114
|
-
code: "single-chunk-system",
|
|
2115
|
-
message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
|
|
2116
|
-
suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't.",
|
|
2117
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2118
|
-
}
|
|
2119
|
-
];
|
|
2195
|
+
|
|
2196
|
+
// src/advisor-rules/consumer-on-stale-model.ts
|
|
2197
|
+
function isStaleStatus(v) {
|
|
2198
|
+
return v === "legacy" || v === "deprecated";
|
|
2120
2199
|
}
|
|
2121
|
-
function
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
"
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
|
|
2138
|
-
suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
|
|
2139
|
-
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
2200
|
+
function asString(v) {
|
|
2201
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
2202
|
+
}
|
|
2203
|
+
function mapRowsToFindings3(rows) {
|
|
2204
|
+
const out = [];
|
|
2205
|
+
for (const raw of rows) {
|
|
2206
|
+
if (!raw || typeof raw !== "object") continue;
|
|
2207
|
+
const r = raw;
|
|
2208
|
+
const archetype = asString(r.intent_archetype) ?? asString(r.applies_to_archetype);
|
|
2209
|
+
const staleModel = asString(r.stale_model) ?? asString(r.applies_to_model);
|
|
2210
|
+
const staleProvider = asString(r.stale_provider);
|
|
2211
|
+
const recommendedModel = asString(r.recommended_model);
|
|
2212
|
+
const family = asString(r.family);
|
|
2213
|
+
const message = asString(r.message);
|
|
2214
|
+
if (!archetype || !staleModel || !recommendedModel || !family || !message) {
|
|
2215
|
+
continue;
|
|
2140
2216
|
}
|
|
2141
|
-
|
|
2217
|
+
if (!isStaleStatus(r.stale_status)) continue;
|
|
2218
|
+
const row = {
|
|
2219
|
+
archetype,
|
|
2220
|
+
staleModel,
|
|
2221
|
+
staleProvider: staleProvider ?? "unknown",
|
|
2222
|
+
staleStatus: r.stale_status,
|
|
2223
|
+
recommendedModel,
|
|
2224
|
+
family,
|
|
2225
|
+
message
|
|
2226
|
+
};
|
|
2227
|
+
const suggestion = asString(r.suggestion);
|
|
2228
|
+
if (suggestion) row.suggestion = suggestion;
|
|
2229
|
+
if (typeof r.observation_count === "number" && Number.isFinite(r.observation_count)) {
|
|
2230
|
+
row.observationCount = r.observation_count;
|
|
2231
|
+
}
|
|
2232
|
+
out.push(row);
|
|
2233
|
+
}
|
|
2234
|
+
return out;
|
|
2142
2235
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2236
|
+
var snapshots5 = /* @__PURE__ */ new Map();
|
|
2237
|
+
var runtime5;
|
|
2238
|
+
var warnedOnce5 = false;
|
|
2239
|
+
var pendingRefreshes5 = /* @__PURE__ */ new Map();
|
|
2240
|
+
function isStaleModelFindingsBrainActive() {
|
|
2241
|
+
return runtime5 !== void 0;
|
|
2242
|
+
}
|
|
2243
|
+
function getStaleModelFindings(opts) {
|
|
2244
|
+
const rt = runtime5;
|
|
2245
|
+
if (!rt) return [];
|
|
2246
|
+
const appId = opts.appId;
|
|
2247
|
+
if (!appId) return [];
|
|
2248
|
+
let snap = snapshots5.get(appId);
|
|
2249
|
+
if (!snap) {
|
|
2250
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2251
|
+
snapshots5.set(appId, snap);
|
|
2148
2252
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2253
|
+
const now = Date.now();
|
|
2254
|
+
const stale = snap.expiresAt <= now;
|
|
2255
|
+
if (stale && !snap.refreshing) {
|
|
2256
|
+
snap.refreshing = true;
|
|
2257
|
+
void asyncRefresh5(rt, appId);
|
|
2258
|
+
}
|
|
2259
|
+
if (opts.archetype) {
|
|
2260
|
+
return snap.data.filter((f) => f.archetype === opts.archetype);
|
|
2261
|
+
}
|
|
2262
|
+
return snap.data;
|
|
2158
2263
|
}
|
|
2159
|
-
function
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
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.`,
|
|
2168
|
-
suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
|
|
2169
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
2264
|
+
async function asyncRefresh5(rt, appId) {
|
|
2265
|
+
const promise = doRefresh5(rt, appId);
|
|
2266
|
+
pendingRefreshes5.set(appId, promise);
|
|
2267
|
+
try {
|
|
2268
|
+
await promise;
|
|
2269
|
+
} finally {
|
|
2270
|
+
if (pendingRefreshes5.get(appId) === promise) {
|
|
2271
|
+
pendingRefreshes5.delete(appId);
|
|
2170
2272
|
}
|
|
2171
|
-
|
|
2273
|
+
}
|
|
2172
2274
|
}
|
|
2173
|
-
function
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
if (!chosenHasRoomToGrow) return [];
|
|
2180
|
-
let bestAlt = null;
|
|
2181
|
-
for (const altId of phase2.fallbackChain) {
|
|
2182
|
-
const altProfile = phase2.profileResolver(altId);
|
|
2183
|
-
if (!altProfile) continue;
|
|
2184
|
-
if (altProfile.id === profile.id) continue;
|
|
2185
|
-
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2186
|
-
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2187
|
-
if (altScore.score < chosenScore.score) continue;
|
|
2188
|
-
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
2189
|
-
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
2190
|
-
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2191
|
-
}
|
|
2275
|
+
async function doRefresh5(rt, appId) {
|
|
2276
|
+
const url = `${rt.endpoint}?app_id=${encodeURIComponent(appId)}`;
|
|
2277
|
+
let snap = snapshots5.get(appId);
|
|
2278
|
+
if (!snap) {
|
|
2279
|
+
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2280
|
+
snapshots5.set(appId, snap);
|
|
2192
2281
|
}
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
2198
|
-
return [
|
|
2199
|
-
{
|
|
2200
|
-
level: "warn",
|
|
2201
|
-
code: "cost-mismatched-archetype",
|
|
2202
|
-
message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
|
|
2203
|
-
suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
|
|
2204
|
-
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
2205
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2282
|
+
try {
|
|
2283
|
+
const res = await rt.fetchImpl(url, { method: "GET" });
|
|
2284
|
+
if (!res.ok) {
|
|
2285
|
+
throw new Error(`stale-model findings ${res.status}: ${res.statusText}`);
|
|
2206
2286
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
recommendationType: "prompt-fix",
|
|
2221
|
-
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2287
|
+
const body = await res.json();
|
|
2288
|
+
if (runtime5 !== rt) return;
|
|
2289
|
+
const rows = Array.isArray(body) ? mapRowsToFindings3(body) : [];
|
|
2290
|
+
snap.data = rows;
|
|
2291
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2292
|
+
snap.refreshing = false;
|
|
2293
|
+
} catch (err) {
|
|
2294
|
+
if (runtime5 !== rt) return;
|
|
2295
|
+
snap.refreshing = false;
|
|
2296
|
+
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2297
|
+
if (!warnedOnce5) {
|
|
2298
|
+
warnedOnce5 = true;
|
|
2299
|
+
(rt.onError ?? defaultOnError5)(err);
|
|
2222
2300
|
}
|
|
2223
|
-
|
|
2301
|
+
}
|
|
2224
2302
|
}
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2303
|
+
function defaultOnError5(err) {
|
|
2304
|
+
console.warn(
|
|
2305
|
+
"[kgauto] stale-model findings fetch failed (using empty fallback):",
|
|
2306
|
+
err
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
var CONSUMER_ON_STALE_MODEL_RULE_CODE = "consumer-on-stale-model";
|
|
2310
|
+
function advisorRuleConsumerOnStaleModel(ir) {
|
|
2311
|
+
if (!isStaleModelFindingsBrainActive()) return [];
|
|
2312
|
+
if (!ir.appId) return [];
|
|
2313
|
+
const findings = getStaleModelFindings({
|
|
2314
|
+
appId: ir.appId,
|
|
2315
|
+
archetype: ir.intent.archetype
|
|
2316
|
+
});
|
|
2317
|
+
if (findings.length === 0) return [];
|
|
2318
|
+
const ranked = [...findings].sort((a, b) => {
|
|
2319
|
+
if (a.staleStatus !== b.staleStatus) {
|
|
2320
|
+
return a.staleStatus === "deprecated" ? -1 : 1;
|
|
2243
2321
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
const
|
|
2322
|
+
return a.staleModel.localeCompare(b.staleModel);
|
|
2323
|
+
});
|
|
2324
|
+
const top = ranked[0];
|
|
2325
|
+
const extraCount = findings.length - 1;
|
|
2326
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more stale model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
2247
2327
|
return [
|
|
2248
2328
|
{
|
|
2249
2329
|
level: "warn",
|
|
2250
|
-
code:
|
|
2251
|
-
message:
|
|
2252
|
-
suggestion: `
|
|
2253
|
-
recommendationType: "
|
|
2330
|
+
code: CONSUMER_ON_STALE_MODEL_RULE_CODE,
|
|
2331
|
+
message: `${top.message}${extraNote}`,
|
|
2332
|
+
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}.`,
|
|
2333
|
+
recommendationType: "model-swap",
|
|
2254
2334
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2255
2335
|
}
|
|
2256
2336
|
];
|
|
2257
2337
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2338
|
+
|
|
2339
|
+
// src/archetype-fits.ts
|
|
2340
|
+
var ARCHETYPE_FAMILY_FITS = Object.freeze([
|
|
2341
|
+
{
|
|
2342
|
+
archetype: "plan",
|
|
2343
|
+
betterFitFamily: "deepseek-reasoner",
|
|
2344
|
+
reason: "Plan archetype is reasoning-shaped (multi-step chains, hypothesis-and-check, sub-goal decomposition) \u2014 exactly where reasoner-family models excel. Sonnet/Opus produce plans but at higher cost; reasoners produce equivalent-or-better plans at 7-17x lower cost at current promo pricing (deepseek-v4-pro $0.435/$0.87 per 1M promo through 2026-05-31 vs sonnet $3/$15).",
|
|
2345
|
+
costGuidance: "substantially cheaper at current pricing (deepseek-v4-pro promo: ~7-17x cheaper than sonnet)"
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
archetype: "critique",
|
|
2349
|
+
betterFitFamily: "deepseek-reasoner",
|
|
2350
|
+
reason: "Critique archetype rewards epistemic humility and explicit reasoning \u2014 reasoner-family default behavior. Sonnet/Opus over-confident on critique tasks; reasoners surface uncertainty productively.",
|
|
2351
|
+
costGuidance: "comparable or cheaper at current pricing"
|
|
2352
|
+
}
|
|
2353
|
+
]);
|
|
2354
|
+
function findBetterFit(archetype, currentFamily) {
|
|
2355
|
+
for (const fit of ARCHETYPE_FAMILY_FITS) {
|
|
2356
|
+
if (fit.archetype !== archetype) continue;
|
|
2357
|
+
if (fit.betterFitFamily === currentFamily) return null;
|
|
2358
|
+
return fit;
|
|
2359
|
+
}
|
|
2360
|
+
return null;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
// src/advisor-rules/cross-family-fit.ts
|
|
2364
|
+
function familyHasCurrentActiveModel(family) {
|
|
2365
|
+
for (const profile of allProfiles()) {
|
|
2366
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
2367
|
+
if (profileFamily !== family) continue;
|
|
2368
|
+
if (profile.status !== "current") continue;
|
|
2369
|
+
if (profile.active === false) continue;
|
|
2370
|
+
return true;
|
|
2371
|
+
}
|
|
2372
|
+
return false;
|
|
2373
|
+
}
|
|
2374
|
+
function listCandidatesInFamily(family) {
|
|
2375
|
+
const candidates = [];
|
|
2376
|
+
for (const profile of allProfiles()) {
|
|
2377
|
+
const profileFamily = profile.family ?? deriveFamilyFromModelId(profile.id);
|
|
2378
|
+
if (profileFamily !== family) continue;
|
|
2379
|
+
if (profile.status !== "current") continue;
|
|
2380
|
+
if (profile.active === false) continue;
|
|
2381
|
+
candidates.push(profile.id);
|
|
2382
|
+
if (candidates.length >= 3) break;
|
|
2276
2383
|
}
|
|
2384
|
+
return candidates;
|
|
2385
|
+
}
|
|
2386
|
+
function advisorRuleCrossFamilyFit(ctx) {
|
|
2387
|
+
if (!ctx.resolvedPrimary) return [];
|
|
2388
|
+
const currentFamily = deriveFamilyFromModelId(ctx.resolvedPrimary);
|
|
2389
|
+
if (!currentFamily) return [];
|
|
2390
|
+
const fit = findBetterFit(ctx.archetype, currentFamily);
|
|
2391
|
+
if (!fit) return [];
|
|
2392
|
+
if (!familyHasCurrentActiveModel(fit.betterFitFamily)) return [];
|
|
2393
|
+
const candidates = listCandidatesInFamily(fit.betterFitFamily);
|
|
2394
|
+
if (candidates.length === 0) return [];
|
|
2395
|
+
const candidateStr = candidates.join(", ");
|
|
2396
|
+
const message = `Your ${currentFamily} call on ${ctx.archetype} could shift to ${fit.betterFitFamily} \u2014 typically better quality + ${fit.costGuidance}. Suggested candidates: ${candidateStr}.`;
|
|
2277
2397
|
return [
|
|
2278
2398
|
{
|
|
2279
|
-
level: "
|
|
2280
|
-
code: "
|
|
2281
|
-
|
|
2282
|
-
|
|
2399
|
+
level: "info",
|
|
2400
|
+
code: "cross-family-fit-candidate",
|
|
2401
|
+
ownership: "consumer-actionable",
|
|
2402
|
+
message,
|
|
2403
|
+
suggestion: `Swap the model literal in \`ir.models\` to one of: ${candidateStr}. Or call \`getRecommendedPrimary({ family: '${fit.betterFitFamily}', archetype: '${ctx.archetype}', fallback: { id: '${candidates[0]}', reason: 'cross-family-fit-recommendation' } })\` to let kgauto resolve to the current+active family member.`,
|
|
2283
2404
|
recommendationType: "model-swap",
|
|
2284
2405
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2285
2406
|
}
|
|
2286
2407
|
];
|
|
2287
2408
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2409
|
+
|
|
2410
|
+
// src/advisor.ts
|
|
2411
|
+
var QUALITY_FLOOR_FOR_RECOMMENDATION = 6;
|
|
2412
|
+
var TIER_DOWN_COST_RATIO = 0.5;
|
|
2413
|
+
var COST_MISMATCHED_CHOSEN_SCORE_CEILING = 7;
|
|
2414
|
+
var PRODUCER_OWNED_RULE_CODES = Object.freeze(
|
|
2415
|
+
/* @__PURE__ */ new Set(["model-stale-evidence", "promote-ready"])
|
|
2416
|
+
);
|
|
2417
|
+
function deriveOwnership(code, selfDeclared) {
|
|
2418
|
+
if (selfDeclared) return selfDeclared;
|
|
2419
|
+
return PRODUCER_OWNED_RULE_CODES.has(code) ? "producer-owned" : "consumer-actionable";
|
|
2420
|
+
}
|
|
2421
|
+
function runAdvisor(ir, result, profile, policy, phase2) {
|
|
2422
|
+
const out = [];
|
|
2423
|
+
out.push(...detectCachingOff(ir, profile));
|
|
2424
|
+
out.push(...detectSingleChunkSystem(ir, profile));
|
|
2425
|
+
out.push(...detectToolBloat(ir, result));
|
|
2426
|
+
out.push(...detectHistoryUncached(ir, profile));
|
|
2427
|
+
out.push(...detectSingleModelArray(ir, policy));
|
|
2428
|
+
if (policy?.posture !== "locked") {
|
|
2429
|
+
out.push(...detectCostMismatchedArchetype(ir, profile, phase2));
|
|
2430
|
+
out.push(...detectModelStaleEvidence(ir, profile));
|
|
2431
|
+
out.push(...detectTierDown(ir, profile, phase2));
|
|
2432
|
+
}
|
|
2433
|
+
if (!translatorClearedToolCallCliff(phase2)) {
|
|
2434
|
+
out.push(...detectArchetypePerfFloorBreach(ir, profile));
|
|
2435
|
+
}
|
|
2436
|
+
if (policy?.posture !== "locked") {
|
|
2437
|
+
out.push(...detectStaleExclusionCandidate(ir));
|
|
2438
|
+
}
|
|
2439
|
+
if (policy?.posture !== "locked" && ir.appId) {
|
|
2440
|
+
out.push(
|
|
2441
|
+
...advisorRulePromoteReady({
|
|
2442
|
+
appId: ir.appId,
|
|
2443
|
+
archetype: ir.intent.archetype,
|
|
2444
|
+
resolvedPrimary: profile.id
|
|
2445
|
+
})
|
|
2446
|
+
);
|
|
2447
|
+
out.push(...advisorRuleConsumerOnStaleModel(ir));
|
|
2448
|
+
}
|
|
2449
|
+
if (policy?.posture !== "locked") {
|
|
2450
|
+
out.push(
|
|
2451
|
+
...advisorRuleCrossFamilyFit({
|
|
2452
|
+
archetype: ir.intent.archetype,
|
|
2453
|
+
resolvedPrimary: profile.id
|
|
2454
|
+
})
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
return out;
|
|
2458
|
+
}
|
|
2459
|
+
function translatorClearedToolCallCliff(phase2) {
|
|
2460
|
+
const rewrites = phase2?.sectionRewritesApplied;
|
|
2461
|
+
if (!rewrites || rewrites.length === 0) return false;
|
|
2462
|
+
for (const rw of rewrites) {
|
|
2463
|
+
if (rw.kind === "tool_call_contract") return true;
|
|
2464
|
+
}
|
|
2465
|
+
return false;
|
|
2466
|
+
}
|
|
2467
|
+
function detectCachingOff(ir, profile) {
|
|
2468
|
+
if (profile.provider !== "anthropic") return [];
|
|
2469
|
+
const totalChars = ir.sections.reduce((s, sec) => s + sec.text.length, 0);
|
|
2470
|
+
if (totalChars < 2e3) return [];
|
|
2471
|
+
const anyCacheable = ir.sections.some((s) => s.cacheable === true);
|
|
2472
|
+
if (anyCacheable) return [];
|
|
2305
2473
|
return [
|
|
2306
2474
|
{
|
|
2307
|
-
level: "
|
|
2308
|
-
code: "
|
|
2309
|
-
message:
|
|
2310
|
-
suggestion:
|
|
2311
|
-
recommendationType: "tier-down",
|
|
2475
|
+
level: "warn",
|
|
2476
|
+
code: "caching-off-on-claude",
|
|
2477
|
+
message: `System prompt is ${totalChars} chars on Anthropic but no PromptSection has cacheable=true. Anthropic prompt caching cuts cached-prefix input cost by ~90% on subsequent calls; without it, every turn re-pays full price for the static system context.`,
|
|
2478
|
+
suggestion: "Mark stable system sections (role, persona, tool policy) with `cacheable: true`. The lowering pass concatenates cacheable sections into a single cache-controlled block before the dynamic ones.",
|
|
2312
2479
|
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2313
2480
|
}
|
|
2314
2481
|
];
|
|
2315
2482
|
}
|
|
2316
|
-
function
|
|
2317
|
-
if (
|
|
2318
|
-
if (
|
|
2319
|
-
|
|
2483
|
+
function detectSingleChunkSystem(ir, profile) {
|
|
2484
|
+
if (profile.provider !== "anthropic") return [];
|
|
2485
|
+
if (ir.sections.length !== 1) return [];
|
|
2486
|
+
const only = ir.sections[0];
|
|
2487
|
+
if (!only || only.text.length <= 1e3) return [];
|
|
2488
|
+
return [
|
|
2489
|
+
{
|
|
2490
|
+
level: "info",
|
|
2491
|
+
code: "single-chunk-system",
|
|
2492
|
+
message: `System prompt is a single ${only.text.length}-char chunk. Splitting into NamedChunks (static role/persona vs dynamic context) gives the lowering pass a finer cache-marker boundary \u2014 only the static portion needs to be byte-stable for the cache to hit.`,
|
|
2493
|
+
suggestion: "Refactor the system builder to return an array of `PromptSection` shaped { id, text, cacheable?: boolean }. Static chunks (role, persona, tool policy) get `cacheable: true`; dynamic ones (current context, today's date) don't. NOTE: the lowering pass HOISTS cacheable sections ahead of dynamic ones on the Anthropic wire (prefix caching requires it) \u2014 if your prompt has a protected ordering (e.g. a voice/persona block that must precede boilerplate), splitting will reorder the compiled output; declining this advisory is then correct. Also: a cacheable block under ~1024 tokens gets NO cache_control marker (provider minimum), so marking small sections is inert, not harmful.",
|
|
2494
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2495
|
+
}
|
|
2496
|
+
];
|
|
2320
2497
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2341
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2342
|
-
4. Label each claim: mark it observed, inferred, or assumed.
|
|
2343
|
-
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2344
|
-
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
2345
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2346
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2347
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2348
|
-
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2349
|
-
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2350
|
-
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2351
|
-
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2352
|
-
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2353
|
-
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2354
|
-
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
2355
|
-
"hunt",
|
|
2356
|
-
"summarize",
|
|
2357
|
-
"plan",
|
|
2358
|
-
"critique",
|
|
2359
|
-
"judge"
|
|
2360
|
-
]);
|
|
2361
|
-
function matchRule(kind, profile, archetype, ctx) {
|
|
2362
|
-
if (kind === "discipline_contract") {
|
|
2363
|
-
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
2364
|
-
if (ctx.outputMode !== "text") {
|
|
2365
|
-
return {
|
|
2366
|
-
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
2367
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
2368
|
-
};
|
|
2498
|
+
function detectToolBloat(ir, result) {
|
|
2499
|
+
const SHORT_OUTPUT = /* @__PURE__ */ new Set([
|
|
2500
|
+
"classify",
|
|
2501
|
+
"extract",
|
|
2502
|
+
"summarize",
|
|
2503
|
+
"transform",
|
|
2504
|
+
"critique"
|
|
2505
|
+
]);
|
|
2506
|
+
if (!ir.tools || ir.tools.length === 0) return [];
|
|
2507
|
+
const toolsKept = result.diagnostics.toolsKept;
|
|
2508
|
+
if (toolsKept <= 10) return [];
|
|
2509
|
+
if (!SHORT_OUTPUT.has(ir.intent.archetype)) return [];
|
|
2510
|
+
return [
|
|
2511
|
+
{
|
|
2512
|
+
level: "warn",
|
|
2513
|
+
code: "tool-bloat",
|
|
2514
|
+
message: `${toolsKept} tools kept after the relevance pass for archetype="${ir.intent.archetype}" (consumer declared ${ir.tools.length}). This archetype is short-output and rarely needs more than 3 tools; each tool definition eats ~350 tokens of context budget.`,
|
|
2515
|
+
suggestion: "Tighten `relevanceByIntent: { [archetype]: 0..1 }` per ToolDefinition. Tools below `toolRelevanceThreshold` (default 0.2) get dropped. Without `relevanceByIntent`, every tool defaults to neutral (0.5) and stays.",
|
|
2516
|
+
docsUrl: "https://github.com/stue/kgauto/blob/main/v2/README.md#tools"
|
|
2369
2517
|
}
|
|
2370
|
-
|
|
2371
|
-
id: RULE_DISCIPLINE_GATES_V1,
|
|
2372
|
-
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
2373
|
-
};
|
|
2374
|
-
}
|
|
2375
|
-
if (kind === "tool_call_contract") {
|
|
2376
|
-
if (!profile.archetypePerf) return null;
|
|
2377
|
-
const archetypeScore = profile.archetypePerf[archetype];
|
|
2378
|
-
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
2379
|
-
return null;
|
|
2380
|
-
}
|
|
2381
|
-
return {
|
|
2382
|
-
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
2383
|
-
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
2384
|
-
wireOverrides: { parallelToolCalls: false }
|
|
2385
|
-
};
|
|
2386
|
-
}
|
|
2387
|
-
if (kind === "narration_contract") {
|
|
2388
|
-
if (profile.provider === "anthropic") {
|
|
2389
|
-
return {
|
|
2390
|
-
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
2391
|
-
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
2392
|
-
};
|
|
2393
|
-
}
|
|
2394
|
-
if (profile.provider === "deepseek") {
|
|
2395
|
-
return {
|
|
2396
|
-
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
2397
|
-
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
return null;
|
|
2401
|
-
}
|
|
2402
|
-
return null;
|
|
2403
|
-
}
|
|
2404
|
-
function applySectionRewrites(args) {
|
|
2405
|
-
const { ir, profile, archetype } = args;
|
|
2406
|
-
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
2407
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
2408
|
-
}
|
|
2409
|
-
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
2410
|
-
declared: ir.constraints?.outputMode,
|
|
2411
|
-
structuredOutput: ir.constraints?.structuredOutput,
|
|
2412
|
-
toolCount: ir.tools?.length ?? 0
|
|
2413
|
-
});
|
|
2414
|
-
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
2415
|
-
const ctx = { outputMode, hasTools };
|
|
2416
|
-
const rewrites = [];
|
|
2417
|
-
const newSections = ir.sections.map((section) => {
|
|
2418
|
-
if (!section.kind || section.kind === "arbitrary") return section;
|
|
2419
|
-
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
2420
|
-
if (!rule) return section;
|
|
2421
|
-
const originalText = section.text;
|
|
2422
|
-
const transformedText = `${rule.preamble}
|
|
2423
|
-
|
|
2424
|
-
${originalText}`;
|
|
2425
|
-
rewrites.push({
|
|
2426
|
-
sectionId: section.id,
|
|
2427
|
-
kind: section.kind,
|
|
2428
|
-
rule: rule.id,
|
|
2429
|
-
originalText,
|
|
2430
|
-
transformedText,
|
|
2431
|
-
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
2432
|
-
});
|
|
2433
|
-
return { ...section, text: transformedText };
|
|
2434
|
-
});
|
|
2435
|
-
if (rewrites.length === 0) {
|
|
2436
|
-
return { rewrittenIR: ir, rewrites: [] };
|
|
2437
|
-
}
|
|
2438
|
-
const rewrittenIR = { ...ir, sections: newSections };
|
|
2439
|
-
return { rewrittenIR, rewrites };
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// src/promotions-brain.ts
|
|
2443
|
-
function isRawPromotionRow(x) {
|
|
2444
|
-
if (!x || typeof x !== "object") return false;
|
|
2445
|
-
const r = x;
|
|
2446
|
-
return (typeof r.id === "number" || typeof r.id === "string") && typeof r.intent_archetype === "string" && typeof r.promoted_model === "string" && typeof r.incumbent_model === "string";
|
|
2447
|
-
}
|
|
2448
|
-
function coerceId(v) {
|
|
2449
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
2450
|
-
if (typeof v === "string") {
|
|
2451
|
-
const n = Number(v);
|
|
2452
|
-
return Number.isFinite(n) ? n : null;
|
|
2453
|
-
}
|
|
2454
|
-
return null;
|
|
2455
|
-
}
|
|
2456
|
-
function mapRowsToPromotions(rows) {
|
|
2457
|
-
const out = [];
|
|
2458
|
-
for (const row of rows) {
|
|
2459
|
-
if (!isRawPromotionRow(row)) continue;
|
|
2460
|
-
const id = coerceId(row.id);
|
|
2461
|
-
if (id === null) continue;
|
|
2462
|
-
const mode = row.mode === "strategy" ? "strategy" : row.mode === "downswap" || row.mode === void 0 ? "downswap" : null;
|
|
2463
|
-
if (mode === null) continue;
|
|
2464
|
-
out.push({
|
|
2465
|
-
id,
|
|
2466
|
-
archetype: row.intent_archetype,
|
|
2467
|
-
mode,
|
|
2468
|
-
strategy: typeof row.strategy === "string" ? row.strategy : null,
|
|
2469
|
-
promotedModel: row.promoted_model,
|
|
2470
|
-
incumbentModel: row.incumbent_model,
|
|
2471
|
-
evalRunId: coerceId(row.eval_run_id ?? null),
|
|
2472
|
-
suppressQualityGate: row.suppress_quality_gate === true,
|
|
2473
|
-
promotedAt: typeof row.promoted_at === "string" ? row.promoted_at : ""
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
return out;
|
|
2477
|
-
}
|
|
2478
|
-
var snapshots4 = /* @__PURE__ */ new Map();
|
|
2479
|
-
var runtime4;
|
|
2480
|
-
var warnedOnce4 = false;
|
|
2481
|
-
var DEFAULT_PROMOTIONS_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/promotions";
|
|
2482
|
-
function isAutoPromoteEnabledFromEnv(envSource) {
|
|
2483
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
2484
|
-
const raw = (env.KGAUTO_AUTO_PROMOTE ?? "").trim().toLowerCase();
|
|
2485
|
-
return raw === "1" || raw === "true";
|
|
2486
|
-
}
|
|
2487
|
-
function configurePromotionsBrain(rt) {
|
|
2488
|
-
runtime4 = rt;
|
|
2489
|
-
snapshots4.clear();
|
|
2490
|
-
warnedOnce4 = false;
|
|
2491
|
-
}
|
|
2492
|
-
function isPromotionsBrainActive() {
|
|
2493
|
-
return runtime4 !== void 0;
|
|
2518
|
+
];
|
|
2494
2519
|
}
|
|
2495
|
-
function
|
|
2496
|
-
|
|
2497
|
-
if (!
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
let snap = snapshots4.get(appId);
|
|
2501
|
-
if (!snap) {
|
|
2502
|
-
snap = { data: [], expiresAt: 0, refreshing: false };
|
|
2503
|
-
snapshots4.set(appId, snap);
|
|
2504
|
-
}
|
|
2505
|
-
const now = Date.now();
|
|
2506
|
-
const stale = snap.expiresAt <= now;
|
|
2507
|
-
if (stale && !snap.refreshing) {
|
|
2508
|
-
snap.refreshing = true;
|
|
2509
|
-
void asyncRefresh4(rt, appId);
|
|
2520
|
+
function detectHistoryUncached(ir, profile) {
|
|
2521
|
+
if (profile.provider !== "anthropic") return [];
|
|
2522
|
+
if (!ir.history || ir.history.length < 2) return [];
|
|
2523
|
+
if (ir.historyCachePolicy && ir.historyCachePolicy.strategy !== "none") {
|
|
2524
|
+
return [];
|
|
2510
2525
|
}
|
|
2511
|
-
return
|
|
2512
|
-
|
|
2513
|
-
|
|
2526
|
+
return [
|
|
2527
|
+
{
|
|
2528
|
+
level: "warn",
|
|
2529
|
+
code: "history-uncached-on-claude",
|
|
2530
|
+
message: `${ir.history.length} history messages on Anthropic with no historyCachePolicy. Every turn re-pays for the full conversation context; with caching, subsequent turns hit the cache at ~10% the input cost.`,
|
|
2531
|
+
suggestion: "Set `historyCachePolicy: { strategy: 'all-but-latest' }` on this IR. The lowering pass marks the message immediately preceding currentTurn with cache_control; subsequent turns whose history prefix matches byte-for-byte hit the cache.",
|
|
2532
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2533
|
+
}
|
|
2534
|
+
];
|
|
2514
2535
|
}
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2536
|
+
function detectSingleModelArray(ir, policy) {
|
|
2537
|
+
if (ir.models.length !== 1) return [];
|
|
2538
|
+
if (policy?.posture === "locked") return [];
|
|
2539
|
+
const only = ir.models[0];
|
|
2540
|
+
return [
|
|
2541
|
+
{
|
|
2542
|
+
level: "warn",
|
|
2543
|
+
code: "single-model-array",
|
|
2544
|
+
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.`,
|
|
2545
|
+
suggestion: "Use `getDefaultFallbackChain({ archetype: ir.intent.archetype, primary: '" + only + "', posture: 'preferred' })` for a user-anchored chain, or `getDefaultFallbackChain({ archetype, posture: 'open' })` for library-picked. If single-model is intentional (compliance/brand promise), set `policy.posture = 'locked'` to silence this rule.",
|
|
2546
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#single-model-array"
|
|
2524
2547
|
}
|
|
2525
|
-
|
|
2548
|
+
];
|
|
2526
2549
|
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2550
|
+
function detectCostMismatchedArchetype(ir, profile, phase2) {
|
|
2551
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
2552
|
+
if (!phase2.profileResolver) return [];
|
|
2553
|
+
const archetype = ir.intent.archetype;
|
|
2554
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
2555
|
+
const chosenHasRoomToGrow = chosenScore.grounding === "judgment" || chosenScore.score < COST_MISMATCHED_CHOSEN_SCORE_CEILING;
|
|
2556
|
+
if (!chosenHasRoomToGrow) return [];
|
|
2557
|
+
let bestAlt = null;
|
|
2558
|
+
for (const altId of phase2.fallbackChain) {
|
|
2559
|
+
const altProfile = phase2.profileResolver(altId);
|
|
2560
|
+
if (!altProfile) continue;
|
|
2561
|
+
if (altProfile.id === profile.id) continue;
|
|
2562
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2563
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2564
|
+
if (altScore.score < chosenScore.score) continue;
|
|
2565
|
+
if (altProfile.costInputPer1m >= profile.costInputPer1m) continue;
|
|
2566
|
+
if (getMeasuredFailureVerdict({
|
|
2567
|
+
appId: ir.appId,
|
|
2568
|
+
archetype,
|
|
2569
|
+
model: altProfile.id
|
|
2570
|
+
})?.gated === true) {
|
|
2571
|
+
continue;
|
|
2538
2572
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2573
|
+
if (ir.constraints?.structuredOutput && effectiveConventions(altProfile).some(
|
|
2574
|
+
(c) => c.archetype === archetype && c.structuredOutputHint === "avoid"
|
|
2575
|
+
)) {
|
|
2576
|
+
continue;
|
|
2577
|
+
}
|
|
2578
|
+
if (getRecentRollback({
|
|
2579
|
+
appId: ir.appId,
|
|
2580
|
+
archetype,
|
|
2581
|
+
model: altProfile.id
|
|
2582
|
+
}) !== void 0) {
|
|
2583
|
+
continue;
|
|
2584
|
+
}
|
|
2585
|
+
if (!bestAlt || altScore.score > bestAlt.score.score || altScore.score === bestAlt.score.score && altProfile.costInputPer1m < bestAlt.profile.costInputPer1m) {
|
|
2586
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2552
2587
|
}
|
|
2553
2588
|
}
|
|
2589
|
+
if (!bestAlt) return [];
|
|
2590
|
+
const tierDownWouldFire = bestAlt.score.grounding === "measured" && bestAlt.profile.costInputPer1m <= profile.costInputPer1m * TIER_DOWN_COST_RATIO;
|
|
2591
|
+
if (tierDownWouldFire) return [];
|
|
2592
|
+
const chosenGrounding = chosenScore.grounding === "judgment" ? `archetypePerf.${archetype}=judgment` : `archetypePerf.${archetype}=${chosenScore.score}`;
|
|
2593
|
+
const altGrounding = bestAlt.score.grounding === "measured" ? `archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}` : `archetypePerf.${archetype}=${bestAlt.score.score}, judgment`;
|
|
2594
|
+
return [
|
|
2595
|
+
{
|
|
2596
|
+
level: "warn",
|
|
2597
|
+
code: "cost-mismatched-archetype",
|
|
2598
|
+
message: `Cost-mismatched-archetype: target=${profile.id} (${chosenGrounding}) selected for ${archetype}. Alternative ${bestAlt.id} (${altGrounding}) is cheaper ($${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} per 1M) at equal-or-better quality.`,
|
|
2599
|
+
suggestion: `Consider declaring \`${bestAlt.id}\` as the primary model for this archetype, or relax to posture='open' to let kgauto select among the chain. If the chosen model is required for compliance/brand reasons, set \`policy.posture = 'locked'\` to silence this rule.`,
|
|
2600
|
+
recommendationType: profile.provider === bestAlt.profile.provider ? "tier-down" : "model-swap",
|
|
2601
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2602
|
+
}
|
|
2603
|
+
];
|
|
2554
2604
|
}
|
|
2555
|
-
function
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
);
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
// src/measured-failure-brain.ts
|
|
2573
|
-
function coerceCount(v) {
|
|
2574
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
2575
|
-
if (typeof v === "string") {
|
|
2576
|
-
const n = Number(v);
|
|
2577
|
-
return Number.isFinite(n) ? n : null;
|
|
2578
|
-
}
|
|
2579
|
-
return null;
|
|
2580
|
-
}
|
|
2581
|
-
function isRawFailureRow(x) {
|
|
2582
|
-
if (!x || typeof x !== "object") return false;
|
|
2583
|
-
const r = x;
|
|
2584
|
-
return typeof r.intent_archetype === "string" && typeof r.model === "string" && (typeof r.n === "number" || typeof r.n === "string");
|
|
2585
|
-
}
|
|
2586
|
-
function mapRows(rows) {
|
|
2587
|
-
const out = [];
|
|
2588
|
-
for (const row of rows) {
|
|
2589
|
-
if (!isRawFailureRow(row)) continue;
|
|
2590
|
-
const n = coerceCount(row.n);
|
|
2591
|
-
const nFail = coerceCount(row.n_fail) ?? 0;
|
|
2592
|
-
if (n === null || n <= 0) continue;
|
|
2593
|
-
out.push({
|
|
2594
|
-
archetype: row.intent_archetype,
|
|
2595
|
-
model: row.model,
|
|
2596
|
-
n,
|
|
2597
|
-
nFail
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
return out;
|
|
2601
|
-
}
|
|
2602
|
-
var MEASURED_FAILURE_CFG = {
|
|
2603
|
-
/**
|
|
2604
|
-
* Hard minimum attempts before ANY gate may be created. Guards against
|
|
2605
|
-
* pathological tiny samples that the confidence bound alone would let
|
|
2606
|
-
* through in edge cases. At 5-for-5 the bound clears the threshold; at
|
|
2607
|
-
* 3-for-3 it does not, which is the behaviour we want (three failures is
|
|
2608
|
-
* a bad day, five in a row is a pattern).
|
|
2609
|
-
*/
|
|
2610
|
-
minSample: 5,
|
|
2611
|
-
/**
|
|
2612
|
-
* Gate when we are 95% confident the model fails MORE OFTEN THAN IT
|
|
2613
|
-
* SUCCEEDS on this surface. Deliberately unarguable rather than tuned —
|
|
2614
|
-
* a model that probably fails the majority of the time has no business
|
|
2615
|
-
* leading a surface, whatever its declared scores say.
|
|
2616
|
-
*/
|
|
2617
|
-
lowerBoundThreshold: 0.5,
|
|
2618
|
-
/** 95% one-sided-ish confidence (standard two-sided z at α=0.05). */
|
|
2619
|
-
z: 1.96,
|
|
2620
|
-
/** Must match the view's window. Documented here for the advisory text. */
|
|
2621
|
-
windowDays: 28
|
|
2622
|
-
};
|
|
2623
|
-
function wilsonLowerBound(failures, n, z = MEASURED_FAILURE_CFG.z) {
|
|
2624
|
-
if (n <= 0) return 0;
|
|
2625
|
-
const p = failures / n;
|
|
2626
|
-
const z2 = z * z;
|
|
2627
|
-
const denom = 1 + z2 / n;
|
|
2628
|
-
const centre = p + z2 / (2 * n);
|
|
2629
|
-
const margin = z * Math.sqrt(p * (1 - p) / n + z2 / (4 * n * n));
|
|
2630
|
-
const lower2 = (centre - margin) / denom;
|
|
2631
|
-
return lower2 < 0 ? 0 : lower2;
|
|
2632
|
-
}
|
|
2633
|
-
function judgeMeasuredFailure(row, cfg = MEASURED_FAILURE_CFG) {
|
|
2634
|
-
if (!row || row.n < cfg.minSample) return void 0;
|
|
2635
|
-
const lowerBound = wilsonLowerBound(row.nFail, row.n, cfg.z);
|
|
2636
|
-
return {
|
|
2637
|
-
gated: lowerBound > cfg.lowerBoundThreshold,
|
|
2638
|
-
rate: row.nFail / row.n,
|
|
2639
|
-
lowerBound,
|
|
2640
|
-
n: row.n,
|
|
2641
|
-
nFail: row.nFail
|
|
2642
|
-
};
|
|
2643
|
-
}
|
|
2644
|
-
var snapshots5 = /* @__PURE__ */ new Map();
|
|
2645
|
-
var runtime5;
|
|
2646
|
-
var warnedOnce5 = false;
|
|
2647
|
-
var DEFAULT_MEASURED_FAILURE_ENDPOINT = "https://kgauto-dashboard.vercel.app/api/kgauto-v2/measured-failure";
|
|
2648
|
-
function isMeasuredFailureGateEnabledFromEnv(envSource) {
|
|
2649
|
-
const env = envSource ?? (typeof process !== "undefined" && process.env ? process.env : {});
|
|
2650
|
-
const raw = (env.KGAUTO_MEASURED_FAILURE_GATE ?? "").trim().toLowerCase();
|
|
2651
|
-
return !(raw === "0" || raw === "false");
|
|
2652
|
-
}
|
|
2653
|
-
function configureMeasuredFailureBrain(rt) {
|
|
2654
|
-
runtime5 = rt;
|
|
2655
|
-
snapshots5.clear();
|
|
2656
|
-
warnedOnce5 = false;
|
|
2657
|
-
}
|
|
2658
|
-
function isMeasuredFailureBrainActive() {
|
|
2659
|
-
return runtime5 !== void 0;
|
|
2605
|
+
function detectModelStaleEvidence(ir, profile) {
|
|
2606
|
+
if (!isBrainQueryActiveFor("kgauto_archetype_perf")) return [];
|
|
2607
|
+
const archetype = ir.intent.archetype;
|
|
2608
|
+
const chosen = getArchetypePerfScore(profile.id, archetype);
|
|
2609
|
+
if (chosen.grounding !== "judgment") return [];
|
|
2610
|
+
return [
|
|
2611
|
+
{
|
|
2612
|
+
level: "info",
|
|
2613
|
+
code: "model-stale-evidence",
|
|
2614
|
+
message: `Model-stale-evidence: target=${profile.id} archetype=${archetype} is judgment-grounded (n=${chosen.n}, cross-app 90d window) despite brain-query mode being active. Fewer than 10 outcomes back this (model, archetype) tuple across ALL consumers \u2014 routing decisions remain pre-measured for this slot.`,
|
|
2615
|
+
suggestion: "Verify that `record()` is being called on every call() outcome. Counts are cross-app (migration 050 view): once ANY consumers accumulate n>=10 rows on this tuple, the score promotes from judgment to measured automatically within the 5-min SWR window. (Before alpha.78 this promotion was advertised but had no implementing mechanism \u2014 n was never populated; if this advisory has been firing for weeks at n=0 despite real traffic, bump to >=alpha.78 and it will clear on its own.)",
|
|
2616
|
+
recommendationType: "prompt-fix",
|
|
2617
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2618
|
+
}
|
|
2619
|
+
];
|
|
2660
2620
|
}
|
|
2661
|
-
function
|
|
2662
|
-
|
|
2663
|
-
if (!
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2621
|
+
function detectTierDown(ir, profile, phase2) {
|
|
2622
|
+
if (!phase2 || phase2.fallbackChain.length === 0) return [];
|
|
2623
|
+
if (!phase2.profileResolver) return [];
|
|
2624
|
+
const archetype = ir.intent.archetype;
|
|
2625
|
+
const chosenScore = getArchetypePerfScore(profile.id, archetype);
|
|
2626
|
+
const chosenCost = profile.costInputPer1m;
|
|
2627
|
+
let bestAlt = null;
|
|
2628
|
+
for (const altId of phase2.fallbackChain) {
|
|
2629
|
+
const altProfile = phase2.profileResolver(altId);
|
|
2630
|
+
if (!altProfile) continue;
|
|
2631
|
+
if (altProfile.id === profile.id) continue;
|
|
2632
|
+
const altScore = getArchetypePerfScore(altProfile.id, archetype);
|
|
2633
|
+
if (altScore.grounding !== "measured") continue;
|
|
2634
|
+
if (altScore.score < QUALITY_FLOOR_FOR_RECOMMENDATION) continue;
|
|
2635
|
+
if (altScore.score < chosenScore.score) continue;
|
|
2636
|
+
if (altProfile.costInputPer1m > chosenCost * TIER_DOWN_COST_RATIO) continue;
|
|
2637
|
+
if (!bestAlt || altProfile.costInputPer1m < bestAlt.profile.costInputPer1m || altProfile.costInputPer1m === bestAlt.profile.costInputPer1m && altScore.score > bestAlt.score.score) {
|
|
2638
|
+
bestAlt = { id: altId, profile: altProfile, score: altScore };
|
|
2639
|
+
}
|
|
2668
2640
|
}
|
|
2669
|
-
if (
|
|
2670
|
-
const
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2641
|
+
if (!bestAlt) return [];
|
|
2642
|
+
const chosenDesc = chosenScore.grounding === "measured" ? `archetypePerf.${archetype}=${chosenScore.score} (measured, n=${chosenScore.n})` : `archetypePerf.${archetype}=${chosenScore.score} (${chosenScore.grounding})`;
|
|
2643
|
+
return [
|
|
2644
|
+
{
|
|
2645
|
+
level: "warn",
|
|
2646
|
+
code: "tier-down",
|
|
2647
|
+
message: `Tier-down: target=${profile.id} (${chosenDesc}) selected for ${archetype}. Brain shows ${bestAlt.id} delivers equal-or-better quality (archetypePerf.${archetype}=${bestAlt.score.score}, measured, n=${bestAlt.score.n}) at $${bestAlt.profile.costInputPer1m}/$${bestAlt.profile.costOutputPer1m} per 1M vs $${profile.costInputPer1m}/$${profile.costOutputPer1m} \u2014 a measured tier-down opportunity.`,
|
|
2648
|
+
suggestion: `Move \`${bestAlt.id}\` to primary for this archetype. The brain has n=${bestAlt.score.n} measured outcomes backing the recommendation; this is data, not opinion. If posture='locked' is required (compliance/brand promise), set it explicitly to silence this rule.`,
|
|
2649
|
+
recommendationType: "tier-down",
|
|
2650
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2651
|
+
}
|
|
2652
|
+
];
|
|
2676
2653
|
}
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2654
|
+
function detectArchetypePerfFloorBreach(ir, profile) {
|
|
2655
|
+
const compat = getModelCompatibility(profile.id, {
|
|
2656
|
+
archetype: ir.intent.archetype,
|
|
2657
|
+
toolOrchestration: ir.constraints?.toolOrchestration
|
|
2658
|
+
});
|
|
2659
|
+
if (compat.status === "compatible") return [];
|
|
2660
|
+
if (compat.status === "requires-adapter") {
|
|
2661
|
+
return [
|
|
2662
|
+
{
|
|
2663
|
+
level: "warn",
|
|
2664
|
+
code: "archetype-perf-floor-breach",
|
|
2665
|
+
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}`,
|
|
2666
|
+
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.`,
|
|
2667
|
+
recommendationType: "prompt-fix",
|
|
2668
|
+
suggestedAdaptation: compat.adapter,
|
|
2669
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2670
|
+
}
|
|
2671
|
+
];
|
|
2693
2672
|
}
|
|
2673
|
+
return [
|
|
2674
|
+
{
|
|
2675
|
+
level: "critical",
|
|
2676
|
+
code: "archetype-perf-floor-breach",
|
|
2677
|
+
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}`,
|
|
2678
|
+
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.`,
|
|
2679
|
+
recommendationType: "model-swap",
|
|
2680
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2681
|
+
}
|
|
2682
|
+
];
|
|
2694
2683
|
}
|
|
2695
|
-
function
|
|
2696
|
-
|
|
2697
|
-
if (!
|
|
2698
|
-
const
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2684
|
+
function detectStaleExclusionCandidate(ir) {
|
|
2685
|
+
if (!isExclusionFindingsBrainActive()) return [];
|
|
2686
|
+
if (!ir.appId) return [];
|
|
2687
|
+
const findings = getStaleExclusionFindings({
|
|
2688
|
+
appId: ir.appId,
|
|
2689
|
+
archetype: ir.intent.archetype
|
|
2690
|
+
});
|
|
2691
|
+
if (findings.length === 0) return [];
|
|
2692
|
+
const ranked = [...findings].sort((a, b) => {
|
|
2693
|
+
const sa = a.estimatedSavingsUsd30d ?? -Infinity;
|
|
2694
|
+
const sb = b.estimatedSavingsUsd30d ?? -Infinity;
|
|
2695
|
+
if (sa !== sb) return sb - sa;
|
|
2696
|
+
return confidenceRank(b.confidence) - confidenceRank(a.confidence);
|
|
2697
|
+
});
|
|
2698
|
+
const top = ranked[0];
|
|
2699
|
+
const extraCount = findings.length - 1;
|
|
2700
|
+
const extraNote = extraCount > 0 ? ` (+ ${extraCount} more excluded model${extraCount === 1 ? "" : "s"} for this archetype)` : "";
|
|
2701
|
+
return [
|
|
2702
|
+
{
|
|
2703
|
+
level: "info",
|
|
2704
|
+
code: "stale-exclusion-candidate",
|
|
2705
|
+
message: `${top.message}${extraNote}`,
|
|
2706
|
+
suggestion: top.suggestion,
|
|
2707
|
+
recommendationType: "tier-down",
|
|
2708
|
+
docsUrl: "https://github.com/stue/command-center/blob/main/interfaces/kgauto.md#best-practice-advisories"
|
|
2709
|
+
}
|
|
2710
|
+
];
|
|
2714
2711
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2712
|
+
function confidenceRank(c) {
|
|
2713
|
+
if (c === "high") return 3;
|
|
2714
|
+
if (c === "medium") return 2;
|
|
2715
|
+
return 1;
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// src/translator.ts
|
|
2719
|
+
var TRANSLATOR_FLOOR = ARCHETYPE_FLOOR_DEFAULT;
|
|
2720
|
+
var RULE_SEQUENTIAL_TOOL_CLIFF = "sequential-tool-cliff-below-floor";
|
|
2721
|
+
var RULE_NARRATION_DRIFT_ANTHROPIC = "narration-drift-anthropic";
|
|
2722
|
+
var RULE_NARRATION_THINKING_LEAK_DEEPSEEK = "narration-thinking-leak-deepseek";
|
|
2723
|
+
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.";
|
|
2724
|
+
var NARRATION_DRIFT_ANTHROPIC_PREAMBLE = "Output ONLY the requested content. Do not narrate your thought process. Each line \u2264 12 words.";
|
|
2725
|
+
var NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE = "Reasoning is internal. Output ONLY the requested content; do not emit <thinking> blocks or internal monologue as user-facing text.";
|
|
2726
|
+
var RULE_DISCIPLINE_GATES_V1 = "discipline-gates-v1";
|
|
2727
|
+
var DISCIPLINE_GATES_V1_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2728
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2729
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2730
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2731
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2732
|
+
5. Label each claim: mark it observed, inferred, or assumed.
|
|
2733
|
+
6. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2734
|
+
var DISCIPLINE_GATES_V1_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2735
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2736
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2737
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.
|
|
2738
|
+
4. Label each claim: mark it observed, inferred, or assumed.
|
|
2739
|
+
5. A surfaced gap beats a guessed answer: flag what you cannot determine rather than fabricating past it.`;
|
|
2740
|
+
var RULE_DISCIPLINE_GATES_V1_STRUCTURED = "discipline-gates-v1-structured";
|
|
2741
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2742
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2743
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2744
|
+
3. Expand, don't guess: resolve a compressed or referenced item by looking it up rather than inferring its contents.
|
|
2745
|
+
4. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2746
|
+
var DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS = `Work through these gates at every judgment point, explicitly:
|
|
2747
|
+
1. Evidence before reasoning: cite what you observed before concluding from it.
|
|
2748
|
+
2. One extra signal: when a finding feels conclusive, check one more adjacent signal before stating it.
|
|
2749
|
+
3. Innocent explanation first: state the most plausible benign reading before alleging the alarming one.`;
|
|
2750
|
+
var DISCIPLINE_ELIGIBLE_ARCHETYPES = /* @__PURE__ */ new Set([
|
|
2751
|
+
"hunt",
|
|
2752
|
+
"summarize",
|
|
2753
|
+
"plan",
|
|
2754
|
+
"critique",
|
|
2755
|
+
"judge"
|
|
2756
|
+
]);
|
|
2757
|
+
function matchRule(kind, profile, archetype, ctx) {
|
|
2758
|
+
if (kind === "discipline_contract") {
|
|
2759
|
+
if (!DISCIPLINE_ELIGIBLE_ARCHETYPES.has(archetype)) return null;
|
|
2760
|
+
if (ctx.outputMode !== "text") {
|
|
2761
|
+
return {
|
|
2762
|
+
id: RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
2763
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_STRUCTURED_WITH_TOOLS : DISCIPLINE_GATES_V1_STRUCTURED_NO_TOOLS
|
|
2764
|
+
};
|
|
2724
2765
|
}
|
|
2766
|
+
return {
|
|
2767
|
+
id: RULE_DISCIPLINE_GATES_V1,
|
|
2768
|
+
preamble: ctx.hasTools ? DISCIPLINE_GATES_V1_WITH_TOOLS : DISCIPLINE_GATES_V1_NO_TOOLS
|
|
2769
|
+
};
|
|
2725
2770
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2771
|
+
if (kind === "tool_call_contract") {
|
|
2772
|
+
if (!profile.archetypePerf) return null;
|
|
2773
|
+
const archetypeScore = profile.archetypePerf[archetype];
|
|
2774
|
+
if (typeof archetypeScore !== "number" || archetypeScore >= TRANSLATOR_FLOOR) {
|
|
2775
|
+
return null;
|
|
2776
|
+
}
|
|
2777
|
+
return {
|
|
2778
|
+
id: RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
2779
|
+
preamble: SEQUENTIAL_TOOL_PREAMBLE,
|
|
2780
|
+
wireOverrides: { parallelToolCalls: false }
|
|
2781
|
+
};
|
|
2733
2782
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2783
|
+
if (kind === "narration_contract") {
|
|
2784
|
+
if (profile.provider === "anthropic") {
|
|
2785
|
+
return {
|
|
2786
|
+
id: RULE_NARRATION_DRIFT_ANTHROPIC,
|
|
2787
|
+
preamble: NARRATION_DRIFT_ANTHROPIC_PREAMBLE
|
|
2788
|
+
};
|
|
2738
2789
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
} catch (err) {
|
|
2745
|
-
if (runtime5 !== rt) return;
|
|
2746
|
-
snap.refreshing = false;
|
|
2747
|
-
snap.expiresAt = Date.now() + rt.ttlMs;
|
|
2748
|
-
if (!warnedOnce5) {
|
|
2749
|
-
warnedOnce5 = true;
|
|
2750
|
-
(rt.onError ?? defaultOnError5)(err);
|
|
2790
|
+
if (profile.provider === "deepseek") {
|
|
2791
|
+
return {
|
|
2792
|
+
id: RULE_NARRATION_THINKING_LEAK_DEEPSEEK,
|
|
2793
|
+
preamble: NARRATION_THINKING_LEAK_DEEPSEEK_PREAMBLE
|
|
2794
|
+
};
|
|
2751
2795
|
}
|
|
2796
|
+
return null;
|
|
2752
2797
|
}
|
|
2798
|
+
return null;
|
|
2753
2799
|
}
|
|
2754
|
-
function
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
const
|
|
2768
|
-
|
|
2800
|
+
function applySectionRewrites(args) {
|
|
2801
|
+
const { ir, profile, archetype } = args;
|
|
2802
|
+
if (!Array.isArray(ir.sections) || ir.sections.length === 0) {
|
|
2803
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
2804
|
+
}
|
|
2805
|
+
const outputMode = args.outputMode ?? resolveOutputMode({
|
|
2806
|
+
declared: ir.constraints?.outputMode,
|
|
2807
|
+
structuredOutput: ir.constraints?.structuredOutput,
|
|
2808
|
+
toolCount: ir.tools?.length ?? 0
|
|
2809
|
+
});
|
|
2810
|
+
const hasTools = (ir.tools?.length ?? 0) > 0;
|
|
2811
|
+
const ctx = { outputMode, hasTools };
|
|
2812
|
+
const rewrites = [];
|
|
2813
|
+
const newSections = ir.sections.map((section) => {
|
|
2814
|
+
if (!section.kind || section.kind === "arbitrary") return section;
|
|
2815
|
+
const rule = matchRule(section.kind, profile, archetype, ctx);
|
|
2816
|
+
if (!rule) return section;
|
|
2817
|
+
const originalText = section.text;
|
|
2818
|
+
const transformedText = `${rule.preamble}
|
|
2819
|
+
|
|
2820
|
+
${originalText}`;
|
|
2821
|
+
rewrites.push({
|
|
2822
|
+
sectionId: section.id,
|
|
2823
|
+
kind: section.kind,
|
|
2824
|
+
rule: rule.id,
|
|
2825
|
+
originalText,
|
|
2826
|
+
transformedText,
|
|
2827
|
+
...rule.wireOverrides ? { wireOverrides: rule.wireOverrides } : {}
|
|
2828
|
+
});
|
|
2829
|
+
return { ...section, text: transformedText };
|
|
2830
|
+
});
|
|
2831
|
+
if (rewrites.length === 0) {
|
|
2832
|
+
return { rewrittenIR: ir, rewrites: [] };
|
|
2833
|
+
}
|
|
2834
|
+
const rewrittenIR = { ...ir, sections: newSections };
|
|
2835
|
+
return { rewrittenIR, rewrites };
|
|
2769
2836
|
}
|
|
2770
2837
|
|
|
2771
2838
|
// src/compile.ts
|
|
@@ -3895,6 +3962,17 @@ function parseJsonLoose(raw) {
|
|
|
3895
3962
|
var FAILED = /* @__PURE__ */ Symbol("parse-failed");
|
|
3896
3963
|
|
|
3897
3964
|
// src/ir.ts
|
|
3965
|
+
function mutationId(m) {
|
|
3966
|
+
return typeof m === "string" ? m : m.id;
|
|
3967
|
+
}
|
|
3968
|
+
function hasMutation(list, idOrPrefix) {
|
|
3969
|
+
if (!Array.isArray(list)) return false;
|
|
3970
|
+
const prefix = idOrPrefix.endsWith("*") ? idOrPrefix.slice(0, -1) : void 0;
|
|
3971
|
+
return list.some((m) => {
|
|
3972
|
+
const id = mutationId(m);
|
|
3973
|
+
return prefix !== void 0 ? id.startsWith(prefix) : id === idOrPrefix;
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3898
3976
|
var CallError = class extends Error {
|
|
3899
3977
|
attempts;
|
|
3900
3978
|
lastErrorCode;
|
|
@@ -3948,12 +4026,46 @@ async function captureGoldenIr(ctx) {
|
|
|
3948
4026
|
}
|
|
3949
4027
|
|
|
3950
4028
|
// src/streaming.ts
|
|
4029
|
+
function classifyThrownFetchError(err) {
|
|
4030
|
+
const name = err?.name;
|
|
4031
|
+
if (name === "TimeoutError") {
|
|
4032
|
+
return { errorType: "retryable", errorCode: "timeout", message: String(err) };
|
|
4033
|
+
}
|
|
4034
|
+
if (name === "AbortError") {
|
|
4035
|
+
return { errorType: "terminal", errorCode: "aborted", message: "aborted by caller signal" };
|
|
4036
|
+
}
|
|
4037
|
+
return { errorType: "retryable", errorCode: "network_error", message: String(err) };
|
|
4038
|
+
}
|
|
4039
|
+
function createStallGuard(external, stallTimeoutMs) {
|
|
4040
|
+
const ctl = new AbortController();
|
|
4041
|
+
if (external) {
|
|
4042
|
+
if (external.aborted) ctl.abort(external.reason);
|
|
4043
|
+
else external.addEventListener("abort", () => ctl.abort(external.reason), { once: true });
|
|
4044
|
+
}
|
|
4045
|
+
let timer;
|
|
4046
|
+
const arm = () => {
|
|
4047
|
+
if (!stallTimeoutMs || stallTimeoutMs <= 0) return;
|
|
4048
|
+
if (timer) clearTimeout(timer);
|
|
4049
|
+
timer = setTimeout(() => {
|
|
4050
|
+
ctl.abort(
|
|
4051
|
+
new DOMException(`stream stalled: no bytes for ${stallTimeoutMs}ms`, "TimeoutError")
|
|
4052
|
+
);
|
|
4053
|
+
}, stallTimeoutMs);
|
|
4054
|
+
};
|
|
4055
|
+
const clear = () => {
|
|
4056
|
+
if (timer) clearTimeout(timer);
|
|
4057
|
+
timer = void 0;
|
|
4058
|
+
};
|
|
4059
|
+
return { signal: ctl.signal, arm, clear };
|
|
4060
|
+
}
|
|
3951
4061
|
var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
|
|
3952
4062
|
async function streamAnthropic(request, apiKey, opts) {
|
|
3953
4063
|
const { provider: _provider, ...body } = request;
|
|
3954
4064
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
4065
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
3955
4066
|
let res;
|
|
3956
4067
|
try {
|
|
4068
|
+
guard.arm();
|
|
3957
4069
|
res = await fetchFn(ANTHROPIC_URL, {
|
|
3958
4070
|
method: "POST",
|
|
3959
4071
|
headers: {
|
|
@@ -3961,12 +4073,16 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
3961
4073
|
"anthropic-version": "2023-06-01",
|
|
3962
4074
|
"content-type": "application/json"
|
|
3963
4075
|
},
|
|
3964
|
-
body: JSON.stringify({ ...body, stream: true })
|
|
4076
|
+
body: JSON.stringify({ ...body, stream: true }),
|
|
4077
|
+
signal: guard.signal
|
|
3965
4078
|
});
|
|
3966
4079
|
} catch (err) {
|
|
3967
|
-
|
|
4080
|
+
guard.clear();
|
|
4081
|
+
const c = classifyThrownFetchError(err);
|
|
4082
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
3968
4083
|
}
|
|
3969
4084
|
if (!res.ok) {
|
|
4085
|
+
guard.clear();
|
|
3970
4086
|
const errBody = await res.json().catch(() => ({}));
|
|
3971
4087
|
return classifyHttpError(res.status, errBody);
|
|
3972
4088
|
}
|
|
@@ -4029,9 +4145,13 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
4029
4145
|
if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
|
|
4030
4146
|
return;
|
|
4031
4147
|
}
|
|
4032
|
-
});
|
|
4148
|
+
}, guard.arm, guard.signal);
|
|
4033
4149
|
} catch (err) {
|
|
4034
|
-
|
|
4150
|
+
const c = classifyThrownFetchError(err);
|
|
4151
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
4152
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
4153
|
+
} finally {
|
|
4154
|
+
guard.clear();
|
|
4035
4155
|
}
|
|
4036
4156
|
const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
|
|
4037
4157
|
id: b.id,
|
|
@@ -4058,6 +4178,7 @@ async function streamAnthropic(request, apiKey, opts) {
|
|
|
4058
4178
|
async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
4059
4179
|
const { provider: _provider, ...body } = request;
|
|
4060
4180
|
const fetchFn = opts.fetchImpl ?? fetch;
|
|
4181
|
+
const guard = createStallGuard(opts.signal, opts.stallTimeoutMs);
|
|
4061
4182
|
const reqBody = {
|
|
4062
4183
|
...body,
|
|
4063
4184
|
stream: true,
|
|
@@ -4068,18 +4189,23 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4068
4189
|
};
|
|
4069
4190
|
let res;
|
|
4070
4191
|
try {
|
|
4192
|
+
guard.arm();
|
|
4071
4193
|
res = await fetchFn(url, {
|
|
4072
4194
|
method: "POST",
|
|
4073
4195
|
headers: {
|
|
4074
4196
|
authorization: `Bearer ${apiKey}`,
|
|
4075
4197
|
"content-type": "application/json"
|
|
4076
4198
|
},
|
|
4077
|
-
body: JSON.stringify(reqBody)
|
|
4199
|
+
body: JSON.stringify(reqBody),
|
|
4200
|
+
signal: guard.signal
|
|
4078
4201
|
});
|
|
4079
4202
|
} catch (err) {
|
|
4080
|
-
|
|
4203
|
+
guard.clear();
|
|
4204
|
+
const c = classifyThrownFetchError(err);
|
|
4205
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4081
4206
|
}
|
|
4082
4207
|
if (!res.ok) {
|
|
4208
|
+
guard.clear();
|
|
4083
4209
|
const errBody = await res.json().catch(() => ({}));
|
|
4084
4210
|
return classifyHttpError(res.status, errBody);
|
|
4085
4211
|
}
|
|
@@ -4132,9 +4258,13 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4132
4258
|
const details = usage.prompt_tokens_details;
|
|
4133
4259
|
if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
|
|
4134
4260
|
}
|
|
4135
|
-
});
|
|
4261
|
+
}, guard.arm, guard.signal);
|
|
4136
4262
|
} catch (err) {
|
|
4137
|
-
|
|
4263
|
+
const c = classifyThrownFetchError(err);
|
|
4264
|
+
const code = c.errorCode === "network_error" ? "stream_interrupted" : c.errorCode;
|
|
4265
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: code, message: c.message, raw: null };
|
|
4266
|
+
} finally {
|
|
4267
|
+
guard.clear();
|
|
4138
4268
|
}
|
|
4139
4269
|
const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
|
|
4140
4270
|
id: b.id,
|
|
@@ -4157,15 +4287,28 @@ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
|
|
|
4157
4287
|
};
|
|
4158
4288
|
return { ok: true, status: res.status, response };
|
|
4159
4289
|
}
|
|
4160
|
-
async function parseSSEStream(response, handler) {
|
|
4290
|
+
async function parseSSEStream(response, handler, onRead, signal) {
|
|
4161
4291
|
const body = response.body;
|
|
4162
4292
|
if (!body) throw new Error("Response has no body for SSE parse");
|
|
4163
4293
|
const reader = body.getReader();
|
|
4164
4294
|
const decoder = new TextDecoder("utf-8");
|
|
4165
4295
|
let buffer = "";
|
|
4296
|
+
const abortRace = signal ? new Promise((_resolve, reject) => {
|
|
4297
|
+
if (signal.aborted) return reject(signal.reason);
|
|
4298
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
4299
|
+
}) : void 0;
|
|
4166
4300
|
for (; ; ) {
|
|
4167
|
-
|
|
4301
|
+
let readResult;
|
|
4302
|
+
try {
|
|
4303
|
+
readResult = abortRace ? await Promise.race([reader.read(), abortRace]) : await reader.read();
|
|
4304
|
+
} catch (err) {
|
|
4305
|
+
void reader.cancel().catch(() => {
|
|
4306
|
+
});
|
|
4307
|
+
throw err;
|
|
4308
|
+
}
|
|
4309
|
+
const { value, done } = readResult;
|
|
4168
4310
|
if (done) break;
|
|
4311
|
+
onRead?.();
|
|
4169
4312
|
buffer += decoder.decode(value, { stream: true });
|
|
4170
4313
|
let sep;
|
|
4171
4314
|
while (sep = buffer.indexOf("\n\n"), sep !== -1) {
|
|
@@ -4276,9 +4419,6 @@ function extractErrorMessage(body) {
|
|
|
4276
4419
|
if (typeof b.message === "string") return b.message;
|
|
4277
4420
|
return void 0;
|
|
4278
4421
|
}
|
|
4279
|
-
function retryableError(status, code, message, raw) {
|
|
4280
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
4281
|
-
}
|
|
4282
4422
|
|
|
4283
4423
|
// src/execute.ts
|
|
4284
4424
|
var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
|
|
@@ -4323,7 +4463,9 @@ async function executeAnthropic(request, opts) {
|
|
|
4323
4463
|
if (opts.onChunk) {
|
|
4324
4464
|
return streamAnthropic(request, apiKey, {
|
|
4325
4465
|
onChunk: opts.onChunk,
|
|
4326
|
-
fetchImpl: opts.fetchImpl
|
|
4466
|
+
fetchImpl: opts.fetchImpl,
|
|
4467
|
+
signal: opts.signal,
|
|
4468
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4327
4469
|
});
|
|
4328
4470
|
}
|
|
4329
4471
|
const { provider: _provider, ...body } = request;
|
|
@@ -4338,11 +4480,13 @@ async function executeAnthropic(request, opts) {
|
|
|
4338
4480
|
"anthropic-version": "2023-06-01",
|
|
4339
4481
|
"content-type": "application/json"
|
|
4340
4482
|
},
|
|
4341
|
-
body: JSON.stringify(body)
|
|
4483
|
+
body: JSON.stringify(body),
|
|
4484
|
+
signal: opts.signal
|
|
4342
4485
|
});
|
|
4343
4486
|
json = await res.json().catch(() => ({}));
|
|
4344
4487
|
} catch (err) {
|
|
4345
|
-
|
|
4488
|
+
const c = classifyThrownFetchError(err);
|
|
4489
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4346
4490
|
}
|
|
4347
4491
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4348
4492
|
return { ok: true, status: res.status, response: normalizeAnthropic(json) };
|
|
@@ -4374,11 +4518,13 @@ async function executeGoogle(request, opts) {
|
|
|
4374
4518
|
res = await fetchFn(url, {
|
|
4375
4519
|
method: "POST",
|
|
4376
4520
|
headers: { "content-type": "application/json" },
|
|
4377
|
-
body: JSON.stringify(body)
|
|
4521
|
+
body: JSON.stringify(body),
|
|
4522
|
+
signal: opts.signal
|
|
4378
4523
|
});
|
|
4379
4524
|
json = await res.json().catch(() => ({}));
|
|
4380
4525
|
} catch (err) {
|
|
4381
|
-
|
|
4526
|
+
const c = classifyThrownFetchError(err);
|
|
4527
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4382
4528
|
}
|
|
4383
4529
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4384
4530
|
return { ok: true, status: res.status, response: normalizeGoogle(json) };
|
|
@@ -4410,7 +4556,9 @@ async function executeOpenAI(request, opts) {
|
|
|
4410
4556
|
if (opts.onChunk) {
|
|
4411
4557
|
return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
|
|
4412
4558
|
onChunk: opts.onChunk,
|
|
4413
|
-
fetchImpl: opts.fetchImpl
|
|
4559
|
+
fetchImpl: opts.fetchImpl,
|
|
4560
|
+
signal: opts.signal,
|
|
4561
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4414
4562
|
});
|
|
4415
4563
|
}
|
|
4416
4564
|
const { provider: _provider, ...body } = request;
|
|
@@ -4421,11 +4569,13 @@ async function executeOpenAI(request, opts) {
|
|
|
4421
4569
|
res = await fetchFn(OPENAI_URL, {
|
|
4422
4570
|
method: "POST",
|
|
4423
4571
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4424
|
-
body: JSON.stringify(body)
|
|
4572
|
+
body: JSON.stringify(body),
|
|
4573
|
+
signal: opts.signal
|
|
4425
4574
|
});
|
|
4426
4575
|
json = await res.json().catch(() => ({}));
|
|
4427
4576
|
} catch (err) {
|
|
4428
|
-
|
|
4577
|
+
const c = classifyThrownFetchError(err);
|
|
4578
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4429
4579
|
}
|
|
4430
4580
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4431
4581
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4438,7 +4588,9 @@ async function executeDeepSeek(request, opts) {
|
|
|
4438
4588
|
if (opts.onChunk) {
|
|
4439
4589
|
return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
|
|
4440
4590
|
onChunk: opts.onChunk,
|
|
4441
|
-
fetchImpl: opts.fetchImpl
|
|
4591
|
+
fetchImpl: opts.fetchImpl,
|
|
4592
|
+
signal: opts.signal,
|
|
4593
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4442
4594
|
});
|
|
4443
4595
|
}
|
|
4444
4596
|
const { provider: _provider, ...body } = request;
|
|
@@ -4449,11 +4601,13 @@ async function executeDeepSeek(request, opts) {
|
|
|
4449
4601
|
res = await fetchFn(DEEPSEEK_URL, {
|
|
4450
4602
|
method: "POST",
|
|
4451
4603
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4452
|
-
body: JSON.stringify(body)
|
|
4604
|
+
body: JSON.stringify(body),
|
|
4605
|
+
signal: opts.signal
|
|
4453
4606
|
});
|
|
4454
4607
|
json = await res.json().catch(() => ({}));
|
|
4455
4608
|
} catch (err) {
|
|
4456
|
-
|
|
4609
|
+
const c = classifyThrownFetchError(err);
|
|
4610
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4457
4611
|
}
|
|
4458
4612
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4459
4613
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4466,7 +4620,9 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
4466
4620
|
if (opts.onChunk) {
|
|
4467
4621
|
return streamOpenAILike(spec.url, request, apiKey, spec.provider, {
|
|
4468
4622
|
onChunk: opts.onChunk,
|
|
4469
|
-
fetchImpl: opts.fetchImpl
|
|
4623
|
+
fetchImpl: opts.fetchImpl,
|
|
4624
|
+
signal: opts.signal,
|
|
4625
|
+
stallTimeoutMs: opts.stallTimeoutMs
|
|
4470
4626
|
});
|
|
4471
4627
|
}
|
|
4472
4628
|
const { provider: _provider, ...body } = request;
|
|
@@ -4477,11 +4633,13 @@ async function executeOpenAICompatible(request, opts, spec) {
|
|
|
4477
4633
|
res = await fetchFn(spec.url, {
|
|
4478
4634
|
method: "POST",
|
|
4479
4635
|
headers: { authorization: `Bearer ${apiKey}`, "content-type": "application/json" },
|
|
4480
|
-
body: JSON.stringify(body)
|
|
4636
|
+
body: JSON.stringify(body),
|
|
4637
|
+
signal: opts.signal
|
|
4481
4638
|
});
|
|
4482
4639
|
json = await res.json().catch(() => ({}));
|
|
4483
4640
|
} catch (err) {
|
|
4484
|
-
|
|
4641
|
+
const c = classifyThrownFetchError(err);
|
|
4642
|
+
return { ok: false, status: 0, errorType: c.errorType, errorCode: c.errorCode, message: c.message, raw: null };
|
|
4485
4643
|
}
|
|
4486
4644
|
if (!res.ok) return classifyHttpError2(res.status, json);
|
|
4487
4645
|
return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
|
|
@@ -4551,9 +4709,6 @@ function extractErrorMessage2(body) {
|
|
|
4551
4709
|
function terminalError(status, code, message) {
|
|
4552
4710
|
return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
|
|
4553
4711
|
}
|
|
4554
|
-
function retryableError2(status, code, message, raw) {
|
|
4555
|
-
return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
|
|
4556
|
-
}
|
|
4557
4712
|
function tryParseJson2(s) {
|
|
4558
4713
|
if (typeof s !== "string" || s.length === 0) return void 0;
|
|
4559
4714
|
try {
|
|
@@ -4708,6 +4863,15 @@ async function call(ir, opts = {}) {
|
|
|
4708
4863
|
let retriedSameModel = false;
|
|
4709
4864
|
for (let i = 0; i < targetsToTry.length; i++) {
|
|
4710
4865
|
const targetModel = targetsToTry[i];
|
|
4866
|
+
if (opts.abortSignal?.aborted) {
|
|
4867
|
+
attempts.push({
|
|
4868
|
+
model: targetModel,
|
|
4869
|
+
status: "terminal",
|
|
4870
|
+
errorCode: "aborted",
|
|
4871
|
+
message: "Skipped \u2014 caller abortSignal fired before this attempt started"
|
|
4872
|
+
});
|
|
4873
|
+
break;
|
|
4874
|
+
}
|
|
4711
4875
|
const targetProfile = tryGetProfile(targetModel);
|
|
4712
4876
|
const providerFailReason = targetProfile ? failedProviders.get(targetProfile.provider) : void 0;
|
|
4713
4877
|
if (targetProfile && providerFailReason && !opts.noFallback) {
|
|
@@ -4744,16 +4908,29 @@ async function call(ir, opts = {}) {
|
|
|
4744
4908
|
);
|
|
4745
4909
|
const targetSupportsStreaming = targetProfile?.streaming === true;
|
|
4746
4910
|
const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
|
|
4747
|
-
const
|
|
4911
|
+
const mkExecOpts = () => ({
|
|
4748
4912
|
apiKeys: opts.apiKeys,
|
|
4749
4913
|
fetchImpl: opts.fetchImpl,
|
|
4750
4914
|
providerOverrides: opts.providerOverrides,
|
|
4751
|
-
onChunk: streamingOnChunk
|
|
4752
|
-
|
|
4753
|
-
|
|
4915
|
+
onChunk: streamingOnChunk,
|
|
4916
|
+
signal: composeAttemptSignal(
|
|
4917
|
+
streamingOnChunk ? void 0 : opts.attemptTimeoutMs,
|
|
4918
|
+
opts.abortSignal
|
|
4919
|
+
),
|
|
4920
|
+
stallTimeoutMs: streamingOnChunk ? opts.attemptTimeoutMs : void 0
|
|
4921
|
+
});
|
|
4922
|
+
const exec = await execute(activeCompile.request, mkExecOpts());
|
|
4754
4923
|
let validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
|
|
4755
4924
|
let servedByRetry = false;
|
|
4756
|
-
|
|
4925
|
+
let retrySuppressionNote;
|
|
4926
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && getMeasuredFailureVerdict({
|
|
4927
|
+
appId: ir.appId,
|
|
4928
|
+
archetype: ir.intent.archetype,
|
|
4929
|
+
model: targetModel
|
|
4930
|
+
})?.gated === true) {
|
|
4931
|
+
retrySuppressionNote = " [sameModelRetry suppressed: this model carries an active measured-failure gate for this archetype \u2014 retrying it would near-certainly bill a second doomed inference; walking the chain instead]";
|
|
4932
|
+
}
|
|
4933
|
+
if (!validated.ok && isStructuredContractViolation(validated.errorCode) && sameModelRetryEnabled && !retriedSameModel && !retrySuppressionNote) {
|
|
4757
4934
|
retriedSameModel = true;
|
|
4758
4935
|
attempts.push({
|
|
4759
4936
|
model: targetModel,
|
|
@@ -4765,7 +4942,7 @@ async function call(ir, opts = {}) {
|
|
|
4765
4942
|
safeEmit(
|
|
4766
4943
|
() => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
|
|
4767
4944
|
);
|
|
4768
|
-
const retryExec = await execute(retryRequest,
|
|
4945
|
+
const retryExec = await execute(retryRequest, mkExecOpts());
|
|
4769
4946
|
validated = retryExec.ok ? validateStructuredContract(retryExec, ir) : retryExec;
|
|
4770
4947
|
servedByRetry = true;
|
|
4771
4948
|
}
|
|
@@ -4892,7 +5069,7 @@ async function call(ir, opts = {}) {
|
|
|
4892
5069
|
model: targetModel,
|
|
4893
5070
|
status: validated.errorType,
|
|
4894
5071
|
errorCode: validated.errorCode,
|
|
4895
|
-
message: validated.message,
|
|
5072
|
+
message: retrySuppressionNote ? validated.message + retrySuppressionNote : validated.message,
|
|
4896
5073
|
...servedByRetry ? { sameModelRetry: true } : {}
|
|
4897
5074
|
});
|
|
4898
5075
|
lastErr = validated;
|
|
@@ -4931,6 +5108,25 @@ async function call(ir, opts = {}) {
|
|
|
4931
5108
|
lastErr?.errorCode
|
|
4932
5109
|
);
|
|
4933
5110
|
}
|
|
5111
|
+
function composeAttemptSignal(attemptTimeoutMs, callerSignal) {
|
|
5112
|
+
const signals = [];
|
|
5113
|
+
if (callerSignal) signals.push(callerSignal);
|
|
5114
|
+
if (typeof attemptTimeoutMs === "number" && attemptTimeoutMs > 0) {
|
|
5115
|
+
signals.push(AbortSignal.timeout(attemptTimeoutMs));
|
|
5116
|
+
}
|
|
5117
|
+
if (signals.length === 0) return void 0;
|
|
5118
|
+
if (signals.length === 1) return signals[0];
|
|
5119
|
+
if (typeof AbortSignal.any === "function") return AbortSignal.any(signals);
|
|
5120
|
+
const ctl = new AbortController();
|
|
5121
|
+
for (const s of signals) {
|
|
5122
|
+
if (s.aborted) {
|
|
5123
|
+
ctl.abort(s.reason);
|
|
5124
|
+
break;
|
|
5125
|
+
}
|
|
5126
|
+
s.addEventListener("abort", () => ctl.abort(s.reason), { once: true });
|
|
5127
|
+
}
|
|
5128
|
+
return ctl.signal;
|
|
5129
|
+
}
|
|
4934
5130
|
function compileAndRegister(ir, opts) {
|
|
4935
5131
|
const result = compile(ir, {
|
|
4936
5132
|
policy: opts.policy,
|
|
@@ -5985,6 +6181,12 @@ async function runGoldenEval(opts) {
|
|
|
5985
6181
|
);
|
|
5986
6182
|
}
|
|
5987
6183
|
}
|
|
6184
|
+
if (wins + ties + losses === 0) {
|
|
6185
|
+
notes.push(
|
|
6186
|
+
"zero cases judged \u2014 run row persisted, evidence advisory deliberately NOT written (a verdict on n=0 is not evidence)"
|
|
6187
|
+
);
|
|
6188
|
+
return result;
|
|
6189
|
+
}
|
|
5988
6190
|
const latestRes = await fetchFn(
|
|
5989
6191
|
rest(
|
|
5990
6192
|
`compile_outcomes?app_id=eq.${encodeURIComponent(opts.appId)}&select=id&order=id.desc&limit=1`
|
|
@@ -6212,19 +6414,32 @@ function resolveFetch(injected) {
|
|
|
6212
6414
|
function normalizeEndpoint2(endpoint) {
|
|
6213
6415
|
return endpoint.replace(/\/+$/, "");
|
|
6214
6416
|
}
|
|
6417
|
+
function resolveBrainReadTrio(opts) {
|
|
6418
|
+
const env = readBrainReadEnv();
|
|
6419
|
+
const brainEndpoint = opts.brainEndpoint ?? env.endpoint;
|
|
6420
|
+
const brainJwt = opts.brainJwt ?? env.jwt;
|
|
6421
|
+
const brainAnonKey = opts.brainAnonKey ?? env.anonKey;
|
|
6422
|
+
if (brainEndpoint && brainJwt && brainAnonKey) {
|
|
6423
|
+
return { ok: true, brainEndpoint, brainJwt, brainAnonKey };
|
|
6424
|
+
}
|
|
6425
|
+
const missingEnv = [];
|
|
6426
|
+
if (!brainEndpoint) missingEnv.push("KGAUTO_V2_BRAIN_SUPABASE_URL");
|
|
6427
|
+
if (!brainJwt) missingEnv.push("KGAUTO_V2_BRAIN_JWT");
|
|
6428
|
+
if (!brainAnonKey) missingEnv.push("KGAUTO_V2_BRAIN_ANON_KEY");
|
|
6429
|
+
return { ok: false, missingEnv };
|
|
6430
|
+
}
|
|
6215
6431
|
async function getActionableAdvisories(opts) {
|
|
6216
|
-
const {
|
|
6217
|
-
appId,
|
|
6218
|
-
severity,
|
|
6219
|
-
status,
|
|
6220
|
-
brainEndpoint,
|
|
6221
|
-
brainJwt,
|
|
6222
|
-
brainAnonKey,
|
|
6223
|
-
fetch: injectedFetch
|
|
6224
|
-
} = opts;
|
|
6432
|
+
const { appId, severity, status, fetch: injectedFetch } = opts;
|
|
6225
6433
|
if (!appId) {
|
|
6226
6434
|
throw new Error("getActionableAdvisories: appId is required");
|
|
6227
6435
|
}
|
|
6436
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6437
|
+
if (!trio.ok) {
|
|
6438
|
+
throw new Error(
|
|
6439
|
+
`getActionableAdvisories: brain-read-not-configured \u2014 missing ${trio.missingEnv.join(", ")} (pass brainEndpoint/brainJwt/brainAnonKey explicitly or set the canonical env vars)`
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6442
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6228
6443
|
const doFetch = resolveFetch(injectedFetch);
|
|
6229
6444
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
6230
6445
|
const qs = new URLSearchParams();
|
|
@@ -6277,17 +6492,15 @@ async function getActionableAdvisories(opts) {
|
|
|
6277
6492
|
return out;
|
|
6278
6493
|
}
|
|
6279
6494
|
async function markAdvisoryResolved(opts) {
|
|
6280
|
-
const {
|
|
6281
|
-
id,
|
|
6282
|
-
resolutionNote,
|
|
6283
|
-
brainEndpoint,
|
|
6284
|
-
brainJwt,
|
|
6285
|
-
brainAnonKey,
|
|
6286
|
-
fetch: injectedFetch
|
|
6287
|
-
} = opts;
|
|
6495
|
+
const { id, resolutionNote, fetch: injectedFetch } = opts;
|
|
6288
6496
|
if (!id) {
|
|
6289
6497
|
return { ok: false, reason: "id_required" };
|
|
6290
6498
|
}
|
|
6499
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6500
|
+
if (!trio.ok) {
|
|
6501
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
6502
|
+
}
|
|
6503
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6291
6504
|
const doFetch = resolveFetch(injectedFetch);
|
|
6292
6505
|
const base = normalizeEndpoint2(brainEndpoint);
|
|
6293
6506
|
const lookupUrl = `${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=app_id,rule`;
|
|
@@ -6372,7 +6585,7 @@ async function markAdvisoryResolved(opts) {
|
|
|
6372
6585
|
}
|
|
6373
6586
|
}
|
|
6374
6587
|
if (outcomeIds.length === 0) {
|
|
6375
|
-
return { ok: true };
|
|
6588
|
+
return { ok: true, firingsResolved: 0, status: "unknown" };
|
|
6376
6589
|
}
|
|
6377
6590
|
const inList = outcomeIds.join(",");
|
|
6378
6591
|
const patchUrl = `${base}/rest/v1/compile_outcome_advisories?outcome_id=in.(${inList})&code=eq.${encodeURIComponent(code)}&resolved_at=is.null`;
|
|
@@ -6392,8 +6605,10 @@ async function markAdvisoryResolved(opts) {
|
|
|
6392
6605
|
apikey: brainAnonKey,
|
|
6393
6606
|
"Content-Type": "application/json",
|
|
6394
6607
|
Accept: "application/json",
|
|
6395
|
-
//
|
|
6396
|
-
|
|
6608
|
+
// alpha.78 — count what actually changed. An RLS-filtered PATCH
|
|
6609
|
+
// returns 2xx with zero rows; return=representation makes that
|
|
6610
|
+
// visible instead of success-shaped.
|
|
6611
|
+
Prefer: "return=representation"
|
|
6397
6612
|
},
|
|
6398
6613
|
body: JSON.stringify(patchBody)
|
|
6399
6614
|
});
|
|
@@ -6410,7 +6625,33 @@ async function markAdvisoryResolved(opts) {
|
|
|
6410
6625
|
if (!patchRes.ok) {
|
|
6411
6626
|
return { ok: false, reason: `patch_failed:${patchRes.status}` };
|
|
6412
6627
|
}
|
|
6413
|
-
|
|
6628
|
+
let patchedRows = [];
|
|
6629
|
+
try {
|
|
6630
|
+
patchedRows = await patchRes.json();
|
|
6631
|
+
} catch {
|
|
6632
|
+
}
|
|
6633
|
+
const firingsResolved = Array.isArray(patchedRows) ? patchedRows.length : 0;
|
|
6634
|
+
let status = "unknown";
|
|
6635
|
+
try {
|
|
6636
|
+
const statusRes = await doFetch(
|
|
6637
|
+
`${base}/rest/v1/actionable_advisories_v?id=eq.${encodeURIComponent(id)}&select=status`,
|
|
6638
|
+
{
|
|
6639
|
+
method: "GET",
|
|
6640
|
+
headers: {
|
|
6641
|
+
Authorization: `Bearer ${brainJwt}`,
|
|
6642
|
+
apikey: brainAnonKey,
|
|
6643
|
+
Accept: "application/json"
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
);
|
|
6647
|
+
if (statusRes.ok) {
|
|
6648
|
+
const rows = await statusRes.json();
|
|
6649
|
+
const s = Array.isArray(rows) ? rows[0]?.status : void 0;
|
|
6650
|
+
if (s === "open" || s === "resolved") status = s;
|
|
6651
|
+
}
|
|
6652
|
+
} catch {
|
|
6653
|
+
}
|
|
6654
|
+
return { ok: true, firingsResolved, status };
|
|
6414
6655
|
}
|
|
6415
6656
|
async function markExclusionFindingHandled(opts) {
|
|
6416
6657
|
const {
|
|
@@ -6419,12 +6660,14 @@ async function markExclusionFindingHandled(opts) {
|
|
|
6419
6660
|
excludedModel,
|
|
6420
6661
|
resolution,
|
|
6421
6662
|
resolutionNote,
|
|
6422
|
-
brainEndpoint,
|
|
6423
|
-
brainJwt,
|
|
6424
|
-
brainAnonKey,
|
|
6425
6663
|
fetch: injectedFetch
|
|
6426
6664
|
} = opts;
|
|
6427
6665
|
if (!appId) return { ok: false, reason: "app_id_required" };
|
|
6666
|
+
const trio = resolveBrainReadTrio(opts);
|
|
6667
|
+
if (!trio.ok) {
|
|
6668
|
+
return { ok: false, reason: `brain_read_not_configured:${trio.missingEnv.join(",")}` };
|
|
6669
|
+
}
|
|
6670
|
+
const { brainEndpoint, brainJwt, brainAnonKey } = trio;
|
|
6428
6671
|
if (!archetype) return { ok: false, reason: "archetype_required" };
|
|
6429
6672
|
if (!excludedModel) {
|
|
6430
6673
|
return { ok: false, reason: "excluded_model_required" };
|
|
@@ -6690,6 +6933,7 @@ export {
|
|
|
6690
6933
|
ALL_ARCHETYPES,
|
|
6691
6934
|
ARCHETYPE_FAMILY_FITS,
|
|
6692
6935
|
ARCHETYPE_FLOOR_DEFAULT,
|
|
6936
|
+
BRAIN_READ_ENV_NAMES,
|
|
6693
6937
|
COACH_CFG,
|
|
6694
6938
|
CallError,
|
|
6695
6939
|
DECOMPOSITION_TEMPLATES,
|
|
@@ -6708,6 +6952,7 @@ export {
|
|
|
6708
6952
|
MEASURED_GROUNDING_MIN_N,
|
|
6709
6953
|
PRODUCER_OWNED_RULE_CODES,
|
|
6710
6954
|
PROVIDER_ENV_KEYS,
|
|
6955
|
+
ROLLBACK_SUPPRESSION_WINDOW_DAYS,
|
|
6711
6956
|
RULE_DISCIPLINE_GATES_V1,
|
|
6712
6957
|
RULE_DISCIPLINE_GATES_V1_STRUCTURED,
|
|
6713
6958
|
RULE_SEQUENTIAL_TOOL_CLIFF,
|
|
@@ -6761,12 +7006,14 @@ export {
|
|
|
6761
7006
|
getPerAxisMetrics,
|
|
6762
7007
|
getProfile,
|
|
6763
7008
|
getReachabilityDiagnostic,
|
|
7009
|
+
getRecentRollback,
|
|
6764
7010
|
getRecommendedPrimary,
|
|
6765
7011
|
getSequentialStarterChain,
|
|
6766
7012
|
getSequentialStarterChainWithGrounding,
|
|
6767
7013
|
getStaleExclusionFindings,
|
|
6768
7014
|
getStarterChain,
|
|
6769
7015
|
getStarterChainWithGrounding,
|
|
7016
|
+
hasMutation,
|
|
6770
7017
|
hashShape,
|
|
6771
7018
|
isArchetype,
|
|
6772
7019
|
isAutoPromoteEnabledFromEnv,
|
|
@@ -6787,9 +7034,11 @@ export {
|
|
|
6787
7034
|
loadChainsFromBrain,
|
|
6788
7035
|
loadModelsFromBrain,
|
|
6789
7036
|
loadPricingFromBrain,
|
|
7037
|
+
mapMeasuredFailureRows,
|
|
6790
7038
|
markAdvisoryResolved,
|
|
6791
7039
|
markExclusionFindingHandled,
|
|
6792
7040
|
markPromoteReadyHandled,
|
|
7041
|
+
mutationId,
|
|
6793
7042
|
parseGoldenCaptureRate,
|
|
6794
7043
|
parseJudgeVerdict,
|
|
6795
7044
|
peekBrainDeadLetter,
|