fix(repairs re-review HIGH-1): preflight exempts behavior cases from production-ref resolution

The restack onto slice-05 Q3 added the `behavior` EvalCategory to the shared live-model
harness. Behavior cases assert observable behavior and carry NO `rig context get <ref>`
contract, but the Repair-2 preflight ran resolveCaseRefs on EVERY case and refused the whole
run (run-evals.mjs exit 2) on any unresolved one — so a conforming behavior case could never
travel through the shipped eval entry. Latent at 35f7d8dea (pre-slice-05, no behavior cases);
activated by the restack, exactly the base interaction the re-review checked.

Fix: resolveCaseRefs stamps requiresRef (true only for selection/loading — the categories whose
contract pulls context); unresolvedCases enforces only on requiresRef cases. Behavior cases are
never refused. The hermetic guard applies canonicality only where the contract requires it.

RED evidence: mixed selection+behavior set failed (behavior-observable in unresolvedCases,
requiresRef undefined) → 2 failing; GREEN after fix, 46/46 eval+router+ref+behavior-bridge.

Pin at the actual entry/preflight seam (run-evals.mjs:55 composition), not a direct runEvals
unit: a mixed selection+behavior invocation proves the entry can never refuse a valid behavior
case while genuinely-broken selection cases still refuse.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
v-openrig-buildandClaude Opus 4.8 authored and mvschwarz committed 2026-08-25 18:42:22 -07:00
1 parent 2a756f9ab1
commit a8cd1cf249
3 files changed
+34 -7

No files matched your search

@@ -23,9 +23,11 @@ describe("REPAIR (re-review HIGH-1) — refs resolve in a freshly-built producti
afterAll(built.cleanup);
const resolutions = resolveCaseRefs(cases, built.dir);
it("yields a canonical ref for EVERY case (per-case, not a suite-wide count)", () => {
it("yields a canonical ref for EVERY ref-bearing case (per-case, not a suite-wide count)", () => {
expect(resolutions).toHaveLength(cases.length);
const bad = resolutions.filter((r) => r.ref === null || !r.canonical).map((r) => r.caseId);
// Only selection/loading cases contract to pull context; a behavior case (slice-05 Q3) carries
// no ref and is exempt — enforce canonicality exactly where the contract requires it.
const bad = resolutions.filter((r) => r.requiresRef && (r.ref === null || !r.canonical)).map((r) => r.caseId);
expect(bad).toEqual([]);
});
@@ -22,6 +22,9 @@ const CASES: EvalCase[] = [
{ id: "bad-absent", name: "x", category: "selection", prompt: "p", expectedPatterns: ["rig context get\\s+skills/core/absent"] },
{ id: "bad-bare", name: "x", category: "selection", prompt: "p", expectedPatterns: ["rig context get\\s+core/known"] },
{ id: "bad-noref", name: "x", category: "selection", prompt: "p", expectedPatterns: ["do the thing"] },
// slice-05 Q3 behavior case — asserts observable behavior, NO context-pull contract, so it must
// never be refused by the production-ref preflight (re-review restack HIGH-1).
{ id: "behavior-observable", name: "x", category: "behavior", prompt: "p", expectedPatterns: ["rig context (get|profile)"] },
];
describe("eval-ref-resolution — per-case, by name", () => {
@@ -35,11 +38,23 @@ describe("eval-ref-resolution — per-case, by name", () => {
expect(res.find((r) => r.caseId === "good")?.resolved).toBe(true);
});
it("flags absent / bare (non-canonical) / no-ref cases — each by caseId", () => {
it("flags only the SELECTION/LOADING cases (absent / bare / no-ref) — never the behavior case", () => {
// The behavior case has no context-pull contract, so it is NOT among the refused cases.
expect(unresolvedCases(res).map((r) => r.caseId).sort()).toEqual(["bad-absent", "bad-bare", "bad-noref"]);
expect(res.find((r) => r.caseId === "bad-noref")?.ref).toBeNull();
expect(res.find((r) => r.caseId === "bad-bare")?.canonical).toBe(false);
});
it("does NOT refuse a behavior case — it has no context-pull contract (re-review restack HIGH-1)", () => {
const behavior = res.find((r) => r.caseId === "behavior-observable");
expect(behavior?.requiresRef).toBe(false);
// run-evals.mjs refuses the whole run iff `unresolvedCases(resolveCaseRefs(...))` is non-empty
// (the entry's exit-2 preflight). Pinning that composition on a MIXED selection+behavior set proves
// the entry can never refuse a valid behavior case, while genuinely-broken selection cases still do.
const refused = unresolvedCases(res);
expect(refused.map((r) => r.caseId)).not.toContain("behavior-observable");
expect(refused.every((r) => r.requiresRef)).toBe(true);
});
});
describe("buildProductionPackage — cleans up its temp package (re-review MEDIUM-2)", () => {
@@ -10,10 +10,13 @@ import { execFileSync } from "node:child_process";
import { existsSync, mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { EvalCase } from "./eval-grader.js";
import type { EvalCase, EvalCategory } from "./eval-grader.js";
export interface CaseRefResolution {
caseId: string;
category: EvalCategory;
/** Only selection/loading cases have a context-pull contract; behavior cases do not. */
requiresRef: boolean;
/** The canonical ref extracted from the case, or null if none was found. */
ref: string | null;
/** ref is a canonical full path (skills/<ns>/<name>). */
@@ -31,18 +34,25 @@ export function extractRef(pattern: string): string | null {
/** One resolution per case (never fewer), against a built production package directory. */
export function resolveCaseRefs(cases: EvalCase[], productionPackageDir: string): CaseRefResolution[] {
return cases.map((c) => {
// Only selection/loading cases contract to pull context; behavior cases (slice-05 Q3) assert
// observable behavior and carry no context ref, so they are never subject to ref resolution.
const requiresRef = c.category === "selection" || c.category === "loading";
const patterns = [...(c.expectedPatterns ?? [])];
if (c.order?.getPattern) patterns.push(c.order.getPattern);
const ref = patterns.map(extractRef).find((r) => r !== null) ?? null;
const canonical = ref !== null && ref.startsWith("skills/");
const resolved = canonical && existsSync(join(productionPackageDir, ref, "manifest.yaml"));
return { caseId: c.id, ref, canonical, resolved };
return { caseId: c.id, category: c.category, requiresRef, ref, canonical, resolved };
});
}
/** Cases whose ref is missing, non-canonical, or absent from the production package. */
/**
* Cases that MUST resolve a canonical production ref but do not — i.e. selection/loading cases whose
* ref is missing, non-canonical, or absent from the production package. Behavior cases carry no
* context-pull contract and are never included, so a valid behavior case never refuses the run.
*/
export function unresolvedCases(resolutions: CaseRefResolution[]): CaseRefResolution[] {
return resolutions.filter((r) => !r.resolved);
return resolutions.filter((r) => r.requiresRef && !r.resolved);
}
export interface BuiltProductionPackage {