@rulvar/evals 1.0.0 → 1.1.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,4 +1,4 @@
1
- import { CompiledWorkflow, Effort, Engine, EvidenceRef, Json, JsonSchema, ModelClaim, ModelKnowledgeStore, ModelRef, ModelSpec, RunOutcome, SchemaSpec, TaskClass, Usage, WireError, Workflow } from "@rulvar/core";
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
3
  //#region src/case.d.ts
4
4
  /**
@@ -332,4 +332,90 @@ declare const SWEEP_THRESHOLD_DEFAULTS: SweepThresholds;
332
332
  */
333
333
  declare function runSweepMatrix(pool: SweepPool, options: RunSweepOptions): Promise<SweepReport>;
334
334
  //#endregion
335
- export { type CanaryDriftReport, type CanaryProbeSet, 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 RubricCriterion, type RubricGraderOptions, 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, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix };
335
+ //#region src/checkpoint.d.ts
336
+ /** One declared checkpoint ladder: rungs are concrete pool members. */
337
+ interface CheckpointLadder extends DeclaredLadder {
338
+ name: string;
339
+ startTier: number;
340
+ rungs: SweepModel[];
341
+ }
342
+ interface CheckpointPool {
343
+ ladders: CheckpointLadder[];
344
+ /** The measurement half; the seeding sweep MUST NOT have seen these. */
345
+ evalCases: SweepCase[];
346
+ }
347
+ interface OrchestratedCase {
348
+ /** The workflow drives an orchestrate-role run; graders judge its outcome. */
349
+ case: EvalCase;
350
+ }
351
+ interface RunCheckpointOptions {
352
+ /** The claims snapshot produced by the seeding sweep (disjoint cases). */
353
+ snapshot: KnowledgeSnapshot;
354
+ /** ISO date of the evaluation (recorded in the report; no wall clock inside). */
355
+ observedAt: string;
356
+ /** An engine per concrete pool member (the caller owns adapters and budgets). */
357
+ engineFor: (member: SweepModel) => Engine | Promise<Engine>;
358
+ /**
359
+ * Criterion 2 engines: withKnowledge true configures the SAME store
360
+ * snapshot behind stores.modelKnowledge; false omits it entirely.
361
+ */
362
+ orchestrateEngineFor?: (withKnowledge: boolean) => Engine | Promise<Engine>;
363
+ orchestratedCases?: OrchestratedCase[];
364
+ suite?: RunEvalSuiteOptions;
365
+ /**
366
+ * Orchestrated runs need room for the orchestrator cap math (the
367
+ * run ceiling must host the finalize reserve; docs/07, 12.2): their
368
+ * suite options default to `suite` but usually carry a larger
369
+ * budgetUsd.
370
+ */
371
+ orchestratedSuite?: RunEvalSuiteOptions;
372
+ }
373
+ interface CheckpointArm {
374
+ passRate: number;
375
+ totalCostUsd: number;
376
+ n: number;
377
+ }
378
+ interface CheckpointCell {
379
+ ladder: string;
380
+ taskClass: TaskClass;
381
+ defaultTier: number;
382
+ /** The tier the treatment arm ran at (default when no recommendation). */
383
+ treatmentTier: number;
384
+ recommended: boolean;
385
+ baseline: CheckpointArm;
386
+ treatment: CheckpointArm;
387
+ passed: boolean;
388
+ }
389
+ interface CriterionOneReport {
390
+ cells: CheckpointCell[];
391
+ cellsPassed: number;
392
+ majorityHolds: boolean;
393
+ pooledBaseline: CheckpointArm;
394
+ pooledTreatment: CheckpointArm;
395
+ pooledHolds: boolean;
396
+ passed: boolean;
397
+ }
398
+ interface CriterionTwoReport {
399
+ baseline: CheckpointArm;
400
+ informed: CheckpointArm;
401
+ passed: boolean;
402
+ }
403
+ interface CheckpointReport {
404
+ observedAt: string;
405
+ criterion1: CriterionOneReport;
406
+ criterion2?: CriterionTwoReport;
407
+ /** Both criteria (criterion 2 counts as failed when unmeasured). */
408
+ passed: boolean;
409
+ }
410
+ /** The OQ-09 cell rule (shared by the per-cell and pooled verdicts). */
411
+ declare function rungRuleHolds(baseline: CheckpointArm, treatment: CheckpointArm): boolean;
412
+ /**
413
+ * Runs the checkpoint over the fixed pool. Sequential in declaration
414
+ * order (deterministic cassette consumption when recorded); every cell
415
+ * runs baseline then treatment.
416
+ */
417
+ declare function runValueCheckpoint(checkpointPool: CheckpointPool, options: RunCheckpointOptions): Promise<CheckpointReport>;
418
+ /** The deterministic render for the M12 gate docs amendment. */
419
+ declare function renderCheckpointReport(report: CheckpointReport): string;
420
+ //#endregion
421
+ 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 };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { KnowledgeCasError, claimExpiry, defineWorkflow } from "@rulvar/core";
1
+ import { KnowledgeCasError, claimExpiry, compileVerifiedLayer, defineWorkflow } from "@rulvar/core";
2
2
  import { createHash } from "node:crypto";
