Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion engine/v5/run-v5.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
evaluateEnforcement,
PLAN_STATES,
} from "./dispatch-plan.mjs";
import { compareDispatchPlanToTopology } from "./plan-diff.mjs";
import {
classifyTopologyParticipation,
DISPATCH_OBSERVABILITY,
Expand Down Expand Up @@ -217,7 +218,27 @@ async function main() {
dispatch_plan: dispatchPlan.dispatches,
dispatch_plan_error: dispatchPlan.error,
});
writeMeta(matchId, { dispatchPlan });
// B's dependent variable is the deviation between the voluntary plan and the
// declared topology. plan-diff.mjs computed it, nothing ever called it, and
// nothing wrote it down — the same shape as biasReport, which judge() produced
// and the manifest dropped. An E2 calibration cell came back with the plan
// recorded and the comparison absent, which is a run that measures A and
// silently collects nothing for B.
let planDiff;
try {
const topo = JSON.parse(fs.readFileSync(path.join(regimeDir, "topology.json"), "utf8"));
planDiff = compareDispatchPlanToTopology(dispatchPlan, topo);
} catch (err) {
// A regime without topology.json cannot be compared; say so rather than
// omitting the field, so an absent comparison is distinguishable from one
// that was never attempted.
planDiff = {
plan_status: dispatchPlan.status,
comparison_available: false,
reason: `topology unavailable: ${err.message}`,
};
}
writeMeta(matchId, { dispatchPlan, planDiff });

const enforcementSetup = enforceDispatch
? loadRequiredOffices(regimeDir)
Expand Down
2 changes: 2 additions & 0 deletions engine/v5/tournament.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ export async function runTournament({
} catch {
/* child may have failed before creating meta.json */
}
result.planDiff = meta.planDiff ?? null;
result.dispatchPlan = meta.dispatchPlan ?? {
status: "parse_failed",
dispatches: null,
Expand All @@ -479,6 +480,7 @@ export async function runTournament({
result.dispatchEnforcement.status === "enforcement_passed";
writeMeta(result.matchId, {
dispatchPlan: result.dispatchPlan,
...(result.planDiff ? { planDiff: result.planDiff } : {}),
dispatchEnforcement: result.dispatchEnforcement,
topologyParticipation: result.topologyParticipation,
});
Expand Down
34 changes: 34 additions & 0 deletions test/r11-instrumentation.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -481,3 +481,37 @@ test("R11 plan events remain observable but cannot contaminate judge or skill tr
const runtime = reconstructRuntimeGraph([plan, execution]);
assert.equal(runtime.observed.dispatch_sequence.length, 0);
});

// ── B's dependent variable has to be written down, not merely computable ─────
//
// plan-diff.mjs was built with two exported functions and zero callers: the
// comparison between the voluntary plan and the declared topology was never
// computed and never persisted. An E2 calibration cell came back with
// dispatchPlan recorded for all ten arms and planDiff absent from every one —
// a run that measures A and silently collects nothing for B. This is the same
// shape as biasReport, which judge() computed and the manifest dropped.
test("R11 run-v5 persists the plan-vs-topology comparison next to the plan", async () => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), "civagent-r11-plandiff-"));
const fake = makeFakeEnvironment(home);
const matchId = "r11-plandiff";
try {
const result = await spawnCollect(process.execPath, [
path.join(ROOT, "engine/v5/run-v5.mjs"),
"--backend", "native", "--no-skill", "china/tang", "test task",
], { ...fake.env, CIVAGENT_MATCH_ID: matchId });
assert.equal(result.code, 0, result.stderr);

const meta = JSON.parse(
fs.readFileSync(path.join(home, ".civagent", "matches", matchId, "meta.json"), "utf8"),
);
assert.ok(meta.dispatchPlan, "precondition: the plan is recorded");
assert.ok(meta.planDiff, "the comparison must be recorded too, not left computable");
assert.equal(meta.planDiff.plan_status, meta.dispatchPlan.status,
"the comparison must describe the plan actually recorded");
assert.ok(meta.planDiff.unsupported_dimensions,
"and must keep naming what it cannot compare, rather than implying full coverage");
} finally {
fs.rmSync(fake.bin, { recursive: true, force: true });
fs.rmSync(home, { recursive: true, force: true });
}
});
Loading