import { readFileSync } from "node:fs"; import { test, expect, describe } from "bun:test"; import { parseConf, serializeConf, lintRegistryForConf, type ParseConfResult, } from "../../src/conf/index.ts"; import { runConfigSchema, runFromConfig, type RunConfig, } from "../../src/level-blocks/runConfig.ts"; import { standardBlockDefs, standardRegistry, STANDARD_CODES, } from "../../src/level-blocks/standard.ts"; import { MON_JAN_5, SAT_JAN_10, makeAssignment, makeProject, makeResource, makeTask, } from "../level-core/fixtures.ts"; const registry = standardRegistry(); const FIXTURES = [ "lexicographic", "variadic-nested-disabled", "minimal", ] as const; function fixtureText(name: string): string { return readFileSync(`${import.meta.dir}/fixtures/${name}.conf`, "utf8"); } function unwrap(result: ParseConfResult): RunConfig { if (!result.ok) throw new Error(`parse failed: ${JSON.stringify(result.issues, null, 2)}`); return result.config; } /** parse(serialize(c)) reproduces c through the schema (value-lossless). */ function roundTrips(config: RunConfig): void { const text = serializeConf(config); const reparsed = parseConf(text); expect(reparsed.ok).toBe(true); expect(runConfigSchema.parse(unwrap(reparsed))).toEqual( runConfigSchema.parse(config), ); } describe("serializeConf — byte-exact goldens", () => { for (const name of FIXTURES) { test(`${name} serializes to its exact fixture bytes`, () => { const text = fixtureText(name); const config = unwrap(parseConf(text, { registry })); expect(serializeConf(config)).toBe(text); }); } test("output is deterministic", () => { const config = unwrap( parseConf(fixtureText("lexicographic"), { registry }), ); expect(serializeConf(config)).toBe(serializeConf(config)); }); }); describe("serializeConf — options", () => { test("header emits top-of-file comment lines", () => { const config = unwrap(parseConf(fixtureText("minimal"), { registry })); const text = serializeConf(config, { header: ["generated by test", "do not edit"], }); expect( text.startsWith("# generated by test\n# do not edit\n\n[levelset]"), ).toBe(true); // Comments are ignored on the way back in — round trip still holds. expect(unwrap(parseConf(text))).toEqual(config); }); test("annotate comments each block with its rendered assumption", () => { const config = unwrap( parseConf(fixtureText("variadic-nested-disabled"), { registry }), ); const text = serializeConf(config, { annotate: registry }); // PeakCap's assumptionTemplate, with {resourceUniqueId}/{cap} substituted. expect(text).toContain( "# peak demand on resource 3 capped at 5 units\n[blocks.peak]", ); expect(unwrap(parseConf(text))).toEqual(config); }); }); describe("serializeConf — round-trip guarantee", () => { test("every standard block at its defaultShadow round-trips", () => { for (const def of standardBlockDefs()) { const config = runConfigSchema.parse({ formatVersion: 1, pipeline: { formatVersion: 1, blocks: [ { instanceId: "b", code: def.code, params: def.defaultShadow }, { instanceId: "s", code: STANDARD_CODES.serialSGS, params: {} }, ], }, }); roundTrips(config); } }); test("each golden fixture round-trips", () => { for (const name of FIXTURES) { roundTrips(unwrap(parseConf(fixtureText(name), { registry }))); } }); test("sweep points preserve nested arrays and inline records", () => { const config = runConfigSchema.parse({ formatVersion: 1, pipeline: { formatVersion: 1, blocks: [ { instanceId: "search", code: STANDARD_CODES.serialSGS, params: {} }, ], }, sweep: { axes: [ { id: "structured", targets: [{ instanceId: "search", paramPath: "options" }], values: [[1, 2], { mode: "fast", limits: [3, 4] }], }, ], }, }); roundTrips(config); }); test("weighted lexicographic tiers preserve their inline weight maps", () => { const config = runConfigSchema.parse({ formatVersion: 1, pipeline: { formatVersion: 1, blocks: [ { instanceId: "search", code: STANDARD_CODES.serialSGS, params: {} }, ], }, objective: { lexicographic: [ "lateness", { weightedSum: { labor: 2, earliness: 0.25 } }, ], }, }); roundTrips(config); }); test("a generated corpus of random subsets/weights/tiers round-trips", () => { // Deterministic PRNG (mulberry32) — no new dev deps, reproducible corpus. let state = 0x9e3779b9; const rand = (): number => { state |= 0; state = (state + 0x6d2b79f5) | 0; let t = Math.imul(state ^ (state >>> 15), 1 | state); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; const defs = standardBlockDefs(); const scorers = defs.filter((d) => d.family === "scorer"); const constraints = defs.filter((d) => d.family === "constraint"); for (let iter = 0; iter < 40; iter++) { const chosen = [...constraints, ...scorers].filter(() => rand() < 0.5); const blocks = chosen.map((def, i) => ({ instanceId: `${def.family}-${String(i)}`, code: def.code, params: def.defaultShadow, // Randomly disable some (never enabled:true — it is the absent default). ...(rand() < 0.25 ? { enabled: false } : {}), ...(rand() < 0.2 ? { provenance: "candidate" as const } : {}), ...(rand() < 0.2 ? { shadow: true } : {}), })); blocks.push({ instanceId: "search", code: STANDARD_CODES.serialSGS, params: {}, }); const scorerIds = blocks .filter((b) => scorers.some((d) => d.code === b.code)) .map((b) => b.instanceId); const weights: Record = {}; for (const id of scorerIds) { const r = rand(); if (r < 0.33) weights[id] = null; // report-only else if (r < 0.66) weights[id] = Math.round(rand() * 100) / 10; // e.g. 4.2 } const config: Record = { formatVersion: 1, pipeline: { formatVersion: 1, blocks }, }; if (Object.keys(weights).length > 0) config["weights"] = weights; if (scorerIds.length > 0 && rand() < 0.4) { config["objective"] = { lexicographic: scorerIds.slice(0, 2) }; } roundTrips(runConfigSchema.parse(config)); } }); }); describe("serializeConf — schema coverage", () => { test("the conf mapping handles every runConfigSchema top-level key", () => { // If a new top-level key lands on runConfigSchema (collectors, sweep, …), // this fails loudly until the conf parse/serialize mapping covers it. expect(Object.keys(runConfigSchema.shape).sort()).toEqual( [ "formatVersion", "levelset", "objective", "pipeline", "resolve", "seed", "sweep", "weights", ].sort(), ); }); test("a config exercising every top-level key round-trips", () => { const config = runConfigSchema.parse({ formatVersion: 1, levelset: "0.1.0", seed: 99, resolve: { epoch: "2026-03-01", horizonDays: 60, workUnits: [{ id: 1, location: "bay-1", taskUniqueIds: [10, 11] }], }, pipeline: { formatVersion: 1, systemCode: "CNV", blocks: [ { instanceId: "make", code: STANDARD_CODES.makespan, params: {} }, { instanceId: "sgs", code: STANDARD_CODES.serialSGS, params: {} }, ], }, weights: { make: null }, objective: { lexicographic: ["make"] }, sweep: { maxCombinations: 4, axes: [ { id: "make-enabled", targets: [{ instanceId: "make", paramPath: "$enabled" }], values: [true, false], }, { id: "zip", targets: [ { instanceId: "make", paramPath: "$enabled", valueKey: "enabled", }, ], values: [{ enabled: true }, { enabled: false }], }, ], }, }); roundTrips(config); }); }); describe("lintRegistryForConf", () => { test("the standard registry is conf-clean", () => { expect(lintRegistryForConf(standardRegistry())).toEqual([]); }); }); describe("conf integration — schedule + provenance stability across a round trip", () => { function crewProject() { const t1 = makeTask({ uniqueId: 1, start: MON_JAN_5, finish: SAT_JAN_10 }); const t2 = makeTask({ uniqueId: 2, start: MON_JAN_5, finish: SAT_JAN_10 }); return makeProject([t1, t2], { resources: [makeResource(100, "Crew")], assignments: [makeAssignment(1, 100), makeAssignment(2, 100)], }); } const confText = `[levelset] format = 1 [blocks.crew-cap] code = "levelset.MaxConcurrentResource.v1" resourceUniqueId = 100 max = 1 [blocks.makespan] code = "levelset.Makespan.v1" [blocks.search] code = "levelset.SerialSGS.v1" [objective] lexicographic = ["makespan"] `; test("conf → run → serialize(filled) → parse → run yields the same schedule, scores, and provenance", async () => { const project = crewProject(); const run1 = runFromConfig( project, unwrap(parseConf(confText, { registry })), { registry }, ); const best1 = await run1.best(); expect(best1).not.toBeNull(); // Serialize the *filled* config (provenance artifact) and run it again. const text2 = serializeConf(run1.config); const run2 = runFromConfig( project, unwrap(parseConf(text2, { registry })), { registry }, ); const best2 = await run2.best(); expect(best2).not.toBeNull(); expect(best2!.makespan).toBe(best1!.makespan); expect(run2.scores(best2!)).toEqual(run1.scores(best1!)); // The filled config is what provenance embeds; it must be identical. expect(run2.config).toEqual(run1.config); expect(run2.materialize(best2!).levelset!.runConfig).toEqual( run1.materialize(best1!).levelset!.runConfig, ); }); });