Files
alkhttp/docs/architecture/decisions/072-http-surface-ownership-wiring.md
glm-5.3-flash ad63507124 feat(server): thread alkcall 0.10 ownership wiring onto the HTTP surface
Ride the alkcall 0.10 gateway ownership seam (review 012, ADR-048
amendment) into the door layer:

- HttpAdapter::with_ownership_provider /
  HttpAdapter::with_static_ownership_fallback mirror alkcall's builder
  shape; OwnershipProvider re-exported next to GatewayDispatch
- RouterState carries the wiring; GatewayState::dispatch is the single
  spine builder for both the gateway routes and the MCP nest
- loud default passes through: a resource_type-gated op on an unwired
  spine is refused (FORBIDDEN envelope -> 403/401) on HTTP exactly as
  on the native wire
- WS native-session path untouched (consults ownership natively)

fix: with_ws_max_sessions never stored the fresh session-slot
semaphore; later builder calls silently reverted the cap. Builder
state now assembled through one router_state() helper.

infra: fuzz/shared follows to alkcall 0.10 (two alkcall copies in one
build would type-clash); alkhttp 0.8.0 per the dep-wave convention.

Verification: cargo test (444 pass, incl. 3 new call_resource_gated_*
HTTP-surface pins), clippy --all-targets -D warnings, fmt --check,
test --all-features (520), fuzz corpus replay (21/21), doc.
2026-10-17 16:00:12 +00:00

3.8 KiB

ADR-072: HTTP-Surface Ownership Wiring (Gateway + MCP)

Status

Accepted

Context

alkcall 0.10.0 landed the gateway ownership seam (alkcall review 012, downstream evidence from alkgit's LFS POC): the dispatch spine's previously-silent static Identity.resources fallback becomes loud — a resource_type-gated op served through an unwired spine is refused with a FORBIDDEN error envelope unless the deployment wires a runtime ownership provider (GatewayDispatch::with_ownership_provider) or declares the static fallback explicitly (GatewayDispatch::with_static_ownership_fallback). The change closes the two-authorization-paths drift: a namespace-ACL-gated op served through an HTTP door previously ran against the token-carried static claim, while the same op on the native wire consulted the revocable grant table.

alkhttp is the only live spine consumer. It constructs the spine internally in two places — the gateway endpoints' per-request spine (gateway::routes::GatewayState) and the MCP nest's spine (server::build_router) — so an assembly layer wiring a provider for the native wire cannot reach those spines through alkcall's API. Once alkhttp rode 0.10, any resource_type-gated op in the registry would break on the HTTP surface even for deployments that wired providers correctly.

Decision

alkhttp mirrors alkcall's builder shape on HttpAdapter and threads the wiring into every spine it constructs.

  • HttpAdapter::with_ownership_provider(Arc<dyn OwnershipProvider>) and HttpAdapter::with_static_ownership_fallback() — the two alkcall-named opt-ins, one method each, no alkcall-specific vocabulary changed. OwnershipProvider is re-exported from alkhttp::gateway next to GatewayDispatch.
  • The wiring lives on RouterState and is threaded by GatewayState::dispatch() (routes) and RouterState::dispatch() (the MCP nest — the gateway routes extract GatewayState via FromRef, so GatewayState is the single wiring point).
  • Both spines share one wiring: the gateway endpoints and the MCP surface are the same door contract; no separate MCP wiring exists.

The door contract (alkcall review 012, unchanged here): any deployment serving resource_type-gated ops through the HTTP surface must wire the same provider the native wire consults — visible-surface = authorized-surface. The loud default passes through from alkcall unchanged: an unwired, undeclared spine refuses a resource_type-gated op with the spine's FORBIDDEN envelope, which the gateway error mapping serves as 403 (with identity) / 401 (without).

The WS native-session path needs no wiring — it speaks the native call protocol, which consults ownership natively in alkcall's serving layer (alkcall ADR-048).

Consequences

Positive:

  • The HTTP surface cannot silently diverge from the native wire's ownership truth — alkcall's loud default is inheritable at the door layer, and with_static_ownership_fallback keeps the static-claims posture a code-review-visible declaration here too.
  • alkcall's builder shape is mirrored on alkhttp's builder, so an assembly layer configures both spines the same way.

Negative:

  • Deployments upgrading alkhttp to the 0.10-riding release must wire the provider (or declare the fallback) before serving resource_type-gated ops over HTTP — the 403 refusal is the reminder, not a compile error.

References

  • alkcall review 012 (/workspace/@alkdev/alkcall/docs/reviews/012-gateway-ownership-seam-consumer-input.md) — the consumer evidence and the two recorded decisions
  • alkcall 0.10.0 CHANGELOG (the loud-default behavior break)
  • ADR-048 — the WS path this does not touch
  • Review 008 — the same consumption round's alkhttp-filed findings (AH-1, AH-2)