Commit Graph
71 Commits
Author SHA1 Message Date
glm-5.3-flash da65e5b6db feat(websocket): write-progress timeout on the WS write pump (WS-18)
- DEFAULT_WS_WRITE_TIMEOUT (60 s, WS-01 knob family): one outbound WS
  send that stays unsent past the window (peer stopped reading)
  evicts the connection — the send path was slot-bounded but
  time-unbounded
- bound is per send call: a slow-but-draining peer resets it with
  every message emitted; only a fully stalled sink trips it
- on timeout the pump signals the stream error (InvalidData naming
  the write stall) and ends WITHOUT a close frame — a clogged socket
  cannot receive one and the close send would park on it
- test-support split helper with both knobs explicit backs the
  scaled eviction test (clogged duplex, eviction well inside the
  5 s slack)

Verification: scripts/verify.sh OK (343 passed), clippy -D warnings
clean, fmt clean
2026-08-30 21:36:53 +00:00
glm-5.3-flash d6d5509c6d fix(websocket): carry per-cause close reasons on CloseWith (WS-15)
- WriteMsg::CloseWith now carries (code, reason); the write pump sent
  the hardcoded "text messages not supported" string for the idle
  (1001) and inbound-frame (1011) closes as well as the text (1002)
  close
- close_reason module: canonical per-cause reason strings, shared by
  the close frames and the stream-error diagnostics (they already
  matched for idle/oversize; now single-sourced)
- reason asserts added to the idle-stall and forever-dribble 1001
  tests (reason names the no-chunk-progress cause) and a new text
  frame test asserts 1002 + the distinct text reason

Verification: scripts/verify.sh OK (342 passed), clippy -D warnings
clean, fmt clean
2026-08-30 21:03:29 +00:00
glm-5.3-flash 9ef2352568 fix(websocket): reject over-cap writes in poll_write before the queue (WS-14)
- byte-cap check moved ahead of poll_ready/try_send so the mux sees
  the InvalidData stream error at the failing call instead of the
  write pump emitting a mid-stream 1011 close (the chunk was already
  committed to the wire path)
- pump-side over-cap arm replaced by a debug_assert (defense-in-depth
  invariant; the error was unreachable for single-call writes once the
  pre-send check exists)
- write_tx_mut renamed write_tx_ref with an updated doc contract
- WS-05's over-cap pump test replaced by a pre-send rejection test
  (error surfaces at the first write, names the cap) + a cap-edge
  test (exactly PENDING_BUFFER_CAP still flows)

Verification: scripts/verify.sh OK (341 passed), clippy -D warnings
clean, fmt clean
2026-08-30 20:56:56 +00:00
glm-5.3-flash f0c9bbafbd Merge branch 'wt/review-002-oai13-path-item-wildcards' 2026-08-30 20:36:33 +00:00
glm-5.3-flash a41d3b2caa Merge branch 'wt/review-002-gw15-publish-body-cap' 2026-08-30 20:35:33 +00:00
glm-5.3-flash 05ebbef43e fix(gateway): cap /publish body buffering + explicit body-limit layer (GW-15)
Review 002 GW-15 [major]: /publish lost both claimed memory bounds in
the GW-06 streaming rewrite. Unauthenticated POST /publish with chunked
'a'-forever (no newline) grew the heap with the upload until OOM, and
each poll re-scanned the whole buffer (O(n^2) on top).

- BufferedLines: cap the unterminated tail against
  MAX_PUBLISH_LINE_BYTES immediately after every chunk read (checked
  before yielding, even with no \n seen) and re-check on the
  trailing-EOF mem::take path; a breach aborts the whole reader
  (pending lines included) with the same terminal INVALID_INPUT
  LineCap error. Complete lines keep the baseline at-cap semantics.