3
3
  //#region src/case.ts
4
4
  /**
@@ -394,6 +394,146 @@ async function flipStaleOnCanaryDrift(store, model, freshFingerprint, options) {
394
394
  throw lastCas ?? /* @__PURE__ */ new Error("flipStaleOnCanaryDrift: unreachable");
395
395
  }
396
396
  //#endregion
397
+ //#region src/checkpoint.ts
398
+ /**
399
+ * The phases 1-2 measured-value checkpoint (M12-T01; docs/05, section
400
+ * "Phases and placement"; the quantitative criteria of OQ-09,
401
+ * 14-open-questions.md, closed at M11-T06). The M12 gate: kb_propose
402
+ * and the proposal loop ship ONLY if the knowledge card demonstrably
403
+ * improves tier and agentType selection on eval cases.
404
+ *
405
+ * Two experiments, both A/B under identical fixed pools:
406
+ *
407
+ * 1. RUNG SELECTION, per (ladder, taskClass) cell: the baseline arm
408
+ * runs every eval case at the ladder's DEFAULT start tier; the
409
+ * treatment arm runs at the tier recommended by
410
+ * compileVerifiedLayer over the store's claims (default when no
411
+ * recommendation). A cell passes when the treatment reaches a pass
412
+ * rate at least equal to the baseline at no more than 90 percent
413
+ * of its cost, OR at least 5 points above it at no more than its
414
+ * cost. Criterion 1 holds when a MAJORITY of cells pass AND the
415
+ * pooled aggregate passes the same rule.
416
+ *
417
+ * 2. AGENTTYPE SELECTION, pooled: the same orchestrate-role cases run
418
+ * with and without the knowledge store configured (the card docks
419
+ * into the spawn tool description when configured). Criterion 2
420
+ * holds when the card-informed arm matches or beats the baseline
421
+ * pass rate at no more than 105 percent of its cost.
422
+ *
423
+ * The checkpoint PASSES only when both criteria hold. Methodology
424
+ * guard: the claims the treatment consumes MUST come from a seeding
425
+ * sweep over a DISJOINT case set (the seed/eval split is the caller's
426
+ * pool contract), or the measurement is leakage.
427
+ */
428
+ /** IEEE754 guard for the rule boundaries (0.8 + 0.05 exceeds 0.85). */
429
+ const EPSILON = 1e-9;
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 && informed.passRate >= baseline.passRate && informed.totalCostUsd <= 1.05 * baseline.totalCostUsd + EPSILON
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
+ //#endregion
397
537
  //#region src/sweeps.ts
398
538
  const SWEEP_THRESHOLD_DEFAULTS = {
399
539
  strength: .9,
@@ -485,4 +625,4 @@ async function runSweepMatrix(pool, options) {
485
625
  return report;
486
626
  }
487
627
  //#endregion
488
- export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix };
628
+ export { EvalJudgeError, JUDGE_VERDICT_SCHEMA, SWEEP_THRESHOLD_DEFAULTS, canaryFingerprint, commitEvalMeasured, evalMeasuredClaim, flipStaleOnCanaryDrift, goldenGrader, judgeGrader, normalizeCanaryOutput, renderCheckpointReport, rubricGrader, runEvalCase, runEvalMatrix, runEvalSuite, runSweepMatrix, runValueCheckpoint, rungRuleHolds };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/evals",
3
- "version": "1.0.0",
3
+ "version": "1.1.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.0.0",
26
- "@rulvar/core": "1.0.0"
25
+ "@rulvar/core": "1.1.0",
26
+ "@rulvar/testing": "1.1.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^22.20.0",