[GH-ISSUE #4936] [Bug] Output schema identity treats literal metadata objects as active references #3062

Closed
opened 2026-10-31 00:35:44 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @morluto on GitHub (Oct 18, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4936

Before submitting

  • Searched related open and closed issues; #723 and #1065 cover broader catalog costs, not this literal-data classification defect.
  • Removed credentials and sensitive target data.

Area / interface / earliest failure stage

MCP or agent integration / MCP / Transport or result projection.

Observed behavior

toolOutputSchemaWithMetadata omits its generated content-bound $id when valid const, enum, default, or examples data contains objects with reference-looking keys such as $ref: "child.json" or $id: "literal-id". The reference detector recursively interprets every object as a schema, including literal instance values.

Reproduction and evidence

Executed eight source regressions (four keywords × draft-2020-12/draft-07) against 9df559b7e7: eight failed $id assertions; ten existing controls passed. Reduced input: z.strictObject({payload: z.record(z.string(), z.string())}).meta({examples: [{payload: {$ref: "child.json", $id: "literal-id"}}]}), passed as the output schema to toolOutputSchemaWithMetadata, then projected with jsonSchema.output({target: "draft-2020-12"}). The returned schema has no generated $id.

Source: reference detector.

Expected behavior

Preserve literal metadata and canonical validation while generating the same content-bound identity policy used for other base-independent output schemas. Genuine relative references must still preserve their original base, including when a local JSON Pointer explicitly reaches a schema stored inside an otherwise literal annotation. JSON Schema validation defines these keywords as instance values rather than ordinary subschema positions.

Impact and scope

Confirmed projection-boundary defect. All 182 current built-in output advertisements already had root IDs in the sampled catalog; no current built-in tool failure, validator acceptance defect, or measured host/model context impact is claimed. Future/custom metadata can silently lose the intended stable identity. Broader catalog-size work remains #723/#1065.

REA version / runtime / target context

Source 9df559b7e7; Node 24.18.0, macOS arm64; Zod 4.4.3, MCP SDK 2.3.1. Controlled legal JSON Schema metadata; native providers not applicable.

Analysis / proposed check

Classify JSON Schema vocabulary positions at the owning projection boundary, sharing keyword roles with input presentation. Follow local pointer references with URI-fragment/RFC 6901 decoding; retain conservative behavior for unknown vocabulary or unresolved pointers. Validate both dialects, real external references, property names matching literal keywords, and an SDK MCP catalog/call workflow. Native providers and live external client applications are unverified and unnecessary to reproduce this boundary defect.

Originally created by @morluto on GitHub (Oct 18, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4936 ### Before submitting - [x] Searched related open and closed issues; #723 and #1065 cover broader catalog costs, not this literal-data classification defect. - [x] Removed credentials and sensitive target data. ### Area / interface / earliest failure stage MCP or agent integration / MCP / Transport or result projection. ### Observed behavior `toolOutputSchemaWithMetadata` omits its generated content-bound `$id` when valid `const`, `enum`, `default`, or `examples` data contains objects with reference-looking keys such as `$ref: "child.json"` or `$id: "literal-id"`. The reference detector recursively interprets every object as a schema, including literal instance values. ### Reproduction and evidence Executed eight source regressions (four keywords × draft-2020-12/draft-07) against 9df559b7e712298a761ff26e9caae8d659b7e54b: eight failed `$id` assertions; ten existing controls passed. Reduced input: `z.strictObject({payload: z.record(z.string(), z.string())}).meta({examples: [{payload: {$ref: "child.json", $id: "literal-id"}}]})`, passed as the output schema to `toolOutputSchemaWithMetadata`, then projected with `jsonSchema.output({target: "draft-2020-12"})`. The returned schema has no generated `$id`. Source: [reference detector](https://github.com/morluto/rea/blob/9df559b7e712298a761ff26e9caae8d659b7e54b/src/contracts/toolSchemaMetadata.ts#L156). ### Expected behavior Preserve literal metadata and canonical validation while generating the same content-bound identity policy used for other base-independent output schemas. Genuine relative references must still preserve their original base, including when a local JSON Pointer explicitly reaches a schema stored inside an otherwise literal annotation. [JSON Schema validation](https://json-schema.org/draft/2020-12/json-schema-validation) defines these keywords as instance values rather than ordinary subschema positions. ### Impact and scope Confirmed projection-boundary defect. All 182 current built-in output advertisements already had root IDs in the sampled catalog; no current built-in tool failure, validator acceptance defect, or measured host/model context impact is claimed. Future/custom metadata can silently lose the intended stable identity. Broader catalog-size work remains #723/#1065. ### REA version / runtime / target context Source 9df559b7e712298a761ff26e9caae8d659b7e54b; Node 24.18.0, macOS arm64; Zod 4.4.3, MCP SDK 2.3.1. Controlled legal JSON Schema metadata; native providers not applicable. ### Analysis / proposed check Classify JSON Schema vocabulary positions at the owning projection boundary, sharing keyword roles with input presentation. Follow local pointer references with URI-fragment/RFC 6901 decoding; retain conservative behavior for unknown vocabulary or unresolved pointers. Validate both dialects, real external references, property names matching literal keywords, and an SDK MCP catalog/call workflow. Native providers and live external client applications are unverified and unnecessary to reproduce this boundary defect.
bsakaguchi 2026-10-31 00:35:44 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@morluto commented on GitHub (Oct 18, 2026):

Fixed in ba9813004 (pushed to main).

Root cause: output-reference discovery walked literal instance data as though every object were a schema. The producer now shares JSON Schema keyword roles with input presentation and preserves literal const/enum/default/examples values. Real schema-map entries remain schemas even when named after those keywords.

Explicit local references can make literal locations active schemas, so the traversal follows decoded URI-fragment JSON Pointers, tracks containing resources and traversal roles, and conservatively retains the original base for unresolved pointers, unknown vocabulary references, or intermediate $id scope crossings. Existing explicit IDs remain authoritative.

Validation: initial eight identity regressions failed before the change. Final focused gate: 84 passing tests across six files, including both dialects, real relative-reference validation, escaped/percent-encoded pointer components, keyword-named properties, nested resources, malformed/unknown pointer cases, and a linked MCP SDK server/client catalog + call workflow. Typecheck/lint, docs check (five conformance tests), discovery of all 1,570 test files, and commit/push hooks passed. Two read-only reviews found no remaining blockers; their intermediate-scope finding was fixed and covered before publication.

Bounded task comparison (six completed SDK MCP discovery/call/close tasks; first process plus alternating warm repeats): all complete catalog payloads and normalized tool results matched. Warm medians: elapsed 1.9435 → 1.9175 s; process-family CPU 2.5357 → 2.5394 s; sampled peak family RSS 684,834,816 → 681,172,992 bytes. Serialized cl100k estimates unchanged: reply 425, full catalog 895,772. No speedup or actual model-context reduction is claimed. Initial samples overlapped documentation/static checks; no filesystem-cache flush; 10 ms RSS sampling can miss brief/reparented children; elapsed includes parent JSON validation. Modified custom-metadata fixture cost and actual host/model token usage are unmeasured.

Scope: all 182 current built-in advertisements already had root IDs; this corrects the projection boundary for legal custom/future metadata. Native providers and external client applications were not exercised. Broader catalog optimization remains #723/#1065. Heavy stress/profiling stayed stopped.

<!-- gh-comment-id:6244269335 --> @morluto commented on GitHub (Oct 18, 2026): Fixed in ba9813004 (pushed to main). Root cause: output-reference discovery walked literal instance data as though every object were a schema. The producer now shares JSON Schema keyword roles with input presentation and preserves literal `const`/`enum`/`default`/`examples` values. Real schema-map entries remain schemas even when named after those keywords. Explicit local references can make literal locations active schemas, so the traversal follows decoded URI-fragment JSON Pointers, tracks containing resources and traversal roles, and conservatively retains the original base for unresolved pointers, unknown vocabulary references, or intermediate `$id` scope crossings. Existing explicit IDs remain authoritative. Validation: initial eight identity regressions failed before the change. Final focused gate: 84 passing tests across six files, including both dialects, real relative-reference validation, escaped/percent-encoded pointer components, keyword-named properties, nested resources, malformed/unknown pointer cases, and a linked MCP SDK server/client catalog + call workflow. Typecheck/lint, docs check (five conformance tests), discovery of all 1,570 test files, and commit/push hooks passed. Two read-only reviews found no remaining blockers; their intermediate-scope finding was fixed and covered before publication. Bounded task comparison (six completed SDK MCP discovery/call/close tasks; first process plus alternating warm repeats): all complete catalog payloads and normalized tool results matched. Warm medians: elapsed 1.9435 → 1.9175 s; process-family CPU 2.5357 → 2.5394 s; sampled peak family RSS 684,834,816 → 681,172,992 bytes. Serialized cl100k estimates unchanged: reply 425, full catalog 895,772. No speedup or actual model-context reduction is claimed. Initial samples overlapped documentation/static checks; no filesystem-cache flush; 10 ms RSS sampling can miss brief/reparented children; elapsed includes parent JSON validation. Modified custom-metadata fixture cost and actual host/model token usage are unmeasured. Scope: all 182 current built-in advertisements already had root IDs; this corrects the projection boundary for legal custom/future metadata. Native providers and external client applications were not exercised. Broader catalog optimization remains #723/#1065. Heavy stress/profiling stayed stopped.
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
morluto/rea#3062
No description provided.