- Whole gateway router: explicit request-body-limit layer
  (GATEWAY_BODY_LIMIT = 2 MiB + 64 KiB framing headroom). A raw-Body
  handler never consults axum's DefaultBodyLimit (that is an extension
  extractors read), so /publish had no whole-body cap at all. The layer
  pre-rejects oversized declared Content-Length and wraps chunked
  uploads in a counting stream; both answer plain-text 413. It
  deliberately sits above the per-line cap so a single over-cap line
  still surfaces the semantic line-cap error. Upstream body read
  failures are not flagged as limit-exceeded (disconnects are not 413).

- New wire tests: streamed never-newline over-cap rejections (both the
  streamed multi-chunk and trailing-EOF shapes), 413 on chunked
  over-limit uploads, 413 on oversized declared Content-Length, cap
  breach batched with complete lines, at-cap line still round-trips,
  declared-length over-limit pre-rejection.

Module status mapping note (GW-16 tracks the drift): hand-rolled
pre-dispatch rejections use 400/INVALID_INPUT while mid-stream chunk
errors map 422 through gateway::error; normalizing is GW-16, not GW-15.

Verification: cargo test 305+5 pass, cargo test --all-features pass,
cargo clippy --all-targets -- -D warnings pass (--all-features too),
cargo fmt --check pass.
2026-08-30 20:29:35 +00:00
glm-5.3-flash a6c2af717d feat(adapters): path-item params merge, response wildcards, loud webhooks (OAI-13)
- path-item-level `parameters` parse into PathItem and merge into every
  operation's input schema; operation-level entries override shared
  name+in duplicates (last-insert wins)
- success sweep accepts `2XX` after concrete 2XX keys and before
  `default` (SSE detection + output schema; concrete outranks wildcard)
- `4XX`/`5XX` error keys project to their class representative status
  (`HTTP_400`/`HTTP_500`) instead of dropping silently; `default`
  still drops loudly (no implied range)
- top-level `webhooks` fails import naming the feature (inbound
  callbacks are outside the single-endpoint outbound adapter model)
- unbound-placeholder error names the parameter-merge state so the
  diagnosis no longer dead-ends

Verification: cargo test (174 lib tests), cargo fmt
2026-08-30 20:20:32 +00:00
glm-5.3-flash 85407f62c9 Merge branch 'wt/review-002-oai11-ref-memoization' 2026-08-30 20:05:30 +00:00
glm-5.3-flash 48ae5103e0 Merge branch 'wt/review-002-srv11-srv12-router-ordering' 2026-08-30 19:59:21 +00:00
glm-5.3-flash 76c9e7e178 Merge branch 'wt/review-002-con17-mcp-pagination' 2026-08-30 19:58:43 +00:00
glm-5.3-flash 84bf538c1d Merge branch 'wt/review-002-ws13-idle-progress' 2026-08-30 19:57:04 +00:00
glm-5.3-flash c7873ad5aa fix(adapters): memoize $ref expansion with node-budget accounting (OAI-11)
- memo-hit clones counted against MAX_REF_EXPANSION_NODES (closes the
  unbounded-clone hole that let acyclic diamond chains materialize
  exponentially before tripping the budget)
- split MAX_REF_HOP_DEPTH (64, chain length) from structural nesting
  depth (128, schema height) — legit 40-hop chains were tripping the
  old combined budget
- shared-chain acceptance test: bounded node-budget error inside 1s
  wall (debug-profile margin); single-chain test asserts full expansion
- tested: cargo test 308+5 pass, clippy -D warnings, fmt --check

Verified under systemd-run MemoryMax=2G scope (the previous
unbounded-clone bug OOM-killed the dev server twice during review-002).

