diff --git a/docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md b/docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md index e3146da..5ed3a79 100644 --- a/docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md +++ b/docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md @@ -149,6 +149,56 @@ error projection and the success sweep (SSE detection + output-schema selection), where the precedence order is: concrete 2XX statuses, then `2XX`, then `default`. +### Forwarding contract decisions (review 002 FWD-17/18/19) + +The shared forwarding core (`src/adapters/forward.rs`) used by both +`from_openapi` and `from_jsonschema` pins three contracts. Each is a +decision the code half-implied; the module doc carries the full normative +text, this section records the reasoning at the assembly-trust boundary +that ADR-066 establishes: an assembly's spec/template/base-URL inputs +are trusted configuration, while peer call-time input never is. + +**FWD-17 — SSE payload contract (decided: carry raw payload + event +name for non-JSON frames).** A subscription frame's `data:` payload +that is itself valid JSON surfaces as the decoded value (`123` stays a +number, `"123"` stays a string). Any other payload surfaces as the +`{"data": , "event": }` wrapper: a legitimately +non-JSON stream stays field-addressable and an upstream's named-event +conventions (`event: error`) are visible instead of indistinguishable +from data. The `event` value is the frame's `event:` field under +WHATWG last-wins semantics — `null` when absent, never the implicit +`message` default, so "named" and "default" stay distinguishable. JSON +frames surface as themselves even under a named event; consequence: +the event name is only visible on non-JSON frames. Chosen over the +alternative (document the old JSON-or-string contract) because the old +behavior silently erased payload shape and made an upstream's error +convention unobservable — a fidelity loss on the same axis ADR-023 +already rejects for status mapping. + +**FWD-18 — placeholder routing rule (decided: fix + structural-value +error).** A key matching a path-template placeholder is consumed by the +path and never also emits as a query parameter, whatever its value's +shape (the renderer's placeholder check precedes query routing — fixed +behavior, pinned by test). A placeholder renders exactly one literal +path segment, so a structural value (object/array) under a placeholder +key fails with `INVALID_INPUT` rather than splicing minified JSON into +the path. Scalar values (string/number/boolean/null) render +percent-encoded as before. + +**FWD-19 — literal `%` in template text (decided: documented +trade-off, not rejection).** A `%` inside a *value* is always encoded +(`%` → `%25`), so a value can never inject or fake percent escapes. A +`%` in *template or base text* survives verbatim: an assembly writing +`%2F` into a template is presumed to intend a pre-encoded segment for +upstreams that route `%2F` differently from `/`. Inputs are +assembly-supplied (this ADR's trust boundary), so the strict shape +(call-time rejection of raw `%` in template text) is rejected — it +would break legitimate pre-encoded templates without adding safety: +the surviving `%2F` still renders as a single literal segment, cannot +change the origin, and cannot be forged from peer input. The trade-off +(upstream-dependent routing of bare percent escapes) is the +assembly's choice, not this crate's. + ## Consequences **Positive**: