The OAI-06 matrix lived only in the completed review-001 task notes;
acceptance for the review-002 loudness cluster requires the successor
doc section. New 'Loud unsupported-feature handling' section on
from_openapi: refused/warned/projected feature tables covering cookie
params, style/explode forms, servers, webhooks, callbacks, security,
oneOf requestBodies, unresolvable or content-less requestBody refs,
path-template validation, collision rejection, ref-sibling warns,
discriminator/xml warns, error-projection mappings, and the OAI-17
error-bounding contract.
Internal/ACL-restricted op specs were readable through
POST /call {"operation":"services/schema","input":{"name":...}}
(the MCP call/batch tools identically): the outer-name pre-checks pass
(services/schema is External) and alkcall's services_schema_handler
projects any registered spec with no visibility/ACL check of its own
(alkcall CF-004 is the complete fix there).
- GatewayDispatch.invoke/invoke_streaming now apply the GET /schema
route's is-internal + access-control checks to the meta-op's inner
name input before dispatch (404 Internal / FORBIDDEN ACL), one
interception point covering /call, /batch, /subscribe and the MCP
call/batch tools; /publish cannot reach the Query-typed meta-op
- the visibility+ACL check is one shared fn (schema_disclosure_denial)
used by the HTTP /schema route, the dispatch guard, and the MCP
schema tool, so transports cannot drift
- when CF-004 lands, this guard remains as defense-in-depth (ADR-071)
Tests: dispatch-spine guard unit tests; /call 404 + 401/403 matrix,
/batch NOT_FOUND entry, /subscribe error event; MCP call/batch tools
via services/schema with an Internal inner name (mcp feature).
Verify: cargo test (405), --all-features (523), clippy default and
--all-features --all-targets -D warnings, fmt --check — all pass.
GW-16: empty body / malformed first line / missing header fields /
per-line cap / batch over-cap rejections now route through
call_error_to_http_response_with_identity, mapping INVALID_INPUT to
422 — same status as mid-stream chunk errors. One error class, one
status.
GW-17: invoke_sink wraps the registry sink invoke in the same 30 s
tokio::time::timeout the Once-op invoke uses; a hung sink handler
surfaces as a TIMEOUT (504, retryable) error envelope instead of
holding the HTTP connection forever. The sink wrapper bounds the
whole dispatch (chunk pacing included), matching http-server.md's
deadline contract.
Docs: http-server.md error table documents the 422 triggers and the
sink deadline; http-adapters.md batch cap status corrected.
to_openapi: gateway spec version 1.3.0 -> 1.4.0 (ADR-045 minor):
/publish framing faults and /batch cap reject documented at 422 (the
400 slots moved with the runtime); /publish 400 slot removed; 504
description covers the sink dispatch.
Verification: scripts/verify.sh OK (397 tests); cargo test
--all-features OK (513 tests); clippy --all-features --all-targets -D
warnings OK; cargo fmt --check OK.
§5 records the post-OAI-12 from_yaml contract: duplicate keys rejected
loudly on YAML (with the empirical correction that serde_json 1.0.151's
Value path last-wins rather than errors — the YAML side is the stricter
one), non-finite floats rejected with JSON-pointer context, merge keys
applied via apply_merge (shallow, referencing keys win; the one
deliberate YAML 1.2 deviation), scalar-key stringification matching the
core schema, and the no-new-bounds note (the walk stays inside
yaml_serde's parse-time limits).
Verification: cargo doc --no-deps clean; module-doc cross-check in
openapi_spec.rs matches this contract.
Documents the CON-18 disposition in the v1 session-lifetime section
(teardown handle still future work, but a dead import no longer leaves
a permanent monitor task) and adds the consequence pair: dead imports
self-clean after EOF + bounded grace; registrations landing past the
grace window fall back to the 30s sweeper deadline.
SSE payload contract (non-JSON frames carry {data, event}; JSON frames
surface as themselves), the placeholder routing rule (placeholder keys
never double-emit as query; structural path values are INVALID_INPUT),
and the literal-percent trade-off (% in values always encoded; % in
assembly-supplied template text survives — the assembly owns the
upstream-semantics choice, per the ADR-066 trust boundary).
Decides the WS-13 legitimate-silence question as option (b): 60s of no
chunk progress is an intentional eviction line even for silent
subscriptions; no WS ping/pong keepalive is added because a keepalive
can only rescue app-silence by re-arming the deadline, which reopens
the dribble hole the knob exists to seal. Documented in the byte_adapter
module doc, on DEFAULT_WS_IDLE_TIMEOUT, on the unchanged
HttpAdapter::with_ws_idle_timeout knob, and in websocket.md (new
'Idle-read timeout' section, including the FWD-15 SSE-keepalive
layering note). Deployment posture for long-lived silent sessions:
with_ws_idle_timeout(None) + WsSessions abort + write-side caps.
http-adapters.md: forwarding-handler step 5 documents the time/bytes
split for Sub forwards (stream client without the total request
timeout, total streamed-bytes cap, read timeout as the staleness
guard); the HTTP Client section documents the two derived clients
(request vs stream), the reqwest 0.13 per-request-override limitation
that forces the derived-client design, and that only the total timeout
differs between them.
http-server.md: the per-endpoint dispatch deadline paragraph now covers
the outbound half — unbounded time, bounded bytes on both the gateway
and the forwarding side (ADR-049/021 note: unbounded time by design for
subscriptions, bounded bytes per subscription).
Verified: cargo doc --no-deps clean, full suite green.
Record the v1 cut for browser-opened data channels over WS (review-001
WS-03): the design (ADR-067) stands; only the wiring is deferred.
- open-questions.md: add OQ-05 (deferred(scope: v1 cut)) with gap
detail and deferred scope
- ADR-067: dated status amendment + v1-cut note at the data-channel
step; References point to OQ-05
- ADR-048: dated reconciliation note — overlay bidirectionality is
decided design, not a v1 implementation commitment
- websocket.md: status notes on §"Data channels for browsers" and
upgrade step 7
- task review-001-ws-data-channel-decision: completed, Summary filled
Verification: taskgraph validate (38 tasks OK); cargo doc --no-deps
(pre-existing warnings only).
- /search, /schema: document the envelope wrapper and the real item/spec
fields (PRJ-01/02); /search drops unreachable 401/403, documents 404
(PRJ-15)
- error statuses: 422 for dispatch-path INVALID_INPUT /
INVALID_OPERATION_TYPE; extractor 400s documented as the plain-text
gap they are (PRJ-03); operation-declared errors projected by
http_status with x-runtime-behavior: 500 on non-HTTP_* codes (PRJ-04
project-honest decision) — no runtime changes
- /subscribe: 200+SSE only; event:error terminal contract documented
(PRJ-05, GW-12)
- components for requests/responses; CallRequest no longer inlined
per-path (PRJ-14); all library expect() paths removed (PRJ-11)
- error projections folded into BTreeMaps: same registry =>
byte-identical doc, sorted enums (PRJ-12)
- components.securitySchemes.bearerAuth + top-level security (PRJ-15)
- info.version 1.1.0 -> 1.2.0 (ADR-045 minor: additive documentation of
the settled runtime contract)
- 31 unit tests incl. golden print-level assertions mirroring the routes
tests' actual bodies and a determinism test
Verification: cargo test (288), clippy --all-targets -D warnings, fmt
--check, cargo doc --no-deps, cargo test --all-features (all green in a
clean worktree at HEAD; shared tree carries parallel agents' edits).
- apply bearer_auth_middleware route_layer AFTER the extra_routes merge,
so assembly-layer custom routes resolve the bearer token by default
(ADR-046 §4); per-route opt-out via the route's own layer remains
- enforce RESERVED_PATHS per-method at build time: a probe MethodRouter
occupied on all methods is pre-merged against extras, so a custom
POST /search panics like a same-method overlap (ADR-046 §3)
- tests: auth resolves through an extra route; an extra route with its
own layer opts out; reserved-path merge panics; non-reserved
different-method merge stays legal; MCP bearer-gate test stays green
- ADR-046 §3: one sentence restating the per-method rejection rule
Verification: cargo test (215) ok, cargo test --all-features (260 +
integration) ok, clippy -D warnings (default + all-features) ok,
cargo fmt --check ok.
- GW-01: /publish validates every NDJSON chunk against the op's
publish_schema (incl. the first-line chunk) via NdjsonChunkStream —
terminal Err(INVALID_INPUT)/422 on violation, matching the wire
dispatcher's per-chunk contract. Route-level fix; the alkcall spine
was explored and rejected (wire validation is pump-side by design).
- GW-06: the body is streamed, not buffered — Body::into_data_stream()
-> newline-framed BufferedLines -> lazily parsed chunk stream.
ADR-068 documents the streamed semantics and the 2 MiB per-line cap.
- GW-08: /batch capped at 100 operations (INVALID_INPUT 400).
- GW-09: internal-op batch entries now carry generated UUID request ids.
- GW-10: first publish line missing `chunk` is rejected INVALID_INPUT.
- GW-11: redundant /publish pre-checks removed; enforcement rides on
invoke_sink via the shared dispatch spine.
- HY-13: the vacuous stub test was replaced by a body-cut-short test.
- Adjacent: INVALID_OPERATION_TYPE now maps 422 (with identity) / 401
(without) in error.rs — the route relies on the shared mapper since
the pre-checks are gone (GW-03's finding; was a 500 fall-through).
Verification: cargo test 211 passed; cargo clippy --all-targets -- -D
warnings clean; cargo fmt --check clean.
Full-surface integration suite (tests/full_surface.rs, mcp feature):
- one HttpAdapter over real TCP (ProtocolHandler::handle path) serving
gateway endpoints, /openapi.json, /mcp, and the WS channels session
- gateway: search/schema/call/subscribe/batch/publish presence,
envelope shapes, error fidelity end-to-end
- from_openapi import -> Internal-by-default invisible from the wire ->
External facade composes it via env.invoke -> upstream HTTP API
called end-to-end (ADR-015 composition model exercised)
- to_openapi 6-path doc validated against openapiv3 over the wire
- to_mcp: MCP client connects to /mcp on the served adapter, lists the
4 gateway tools, search returns ACL-filtered ops (Sub excluded)
Production fix: the WS upgrade route was reserved but never wired into
HttpAdapter's router (the ws-upgrade-session tests built their own
router). Now wired with ws_bearer_auth (401 without a resolvable
token) around ws_upgrade_handler.
Docs sync: all 28 'Port notes' sections/blockquotes stripped from
ported ADRs/specs; OQ-01/OQ-02 statuses corrected to resolved in
overview.md, websocket.md, and the README table (open-questions.md was
already current).
Publish prep: cargo publish --dry-run --allow-dirty succeeds;
cargo doc --no-deps warning-free (ADR link targets fixed); feature
combinations (default / test-support / mcp / wss / all) compile
warning-free under clippy -D warnings.
Verified: cargo test (182 lib default), --all-features (227 lib + 29
integration), clippy -D warnings x3 feature sets, fmt, doc,
publish --dry-run.
Spike against alkcall source resolved ADR-067 assumptions:
- write_chunk issues header+payload as separate write_alls; channel
0's write_frame issues prefix+body separately — a logical write can
surface as multiple chunks, so the WS adapter must parse outgoing
chunk boundaries (byte-stream treatment both directions), not assume
write-per-chunk or message-per-chunk
- MAX_CHUNK_LEN is 16 MiB; the WS path needs a practical message cap
with oversized chunks split across messages
- install_channel_zero + run_loop_single_stream confirmed as the exact
server-side seam; EOF/teardown invariants already specified by
alkcall (REQ-CH-01/02)
Corrections applied to websocket.md, ADR-067, OQ-01.
docs/plans/implementation.md: scoped plan guiding task decomposition —
spike findings, 4-phase build order, OQ dispositions, task conventions.