Verification:
- cargo test: 308 lib + 5 integration, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
2026-08-30 19:56:44 +00:00
glm-5.3-flash 9de88f45d7 wip(adapters): fix node budget accounting + split hop depth (coordinator) 2026-08-30 19:54:50 +00:00
glm-5.3-flash 84bffae697 wip(adapters): OAI-11 finisher checkpoint 2 2026-08-30 19:34:20 +00:00
glm-5.3-flash 4bc9d4c236 wip(adapters): drop broken visitor assertion from chain test (crashed session residue) 2026-08-30 12:50:30 +00:00
glm-5.3-flash f52b32a68f wip(adapters): OAI-11 staging checkpoint (crashed session) 2026-08-30 12:43:40 +00:00
glm-5.3-flash f431dea5b5 fix(server): single token resolution on WS upgrade and /mcp routes (SRV-11)
The router-wide bearer_auth_middleware route_layer wraps every route
registered before the call (axum 0.8 RouteManager semantics, verified
against the vendored axum 0.8.9 source), so it double-wrapped the WS
upgrade route (own ws_bearer_auth layer) and the /mcp nest (own inner
bearer layer) — the token resolved twice per request on both, and the
SRV-10 comment claimed the opposite.

Restructure build_router: /mcp is merged and the WS upgrade route is
registered after the router-wide route_layer, so each keeps exactly one
auth layer. The WS MethodRouter now carries the decoy 405 fallback
explicitly (MethodRouter::route_layer wraps method endpoints, not the
fallback), preserving the SRV-07 decoy shape for wrong-method probes on
/alk/channels. Comments state axum's actual route_layer semantics.

Tests: a counting IdentityProvider pins one resolution per WS upgrade
request and per /mcp initialize (verified to fail with left: 2/3 under
the pre-fix ordering); the 401 enforcement and the WS-path decoy 405
are pinned.

Verification: cargo test (304), cargo test --all-features (376 + 5
integration suites), clippy -D warnings (default + all-features),
cargo fmt --check, cargo doc --no-deps
2026-08-30 12:17:32 +00:00
glm-5.3-flash 3f1d5913e7 docs(websocket): record WS-13 no-keepalive decision + progress semantics (WS-13)
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.
2026-08-30 12:09:30 +00:00
glm-5.3-flash f834835b8b fix(websocket): idle deadline off demux chunk-progress (WS-13)
The WS-01 idle timer reset on WS message arrival, so the forever-dribble
stall (declare a chunk, deliver its payload one byte per message) reset
the deadline forever while the demux stayed parked on the partial chunk;
conversely the reset-on-message rule was the only thing slow-but-alive
sessions survived on.

Semantics: the deadline now resets on demux progress — bytes actually
forwarded into read_tx that complete inbound chunks (the frames the
demux routes), tracked byte-for-byte in line with alkcall's parse walk
(8-byte header -> payload skip). Message arrival without a completed
chunk resets nothing, so the dribble hits the deadline; every completed
chunk (even one per message, slowly) re-arms the window.

Tests (tungstenite path): the forever-dribble eviction with 1001 despite
arriving messages; a productive-progress session that survives across
many windows; the knob-disabled (None) arm; the stall and text/cap arms
unchanged.
2026-08-30 12:07:50 +00:00
glm-5.3-flash 7f89db1058 fix(adapters): subscriptions escape the 30s total timeout + total SSE byte cap (FWD-15, FWD-14)
FWD-15: forward_stream now sends through SharedHttpClient::stream_client
— a client derived from the same config with the total request timeout
removed and connect + read timeouts retained. reqwest 0.13's per-request
override can lengthen a client-level total timeout but never clear it
(request-scoped None falls back to the client default), so the derived
client is the only correct mechanism. Both clients rebuild-and-swap
together atomically (FWD-12). A healthy >30s subscription survives; the
read timeout stays as the staleness guard, matching the gateway's
deadline: None dispatch contract (alkcall ADR-021).

FWD-14: the streaming branch enforces a total streamed-bytes cap per
subscription (HttpClientConfig::stream_total_byte_cap, default 1 GiB),
accumulated across every chunk fed to the SSE parser; exceeding it
terminates with a single terminal HTTP_413 error envelope. The SSE
line-cap check moved before extend_from_slice so the reassembly buffer
can never exceed the cap. Removing the total timeout without this cap
would open an unbounded-memory window, so both land together.

