Batch-fixes every remaining open alkcall-side finding from downstream
consumers so 0.7.0 is the only release they need to absorb.
- CF-006 (the CF-005 corollary): run_open_wrapper derives a per-call
AuthContext — the opener's dispatch-resolved identity (the same
identity the ACL gate and cap check saw) overlaid onto the
install-time context — and passes it to both the establisher and
the pump handler. Identity-less calls keep the install-time
identity (no synthetic-anonymous rewrite); transport-truthful
fields are never rewritten. Signatures unchanged — behavior-only;
identical on per-connection registries, hub-forwarded opens now
show the end client. Gates:
open_wrapper_overlays_per_call_identity_on_install_time_auth +
open_wrapper_keeps_install_time_identity_when_call_identityless.
- CF-007 (alkhttp review 006 Part C doc drift): ADR-016 amended to
the eight-code list — ALREADY_EXISTS + CONNECTION_CLOSED in the
Context, §3 table, and from_openapi collision rule; new §2a
documents the undelivered-vs-ambiguous write-failure distinction.
- ChannelPlan type doc now states the Send + Sync payload constraint
(alktunnels POC F-1 re-derived it by compiler error).
- Ledger: CF-006 and CF-007 filed + resolved; CF-005's corollary
note points at CF-006; the Open section is empty. ADR-049 gains
the per-call-identity note. Changelog 0.7.0 covers the batch.
Sweep result (all downstream reviews): no other open alkcall items —
alktunnels W3/F-2 are downstream-by-design, alktty R3/P14 are closed
constraints, alknet has none; OQ-24/37/39/40/41 stay
deferred-by-design (no consumer pull yet).
Verification: cargo test (631) + --all-features (648), clippy
(all-targets, all-features, -D warnings), fmt --check, doc
--no-deps, wasm32 check, semver-checks (no update required),
publish --dry-run.
Verifies and fixes CF-005 (alktunnels reverse-flow POC W1): the
connect-side serving path built channel 0 internally and never set an
identity, so a scope-gated serving op could only be satisfied via the
payload auth_token. Token is now the fallback (hub-forwarding /
browser path); transport/key-based identity is the primary path.
- ServingConfig gains identity: Option<Identity> — the explicit
override (remediation a). Semver-relevant struct-literal change →
0.7.0 (minor bump at 0.x, wire surface unchanged).
- from_connection_with_serving propagates the transport
Connection::identity() to the channel-0 connection via set_identity
before the serving loop starts (remediation b) — mirrors the accept
side's install-hook set_identity; process-local, nothing new on the
wire.
- Dispatch identity precedence on the serving loop: payload
auth_token → identity_provider, then ServingConfig.identity, then
transport identity; identity-less dispatch still fails closed
(FORBIDDEN).
- Public core::auth::NoopIdentityProvider (resolves nothing; the
ServingConfig::default() provider — three private test copies
existed).
- Regression gates: four cf005_* e2e tests (transport propagation,
override precedence, identity-less denial, token fallback +
precedence).
- Ledger CF-005 → resolved; ADR-022 §connect-side-serving amended;
README example updated; changelog 0.7.0.
Verification: cargo test (629) + --all-features (646), clippy
(all-targets, all-features, -D warnings), fmt --check, doc
--no-deps, wasm32 check, semver-checks (no update required at
0.7.0), publish --dry-run.
- 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.
Found via alkhttp Review 002 (PRJ-16): the services/schema handler
does a bare registry.registration(name) with no Visibility and no
AccessControl check, so POST /call (and the MCP call tool) can fetch
any Internal op's complete spec unauthenticated. The GET /schema route
and MCP schema tool enforce the pre-checks; the /call path is the
hole.
Found while fixing alkhttp's HTTP-side instance
(review-001-publish-schema-validation-robust): the identical
warn-and-skip pattern exists at src/protocol/dispatch.rs:352-366 — a
compile failure of a Pub op's publish_schema proceeds with
validator: None, so arbitrary unvalidated JSON reaches the sink
handler over the wire. Suggested direction: fail-closed + per-
registration validator cache (worked shape in alkhttp 1572a9d,
src/gateway/schema_cache.rs) + registration-time schema compilation.
Single compile site verified (both pump arms consume the one
InFlightSink.publish_validator).
Cross-crate finding from alkhttp Review 001 (WS-12), filed here per the
ledger's purpose (consumer-surfaced alkcall findings).
src/channels/adapter.rs:144: the ChunkError::TooLarge arm allocates
vec![0u8; length] from the peer's untrusted 8-byte header before
reading; length is u32, so ~4 GiB can be pinned per connection and held
indefinitely by a dribbling peer. Reachable via the alkhttp WS path by
any authenticated browser. Skip/resync logic is correct; the memory
shape is wrong — stream-skip with a bounded buffer instead.
The normal payload arm (:159) is safe (parse_header bounds it at
MAX_CHUNK_LEN); only the TooLarge arm is unbounded.