Decision: advertise == enforce. The key allowlist (OAI-02) stays as the
first gate with its established unknown-key message; a compiled leaf
validator now runs second, so required/type/enum/pattern/bounds
violations surface as INVALID_INPUT 422 naming the keyword — not as
upstream round-trips.
- new src/adapters/input_validation.rs: CompiledInputSchema compiles an
op's input_schema once at import with the jsonschema crate (same
2020-12 dialect publish_schema uses) and validates peer input at call
time; the compile-time copy is hardened closed-by-default
(additionalProperties: false injected when absent) so the validator
reproduces the allowlist's unknown-key semantics; explicit
additionalProperties:true catch-all and schema values are preserved;
the original spec value is never mutated
- from_openapi/from_jsonschema import(): compile per registration,
capture the validator in the handler closure (re-import recompiles —
the closure capture is the invalidation story); a non-compilable
input schema fails import loudly (AdapterError::SchemaParse naming
the operation), matching the publish_schema fail-closed precedent
- from_openapi generated input schemas now carry
additionalProperties:false explicitly, so the /schema advert states
the enforced rule and external schema-driven validators reach the
same verdicts
- forward/forward_stream/build_request gain an
Option<&CompiledInputSchema> parameter; enforcement runs after the
allowlist
- round-trip test (review 002 Test-gap 10): the /schema-exported
input_schema is compiled with the same validator and driven against
build_request over a 10-input violation matrix — accept-sets exactly
equal in both directions; the chain-test that lets advertise/enforce
drift surface as a CI failure
- ADR-066: new decision section (advertise==enforce) with the trust-
boundary reasoning and the rejected option (b) rationale
- module + enforce_input_schema docs updated to the two-gate shape
cargo test --all-features 596 pass; clippy --all-features/-D warnings,
fmt, doc gates clean.
docs(tasks): mark review-002-fu-oai18-decision done
The bracketed planning task's five candidates, re-validated against
the post-bulk tree (0a932e5, all 23 bulk tasks done, 446/568 green):
1. OAI-18 -> review-002-fu-oai18-decision: still key-allowlist-only;
bulk blockers (yaml/path-item) both landed. Task records the
enforce-vs-scope-the-advert decision framework from review 002.
2. CON-08/CON-09 close() lever: deferred still (documented v1
contracts unchanged, no consumer pressure) — not tasked.
3. Cross-crate: CF-001..004 all open in alkcall, no alkhttp-side task;
PRJ-16 guard already documents its defense-in-depth re-scope
disposition (no work when CF-004 lands).
4. Stale-comment sweep: mostly absorbed by the bulk (SRV-11 comment
rewritten in-fix); residue = 3 cargo-doc warnings + WsTimeouts
redundant Default -> review-002-fu-doc-warnings.
5. ADR-045 version audit: clean (1.4.0 + annotations + test pin).
Post-bulk coverage re-pass (95.89% regions, bulk's new code fully
covered: PRJ-16 guard, body cap, router reorder, batch cap, WS
knobs, OAI-11 node budget all exercised) surfaced one unanticipated
residue -> review-002-fu-stream-error-coverage (forward_stream
build-error arm never exercised on a Sub op; PEM read-failure arms;
over-cap poll_write; SSE split-CRLF edge; from_value structural
rejects; WsTimeouts Default).
Bracketed pass itself marked completed (planning consumed).
taskgraph: 69 valid, no cycles; 3 pending.
- build_error_schemas: default/wildcard response keys dropped with a
warn instead of emitting a dead HTTP_0 ErrorDefinition — /search
never advertises a code that can't match (the runtime mapper already
synthesizes HTTP_<actual> for unmapped statuses)
- check_parameter_style: non-default style/explode parameter forms
(spaceDelimited, pipeDelimited, deepObject, form+explode:false,
simple+explode:true) fail import with a feature-naming SchemaParse;
wire-equivalent defaults (form, simple) import unchanged — no more
silent "[1,2]" array mis-serialization
- servers overrides rejected at import at all three levels (document,
path, operation) — the adapter pins one base_url at assembly time
- trace-only paths: skip is now logged (warn naming path + methods),
documented-as-inert instead of silent
- detect_op_type + build_output_schema sweep 2XX/default keys for
text/event-stream — a default-declared SSE stream classifies as Sub
instead of returning one giant text body
Tests: 11 new (error-drop, style rejections + default accept, servers
3-level rejections + baseline, trace skip, SSE default/2XX detection).
Verified: cargo test (299), --all-features (370 + suites), clippy
--all-targets -D warnings (default + all-features), fmt --check.
Tasks: review-001-openapi-loud-degradation
Found in the sweep of completed review-001 remediation:
- /publish schema validation fails open on compile error + recompiles
per request (remediation-introduced, routes.rs:254-268)
- OAI-06 loud-degradation unblocked and still open (HTTP_0 marker)
- HY-06 ExponentialBackoff in public API + COV-02 mTLS success path
both unblocked post client-config rework
- HY-02/04/11 publish-prep docs gate (104 missing-docs warnings
re-measured)
Also flagged, not tasked here: WS-12 (alkcall demux 4 GiB discard
alloc) was never actually filed in alkcall's consumer-findings-ledger —
only CF-001 is there. File it when next touching alkcall.
taskgraph: validate clean (42), no cycles
- build_request takes the op's input_schema and rejects undeclared
input keys (INVALID_INPUT) before any outbound request is built;
explicit `additionalProperties: true` opts into catch-all input;
non-object inputs rejected (OAI-02)
- in: header parameters are stamped `wire: header` in the generated
input schema and sent as upstream request headers, not query params;
in: cookie fails import with a clear error (OAI-03)
- a spec parameter named `body` is rejected at import unconditionally
(OAI-07)
- FromJsonSchema::new returns Result and validates method/path template/
base_url at construction; registered visibility forced to Internal
like from_openapi; module doc corrected (OAI-09)
Verified: cargo test, cargo test --all-features, clippy (both feature
sets, -D warnings), cargo fmt --check
- index components/parameters + requestBodies in OpenAPISpec; resolve
bare $ref parameter and requestBody entries through the cycle-guarded
resolver; unresolvable refs abort spec parse loudly
- reject path placeholders with no matching input-schema property at
registration (no more silently percent-encoded literal placeholders)
- reject duplicate operationIds and path+method routes in one import
batch instead of silent last-write-wins registration
- also reject a parameter named 'body' shadowed by requestBody (OAI-07
adjacency, same code path)
Verified: cargo test (243), --all-features (322), clippy -D warnings
(default + all-features), fmt --check, doc --no-deps
Replace per-chunk parse_sse_frames with SseParser holding raw bytes
across chunks: reassembles frames split at TCP boundaries (the review's
silently-losing case), decodes UTF-8 per complete line so multi-byte
chars split across chunks survive, caps the buffer at 1 MiB
(SSE_EVENT_BUFFER_CAP) and dispatches a pending event at EOF.
forward_stream threads the parser through its unfold state and emits a
terminal error envelope on cap overflow. Existing single-chunk SSE test
assertions preserved; added multi-chunk, split-UTF-8, EOF-dispatch, and
cap tests.
Verified: cargo test (219 pass), clippy -D warnings, fmt --check.
resolve_refs_recursive recursed with no cycle detection and no depth
budget; a self-referential OpenAPI component stack-overflowed and
aborted the process (uncatchable, kills import()).
- add branch-scoped visited set on the JSON-pointer ref path: a ref
re-entering its own expansion chain errors cleanly with
AdapterError::SchemaParse naming the offending ref (OAI-01)
- add depth budget (MAX_REF_RESOLUTION_DEPTH = 64) bounding $ref hop
chains and schema nesting height; over-deep specs error cleanly
instead of exhausting the stack (OAI-01)
- clean loud error over depth-limited expansion: recursive schemas
(trees, linked lists, cursor pagination) fail import rather than
expand unboundedly
- shared refs to a common schema (diamond/repeated) still resolve —
visited set is branch-scoped, not global
- OAI-08: replace the two guarded expects in from_value
("paths is object", "schemas is object") with if-let paths
Verification: cargo test 188 passed / 0 failed; clippy
--all-targets -D warnings clean; fmt --check clean
21 review-001 tasks across server/adapters/client/gateway/websocket/infra,
chunked from the 7-unit remediation plan in
docs/reviews/001-initial-implementation-review.md.
- Scope split by mechanism, not one-per-finding: 15 tasks in generation 1
(parallelizable), 6 sequenced after their file-sharing precursors
- Deliberately deferred until dependent fixes land: projection/doc
fidelity partial (Unit 6 beyond dependency hygiene), coverage backfills
(COV-01..07 via in-task acceptance for forward.rs), and per-finding
minors (OAI-06/07, HY-02/04/06/10/11, CON-08)
- Cross-crate WS-12 (alkcall demux 4 GiB discard alloc) noted for filing
in alkcall, not here
taskgraph: validate clean, no cycles, 6 generations