Wire tests: keepalive trickle past a scaled total-timeout deadline keeps
delivering; over-cap stream terminates with exactly one terminal error;
parser boundary tests for pre-extend cap checks.

Verified: cargo test (302+5), --all-features (373+41), clippy
--all-targets -D warnings (default + all-features), fmt --check,
doc --no-deps clean.
2026-08-30 12:01:55 +00:00
glm-5.3-flash 6cd0fd3f86 fix(server): decoy 405 covers merged extra routes (SRV-12)
method_not_allowed_fallback(decoy_method_not_allowed) was registered on
the default router before the extras merge; axum applies the 405
fallback only to MethodRouters present at call time, so wrong-method
probes on extra routes returned axum's bare 405 (no body, no
Server: nginx) — the exact stealth probe SRV-07 neutralized for the
default surface.

Re-apply the fallback after the extras merge (idempotent for routers
the earlier call covered — axum 0.8.9 replaces only Fallback::Default).
Tests pin both shapes: decoy 405 on an extra route, and no regression
of the default-surface 405 after the merge.

Verification: cargo test, cargo clippy --all-targets -- -D warnings,
cargo fmt --check
2026-08-30 11:53:47 +00:00
glm-5.3-flash 5ecf6c012b fix(adapters): bound from_mcp tools/list pagination (CON-14)
Replace rmcp's unbounded Peer::list_all_tools with a bounded walk over
list_tools: hard cap of 100 pages (MCP_MAX_TOOLS_LIST_PAGES) plus a 60 s
overall deadline (MCP_TOOLS_LIST_DEADLINE). Tripping either budget fails
loudly with AdapterError::DiscoveryFailed naming pages fetched and tools
accumulated — no silent truncation, no partial registration (import
fails closed, as before). A slow or hung page is cut off by a per-page
tokio timeout sized to the remaining budget.

Bounds are documented in the module doc. Integration test added: a
cycling-cursor server (next_cursor always Some("a")) terminates with
the clean budget error inside an outer 10 s guard; the existing 3-page
pagination test is unchanged and passes.

Verification: cargo test (304 pass), cargo test --features mcp,
cargo test --all-features (412 pass), clippy -D warnings both default
and --all-features --all-targets, cargo fmt --check, cargo doc --no-deps.
2026-08-30 11:46:46 +00:00
glm-5.3-flash 91483a74b4 docs: missing_docs sweep — 0 warnings + deny gate + publish-prep decisions (HY-02, HY-04, HY-11)
- document every public-API item across 18 files (openapi_spec model,
  HttpAuthScheme/HttpServiceConfig, HttpClientBuildError + SharedHttpClient
  accessors, RetryAfterMiddleware, GatewayDispatch, gateway error
  mapping, CallRequest/SchemaQuery/SubscribeStream, HttpAdapter +
  ALPNs + builders, decoy/healthz/state, WsSessions/WsPumps,
  from_openapi/from_jsonschema/from_mcp/from_wss/to_mcp, lib.rs module
  docs)
- enforcement: #![deny(missing_docs)] at crate root — stronger than CI
  rustdocflags (every build incl. cfg(test), where rustdoc misses the
  test-support module docs)
- HY-10 (opportunistic): all 8 docs.rs/alkhttp placeholder ADR links +
  the one relative ../docs link converted to plain text; the 10
  pre-existing private/redundant intra-doc-link warnings fixed —
  RUSTDOCFLAGS="-D warnings" cargo doc is fully clean
- HY-11 decision: docs/ + tasks/ excluded from the published package
  (contributor-facing design/process material; ADR references degrade
  to plain text uniformly). cargo publish --dry-run: 38 files, ~889 KiB,
  zero docs/ or tasks/ entries
- HY-04 decision: keep + document — frame_channel0_chunk's unwrap is
  on serializing the acyclic EventEnvelope (unreachable failure);
  # Panics on it and the adjacent WsClient senders state the contract

