@rulvar/cli 1.16.1 → 1.17.0
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/cli.js +3 -2
- package/dist/index.d.ts +25 -1
- package/dist/index.js +1 -1
- package/dist/{io-Bex4Du1t.js → io-Dp2LeOi7.js} +404 -134
- package/package.json +7 -7
- package/dist/dist-A5zLp_kr.js +0 -6993
- package/dist/dist-CdhvWszV.js +0 -626
- package/dist/dist-D9g28MUP.js +0 -95398
- package/dist/src-pgjrRW_Q.js +0 -1174
package/dist/dist-CdhvWszV.js
DELETED
|
@@ -1,626 +0,0 @@
|
|
|
1
|
-
import { KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
//#region ../evals/dist/index.js
|
|
4
|
-
/**
|
|
5
|
-
* @rulvar/evals (M9-T02): EvalCase, the grader contract, and the case and
|
|
6
|
-
* suite runners. A separate quality-measurement package built strictly on
|
|
7
|
-
* the public APIs (L6).
|
|
8
|
-
*
|
|
9
|
-
* Determinism rules (https://docs.rulvar.com/guide/evals and
|
|
10
|
-
* https://docs.rulvar.com/guide/testing): judge graders run THROUGH the
|
|
11
|
-
* engine, so judge calls
|
|
12
|
-
* are journaled, budgeted, and VCR-recordable, and an eval suite replays
|
|
13
|
-
* deterministically from cassettes with zero live calls.
|
|
14
|
-
*/
|
|
15
|
-
/** Thrown when a judge run does not settle ok. */
|
|
16
|
-
var EvalJudgeError = class extends Error {
|
|
17
|
-
judgeRun;
|
|
18
|
-
status;
|
|
19
|
-
constructor(judgeRun, status, detail) {
|
|
20
|
-
super(`eval judge run '${judgeRun}' settled '${status}'${detail === void 0 ? "" : `: ${detail}`}`);
|
|
21
|
-
this.name = "EvalJudgeError";
|
|
22
|
-
this.judgeRun = judgeRun;
|
|
23
|
-
this.status = status;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Runs one EvalCase on the given engine: the target workflow as its own
|
|
28
|
-
* run, pure graders host-side over the outcome, judge graders through the
|
|
29
|
-
* engine via GraderContext.judge. Grader thrown errors are not absorbed:
|
|
30
|
-
* a grader that cannot grade is a defect of the suite, not a failed case.
|
|
31
|
-
*/
|
|
32
|
-
async function runEvalCase(engine, evalCase, options = {}) {
|
|
33
|
-
const name = options.name ?? evalCase.workflow.name;
|
|
34
|
-
const timing = {};
|
|
35
|
-
const handle = engine.run(evalCase.workflow, evalCase.args, {
|
|
36
|
-
name: `eval:${name}`,
|
|
37
|
-
...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
|
|
38
|
-
});
|
|
39
|
-
const offStart = handle.on("run:start", (event) => {
|
|
40
|
-
timing.start ??= event.ts;
|
|
41
|
-
});
|
|
42
|
-
const offEnd = handle.on("run:end", (event) => {
|
|
43
|
-
timing.end ??= event.ts;
|
|
44
|
-
});
|
|
45
|
-
const outcome = await handle.result;
|
|
46
|
-
offStart();
|
|
47
|
-
offEnd();
|
|
48
|
-
let judgeCostUsd = 0;
|
|
49
|
-
let judgeOrdinal = 0;
|
|
50
|
-
const context = {
|
|
51
|
-
value: outcome.value,
|
|
52
|
-
outcome,
|
|
53
|
-
async judge(spec) {
|
|
54
|
-
const ordinal = judgeOrdinal;
|
|
55
|
-
judgeOrdinal += 1;
|
|
56
|
-
const judged = await runJudge(engine, `${name}:${ordinal}`, spec, options.judgeBudgetUsd);
|
|
57
|
-
judgeCostUsd += judged.costUsd;
|
|
58
|
-
return judged.output;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
const verdicts = [];
|
|
62
|
-
for (const grader of evalCase.graders) verdicts.push(await grader.grade(context));
|
|
63
|
-
const latencyMs = timing.start !== void 0 && timing.end !== void 0 ? Math.max(0, Date.parse(timing.end) - Date.parse(timing.start)) : 0;
|
|
64
|
-
return {
|
|
65
|
-
name,
|
|
66
|
-
status: outcome.status,
|
|
67
|
-
passed: outcome.status === "ok" && verdicts.every((verdict) => verdict.passed),
|
|
68
|
-
verdicts,
|
|
69
|
-
costUsd: outcome.cost.totalUsd + judgeCostUsd,
|
|
70
|
-
judgeCostUsd,
|
|
71
|
-
latencyMs,
|
|
72
|
-
usage: outcome.usage,
|
|
73
|
-
...outcome.error === void 0 ? {} : { error: outcome.error }
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
async function runJudge(engine, judgeName, spec, budgetUsd) {
|
|
77
|
-
const workflowName = `eval-judge:${judgeName}`;
|
|
78
|
-
const judgeWorkflow = defineWorkflow({ name: workflowName }, async (ctx) => {
|
|
79
|
-
return await ctx.agent(spec.prompt, {
|
|
80
|
-
model: spec.model,
|
|
81
|
-
schema: spec.schema,
|
|
82
|
-
label: "eval-judge",
|
|
83
|
-
onError: "throw"
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
const outcome = await engine.run(judgeWorkflow, null, {
|
|
87
|
-
name: workflowName,
|
|
88
|
-
...budgetUsd === void 0 ? {} : { budgetUsd }
|
|
89
|
-
}).result;
|
|
90
|
-
if (outcome.status !== "ok") throw new EvalJudgeError(workflowName, outcome.status, outcome.error?.message);
|
|
91
|
-
return {
|
|
92
|
-
output: outcome.value ?? null,
|
|
93
|
-
costUsd: outcome.cost.totalUsd
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Runs cases sequentially (deterministic journal and cassette order) and
|
|
98
|
-
* aggregates. Duplicate workflow names get '#<ordinal>' suffixes so every
|
|
99
|
-
* result row and judge journal is unambiguous.
|
|
100
|
-
*/
|
|
101
|
-
async function runEvalSuite(engine, cases, options = {}) {
|
|
102
|
-
const seen = /* @__PURE__ */ new Map();
|
|
103
|
-
const results = [];
|
|
104
|
-
for (const evalCase of cases) {
|
|
105
|
-
const base = evalCase.workflow.name;
|
|
106
|
-
const ordinal = seen.get(base) ?? 0;
|
|
107
|
-
seen.set(base, ordinal + 1);
|
|
108
|
-
const name = ordinal === 0 ? base : `${base}#${ordinal}`;
|
|
109
|
-
results.push(await runEvalCase(engine, evalCase, {
|
|
110
|
-
name,
|
|
111
|
-
...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd },
|
|
112
|
-
...options.judgeBudgetUsd === void 0 ? {} : { judgeBudgetUsd: options.judgeBudgetUsd }
|
|
113
|
-
}));
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
results,
|
|
117
|
-
passRate: results.length === 0 ? 0 : results.filter((r) => r.passed).length / results.length,
|
|
118
|
-
totalCostUsd: results.reduce((sum, r) => sum + r.costUsd, 0),
|
|
119
|
-
meanLatencyMs: results.length === 0 ? 0 : results.reduce((sum, r) => sum + r.latencyMs, 0) / results.length
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Runs the same case list against every cell's engine, sequentially and
|
|
124
|
-
* in declaration order (deterministic cassette consumption), and reports
|
|
125
|
-
* per-cell aggregates for side-by-side comparison.
|
|
126
|
-
*/
|
|
127
|
-
async function runEvalMatrix(cells, cases, options = {}) {
|
|
128
|
-
const reports = [];
|
|
129
|
-
for (const cell of cells) {
|
|
130
|
-
const suite = await runEvalSuite(await cell.engine(), cases, options);
|
|
131
|
-
reports.push({
|
|
132
|
-
cell: cell.name,
|
|
133
|
-
passRate: suite.passRate,
|
|
134
|
-
totalCostUsd: suite.totalCostUsd,
|
|
135
|
-
meanLatencyMs: suite.meanLatencyMs,
|
|
136
|
-
results: suite.results
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return { cells: reports };
|
|
140
|
-
}
|
|
141
|
-
function deepEqual(a, b) {
|
|
142
|
-
if (a === b) return true;
|
|
143
|
-
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
144
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
145
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
146
|
-
return a.every((item, index) => deepEqual(item, b[index]));
|
|
147
|
-
}
|
|
148
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
149
|
-
const aKeys = Object.keys(a).sort();
|
|
150
|
-
const bKeys = Object.keys(b).sort();
|
|
151
|
-
if (aKeys.length !== bKeys.length || aKeys.some((key, index) => key !== bKeys[index])) return false;
|
|
152
|
-
return aKeys.every((key) => deepEqual(a[key], b[key]));
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
function goldenGrader(expected, options = {}) {
|
|
157
|
-
const name = options.name ?? "golden";
|
|
158
|
-
return {
|
|
159
|
-
name,
|
|
160
|
-
grade(context) {
|
|
161
|
-
const passed = deepEqual(context.value, expected);
|
|
162
|
-
return {
|
|
163
|
-
grader: name,
|
|
164
|
-
passed,
|
|
165
|
-
...passed ? {} : { details: {
|
|
166
|
-
expected,
|
|
167
|
-
actual: context.value ?? null
|
|
168
|
-
} }
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
function rubricGrader(criteria, options = {}) {
|
|
174
|
-
const name = options.name ?? "rubric";
|
|
175
|
-
const threshold = options.passThreshold ?? 1;
|
|
176
|
-
return {
|
|
177
|
-
name,
|
|
178
|
-
grade(context) {
|
|
179
|
-
const rows = criteria.map((criterion) => ({
|
|
180
|
-
name: criterion.name,
|
|
181
|
-
passed: criterion.check(context.value)
|
|
182
|
-
}));
|
|
183
|
-
const score = criteria.length === 0 ? 1 : rows.filter((row) => row.passed).length / rows.length;
|
|
184
|
-
return {
|
|
185
|
-
grader: name,
|
|
186
|
-
passed: score >= threshold,
|
|
187
|
-
score,
|
|
188
|
-
details: { criteria: rows }
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
/** The default judge verdict shape. */
|
|
194
|
-
const JUDGE_VERDICT_SCHEMA = {
|
|
195
|
-
type: "object",
|
|
196
|
-
properties: {
|
|
197
|
-
passed: { type: "boolean" },
|
|
198
|
-
reasoning: { type: "string" }
|
|
199
|
-
},
|
|
200
|
-
required: ["passed"],
|
|
201
|
-
additionalProperties: false
|
|
202
|
-
};
|
|
203
|
-
function defaultToVerdict(output) {
|
|
204
|
-
return { passed: typeof output === "object" && output !== null && !Array.isArray(output) && output.passed === true };
|
|
205
|
-
}
|
|
206
|
-
function judgePrompt(instruction, value) {
|
|
207
|
-
return [
|
|
208
|
-
"You are an evaluation judge. Judge the candidate output below against the instruction.",
|
|
209
|
-
"",
|
|
210
|
-
`Instruction: ${instruction}`,
|
|
211
|
-
"",
|
|
212
|
-
"Candidate output (JSON):",
|
|
213
|
-
JSON.stringify(value ?? null),
|
|
214
|
-
"",
|
|
215
|
-
"Return a verdict object matching the response schema."
|
|
216
|
-
].join("\n");
|
|
217
|
-
}
|
|
218
|
-
function judgeGrader(options) {
|
|
219
|
-
const name = options.name ?? "judge";
|
|
220
|
-
if (options.schema !== void 0 && options.toVerdict === void 0) throw new Error(`judgeGrader '${name}': a custom schema requires toVerdict`);
|
|
221
|
-
const toVerdict = options.toVerdict ?? defaultToVerdict;
|
|
222
|
-
const schema = options.schema ?? JUDGE_VERDICT_SCHEMA;
|
|
223
|
-
return {
|
|
224
|
-
name,
|
|
225
|
-
async grade(context) {
|
|
226
|
-
if (context.outcome.status !== "ok") return {
|
|
227
|
-
grader: name,
|
|
228
|
-
passed: false,
|
|
229
|
-
details: { skipped: `target run settled '${context.outcome.status}'` }
|
|
230
|
-
};
|
|
231
|
-
const output = await context.judge({
|
|
232
|
-
model: options.model,
|
|
233
|
-
prompt: judgePrompt(options.instruction, context.value),
|
|
234
|
-
schema
|
|
235
|
-
});
|
|
236
|
-
const verdict = toVerdict(output);
|
|
237
|
-
return {
|
|
238
|
-
grader: name,
|
|
239
|
-
passed: verdict.passed,
|
|
240
|
-
...verdict.score === void 0 ? {} : { score: verdict.score },
|
|
241
|
-
details: { output }
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* The eval-committer identity (M11-T01; https://docs.rulvar.com/guide/model-knowledge).
|
|
248
|
-
* The pipeline-side commit path: builds
|
|
249
|
-
* eval-committer-gated ops (the coherence square: class eval-measured,
|
|
250
|
-
* author eval-pipeline, metrics present) and commits them with the
|
|
251
|
-
* documented CAS-rebase recipe. Humans never call this; their path is
|
|
252
|
-
* the human gate and it structurally cannot carry metrics.
|
|
253
|
-
*/
|
|
254
|
-
/** One measured claim; claimExpiry applies the TTL from the decay table. */
|
|
255
|
-
function evalMeasuredClaim(input, committerId) {
|
|
256
|
-
return {
|
|
257
|
-
id: input.id,
|
|
258
|
-
subject: input.subject,
|
|
259
|
-
taskClass: input.taskClass,
|
|
260
|
-
polarity: input.polarity,
|
|
261
|
-
statement: input.statement,
|
|
262
|
-
class: "eval-measured",
|
|
263
|
-
status: "active",
|
|
264
|
-
evidence: input.evidence,
|
|
265
|
-
metrics: input.metrics,
|
|
266
|
-
confidence: input.confidence,
|
|
267
|
-
observedAt: input.observedAt,
|
|
268
|
-
expiresAt: claimExpiry("eval-measured", input.polarity, input.observedAt),
|
|
269
|
-
...input.modelEpoch === void 0 ? {} : { modelEpoch: input.modelEpoch },
|
|
270
|
-
author: {
|
|
271
|
-
kind: "eval-pipeline",
|
|
272
|
-
id: committerId
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Commits measured claims through the eval-committer gate with the
|
|
278
|
-
* documented rebase recipe: on a CAS rejection, re-read current() and
|
|
279
|
-
* retry against the fresh version. Returns the committed version.
|
|
280
|
-
*/
|
|
281
|
-
async function commitEvalMeasured(store, claims, options) {
|
|
282
|
-
const gate = {
|
|
283
|
-
kind: "eval-committer",
|
|
284
|
-
committerId: options.committerId,
|
|
285
|
-
reportId: options.reportId
|
|
286
|
-
};
|
|
287
|
-
const ops = claims.map((input) => ({
|
|
288
|
-
op: "add",
|
|
289
|
-
claim: evalMeasuredClaim(input, options.committerId),
|
|
290
|
-
gate
|
|
291
|
-
}));
|
|
292
|
-
const attempts = options.attempts ?? 3;
|
|
293
|
-
let lastCas;
|
|
294
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
295
|
-
const snapshot = await store.current();
|
|
296
|
-
try {
|
|
297
|
-
return await store.commit(ops, snapshot.version);
|
|
298
|
-
} catch (thrown) {
|
|
299
|
-
if (thrown instanceof KnowledgeCasError) {
|
|
300
|
-
lastCas = thrown;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
throw thrown;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
throw lastCas ?? /* @__PURE__ */ new Error("commitEvalMeasured: unreachable");
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* The canary fingerprint (M11-T04; OQ-06). The optional compensation for silent alias
|
|
310
|
-
* re-pointing that modelEpoch honestly cannot catch: a FIXED probe set,
|
|
311
|
-
* run through the ordinary engine (journaled, budgeted,
|
|
312
|
-
* VCR-recordable), hashed over normalized outputs. Sampling parameters
|
|
313
|
-
* are not pinned; drift detection rests on the fixed prompts, the
|
|
314
|
-
* normalization, and exact fingerprint comparison. A fingerprint change
|
|
315
|
-
* flips the model's eval claims to stale in one command.
|
|
316
|
-
*
|
|
317
|
-
* The committed v1 design (closing OQ-06): the probe set is CALLER
|
|
318
|
-
* data (fixed, versioned alongside the store); normalization is NFC,
|
|
319
|
-
* trim, and whitespace collapse per output; the fingerprint is the
|
|
320
|
-
* sha256 of the JCS-serialized normalized output array, prefixed with
|
|
321
|
-
* the probe count so a probe-set edit never collides with drift.
|
|
322
|
-
*/
|
|
323
|
-
/** The committed v1 normalization (OQ-06): NFC, trim, collapse whitespace. */
|
|
324
|
-
function normalizeCanaryOutput(output) {
|
|
325
|
-
return (typeof output === "string" ? output : JSON.stringify(output ?? null)).normalize("NFC").trim().replace(/\s+/gu, " ");
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Runs the fixed probe set through the ordinary engine and returns the
|
|
329
|
-
* fingerprint. Probes run sequentially in declaration order, one run
|
|
330
|
-
* per probe, so recordings replay deterministically.
|
|
331
|
-
*/
|
|
332
|
-
async function canaryFingerprint(engine, probes) {
|
|
333
|
-
const outputs = [];
|
|
334
|
-
for (const [index, prompt] of probes.prompts.entries()) {
|
|
335
|
-
const workflow = defineWorkflow({ name: `kb-canary:${String(index)}` }, async (ctx) => await ctx.agent(prompt, { agentType: probes.agentType }));
|
|
336
|
-
const outcome = await engine.run(workflow, null).result;
|
|
337
|
-
outputs.push(outcome.status === "ok" ? normalizeCanaryOutput(outcome.value) : `!${outcome.status}`);
|
|
338
|
-
}
|
|
339
|
-
const body = JSON.stringify([probes.prompts.length, outputs]);
|
|
340
|
-
return createHash("sha256").update(body, "utf8").digest("hex");
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Flips the model's ACTIVE eval-measured claims to stale when their
|
|
344
|
-
* recorded canary fingerprint differs from the fresh one. Claims
|
|
345
|
-
* without a recorded fingerprint have no baseline and
|
|
346
|
-
* stay untouched (the documented no-probe posture); a second run is
|
|
347
|
-
* an idempotent noop. CAS-rebased like every maintenance commit.
|
|
348
|
-
*/
|
|
349
|
-
async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
|
|
350
|
-
const attempts = options?.attempts ?? 3;
|
|
351
|
-
let lastCas;
|
|
352
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
353
|
-
const snapshot = await store.current();
|
|
354
|
-
const drifted = snapshot.claims.filter((claim) => claim.status === "active" && claim.class === "eval-measured" && claim.subject.model === model && claim.modelEpoch?.canaryFingerprint !== void 0 && claim.modelEpoch.canaryFingerprint !== freshFingerprint);
|
|
355
|
-
if (drifted.length === 0) return {
|
|
356
|
-
model,
|
|
357
|
-
freshFingerprint,
|
|
358
|
-
flipped: []
|
|
359
|
-
};
|
|
360
|
-
const ops = drifted.map((claim) => ({
|
|
361
|
-
op: "mark_stale",
|
|
362
|
-
claimId: claim.id,
|
|
363
|
-
reason: "canary-drift"
|
|
364
|
-
}));
|
|
365
|
-
try {
|
|
366
|
-
const version = await store.commit(ops, snapshot.version);
|
|
367
|
-
return {
|
|
368
|
-
model,
|
|
369
|
-
freshFingerprint,
|
|
370
|
-
flipped: drifted.map((claim) => claim.id),
|
|
371
|
-
version
|
|
372
|
-
};
|
|
373
|
-
} catch (thrown) {
|
|
374
|
-
if (thrown instanceof KnowledgeCasError) {
|
|
375
|
-
lastCas = thrown;
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
throw thrown;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
throw lastCas ?? /* @__PURE__ */ new Error("flipStaleOnCanaryDrift: unreachable");
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* The phases 1-2 measured-value checkpoint (M12-T01; the quantitative
|
|
385
|
-
* criteria of OQ-09, closed at M11-T06). The M12 gate: kb_propose
|
|
386
|
-
* and the proposal loop ship ONLY if the knowledge card demonstrably
|
|
387
|
-
* improves tier and agentType selection on eval cases.
|
|
388
|
-
*
|
|
389
|
-
* Two experiments, both A/B under identical fixed pools:
|
|
390
|
-
*
|
|
391
|
-
* 1. RUNG SELECTION, per (ladder, taskClass) cell: the baseline arm
|
|
392
|
-
* runs every eval case at the ladder's DEFAULT start tier; the
|
|
393
|
-
* treatment arm runs at the tier recommended by
|
|
394
|
-
* compileVerifiedLayer over the store's claims (default when no
|
|
395
|
-
* recommendation). A cell passes when the treatment reaches a pass
|
|
396
|
-
* rate at least equal to the baseline at no more than 90 percent
|
|
397
|
-
* of its cost, OR at least 5 points above it at no more than its
|
|
398
|
-
* cost. Criterion 1 holds when a MAJORITY of cells pass AND the
|
|
399
|
-
* pooled aggregate passes the same rule.
|
|
400
|
-
*
|
|
401
|
-
* 2. AGENTTYPE SELECTION, pooled: the same orchestrate-role cases run
|
|
402
|
-
* with and without the knowledge store configured (the card docks
|
|
403
|
-
* into the spawn tool description when configured). Criterion 2
|
|
404
|
-
* holds when the card-informed arm matches or beats the baseline
|
|
405
|
-
* pass rate at no more than 105 percent of its cost, OR beats it
|
|
406
|
-
* by at least 15 points at no more than 115 percent of its cost
|
|
407
|
-
* (the quality branch; OQ-09 as amended 2026-07-12: the baseline
|
|
408
|
-
* fails CHEAPLY, so the flat cost bar tightened exactly when the
|
|
409
|
-
* card was winning on quality).
|
|
410
|
-
*
|
|
411
|
-
* The checkpoint PASSES only when both criteria hold. Methodology
|
|
412
|
-
* guard: the claims the treatment consumes MUST come from a seeding
|
|
413
|
-
* sweep over a DISJOINT case set (the seed/eval split is the caller's
|
|
414
|
-
* pool contract), or the measurement is leakage.
|
|
415
|
-
*/
|
|
416
|
-
/** IEEE754 guard for the rule boundaries (0.8 + 0.05 exceeds 0.85). */
|
|
417
|
-
const EPSILON = 1e-9;
|
|
418
|
-
/**
|
|
419
|
-
* The OQ-09 criterion 2 rule (as amended 2026-07-12): match-or-beat at
|
|
420
|
-
* 105 percent of baseline cost, OR at least 15 points better at 115
|
|
421
|
-
* percent (the quality branch: the baseline fails cheaply, so the flat
|
|
422
|
-
* bar tightened exactly when the card won on quality). The vacuous-pass
|
|
423
|
-
* guard stays with the caller.
|
|
424
|
-
*/
|
|
425
|
-
function agentTypeRuleHolds(baseline, informed) {
|
|
426
|
-
const matchesCheaply = informed.passRate >= baseline.passRate - EPSILON && informed.totalCostUsd <= 1.05 * baseline.totalCostUsd + EPSILON;
|
|
427
|
-
const clearlyBetterNearCost = informed.passRate >= baseline.passRate + .15 - EPSILON && informed.totalCostUsd <= 1.15 * baseline.totalCostUsd + EPSILON;
|
|
428
|
-
return matchesCheaply || clearlyBetterNearCost;
|
|
429
|
-
}
|
|
430
|
-
/** The OQ-09 cell rule (shared by the per-cell and pooled verdicts). */
|
|
431
|
-
function rungRuleHolds(baseline, treatment) {
|
|
432
|
-
const equalOrBetterCheaper = treatment.passRate >= baseline.passRate - EPSILON && treatment.totalCostUsd <= .9 * baseline.totalCostUsd + EPSILON;
|
|
433
|
-
const clearlyBetterAtCost = treatment.passRate >= baseline.passRate + .05 - EPSILON && treatment.totalCostUsd <= baseline.totalCostUsd + EPSILON;
|
|
434
|
-
return equalOrBetterCheaper || clearlyBetterAtCost;
|
|
435
|
-
}
|
|
436
|
-
function armOf(suite) {
|
|
437
|
-
return {
|
|
438
|
-
passRate: suite.passRate,
|
|
439
|
-
totalCostUsd: suite.totalCostUsd,
|
|
440
|
-
n: suite.results.length
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function pool(arms) {
|
|
444
|
-
const n = arms.reduce((sum, arm) => sum + arm.n, 0);
|
|
445
|
-
const passed = arms.reduce((sum, arm) => sum + arm.passRate * arm.n, 0);
|
|
446
|
-
const cost = arms.reduce((sum, arm) => sum + arm.totalCostUsd, 0);
|
|
447
|
-
return {
|
|
448
|
-
passRate: n === 0 ? 0 : passed / n,
|
|
449
|
-
totalCostUsd: cost,
|
|
450
|
-
n
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Runs the checkpoint over the fixed pool. Sequential in declaration
|
|
455
|
-
* order (deterministic cassette consumption when recorded); every cell
|
|
456
|
-
* runs baseline then treatment.
|
|
457
|
-
*/
|
|
458
|
-
async function runValueCheckpoint(checkpointPool, options) {
|
|
459
|
-
const recommendations = compileVerifiedLayer(options.snapshot.claims.filter((claim) => claim.status === "active"), checkpointPool.ladders);
|
|
460
|
-
const byTaskClass = /* @__PURE__ */ new Map();
|
|
461
|
-
for (const entry of checkpointPool.evalCases) {
|
|
462
|
-
const bucket = byTaskClass.get(entry.taskClass) ?? [];
|
|
463
|
-
bucket.push(entry.case);
|
|
464
|
-
byTaskClass.set(entry.taskClass, bucket);
|
|
465
|
-
}
|
|
466
|
-
const cells = [];
|
|
467
|
-
for (const ladder of checkpointPool.ladders) for (const [taskClass, cases] of byTaskClass) {
|
|
468
|
-
const recommendation = recommendations.find((row) => row.ladder === ladder.name && row.taskClass === taskClass);
|
|
469
|
-
const treatmentTier = recommendation?.recommendedTier ?? ladder.startTier;
|
|
470
|
-
const baseMember = ladder.rungs[ladder.startTier];
|
|
471
|
-
const treatMember = ladder.rungs[treatmentTier];
|
|
472
|
-
if (baseMember === void 0 || treatMember === void 0) throw new Error(`checkpoint: ladder '${ladder.name}' lacks rung ${String(treatmentTier)}`);
|
|
473
|
-
const baseline = armOf(await runEvalSuite(await options.engineFor(baseMember), cases, options.suite ?? {}));
|
|
474
|
-
const treatment = treatmentTier === ladder.startTier ? baseline : armOf(await runEvalSuite(await options.engineFor(treatMember), cases, options.suite ?? {}));
|
|
475
|
-
cells.push({
|
|
476
|
-
ladder: ladder.name,
|
|
477
|
-
taskClass,
|
|
478
|
-
defaultTier: ladder.startTier,
|
|
479
|
-
treatmentTier,
|
|
480
|
-
recommended: recommendation !== void 0,
|
|
481
|
-
baseline,
|
|
482
|
-
treatment,
|
|
483
|
-
passed: rungRuleHolds(baseline, treatment)
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
const recommendedCells = cells.filter((cell) => cell.recommended);
|
|
487
|
-
const cellsPassed = recommendedCells.filter((cell) => cell.passed).length;
|
|
488
|
-
const majorityHolds = recommendedCells.length > 0 && cellsPassed * 2 > recommendedCells.length;
|
|
489
|
-
const pooledBaseline = pool(cells.map((cell) => cell.baseline));
|
|
490
|
-
const pooledTreatment = pool(cells.map((cell) => cell.treatment));
|
|
491
|
-
const pooledHolds = rungRuleHolds(pooledBaseline, pooledTreatment);
|
|
492
|
-
const criterion1 = {
|
|
493
|
-
cells,
|
|
494
|
-
cellsPassed,
|
|
495
|
-
majorityHolds,
|
|
496
|
-
pooledBaseline,
|
|
497
|
-
pooledTreatment,
|
|
498
|
-
pooledHolds,
|
|
499
|
-
passed: majorityHolds && pooledHolds
|
|
500
|
-
};
|
|
501
|
-
let criterion2;
|
|
502
|
-
if (options.orchestrateEngineFor !== void 0 && options.orchestratedCases !== void 0) {
|
|
503
|
-
const cases = options.orchestratedCases.map((entry) => entry.case);
|
|
504
|
-
const orchestratedSuite = options.orchestratedSuite ?? options.suite ?? {};
|
|
505
|
-
const baseline = armOf(await runEvalSuite(await options.orchestrateEngineFor(false), cases, orchestratedSuite));
|
|
506
|
-
const informed = armOf(await runEvalSuite(await options.orchestrateEngineFor(true), cases, orchestratedSuite));
|
|
507
|
-
criterion2 = {
|
|
508
|
-
baseline,
|
|
509
|
-
informed,
|
|
510
|
-
passed: informed.n > 0 && informed.passRate > 0 && agentTypeRuleHolds(baseline, informed)
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
return {
|
|
514
|
-
observedAt: options.observedAt,
|
|
515
|
-
criterion1,
|
|
516
|
-
...criterion2 === void 0 ? {} : { criterion2 },
|
|
517
|
-
passed: criterion1.passed && criterion2 !== void 0 && criterion2.passed
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
const percent = (rate) => `${(rate * 100).toFixed(1)}%`;
|
|
521
|
-
const usd = (value) => `$${value.toFixed(4)}`;
|
|
522
|
-
/** The deterministic render for the M12 gate docs amendment. */
|
|
523
|
-
function renderCheckpointReport(report) {
|
|
524
|
-
const lines = [
|
|
525
|
-
`Measured-value checkpoint (OQ-09) at ${report.observedAt}: ` + (report.passed ? "PASSED" : "FAILED"),
|
|
526
|
-
"",
|
|
527
|
-
`Criterion 1 (rung selection): ${report.criterion1.passed ? "holds" : "fails"} (${String(report.criterion1.cellsPassed)}/${String(report.criterion1.cells.length)} cells, pooled ${report.criterion1.pooledHolds ? "holds" : "fails"})`
|
|
528
|
-
];
|
|
529
|
-
for (const cell of report.criterion1.cells) lines.push(`* ${cell.ladder} :: ${cell.taskClass}: baseline tier ${String(cell.defaultTier)} ${percent(cell.baseline.passRate)} at ${usd(cell.baseline.totalCostUsd)}; treatment tier ${String(cell.treatmentTier)}${cell.recommended ? "" : " (no recommendation)"} ${percent(cell.treatment.passRate)} at ${usd(cell.treatment.totalCostUsd)}; ${cell.passed ? "pass" : "fail"} (n=${String(cell.baseline.n)})`);
|
|
530
|
-
if (report.criterion2 !== void 0) {
|
|
531
|
-
const c2 = report.criterion2;
|
|
532
|
-
lines.push("", `Criterion 2 (agentType selection): ${c2.passed ? "holds" : "fails"} (baseline ${percent(c2.baseline.passRate)} at ${usd(c2.baseline.totalCostUsd)}; card-informed ${percent(c2.informed.passRate)} at ${usd(c2.informed.totalCostUsd)}; n=${String(c2.baseline.n)})`);
|
|
533
|
-
} else lines.push("", "Criterion 2 (agentType selection): NOT MEASURED (counts as failed)");
|
|
534
|
-
return lines.join("\n");
|
|
535
|
-
}
|
|
536
|
-
const SWEEP_THRESHOLD_DEFAULTS = {
|
|
537
|
-
strength: .9,
|
|
538
|
-
weakness: .5
|
|
539
|
-
};
|
|
540
|
-
/** Deterministic claim id: report-scoped, readable, collision-free. */
|
|
541
|
-
function claimIdOf(reportId, member, taskClass) {
|
|
542
|
-
const effort = member.effort === void 0 ? "" : `@${member.effort}`;
|
|
543
|
-
return `${reportId}/${member.model}${effort}/${taskClass}`;
|
|
544
|
-
}
|
|
545
|
-
/** The typed statement template: never a quote from tool output. */
|
|
546
|
-
function statementOf(cell, polarity) {
|
|
547
|
-
const rate = cell.passRate.toFixed(2);
|
|
548
|
-
const band = polarity === "strength" ? "at or above the strength band" : "in the weakness band";
|
|
549
|
-
return `sweep passRate ${rate} over ${String(cell.n)} ${cell.taskClass} case${cell.n === 1 ? "" : "s"}: ${band}`;
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Runs the fixed matrix sequentially in declaration order
|
|
553
|
-
* (deterministic cassette consumption), aggregates per (model,
|
|
554
|
-
* taskClass) cell, emits threshold-crossing claims, and commits them
|
|
555
|
-
* through the eval-committer identity when a store is given.
|
|
556
|
-
*/
|
|
557
|
-
async function runSweepMatrix(pool, options) {
|
|
558
|
-
const thresholds = {
|
|
559
|
-
...SWEEP_THRESHOLD_DEFAULTS,
|
|
560
|
-
...options.thresholds
|
|
561
|
-
};
|
|
562
|
-
const byTaskClass = /* @__PURE__ */ new Map();
|
|
563
|
-
for (const entry of pool.cases) {
|
|
564
|
-
const bucket = byTaskClass.get(entry.taskClass) ?? [];
|
|
565
|
-
bucket.push(entry);
|
|
566
|
-
byTaskClass.set(entry.taskClass, bucket);
|
|
567
|
-
}
|
|
568
|
-
const cells = [];
|
|
569
|
-
const claims = [];
|
|
570
|
-
for (const member of pool.models) {
|
|
571
|
-
const engine = await options.engineFor(member);
|
|
572
|
-
for (const [taskClass, bucket] of byTaskClass) {
|
|
573
|
-
const suite = await runEvalSuite(engine, bucket.map((entry) => entry.case), options.suite ?? {});
|
|
574
|
-
const cell = {
|
|
575
|
-
model: member.model,
|
|
576
|
-
...member.effort === void 0 ? {} : { effort: member.effort },
|
|
577
|
-
taskClass,
|
|
578
|
-
passRate: suite.passRate,
|
|
579
|
-
n: suite.results.length,
|
|
580
|
-
totalCostUsd: suite.totalCostUsd,
|
|
581
|
-
caseNames: suite.results.map((result) => result.name)
|
|
582
|
-
};
|
|
583
|
-
cells.push(cell);
|
|
584
|
-
const polarity = cell.passRate >= thresholds.strength ? "strength" : cell.passRate <= thresholds.weakness ? "weakness" : void 0;
|
|
585
|
-
if (polarity !== void 0 && cell.n > 0) {
|
|
586
|
-
const epoch = options.modelEpochFor?.(member);
|
|
587
|
-
claims.push({
|
|
588
|
-
id: claimIdOf(options.reportId, member, taskClass),
|
|
589
|
-
subject: {
|
|
590
|
-
model: member.model,
|
|
591
|
-
...member.effort === void 0 ? {} : { effort: member.effort }
|
|
592
|
-
},
|
|
593
|
-
taskClass,
|
|
594
|
-
polarity,
|
|
595
|
-
statement: statementOf(cell, polarity),
|
|
596
|
-
metrics: {
|
|
597
|
-
passRate: cell.passRate,
|
|
598
|
-
n: cell.n,
|
|
599
|
-
graderId: "eval-suite"
|
|
600
|
-
},
|
|
601
|
-
confidence: cell.n >= 20 ? "high" : cell.n >= 5 ? "medium" : "low",
|
|
602
|
-
observedAt: options.observedAt,
|
|
603
|
-
evidence: [{
|
|
604
|
-
kind: "eval",
|
|
605
|
-
reportId: options.reportId,
|
|
606
|
-
caseIds: cell.caseNames
|
|
607
|
-
}],
|
|
608
|
-
...epoch === void 0 ? {} : { modelEpoch: epoch }
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
const report = {
|
|
614
|
-
reportId: options.reportId,
|
|
615
|
-
observedAt: options.observedAt,
|
|
616
|
-
cells,
|
|
617
|
-
claims
|
|
618
|
-
};
|
|
619
|
-
if (options.store !== void 0 && claims.length > 0) report.committedVersion = await commitEvalMeasured(options.store, claims, {
|
|
620
|
-
committerId: options.committerId,
|
|
621
|
-
reportId: options.reportId
|
|
622
|
-
});
|
|
623
|
-
return report;
|
|
624
|
-
}
|
|
625
|
-
//#endregion
|
|
626
|
-
export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
|