Skip to content

Commit 531398d

Browse files
committed
test(output-prompt-conformance): add render->recover round-trip skew guard
1 parent 87d6be1 commit 531398d

1 file changed

Lines changed: 30 additions & 0 deletions

File tree

server/typescript/packages/render/test/output-prompt-conformance.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ import {
66
Format,
77
FieldKind,
88
PromptStyle,
9+
recover,
10+
recoverSchema,
11+
scalar,
12+
enumField,
13+
object,
14+
FieldRecovery,
915
type OutputFormatSpec,
1016
type PromptField,
1117
type PromptOverrides,
18+
type RecoverSchema,
19+
type FieldSpec,
1220
} from "../src/index.js";
1321

1422
// The shared corpus lives at the repo root. This file is at
@@ -60,6 +68,15 @@ function buildOutputSpec(c: CaseSpec): OutputFormatSpec {
6068
return { format: toFormat(c.format), rootName: c.rootName, style: PromptStyle.GUIDE, fields };
6169
}
6270

71+
function buildRecoverSchema(c: CaseSpec): RecoverSchema {
72+
const fields: FieldSpec[] = c.fields.map((f) => {
73+
if (f.kind === "ENUM") return enumField(f.name, f.required, f.enumValues ?? null, {});
74+
if (f.kind === "OBJECT") return object(f.name, f.required, f.array ?? false, f.nested ? buildRecoverSchema(f.nested) : null);
75+
return scalar(f.name, FieldKind[f.kind], f.required);
76+
});
77+
return recoverSchema(toFormat(c.format), c.rootName, fields);
78+
}
79+
6380
describe("output-prompt-conformance corpus", () => {
6481
const names = existsSync(CORPUS)
6582
? readdirSync(CORPUS)
@@ -89,6 +106,19 @@ describe("output-prompt-conformance corpus", () => {
89106
// determinism: identical across runs
90107
expect(renderOutputFormat(ofs, overrides)).toBe(actual);
91108
}
109+
110+
if (spec.roundTrip) {
111+
const exampleFragment = readFileSync(join(dir, "expected.exampleOnly.txt"), "utf8");
112+
const outcome = recover(exampleFragment, buildRecoverSchema(spec));
113+
// Skew guard: a field the renderer emitted must read back cleanly. We assert
114+
// NO state is MALFORMED or LOST_* (robust to DEFAULTED and to how a nested
115+
// OBJECT-container path classifies); any such state means the renderer emitted
116+
// an example the recover parser cannot read.
117+
const bad = [FieldRecovery.MALFORMED, FieldRecovery.LOST_REQUIRED, FieldRecovery.LOST_OPTIONAL] as const;
118+
for (const [field, state] of outcome.report.states()) {
119+
expect([field, bad.includes(state as (typeof bad)[number])]).toEqual([field, false]);
120+
}
121+
}
92122
});
93123
}
94124
});

0 commit comments

Comments
 (0)