Verified: cargo test (299 + 5 TLS), --all-features (370 + suites),
--no-default-features (299), clippy --all-targets -D warnings
(default + all-features), fmt --check, cargo doc -D warnings clean,
cargo publish --dry-run --allow-dirty clean.

Tasks: review-001-missing-docs-sweep (final pending task; 42/42)
2026-08-30 08:25:18 +00:00
glm-5.3-flash 7ce1ca6fbd feat(adapters): loud unsupported-OpenAPI-feature handling (OAI-06)
- 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
2026-08-30 08:10:40 +00:00
glm-5.3-flash edbda6605b refactor(client): owned RetryConfig + TLS/mTLS test coverage (HY-06, COV-02)
- HttpClientConfig.retry_policy: ExponentialBackoff (semver anchor to a
  reqwest-retry concrete type) replaced by retry: RetryConfig — an
  owned struct of plain scalars (max_retries, initial_backoff,
  max_retry_interval, defaults matching the previous backoff exactly);
  the ExponentialBackoff policy is built internally by the middleware
  stack; no reqwest_retry type is public anymore
- ClientCertConfig fields documented (none had docs)
- new tests/client_tls.rs: per-test rcgen private PKI + tokio-rustls
  HTTPS server; drives the real SharedHttpClient through
  HttpClientConfig file paths — CA-bundle success path, private-roots
  rejection (source-chain assertion: invalid peer certificate),
  mTLS end-to-end with client identity, mTLS rejection without
  identity, and reload-to-CA-bundle interplay
- dev-deps: rcgen 0.14, tokio-rustls 0.26, rustls 0.23 (aws_lc_rs),
  rustls-pki-types 1, uuid

Verified: cargo test (288 + 5 TLS), --all-features (359 + suites),
--no-default-features (288; pre-existing warnings only), clippy
--all-targets -D warnings (default + all-features), fmt --check,
cargo doc --no-deps.

Tasks: review-001-client-config-and-cert-coverage
2026-08-30 07:24:34 +00:00
glm-5.3-flash 1572a9d2d0 fix(gateway): fail-closed publish_schema compile + compile-once cache (GW-01 follow-up)
- new gateway::schema_cache — PublishSchemaCache: compile the op's
  publish_schema once per registration (value-keyed invalidation for
  hot reload), cache compile failures (logged once at error level,
  never retried per request)
- /publish compile failure is now fail-closed: the chunk stream
  terminates with INTERNAL (500), the error text stays in the log
  (no schema internals on the wire) — the per-request warn-and-skip
  unvalidated ingest path is removed
- schema resolution is lazy (first chunk poll, after invoke_sink's
  404/403/422 pre-checks — GW-11 order preserved) and keyed by schema
  value, so re-registration/hot reload is picked up (test)
- NdjsonChunkStream: first Err item is terminal (done + stream end),
  mirroring the wire pump's send(Err) + break — Ok chunks can never
  follow an error on the HTTP path either (found by spy-handler test)

Verified: cargo test (308), cargo test --all-features, clippy
--all-targets -D warnings (default + all-features), fmt --check.

Tasks: review-001-publish-schema-validation-robust
2026-08-30 07:02:27 +00:00
glm-5.3-flash 5c16e68f75 chore(deps): prune unused deps, tighten tokio features, align tungstenite (HY-01, HY-05, HY-07, HY-08, HY-12)
- HY-01: openapiv3 -> dev-dependencies (test-only usage)
- HY-05: drop bytes (src sites renamed to axum::body::Bytes re-export),
  keep parking_lot (genuinely used); tokio "full" -> the seven features
  actually used (macros, rt-multi-thread, io-util, net, fs, time, sync)
- HY-08: test-support extends wss; dev tokio-tungstenite matches the
  wss feature set
