fix(confidence-engine): enforce openai strict object invariants

This commit is contained in:
2026-09-07 12:22:03 +01:00
parent e289f0be1f
commit 0d27d4935b
3 changed files with 31 additions and 2 deletions
+2 -1
View File
@@ -37,7 +37,8 @@ If YES, the next live experiment is one timed/costed OpenAI UI investigation mea
- The OpenAI provider now honors a caller-supplied structured-output schema, applying its existing strict-schema transport projection; absent an alternate schema, initial reconstruction retains its existing strict schema and transport normalization.
- The next live run exposed incomplete recursive strict projection: OpenAI rejected `relationships.items` because it lacked `additionalProperties: false`. The projector now recognizes every `type: "object"` node, including property-less objects in array items, and recursively enforces strict object schemas while preserving initial-reconstruction optionality/nullability behavior.
- This deterministically fixes the focused-deconstruction transport failures. Provider and focused-route suites pass with zero live calls.
- The latest Terra request then exposed an inconsistent root `properties`/`required` contract. The projector now derives `required` after projection from the surviving property keys, and recursive tests verify `properties`, `required`, and `additionalProperties` consistency. Property-less object strictness and initial-reconstruction transport behavior remain preserved.
- Provider and focused-route suites pass with zero live calls.
- Next boundary: retry one fresh real Playwright-driven OpenAI/Terra investigation; do not reuse the failed focused-deconstruction attempt.
## Repository checkpoint
+1 -1
View File
@@ -171,7 +171,6 @@ function projectOpenAIStrictSchema(schema, zodSchema, rootSchema, visited) {
}
}
}
if (resolved.properties) resolved.required = Object.keys(resolved.properties);
resolved.additionalProperties = false;
}
@@ -182,6 +181,7 @@ function projectOpenAIStrictSchema(schema, zodSchema, rootSchema, visited) {
for (const [key, property] of Object.entries(resolved.properties)) {
projectOpenAIStrictSchema(property, shape?.[key], rootSchema, visited);
}
resolved.required = Object.keys(resolved.properties);
}
for (const branch of [
...(resolved.anyOf ?? []),
+28
View File
@@ -190,6 +190,30 @@ describe("OllamaLlmProvider chat capability detection", () => {
});
describe("OpenAI reconstruction provider experiment seam", () => {
function assertStrictObjectInvariant(schema, root = schema, visited = new Set()) {
if (!schema || typeof schema !== "object" || visited.has(schema)) return;
visited.add(schema);
if (schema.properties) {
expect(schema.additionalProperties).toBe(false);
expect(new Set(schema.required)).toEqual(new Set(Object.keys(schema.properties)));
Object.values(schema.properties).forEach((property) =>
assertStrictObjectInvariant(property, root, visited),
);
}
if (schema.items) assertStrictObjectInvariant(schema.items, root, visited);
for (const branch of [
...(schema.anyOf ?? []),
...(schema.oneOf ?? []),
...(schema.allOf ?? []),
]) {
assertStrictObjectInvariant(branch, root, visited);
}
for (const definition of Object.values(schema.$defs ?? schema.definitions ?? {})) {
assertStrictObjectInvariant(definition, root, visited);
}
}
it("projects canonical optional fields as required but nullable", () => {
const nativeSchema = z.toJSONSchema(reconstructionV2Schema);
const projectedSchema = createOpenAIStrictSchema(
@@ -351,6 +375,10 @@ describe("OpenAI reconstruction provider experiment seam", () => {
relationships: [],
possibleFollowUpQuestions: [],
});
expect(new Set(schema.required)).toEqual(new Set(Object.keys(schema.properties)));
expect(schema.properties).toHaveProperty("relationships");
expect(schema.required).toContain("relationships");
assertStrictObjectInvariant(schema);
});
it("recursively projects arbitrary nested object schemas as strict", () => {