mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 08:04:25 -10:00
fix(schema): keep required groups of composition branches (Audit finding D04-11, partial)
prune_dangling_required removed every required entry absent from local
properties, including composition branches like apply_patch's
oneOf: [{required: [patch]}, ...] whose names live on the parent. The
generic sanitizer turned that into oneOf: [{}, {}, {}], which no input
can satisfy, and the finance tool's anyOf ticker/symbol into no
constraint. A schema with neither properties nor type is now left alone.
Remaining (documented in place): a type: object schema with no
properties still loses required entries, because DeepSeek strict and
MFJS reject required names outside local properties and this
provider-neutral pass cannot invent their types.
Evidence (keeps_required_groups_of_composition_branches):
- production reverted: test result: FAILED. 1 passed; 10 failed; 0 ignored; 0 measured; 14431 filtered out; finished in 5.15s (all lane proof tests; the 1 pass is the finance overflow-literal disproval test)
failure: left: [{}, {}, {}] right: [{required:[patch]}, ...]
- restored (cmp identical): test result: ok. 374 passed; 0 failed; 0 ignored; 0 measured; 14068 filtered out; finished in 2.95s (lane filter set, all new tests included)
The Kimi/Moonshot and agent schema tests in the filter set pass.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
1 parent
51bf574e47
commit
46835a2fcd
1 file changed
+51
@@ -213,10 +213,26 @@ fn inject_properties_on_bare_objects(schema: &mut Value) {
|
||||
}
|
||||
|
||||
/// Remove entries from `required` that aren't keys in `properties`.
|
||||
///
|
||||
/// A schema with neither `properties` nor `type` is a composition branch
|
||||
/// whose `required` names properties declared by the enclosing schema —
|
||||
/// `oneOf: [{"required": ["patch"]}, ...]` (#6561 D04-11). Its list is kept:
|
||||
/// pruning emptied every branch, turning apply_patch's "exactly one of"
|
||||
/// into an unsatisfiable `oneOf` of three `{}` and the finance tool's
|
||||
/// "ticker or symbol" into no constraint.
|
||||
///
|
||||
/// Known limitation: a `type: "object"` schema without `properties` still
|
||||
/// loses `required` entries (a bare object gets `properties: {}` injected
|
||||
/// first). Strict validators (DeepSeek strict, MFJS) reject `required`
|
||||
/// names absent from local `properties`, and this provider-neutral pass
|
||||
/// cannot invent their property types.
|
||||
fn prune_dangling_required(schema: &mut Value) {
|
||||
let Some(obj) = schema.as_object_mut() else {
|
||||
return;
|
||||
};
|
||||
if !obj.contains_key("properties") && !obj.contains_key("type") {
|
||||
return;
|
||||
}
|
||||
// Collect known property names first (immutable borrow), then prune.
|
||||
let known_keys: Vec<String> = obj
|
||||
.get("properties")
|
||||
@@ -557,6 +573,41 @@ mod tests {
|
||||
assert!(schema.get("required").is_none());
|
||||
}
|
||||
|
||||
/// #6561 D04-11: composition branches name the parent's properties.
|
||||
#[test]
|
||||
fn keeps_required_groups_of_composition_branches() {
|
||||
let mut schema = json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"patch": {"type": "string"},
|
||||
"replace": {"type": "array"},
|
||||
"changes": {"type": "array"}
|
||||
},
|
||||
"oneOf": [
|
||||
{"required": ["patch"]},
|
||||
{"required": ["replace"]},
|
||||
{"required": ["changes"]}
|
||||
]
|
||||
});
|
||||
sanitize(&mut schema);
|
||||
assert_eq!(
|
||||
schema["oneOf"],
|
||||
json!([
|
||||
{"required": ["patch"]},
|
||||
{"required": ["replace"]},
|
||||
{"required": ["changes"]}
|
||||
])
|
||||
);
|
||||
// A typed object still has its dangling names pruned.
|
||||
let mut typed = json!({
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name", "ghost"]
|
||||
});
|
||||
sanitize(&mut typed);
|
||||
assert_eq!(typed["required"], json!(["name"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collapses_single_element_oneof() {
|
||||
let mut schema = json!({
|
||||
|
||||
Reference in new issue
Block a user