- HY-12: tokio-tungstenite 0.28 -> 0.29 to match axum's tungstenite;
  duplicate roots in cargo tree -d: 9 -> 6 (tungstenite, cpufeatures,
  rand dupes collapsed)
- HY-07 ride-along: READ_SLOTS privatized
- CON-10 residue: verified already fixed (full_surface required-features)

Verified: cargo test (300), --all-features (371+36), --features mcp
(355+9), --features wss (316), check --no-default-features, clippy
(all-targets and --all-features, -D warnings), fmt --check
2026-08-29 14:06:36 +00:00
glm-5.3-flash 0e67c7c00f test(adapters): order-independent assert in MCP search filter test 2026-08-29 13:49:28 +00:00
glm-5.3-flash c93f7387ff docs(websocket): detached task lifetime semantics (WS-10) 2026-08-29 13:46:57 +00:00
glm-5.3-flash 25975ac2a8 fix(websocket): configurable WS read idle timeout (WS-01) 2026-08-29 13:45:54 +00:00
glm-5.3-flash fa73684ebe fix(websocket): configurable WS session cap (WS-09) 2026-08-29 13:37:08 +00:00
glm-5.3-flash 4747a12c02 fix(websocket): retain WsPumps handle on the server path (WS-08) 2026-08-29 13:31:33 +00:00
glm-5.3-flash 96560b0b78 fix(websocket): shutdown drops the held write sender (WS-07) 2026-08-29 13:13:19 +00:00
glm-5.3-flash 6fa2d4c036 fix(adapters): MCP tool-gateway fidelity (PRJ-07..10, PRJ-13)
- search honors the optional query substring filter (PRJ-07)
- search excludes both Sub andPub ops per ADR-041/ADR-068; dead match
  arms removed (PRJ-08)
- batch returns {"results": [...]} with {isError, output|error} items;
  tool description states the shape (PRJ-09)
- structuredContent is always an object: non-object outputs wrapped as
  {"result": <output>} (PRJ-10)
- argument errors are CallError values (retryable always present);
  non-string operation no longer reports 'missing required field'
  (PRJ-13)

Verified: cargo test --all-features, cargo test, clippy -D warnings,
fmt --check
2026-08-29 12:53:03 +00:00
glm-5.3-flash a9d17405a7 fix(adapters): input-schema enforcement + header/cookie params (OAI-02, OAI-03, OAI-07, OAI-09)
- 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
2026-08-29 12:48:13 +00:00
glm-5.3-flash 4ef8499bbb fix(adapters): to_openapi runtime-truthful projection (PRJ-01..05, PRJ-11..15)
- /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).
2026-08-29 12:47:10 +00:00
glm-5.3-flash 1db0ea88e5 refactor(websocket): one generic pump implementation (WS-11, COV-03) 2026-08-29 12:11:50 +00:00
glm-5.3-flash 92cc11a74f fix(websocket): byte-based caps for pending buffer + inbound WS sizes (WS-05, WS-06) 2026-08-29 12:00:04 +00:00
glm-5.3-flash 5024d99862 fix(websocket): validate chunk length on write side (WS-04, HY-09) 2026-08-29 11:25:51 +00:00
glm-5.3-flash 239f11323e fix(gateway): internal-op invisibility on /schema + cache headers (SRV-02, PRJ-06, GW-02)
- SRV-02: schema_handler applies the same is_internal_op -> 404 pre-check
  as /call, /batch, /subscribe, /publish (tested unauthenticated,
  anonymous-token, unauthorized-identity)
- PRJ-06: MCP schema tool runs the symmetric pre-check (NOT_FOUND for
  internal, FORBIDDEN for ACL-denied) before dispatch; enshrining test
  fixed
- GW-02: /search + /schema carry Cache-Control: no-store and
  Vary: Authorization on success and error responses

