@rulvar/evals 1.16.2 → 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/index.d.ts CHANGED
@@ -1,5 +1,39 @@
1
1
  import { CompiledWorkflow, DeclaredLadder, Effort, Engine, EvidenceRef, Json, JsonSchema, KnowledgeSnapshot, ModelClaim, ModelKnowledgeStore, ModelRef, ModelSpec, RunOutcome, SchemaSpec, TaskClass, Usage, WireError, Workflow } from "@rulvar/core";
2
2
 
3
+ //#region src/envelope.d.ts
4
+ /** Thrown when authorizing a run's ceiling would exceed the envelope. */
5
+ declare class SweepBudgetError extends Error {
6
+ /** What was about to start, e.g. `eval target 'sweep-math'`. */
7
+ readonly runLabel: string;
8
+ /** The per-run ceiling that did not fit. */
9
+ readonly ceilingUsd: number;
10
+ /** Total already authorized before this refusal. */
11
+ readonly authorizedUsd: number;
12
+ readonly maxTotalUsd: number;
13
+ constructor(runLabel: string, ceilingUsd: number, authorizedUsd: number, maxTotalUsd: number);
14
+ }
15
+ /**
16
+ * One envelope bounds one whole sweep invocation: share the instance
17
+ * across the canary loop and runSweepMatrix so canary, target, and
18
+ * judge runs all draw from the same remainder.
19
+ */
20
+ declare class SpendEnvelope {
21
+ readonly maxTotalUsd: number;
22
+ private readonly maxMicroUsd;
23
+ private authorizedMicroUsd;
24
+ constructor(maxTotalUsd: number);
25
+ /** Total authorized so far (debit-only; never decreases). */
26
+ get authorizedUsd(): number;
27
+ get remainingUsd(): number;
28
+ /**
29
+ * Authorizes one run's immutable ceiling or throws SweepBudgetError.
30
+ * An unbounded run cannot be authorized: under an envelope every run
31
+ * MUST carry an explicit positive ceiling, otherwise the aggregate
32
+ * bound would be unaccountable.
33
+ */
34
+ authorize(ceilingUsd: number | undefined, runLabel: string): void;
35
+ }
36
+ //#endregion
3
37
  //#region src/case.d.ts
4
38
  /**
5
39
  * One quality-measurement case. The shape is the
@@ -79,6 +113,13 @@ interface RunEvalCaseOptions {
79
113
  budgetUsd?: number;
80
114
  /** Run ceiling for each judge run. */
81
115
  judgeBudgetUsd?: number;
116
+ /**
117
+ * Aggregate debit-only envelope (v1.16.2 review P1-2): every target
118
+ * and judge run authorizes its ceiling here BEFORE starting, and an
119
+ * envelope requires the matching per-run ceiling to be set. A
120
+ * refusal throws SweepBudgetError before any provider work.
121
+ */
122
+ envelope?: SpendEnvelope;
82
123
  }
83
124
  /** Thrown when a judge run does not settle ok. */
