[P0 BUG] Enforce declared numeric bounds in rlm_tool schema validation #234

Closed
opened 2026-08-26 09:04:49 +00:00 by lost-rob0t · 1 comment
lost-rob0t commented 2026-08-26 09:04:49 +00:00 (Migrated from github.com)

Bug

rlm_tool currently accepts numeric schema keywords such as exclusiveMinimum in declarative tool schemas but does not enforce them during invocation validation.

Exact source baseline: main 267697bef10a3fffff7c093e1435ece770e7444b.

Current validate_schema_definition/1 accepts type:number property schemas carrying additional bound keys. At runtime, however, validate_type(number, _, Value, _) checks only number(Value) and succeeds. The same gap applies to integer values because validate_type(integer,...) likewise checks only the primitive type.

This was exposed while adversarially reviewing #175/#221: rlm_subagent advertises timeout_seconds:_{type:number,exclusiveMinimum:0}. #221 correctly has an independent host-owned timeout-policy rejection before child spawn, so this bug must be fixed generically rather than smuggled into that deadline transaction.

No matching downstream implementation was found in lost-rob0t/a0-symbolics or lost-rob0t/agentProlog; this is a reusable core schema/runtime contract.

RAGE / TDD contract

Falsifiable regression

Register a normal read-only fixture tool whose argument schema declares a numeric lower bound, then invoke it with a value that violates the bound. On the baseline runtime the handler is reachable because the bound is ignored. The fixed runtime must reject the value at the canonical schema boundary before capability/authority/handler execution, returning the existing structured tool_error{phase:schema,kind:schema_validation_failed,...} family.

Negative tests assert the expected structured rejection while the test suite stays green; do not use intentional-red CI, xfail, skips, swallowed errors, or weaker host-side validation as a substitute.

Analyze / design

Keep one schema validator in rlm_tool; do not add domain-specific checks to callers.

Before realization, inspect all numeric bound keywords already admitted/used by repository schemas and choose the smallest coherent supported set. At minimum, a declared exclusiveMinimum must no longer be decorative. If minimum, maximum, and exclusiveMaximum are already part of the accepted schema dialect or used by current declarations, enforce them through the same helper rather than creating asymmetric one-off behavior.

Schema-definition validation must also reject malformed bound declarations (non-numeric bound values and internally contradictory bounds) rather than preserving impossible/deceptive contracts.

The runtime must preserve:

  • validation before capability/authority/effects/handler execution;
  • exact existing structured schema-error envelope;
  • integer values remaining valid number values where the current dialect permits them;
  • no model-generated callable terms;
  • no authority/capability widening;
  • result-schema validation using the same bound semantics as argument-schema validation.

Adversarial cases

Cover at least:

  1. exclusiveMinimum:0 rejects 0 and a negative value;
  2. the same schema accepts a positive value;
  3. boundary semantics are exact for integer and number schemas;
  4. nested object property paths preserve the failing field path;
  5. result schemas enforce the same numeric constraint before returning success;
  6. malformed/non-number bound metadata is rejected at tool registration;
  7. the handler is not called for invalid arguments;
  8. if multiple supported bounds coexist, contradictory or empty intervals fail at registration rather than at arbitrary invocation time.

Decision gate

GO only if the fix remains a small extension of the existing validate_schema_definition/1 + validate_schema_value/3 path. HOLD/re-design if supporting the declared keywords would require a second schema subsystem or change the public schema dialect broadly.

Verification

Run focused tool-schema tests, canonical deterministic suite, benchmark/conformance, CLI/trace, Nix flake, clean pack install, Tree-sitter FFI, and configured REAL/Paid OpenRouter gates on the exact candidate head. Any changed head invalidates earlier evidence.

Non-goals

  • no #175 timeout-policy rewrite;
  • no JSON Schema implementation project;
  • no provider-specific validation layer;
  • no downstream Agent Zero/AgentProlog product logic;
  • no CI weakening.