Verification: cargo test 270 passed; --all-features 337+9+6+8+10 passed;
clippy -D warnings clean (default + --all-features); fmt clean.
2026-08-29 11:13:57 +00:00
glm-5.3-flash 8700ed0fea fix(adapters): consumer adapter hygiene (CON-01, CON-03..CON-13)
- CON-01: from_mcp discovery follows tools/list pagination
  (rmcp list_all_tools); three-page paginating-server test
- CON-03: from_wss refuses ws:// with a Bearer token unless
  FromWss::allow_plaintext() is called explicitly (tests: refusal,
  opt-in, token-less passthrough)
- CON-04: audio variant of content_block_union_schema requires
  ["type","data","mimeType"]; jsonschema-validated audio block
- CON-05/07: import-time credential documented on both adapters;
  dead per-call capability read removed
- CON-06: 401 classification typed-first (downcast to rmcp
  StreamableHttpError<reqwest::Error>; AuthRequired/InsufficientScope/
  Client with status 401); a :40101 URL no longer misclassifies (tested)
- CON-11: transport tools/call failures declare MCP_TRANSPORT_ERROR;
  rmcp JSON-RPC errors preserve code (MCP_JRPC_<code>) and data
- CON-12: tool names validated at import (/, whitespace, empty →
  SchemaParse); unit + integration tests
- CON-13: tokens held as alkcall Secret<String> (zeroize, redacted Debug)
- CON-08/09: no close handles; explicit-limitation notes in from_mcp
  module docs, from_wss module docs, and ADR-070
- CON-10: full_surface [[test]] required-features = ["mcp","test-support"];
  cargo test --features mcp now compiles and passes

Verified: cargo test; cargo test --features mcp; cargo test --all-features;
cargo clippy (--all-features) --all-targets -- -D warnings; cargo fmt --check
2026-08-29 10:54:33 +00:00
glm-5.3-flash 314472012d fix(server): hyper knobs, decoy fidelity, cache + builder fixes (SRV-04..SRV-10)
- SRV-04: TokioTimer on h1+h2 builder, header_read_timeout 10s,
  h1 keep-alive on, h2 keep-alive 30s/10s; concurrency boundary documented
- SRV-05: with_decoy rebuild keeps extra routes (clone, not take)
- SRV-07: method_not_allowed_fallback serves the nginx-shaped 405
- SRV-08: UTF-8 percent-decoding, literal '+', tokio::fs syscalls
- SRV-09: /openapi.json cached at construction, generic 500 body,
  to_openapi returns Result (expect removed)
- SRV-10: ChannelsPolicy extension injection point on the WS upgrade;
  single token resolution via route ordering (WS layer before the
  router-wide auth route_layer)