84
125
  declare class EvalJudgeError extends Error {
@@ -105,6 +146,8 @@ interface EvalSuiteResult {
105
146
  interface RunEvalSuiteOptions {
106
147
  budgetUsd?: number;
107
148
  judgeBudgetUsd?: number;
149
+ /** See RunEvalCaseOptions.envelope; shared across every case of the suite. */
150
+ envelope?: SpendEnvelope;
108
151
  }
109
152
  /**
110
153
  * Runs cases sequentially (deterministic journal and cassette order) and
@@ -232,14 +275,52 @@ interface CanaryProbeSet {
232
275
  /** The fixed prompts; order matters and enters the fingerprint. */
233
276
  prompts: string[];
234
277
  }
278
+ interface CanaryRunOptions {
279
+ /**
280
+ * Immutable ceiling per probe run (v1.16.2 review P1-2): every probe
281
+ * is an ordinary paid engine run and gets its own recorded
282
+ * RunMeta.budgetUsd.
283
+ */
284
+ budgetUsd?: number;
285
+ /**
286
+ * Aggregate debit-only envelope shared with the surrounding sweep;
287
+ * each probe authorizes budgetUsd BEFORE running, and an envelope
288
+ * requires budgetUsd to be set.
289
+ */
290
+ envelope?: SpendEnvelope;
291
+ }
292
+ interface CanaryReport {
293
+ fingerprint: string;
294
+ /**
295
+ * True only when every probe settled ok. A fingerprint containing a
296
+ * non-ok probe status is a measurement artifact (budget exhaustion,
297
+ * transient provider failure), NOT evidence of model drift: never
298
+ * feed it to flipStaleOnCanaryDrift.
299
+ */
300
+ allOk: boolean;
301
+ probes: Array<{
302
+ prompt: string;
303
+ status: RunOutcome<unknown>["status"];
304
+ }>;
305
+ }
235
306
  /** The committed v1 normalization (OQ-06): NFC, trim, collapse whitespace. */
236
307
  declare function normalizeCanaryOutput(output: unknown): string;
237
308
  /**
238
- * Runs the fixed probe set through the ordinary engine and returns the
239
- * fingerprint. Probes run sequentially in declaration order, one run
240
- * per probe, so recordings replay deterministically.
309
+ * Runs the fixed probe set through the ordinary engine. Probes run
310
+ * sequentially in declaration order, one run per probe, so recordings
311
+ * replay deterministically. Each probe run carries the optional
312
+ * immutable ceiling (options.budgetUsd) and authorizes it against the
313
+ * optional envelope before starting. A non-ok probe enters the
314
+ * fingerprint as `!status` and clears allOk: callers gate drift
315
+ * flipping on allOk, because a budget-starved or transiently failing
316
+ * probe fingerprints differently without the model having drifted.
317
+ */
318
+ declare function runCanary(engine: Engine, probes: CanaryProbeSet, options?: CanaryRunOptions): Promise<CanaryReport>;
319
+ /**
320
+ * The fingerprint alone (the pre-v1.16.2-review surface, kept
321
+ * compatible). Prefer runCanary: its allOk is the drift-flip gate.
241
322
  */
242
- declare function canaryFingerprint(engine: Engine, probes: CanaryProbeSet): Promise<string>;
323
+ declare function canaryFingerprint(engine: Engine, probes: CanaryProbeSet, options?: CanaryRunOptions): Promise<string>;
243
324
  interface CanaryDriftReport {
244
325
  model: ModelRef;
245
326
  freshFingerprint: string;
@@ -253,7 +334,13 @@ interface CanaryDriftReport {
253
334
  * recorded canary fingerprint differs from the fresh one. Claims
254
335
  * without a recorded fingerprint have no baseline and
255
336
  * stay untouched (the documented no-probe posture); a second run is
256
- * an idempotent noop. CAS-rebased like every maintenance commit.
337
+ * an idempotent noop. CAS-rebased like every maintenance commit; the
338
+ * retries run no engine work and pay nothing.
339
+ *
340
+ * Only pass fingerprints from an allOk probe set (runCanary): a
341
+ * fingerprint containing a `!status` probe differs from any healthy
342
+ * baseline by construction, and flipping on it would blame the model
343
+ * for a budget ceiling or a transient provider failure.
257
344
  */
258
345
  declare function flipStaleOnCanaryDrift(store: ModelKnowledgeStore, model: ModelRef, freshFingerprint: string, options?: {
259
346
  attempts?: number;
@@ -297,6 +384,17 @@ interface RunSweepOptions {
297
384
  thresholds?: Partial<SweepThresholds>;
298
385
  /** Passed through to every suite run (budget, VCR hooks ride the engine). */
299
386
  suite?: RunEvalSuiteOptions;
387
+ /**
388
+ * Aggregate debit-only envelope over the WHOLE matrix (v1.16.2
389
+ * review P1-2): every target and judge run authorizes its immutable
390
+ * ceiling before starting, so the pool times cases times judge-call
391
+ * product cannot exceed it, falsification pool growth included. An
392
+ * envelope requires suite.budgetUsd (and suite.judgeBudgetUsd once a
393
+ * grader judges); a cell refused by the envelope lands in the report
394
+ * as envelopeExhausted with NO claim. Share the instance with the
395
+ * canary loop so probes draw from the same remainder.
396
+ */
397
+ envelope?: SpendEnvelope;
300
398
  /** When given, emitted claims commit through the committer identity. */
301
399
  store?: ModelKnowledgeStore;
302
400
  /**
@@ -313,6 +411,20 @@ interface SweepCellReport {
313
411
  n: number;
314
412
  totalCostUsd: number;
315
413
  caseNames: string[];
414
+ /**
415
+ * Count of case results whose TARGET run settled 'exhausted' (its
416
+ * per-run ceiling, not the envelope). A budget-starved measurement
417
+ * must not become a model belief, so any exhausted target suppresses
418
+ * the cell's claim even when the degraded passRate crosses a
419
+ * threshold: the alternative is committing a false weakness that
420
+ * blames the model for the ceiling.
421
+ */
422
+ exhaustedRuns?: number;
423
+ /**
424
+ * The aggregate envelope refused a run of this cell before it
425
+ * started; stats cover nothing reliable and the cell emits no claim.
426
+ */
427
+ envelopeExhausted?: true;
316
428
  }
317
429
  interface SweepReport {
318
430
  reportId: string;
@@ -417,4 +529,4 @@ declare function runValueCheckpoint(checkpointPool: CheckpointPool, options: Run
417
529
  /** The deterministic render for the M12 gate docs amendment. */
418
530
  declare function renderCheckpointReport(report: CheckpointReport): string;
419
531
  //#endregion
420
- export { type CanaryDriftReport, type CanaryProbeSet, type CheckpointArm, type CheckpointCell, type CheckpointLadder, type CheckpointPool, type CheckpointReport, type CriterionOneReport, type CriterionTwoReport, type EvalCase, type EvalCaseResult, type EvalCommitterOptions, EvalJudgeError, type EvalMatrixReport, type EvalSuiteResult, type GoldenGraderOptions, type Grader, type GraderContext, type GraderVerdict, JUDGE_VERDICT_SCHEMA, type JudgeGraderOptions, type JudgeSpec, type MatrixCell, type MatrixCellReport, type MeasuredClaimInput, type OrchestratedCase, type RubricCriterion, type RubricGraderOptions, type RunCheckpointOptions, type RunEvalCaseOptions, type RunEvalSuiteOptions, type RunSweepOptions, SWEEP_THRESHOLD_DEFAULTS, type SweepCase, type SweepCellReport, type SweepModel, type SweepPool, type SweepReport, type SweepThresholds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
532
+ export { type CanaryDriftReport, type CanaryProbeSet, type CanaryReport, type CanaryRunOptions, type CheckpointArm, type CheckpointCell, type CheckpointLadder, type CheckpointPool, type CheckpointReport, type CriterionOneReport, type CriterionTwoReport, type EvalCase, type EvalCaseResult, type EvalCommitterOptions, EvalJudgeError, type EvalMatrixReport, type EvalSuiteResult, type GoldenGraderOptions, type Grader, type GraderContext, type GraderVerdict, JUDGE_VERDICT_SCHEMA, type JudgeGraderOptions, type JudgeSpec, type MatrixCell, type MatrixCellReport, type MeasuredClaimInput, type OrchestratedCase, type RubricCriterion, type RubricGraderOptions, type RunCheckpointOptions, type RunEvalCaseOptions, type RunEvalSuiteOptions, type RunSweepOptions, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, type SweepCase, type SweepCellReport, type SweepModel, type SweepPool, type SweepReport, type SweepThresholds, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
1
+ import { ConfigError, KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
2
2
  import { createHash } from "node:crypto";
3
3
  //#region src/case.ts
4
4
  /**
@@ -32,6 +32,7 @@ var EvalJudgeError = class extends Error {
32
32
  async function runEvalCase(engine, evalCase, options = {}) {
33
33
  const name = options.name ?? evalCase.workflow.name;
34
34
  const timing = {};
35
+ options.envelope?.authorize(options.budgetUsd, `eval target '${name}'`);
35
36
  const handle = engine.run(evalCase.workflow, evalCase.args, {
36
37
  name: `eval:${name}`,
37
38
  ...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
@@ -53,6 +54,7 @@ async function runEvalCase(engine, evalCase, options = {}) {
53
54
  async judge(spec) {
54
55
  const ordinal = judgeOrdinal;
55
56
  judgeOrdinal += 1;
57
+ options.envelope?.authorize(options.judgeBudgetUsd, `eval judge '${name}:${String(ordinal)}'`);
56
58
  const judged = await runJudge(engine, `${name}:${ordinal}`, spec, options.judgeBudgetUsd);
57
59
  judgeCostUsd += judged.costUsd;
58
60
  return judged.output;
@@ -109,7 +111,8 @@ async function runEvalSuite(engine, cases, options = {}) {
109
111
  results.push(await runEvalCase(engine, evalCase, {
110
112
  name,
111
113
  ...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd },
112
- ...options.judgeBudgetUsd === void 0 ? {} : { judgeBudgetUsd: options.judgeBudgetUsd }
114
+ ...options.judgeBudgetUsd === void 0 ? {} : { judgeBudgetUsd: options.judgeBudgetUsd },
115
+ ...options.envelope === void 0 ? {} : { envelope: options.envelope }
113
116
  }));
114
117
  }
115
118
  return {
@@ -337,26 +340,54 @@ function normalizeCanaryOutput(output) {
337
340
  return (typeof output === "string" ? output : JSON.stringify(output ?? null)).normalize("NFC").trim().replace(/\s+/gu, " ");
338
341
  }
339
342
  /**
340
- * Runs the fixed probe set through the ordinary engine and returns the
341
- * fingerprint. Probes run sequentially in declaration order, one run
342
- * per probe, so recordings replay deterministically.
343
+ * Runs the fixed probe set through the ordinary engine. Probes run
344
+ * sequentially in declaration order, one run per probe, so recordings
345
+ * replay deterministically. Each probe run carries the optional
346
+ * immutable ceiling (options.budgetUsd) and authorizes it against the
347
+ * optional envelope before starting. A non-ok probe enters the
348
+ * fingerprint as `!status` and clears allOk: callers gate drift
349
+ * flipping on allOk, because a budget-starved or transiently failing
350
+ * probe fingerprints differently without the model having drifted.
343
351
  */
344
- async function canaryFingerprint(engine, probes) {
352
+ async function runCanary(engine, probes, options = {}) {
345
353
  const outputs = [];
354
+ const probeReports = [];
346
355
  for (const [index, prompt] of probes.prompts.entries()) {
356
+ options.envelope?.authorize(options.budgetUsd, `canary probe ${String(index)}`);
347
357
  const workflow = defineWorkflow({ name: `kb-canary:${String(index)}` }, async (ctx) => await ctx.agent(prompt, { agentType: probes.agentType }));
348
- const outcome = await engine.run(workflow, null).result;
358
+ const outcome = await engine.run(workflow, null, options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }).result;
359
+ probeReports.push({
360
+ prompt,
361
+ status: outcome.status
362
+ });
349
363
  outputs.push(outcome.status === "ok" ? normalizeCanaryOutput(outcome.value) : `!${outcome.status}`);
350
364
  }
351
365
  const body = JSON.stringify([probes.prompts.length, outputs]);
352
- return createHash("sha256").update(body, "utf8").digest("hex");
366
+ return {
367
+ fingerprint: createHash("sha256").update(body, "utf8").digest("hex"),
368
+ allOk: probeReports.every((probe) => probe.status === "ok"),
369
+ probes: probeReports
370
+ };
371
+ }
372
+ /**
373
+ * The fingerprint alone (the pre-v1.16.2-review surface, kept
374
+ * compatible). Prefer runCanary: its allOk is the drift-flip gate.
375
+ */
376
+ async function canaryFingerprint(engine, probes, options = {}) {
377
+ return (await runCanary(engine, probes, options)).fingerprint;
353
378
  }
354
379
  /**
355
380
  * Flips the model's ACTIVE eval-measured claims to stale when their
356
381
  * recorded canary fingerprint differs from the fresh one. Claims
357
382
  * without a recorded fingerprint have no baseline and
358
383
  * stay untouched (the documented no-probe posture); a second run is
359
- * an idempotent noop. CAS-rebased like every maintenance commit.
384
+ * an idempotent noop. CAS-rebased like every maintenance commit; the
385
+ * retries run no engine work and pay nothing.
386
+ *
387
+ * Only pass fingerprints from an allOk probe set (runCanary): a
388
+ * fingerprint containing a `!status` probe differs from any healthy
389
+ * baseline by construction, and flipping on it would blame the model
390
+ * for a budget ceiling or a transient provider failure.
360
391
  */
361
392
  async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
362
393
  const attempts = options?.attempts ?? 3;
@@ -393,6 +424,82 @@ async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
393
424
  throw lastCas ?? /* @__PURE__ */ new Error("flipStaleOnCanaryDrift: unreachable");
394
425
  }
395
426
  //#endregion
427
+ //#region src/envelope.ts
428
+ /**
429
+ * The debit-only aggregate spend envelope (v1.16.2 review P1-2). A
430
+ * sweep multiplies paid runs: pool members times cases for targets,
431
+ * one judge run per GraderContext.judge call, one canary run per
432
+ * probe per member, and the falsification union can grow the pool
433
+ * beyond the config. Per-run ceilings alone do not bound that
434
+ * product, so the envelope authorizes each run's IMMUTABLE ceiling
435
+ * BEFORE the run starts: a run whose ceiling does not fit the
436
+ * remainder is refused before any provider work.
437
+ *
438
+ * Authorizations are never returned: not when a run completes under
439
+ * its ceiling, not on CAS retries (they run no paid work), and not on
440
+ * replay. A replayed run authorizes exactly like a fresh one and then
441
+ * spends nothing; the envelope bounds the authorized worst case, not
442
+ * the observed spend, so replay never double-PAYS anything while the
443
+ * accounting stays one-directional. The envelope lives for one
444
+ * invocation and is never persisted.
445
+ *
446
+ * Accounting is integer micro-USD, so exact fits pass: 0.1 + 0.2
447
+ * against a 0.3 envelope is a fit, not a float rejection.
448
+ */
449
+ const MICRO = 1e6;
450
+ /** Thrown when authorizing a run's ceiling would exceed the envelope. */
451
+ var SweepBudgetError = class extends Error {
452
+ /** What was about to start, e.g. `eval target 'sweep-math'`. */
453
+ runLabel;
454
+ /** The per-run ceiling that did not fit. */
455
+ ceilingUsd;
456
+ /** Total already authorized before this refusal. */
457
+ authorizedUsd;
458
+ maxTotalUsd;
459
+ constructor(runLabel, ceilingUsd, authorizedUsd, maxTotalUsd) {
460
+ super(`sweep envelope exhausted: authorizing $${String(ceilingUsd)} for ${runLabel} would exceed maxTotalUsd $${String(maxTotalUsd)} ($${String(authorizedUsd)} already authorized); the run was refused before any provider call`);
461
+ this.name = "SweepBudgetError";
462
+ this.runLabel = runLabel;
463
+ this.ceilingUsd = ceilingUsd;
464
+ this.authorizedUsd = authorizedUsd;
465
+ this.maxTotalUsd = maxTotalUsd;
466
+ }
467
+ };
468
+ /**
469
+ * One envelope bounds one whole sweep invocation: share the instance
470
+ * across the canary loop and runSweepMatrix so canary, target, and
471
+ * judge runs all draw from the same remainder.
472
+ */
473
+ var SpendEnvelope = class {
474
+ maxTotalUsd;
475
+ maxMicroUsd;
476
+ authorizedMicroUsd = 0;
477
+ constructor(maxTotalUsd) {
478
+ if (!Number.isFinite(maxTotalUsd) || maxTotalUsd <= 0) throw new ConfigError(`SpendEnvelope maxTotalUsd must be a positive finite number, got ${String(maxTotalUsd)}`);
479
+ this.maxTotalUsd = maxTotalUsd;
480
+ this.maxMicroUsd = Math.round(maxTotalUsd * MICRO);
481
+ }
482
+ /** Total authorized so far (debit-only; never decreases). */
483
+ get authorizedUsd() {
484
+ return this.authorizedMicroUsd / MICRO;
485
+ }
486
+ get remainingUsd() {
487
+ return Math.max(0, this.maxMicroUsd - this.authorizedMicroUsd) / MICRO;
488
+ }
489
+ /**
490
+ * Authorizes one run's immutable ceiling or throws SweepBudgetError.
491
+ * An unbounded run cannot be authorized: under an envelope every run
492
+ * MUST carry an explicit positive ceiling, otherwise the aggregate
493
+ * bound would be unaccountable.
494
+ */
495
+ authorize(ceilingUsd, runLabel) {
496
+ if (ceilingUsd === void 0 || !Number.isFinite(ceilingUsd) || ceilingUsd <= 0) throw new ConfigError(`the spend envelope requires an explicit positive per-run ceiling for ${runLabel}; got ${String(ceilingUsd)} (an unbounded run under an aggregate envelope would be unaccountable)`);
497
+ const micro = Math.round(ceilingUsd * MICRO);
498
+ if (this.authorizedMicroUsd + micro > this.maxMicroUsd) throw new SweepBudgetError(runLabel, ceilingUsd, this.authorizedUsd, this.maxTotalUsd);
499
+ this.authorizedMicroUsd += micro;
500
+ }
501
+ };
502
+ //#endregion
396
503
  //#region src/checkpoint.ts
397
504
  /**
398
505
  * The phases 1-2 measured-value checkpoint (M12-T01; the quantitative
@@ -549,6 +656,16 @@ function renderCheckpointReport(report) {
549
656
  }
550
657
  //#endregion
551
658
  //#region src/sweeps.ts
659
+ /**
660
+ * Matrix sweeps (M11-T02). The deconfounder of the whole
661
+ * knowledge feature: a FIXED eval matrix (workflow x model x
662
+ * taskClass), independent of current routing, measured through the
663
+ * ordinary engine (journaled, budgeted, VCR-recordable), emitting
664
+ * eval-measured claims through the eval-committer identity.
665
+ *
666
+ * Sweep volume is never authorized by proposal volume: the model pool
667
+ * and the case list are EXPLICIT caller data (fixed pools only).
668
+ */
552
669
  const SWEEP_THRESHOLD_DEFAULTS = {
553
670
  strength: .9,
554
671
  weakness: .5
@@ -575,6 +692,7 @@ async function runSweepMatrix(pool, options) {
575
692
  ...SWEEP_THRESHOLD_DEFAULTS,
576
693
  ...options.thresholds
577
694
  };
695
+ if (options.envelope !== void 0 && options.suite?.budgetUsd === void 0) throw new ConfigError("runSweepMatrix: an aggregate envelope requires suite.budgetUsd (the per-target ceiling); unbounded targets under an envelope would be unaccountable");
578
696
  const byTaskClass = /* @__PURE__ */ new Map();
579
697
  for (const entry of pool.cases) {
580
698
  const bucket = byTaskClass.get(entry.taskClass) ?? [];
@@ -586,7 +704,27 @@ async function runSweepMatrix(pool, options) {
586
704
  for (const member of pool.models) {
587
705
  const engine = await options.engineFor(member);
588
706
  for (const [taskClass, bucket] of byTaskClass) {
589
- const suite = await runEvalSuite(engine, bucket.map((entry) => entry.case), options.suite ?? {});
707
+ let suite;
708
+ try {
709
+ suite = await runEvalSuite(engine, bucket.map((entry) => entry.case), {
710
+ ...options.suite ?? {},
711
+ ...options.envelope === void 0 ? {} : { envelope: options.envelope }
712
+ });
713
+ } catch (error) {
714
+ if (!(error instanceof SweepBudgetError)) throw error;
715
+ cells.push({
716
+ model: member.model,
717
+ ...member.effort === void 0 ? {} : { effort: member.effort },
718
+ taskClass,
719
+ passRate: 0,
720
+ n: 0,
721
+ totalCostUsd: 0,
722
+ caseNames: [],
723
+ envelopeExhausted: true
724
+ });
725
+ continue;
726
+ }
727
+ const exhaustedRuns = suite.results.filter((result) => result.status === "exhausted").length;
590
728
  const cell = {
591
729
  model: member.model,
592
730
  ...member.effort === void 0 ? {} : { effort: member.effort },
@@ -594,11 +732,12 @@ async function runSweepMatrix(pool, options) {
594
732
  passRate: suite.passRate,
595
733
  n: suite.results.length,
596
734
  totalCostUsd: suite.totalCostUsd,
597
- caseNames: suite.results.map((result) => result.name)
735
+ caseNames: suite.results.map((result) => result.name),
736
+ ...exhaustedRuns === 0 ? {} : { exhaustedRuns }
598
737
  };
599
738
  cells.push(cell);
600
739
  const polarity = cell.passRate >= thresholds.strength ? "strength" : cell.passRate <= thresholds.weakness ? "weakness" : void 0;
601
- if (polarity !== void 0 && cell.n > 0) {
740
+ if (polarity !== void 0 && cell.n > 0 && exhaustedRuns === 0) {
602
741
  const epoch = options.modelEpochFor?.(member);
603
742
  claims.push({
604
743
  id: claimIdOf(options.reportId, member, taskClass),
@@ -639,4 +778,4 @@ async function runSweepMatrix(pool, options) {
639
778
  return report;
640
779
  }
641
780
  //#endregion
642
- export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
781
+ export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, SpendEnvelope, SweepBudgetError, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runCanary, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/evals",
3
- "version": "1.16.2",
3
+ "version": "1.17.0",
4
4
  "description": "Rulvar evals: eval cases, golden outputs, rubric and judge graders, matrix sweeps, canary fingerprint.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,8 +22,8 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@rulvar/testing": "1.16.2",
26
- "@rulvar/core": "1.16.2"
25
+ "@rulvar/core": "1.17.0",
26
+ "@rulvar/testing": "1.17.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.0",