mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-23 05:55:09 +00:00
fix(structured): crash-safe validation façade — deep model reply → refusal, not a 500 (closes #181)
#153's cyclic-$ref guard caps the REF-chain depth but not the DATA depth: validateJsonSchema recurses on the value's nesting (properties/items/additionalProps), so a model reply nested ~2000+ levels overflowed the stack with a RangeError, which handleChatCompletions caught as a generic HTTP 500 instead of the spec-correct refusal. (Found in the #153 final review, filed as #181; ≤1 spawn, no crash, no client-only trigger — the value always comes from the model reply.) New exported validateJsonSchemaSafe() wraps the validator: ANY throw (the deep-data RangeError, or any future recursion hazard) becomes a single validation error, so the structured-output retry loop treats a pathological reply as "did not validate" → refusal. A well-formed reply is byte-identical (passes the inner errors through). runStructuredCompletion calls the safe façade. Chose the wrapper over threading a data-depth counter through six recursive call sites: it protects every internal path at once (impossible to miss one) and stays deterministically testable — a 6000-deep fixture reliably overflows on any platform. Tests: +2, mutation-proven (revert the wrapper to a bare call → the deep test throws RED). Suite 431/0. Rule 2: OCP-internal validation, no wire change, no cli.js citation. Closes #181 Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude <claude-opus-4-8> <noreply@anthropic.com>
parent
788cbbcd99
commit
4143993e3b
@@ -209,6 +209,22 @@ export function validateJsonSchema(value, schema, path = "$", strict = false, ro
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Crash-safe façade over validateJsonSchema (issue #181). The validator recurses on the DATA's
|
||||
// nesting depth (properties/items/additionalProperties), which the REF_DEPTH_CAP does NOT bound —
|
||||
// only the ref-chain is. A model reply nested ~2000 levels deep therefore overflowed the stack with
|
||||
// a RangeError, which the request handler caught as a generic HTTP 500 instead of the spec-correct
|
||||
// `refusal`. This wrapper converts ANY throw (the deep-data RangeError, or any future recursion
|
||||
// hazard) into a single validation error, so the structured-output retry loop treats a pathological
|
||||
// reply as "did not validate" → refusal — never a 500, never a crash. A well-formed reply is
|
||||
// unaffected: the inner validator returns and this just passes its errors through.
|
||||
export function validateJsonSchemaSafe(value, schema, path = "$", strict = false, root = schema) {
|
||||
try {
|
||||
return validateJsonSchema(value, schema, path, strict, root);
|
||||
} catch (e) {
|
||||
return [`${path}: schema validation aborted (${e instanceof RangeError ? "value nesting too deep" : "internal error"})`];
|
||||
}
|
||||
}
|
||||
|
||||
function tryJsonParse(s) {
|
||||
try { return { ok: true, value: JSON.parse(s) }; } catch { return { ok: false }; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user