Verification: cargo test 265 passed; --all-features server::/to_openapi::
green; clippy + fmt clean on touched files (remaining tree noise is a
parallel agent's in-flight from_mcp/from_wss/forward work)
2026-08-29 10:47:44 +00:00
glm-5.3-flash 7ac2dc5005 test(adapters): cover FWD-07/08/10 response-decode paths (vendor JSON, caps, loud auth errors, bounded error echo, non-SSE Sub stream) 2026-08-29 10:45:47 +00:00
glm-5.3-flash 9bb8487c6d fix(adapters): upstream response decode fidelity (FWD-07, FWD-08, FWD-10, FWD-12) — core, tests follow 2026-08-29 10:26:03 +00:00
glm-5.3-flash 5df91cda25 fix(adapters): resolve parameter/requestBody refs + reject import collisions (OAI-04, OAI-05)
- 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
2026-08-29 10:13:11 +00:00
glm-5.3-flash 9bc9e669e1 fix(gateway): SSE terminality, deadline, error-mapping fidelity (GW-03..GW-07, GW-12..GW-14)
- GW-04: /subscribe error events are terminal — scan-based emission of
  the error frame, then end of stream (matches call.error semantics).
- GW-05: enforce the 30 s gateway deadline via tokio::time::timeout in
  GatewayDispatch::invoke; hung handlers surface as TIMEOUT (504),
  streaming/sink stay unbounded per ADR-021.
- GW-07: gateway error paths route through the new identity-aware
  call_error_to_http_response_with_identity; retryable HTTP_429/HTTP_503
  now carry Retry-After on /call, /batch, /search, /schema, /publish.
- GW-13: SSE keep-alive (15 s comment frames) + retry: 15000 field.
- GW-14: module doc fixed (6 endpoints; /publish lives in routes.rs).
- GW-03/GW-12: mapping rides d7ee302's INVALID_OPERATION_TYPE mapper
  (documented in http-server.md table); 200-on-stream asymmetry
  documented.

Verification: cargo test (243 passed); cargo clippy --all-targets -- -D
warnings clean; cargo fmt --check clean.
2026-08-29 10:13:03 +00:00
glm-5.3-flash 164a9d7543 fix(adapters): safe outbound URL construction (FWD-01, FWD-02)
- percent-encode path-parameter values with a WHATWG path-segment
  superset (/, %, ?, #, \\, controls): traversal values, query/fragment
  structure, and later-placeholder strings can no longer alter the
  request line (FWD-01)
- single-pass template rendering; rendered values are never
  re-substituted; unbound or unterminated placeholders error loudly
- append the request path to the base URL directory (https://host/v1
  + /chat/completions keeps /v1) instead of Url::join semantics,
  with a post-assembly origin-equality check (FWD-02)
- base_url validation: https/http-only scheme allowlist, explicit
  host required, userinfo rejected (credentials flow via
  Capabilities only); request_path is never empty

Verification: cargo test (238 lib tests incl. 8 new FWD-01/02 tests),
cargo clippy --all-targets -- -D warnings, cargo fmt --check
2026-08-29 10:09:08 +00:00
glm-5.3-flash a4df859771 fix(server): cap /mcp body size (SRV-03)
Add tests for the /mcp body cap and complete the task file.

- oversized POST /mcp with declared Content-Length > 8 MiB -> 413
  before any body read
- oversized chunked POST /mcp -> 413 (counting-stream cut mid-body;
  rmcp maps body errors to 500, so the middleware sources the status)
- normal-size initialize round-trip unchanged
- task file: status completed, Summary filled

Verified: cargo test (219), cargo test --features mcp --lib (257),
cargo test --all-features (269 + integration), clippy default and
--all-features (-D warnings), cargo fmt --check.
2026-08-29 09:42:42 +00:00
glm-5.3-flash a9ac6f6cbd fix(websocket): lossless EOF signal + pending sweep (WS-02, CON-02)
- Replace the axum/tungstenite pump paths' Notify-based read-EOF signal
  with a retained tokio watch channel: a late subscriber (monitor
  spawned after session setup, or pump EOF before the receiver is
  taken) still observes EOF (WS-02).
- from_wss drop monitor: on EOF (or session close) fail all pendings
  retryable, then keep sweeping the pending map every 1 s — calls
  registered after the initial fail_all (the forgotten-session import
  path) resolve instead of hanging (CON-02).
- Tests: drop-during-registration race variants (forget + held
  session) and a post-EOF registration resolved via the sweep; the
  existing no-hang test stays green.

cargo test (219), cargo test --features wss (231, 3x for flake check),
cargo clippy --all-targets -- -D warnings, cargo fmt --check
2026-08-29 09:36:34 +00:00
glm-5.3-flash 5ff88756eb fix(websocket): lossless EOF signal + pending sweep (WS-02, CON-02)
Replace the axum/tungstenite pump paths' Notify-based read-EOF signal
with a retained tokio watch channel so a late subscriber observes EOF
regardless of when it fired. Extend the from_wss drop monitor to sweep
the pending map (1 s interval) once EOF is observed, so calls
registered after the initial fail_all also resolve retryable instead
of hanging.

cargo test; cargo clippy --all-targets -- -D warnings (default +
all-features); cargo fmt --check
2026-08-29 08:48:10 +00:00