## Bug `rlm_tool` currently accepts numeric schema keywords such as `exclusiveMinimum` in declarative tool schemas but does not enforce them during invocation validation. Exact source baseline: `main` `267697bef10a3fffff7c093e1435ece770e7444b`. Current `validate_schema_definition/1` accepts `type:number` property schemas carrying additional bound keys. At runtime, however, `validate_type(number, _, Value, _)` checks only `number(Value)` and succeeds. The same gap applies to integer values because `validate_type(integer,...)` likewise checks only the primitive type. This was exposed while adversarially reviewing #175/#221: `rlm_subagent` advertises `timeout_seconds:_{type:number,exclusiveMinimum:0}`. #221 correctly has an independent host-owned timeout-policy rejection before child spawn, so this bug must be fixed generically rather than smuggled into that deadline transaction. No matching downstream implementation was found in `lost-rob0t/a0-symbolics` or `lost-rob0t/agentProlog`; this is a reusable core schema/runtime contract. ## RAGE / TDD contract ### Falsifiable regression Register a normal read-only fixture tool whose argument schema declares a numeric lower bound, then invoke it with a value that violates the bound. On the baseline runtime the handler is reachable because the bound is ignored. The fixed runtime must reject the value at the canonical schema boundary before capability/authority/handler execution, returning the existing structured `tool_error{phase:schema,kind:schema_validation_failed,...}` family. Negative tests assert the expected structured rejection while the test suite stays green; do not use intentional-red CI, xfail, skips, swallowed errors, or weaker host-side validation as a substitute. ## Analyze / design Keep one schema validator in `rlm_tool`; do not add domain-specific checks to callers. Before realization, inspect all numeric bound keywords already admitted/used by repository schemas and choose the smallest coherent supported set. At minimum, a declared `exclusiveMinimum` must no longer be decorative. If `minimum`, `maximum`, and `exclusiveMaximum` are already part of the accepted schema dialect or used by current declarations, enforce them through the same helper rather than creating asymmetric one-off behavior. Schema-definition validation must also reject malformed bound declarations (non-numeric bound values and internally contradictory bounds) rather than preserving impossible/deceptive contracts. The runtime must preserve: - validation before capability/authority/effects/handler execution; - exact existing structured schema-error envelope; - integer values remaining valid `number` values where the current dialect permits them; - no model-generated callable terms; - no authority/capability widening; - result-schema validation using the same bound semantics as argument-schema validation. ## Adversarial cases Cover at least: 1. `exclusiveMinimum:0` rejects `0` and a negative value; 2. the same schema accepts a positive value; 3. boundary semantics are exact for integer and number schemas; 4. nested object property paths preserve the failing field path; 5. result schemas enforce the same numeric constraint before returning success; 6. malformed/non-number bound metadata is rejected at tool registration; 7. the handler is not called for invalid arguments; 8. if multiple supported bounds coexist, contradictory or empty intervals fail at registration rather than at arbitrary invocation time. ## Decision gate GO only if the fix remains a small extension of the existing `validate_schema_definition/1` + `validate_schema_value/3` path. HOLD/re-design if supporting the declared keywords would require a second schema subsystem or change the public schema dialect broadly. ## Verification Run focused tool-schema tests, canonical deterministic suite, benchmark/conformance, CLI/trace, Nix flake, clean pack install, Tree-sitter FFI, and configured REAL/Paid OpenRouter gates on the exact candidate head. Any changed head invalidates earlier evidence. ## Non-goals - no #175 timeout-policy rewrite; - no JSON Schema implementation project; - no provider-specific validation layer; - no downstream Agent Zero/AgentProlog product logic; - no CI weakening.
lost-rob0t commented 2026-08-26 12:19:20 +00:00 (Migrated from github.com)

RAGE exact-head realization/verification record for PR #237.

Decision: GO on exact candidate head bdc8bd25faacb7c6f056f88aa18c6c536ef700f8 against unchanged canonical main 267697bef10a3fffff7c093e1435ece770e7444b.

The implementation remains inside the existing rlm_tool schema-definition/value-validation path and supports the coherent numeric subset minimum, maximum, exclusiveMinimum, and exclusiveMaximum. Bound metadata is validated at registration; contradictory/empty intervals fail there; argument and result values use the same semantics; nested paths and the existing structured schema-error envelope are preserved; invalid arguments never reach the handler.

Adversarial review removed two risks before promotion: the initial constructed comparison goal was replaced with closed explicit predicates, and the unrelated metadata_context/2 no-op/singleton was removed in a one-hunk cleanup commit inspected before the branch ref moved. No model-controlled data becomes callable and no authority/capability/effect boundary is widened.

Fresh verification ran on synthetic merge commit b70bace37df7854178bddf076001301b840d5705 (exact head into exact main): canonical deterministic PlUnit 78 suites / 895 passed / 0 failed / 0 timeout / 0 blocked / 0 fixme, benchmark/conformance 16/16, deep recursion 15/15, CLI/trace, graph/artifact restart, whitespace, REAL OpenRouter, Paid OpenRouter, Nix flake, clean SWI pack install, and Tree-sitter FFI all passed.

PR #237 is now non-draft and mergeable. Issue remains open until the change is actually merged/reconciled.

RAGE exact-head realization/verification record for PR #237. Decision: **GO** on exact candidate head `bdc8bd25faacb7c6f056f88aa18c6c536ef700f8` against unchanged canonical `main` `267697bef10a3fffff7c093e1435ece770e7444b`. The implementation remains inside the existing `rlm_tool` schema-definition/value-validation path and supports the coherent numeric subset `minimum`, `maximum`, `exclusiveMinimum`, and `exclusiveMaximum`. Bound metadata is validated at registration; contradictory/empty intervals fail there; argument and result values use the same semantics; nested paths and the existing structured schema-error envelope are preserved; invalid arguments never reach the handler. Adversarial review removed two risks before promotion: the initial constructed comparison goal was replaced with closed explicit predicates, and the unrelated `metadata_context/2` no-op/singleton was removed in a one-hunk cleanup commit inspected before the branch ref moved. No model-controlled data becomes callable and no authority/capability/effect boundary is widened. Fresh verification ran on synthetic merge commit `b70bace37df7854178bddf076001301b840d5705` (exact head into exact main): canonical deterministic PlUnit **78 suites / 895 passed / 0 failed / 0 timeout / 0 blocked / 0 fixme**, benchmark/conformance 16/16, deep recursion 15/15, CLI/trace, graph/artifact restart, whitespace, REAL OpenRouter, Paid OpenRouter, Nix flake, clean SWI pack install, and Tree-sitter FFI all passed. PR #237 is now non-draft and mergeable. Issue remains open until the change is actually merged/reconciled.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/prolog-rlm#234
No description provided.