Files
alkcall/docs/reviews/consumer-findings-ledger.md
T
glm-5.3-flash 8cb2a6eb6d fix: remediate consumer findings CF-001..004 (alkhttp ledger)
- CF-004: services_schema_handler now applies the same visibility +
  AccessControl gates as invoke() (identity resolution mirrors invoke:
  handler_identity under internal). Restricted ops return spec-404 NOT_FOUND
  — matches "restricted ops don't exist" and leaks nothing about the
  restricted surface. Closes the unauthenticated /call-path disclosure.
- CF-003: publish_schema compiled at registration time (both
  OperationRegistry::register and OperationRegistryBuilder::store);
  un-compilable schemas are a registration error — an unvalidated ingest
  path can no longer be constructed. Compiled validator cached per-op
  (publish_validator) and consumed by dispatch; per-request compile gone.
  BEHAVIOR CHANGE: register/builder reject un-compilable publish_schema.
- CF-002: demux TooLarge skip streams through a fixed 64 KiB buffer
  instead of allocating the peer-declared length (u32, up to ~4 GiB);
  cumulative 256 MiB skipped-bytes budget tears down dribbling peers.
  Existing resync test passes unchanged.
- CF-001: new retryable CallError::connection_closed (CONNECTION_CLOSED)
  applied only where the call is provably undelivered — request-frame
  write failures on all consumer paths (call/subscribe/publish, both
  stream modes; publish pump tags write stages). Mid-publish failures and
  producer-side fail_all stay non-retryable INTERNAL (delivery ambiguous).
  New code string is additive; retryable flag is the machine-readable
  signal.

Verification: cargo test (558 pass, 15 new), clippy --all-targets -D
warnings, fmt --check, wasm32-unknown-unknown check.
2026-08-31 08:22:55 +00:00

5.8 KiB

Consumer findings ledger — alkhttp consuming alkcall

Findings from writing the first real consumer (alkhttp) against alkcall. alkcall is deliberately minimal; expect missing features and edge-case bugs to surface here first. Extract items into alkcall tasks/reviews as the project sees fit.

Format: date | found-in (alkhttp context) | severity | status.


Open

(none)


Resolved

CF-004 — services_schema_handler discloses Internal/ACL-restricted op specs — no visibility or AccessControl check (2026-08-30) — RESOLVED 2026-08-31

  • Found in: alkhttp Review 002, finding PRJ-16 (alkhttp/docs/reviews/002-post-remediation-review.md, Part F', verified against tree 91483a7 + alkcall source). Filed to this ledger 2026-08-30. src/registry/discovery.rs:327-343 (services_schema_handler) — the handler did a bare registry.registration(&name) and returned spec_to_json(&reg.spec) verbatim, with no Visibility check and no AccessControl::check(peer_identity).
  • Fix: the handler now applies the same two gates as OperationRegistry::invoke — the Internal-visibility rejection (!ctx.internal → spec-404) and AccessControl::check with the same identity resolution invoke() uses (handler_identity when internal, identity otherwise). Restricted ops return NOT_FOUND (spec-404) rather than FORBIDDEN — no information leaks about the restricted surface's existence or shape. The gate is in the handler itself, so every transport (wire /call, HTTP routes, MCP tools) is covered.
  • Status: resolved — 2026-08-30.

CF-003 — publish_schema compile failure is fail-open on the wire dispatch path (2026-08-30) — RESOLVED 2026-08-30

  • Found in: alkhttp Review 001 post-remediation task review-001-publish-schema-validation-robust (the GW-01/#1 follow-up work). While fixing alkhttp's HTTP-side instance of the pattern, the identical instance was verified on the wire dispatch path: src/protocol/dispatch.rs:352-366 (Dispatcher::dispatch, OperationType::Pub arm) — when a Pub op's publish_schema failed to compile, the pump logged a warn and proceeded with validator: None (silent unvalidated ingest, plus per-request recompile cost).
  • Fix: fail-closed at the source — publish_schema is now compiled at registration time in both insertion points (OperationRegistry::register and OperationRegistryBuilder::store); an un-compilable schema is a registration error, so an unvalidated-ingest path can never be constructed. The compiled validator is cached per-op (OperationRegistry::publish_validator) and the dispatch path consumes the cache — the per-request compile is gone. The from_call import path inherits the guarantee (imports register through the same choke point); forwarded-chunk validation on the producer side is the remote peer's dispatch path and now inherits the same fail-closed property.
  • Behavior change (semver-relevant): register/builder methods return Err for an un-compilable publish_schema (previously: accepted). Code that registered garbage schemas will now get a registration error instead of a silently-unvalidated op.
  • Status: resolved — 2026-08-30.

CF-002 — demux TooLarge skip allocates the full peer-declared length up front (up to ~4 GiB from an 8-byte header) (2026-08-30) — RESOLVED 2026-08-30

  • Found in: alkhttp Review 001, finding WS-12 (cross-crate; docs/reviews/001-initial-implementation-review.md, Part B, verified against tree 4a825d3). Filed to this ledger 2026-08-30. src/channels/adapter.rs:144 (run_demux_loop_for_client) — the ChunkError::TooLarge arm allocated let mut discard = vec![0u8; length as usize]; before reading: the buffer was sized from the peer's untrusted 8-byte header (u32 length, up to ~4 GiB).
  • Fix: stream-skip with a fixed 64 KiB buffer + read_exact loop consuming length bytes — no allocation sized from peer input. Plus a cumulative skipped-bytes budget (256 MiB) that tears down the connection when a peer loops TooLarge headers to burn bandwidth/CPU. The budget resets on each valid chunk, so legitimate isolated oversized chunks (the resync path, covered by the existing test) are unaffected. The existing demux_resyncs_after_oversized_chunk test passes unchanged.
  • Status: resolved — 2026-08-30.

CF-001 — call_single_stream write-failure maps to non-retryable INTERNAL (2026-08-29) — RESOLVED 2026-08-30

  • Found in: alkhttp from_wss drop-monitor race tests (WS-02/CON-02, review-001-ws-eof-signal). When the transport mux died mid-call, the write path failed fast and the write-failure mapping produced a non-retryable INTERNAL: failed to write request frame — even though the call never reached the producer and a reconnect/retry would be safe.
  • Fix: new retryable CallError::connection_closed constructor (CONNECTION_CLOSED code, retryable: true), applied only to write failures where the call is provably undelivered: call.requested frame write failures on all consumer paths (call/subscribe/publish, single-stream and stream-per-request; the publish pump tags its write stages so only the request-frame stage is retryable). Mid-publish and completed-frame write failures stay INTERNAL (delivery ambiguous — retry unsafe), and the producer-side fail_all(...) on connection close stays INTERNAL. The new code string is additive to the wire error vocabulary; CONNECTION_CLOSED is new — consumers should treat unknown codes per their existing policy (the retryable flag is the machine-readable signal).
  • Status: resolved — 2026-08-30. alkhttp follow-up: the review-001-ws-eof-signal race test can now tighten back to retryable-only asserts.