From 03a6689c99088d15529bf6f79e09a8bd8053d394 Mon Sep 17 00:00:00 2001 From: "glm-5.3-flash" Date: Sat, 17 Oct 2026 07:28:44 +0000 Subject: [PATCH] =?UTF-8?q?docs(review):=20012=20=E2=80=94=20gateway=20own?= =?UTF-8?q?ership=20seam,=20consumer=20input=20from=20alkgit's=20LFS=20POC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Report-only: GatewayDispatch::invoke hardwires ownership: None, so External namespace-ACL ops through the HTTP gateway fall back to the static Identity.resources model — token-carried claims admit without consulting revocable grant tables (POC-L tests/gateway_gap.rs). Three resolution shapes recorded; disposition is session-owned. --- ...2-gateway-ownership-seam-consumer-input.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/reviews/012-gateway-ownership-seam-consumer-input.md diff --git a/docs/reviews/012-gateway-ownership-seam-consumer-input.md b/docs/reviews/012-gateway-ownership-seam-consumer-input.md new file mode 100644 index 0000000..bfd812a --- /dev/null +++ b/docs/reviews/012-gateway-ownership-seam-consumer-input.md @@ -0,0 +1,105 @@ +# Review 012 — The gateway dispatch's hardwired `ownership: None`: downstream evidence from alkgit's LFS POC + +## Status + +Open — filed 2026-10-17. Report-only consumer input; the resolution +(session-owned) either threads an ownership provider through the +gateway dispatch path or blesses/documents the door-side compensation +pattern as the durable answer. Filed here rather than in the consumer +because the anchor is this crate's surface +(`src/gateway/dispatch.rs`), and because the family precedent exists +(review 011 — downstream input filed in the crate that owns the +seam). + +Input basis: + +- **POC-L** (alkgit, 2026-10-16; findings at + `/workspace/@alkdev/alkgit/docs/research/pocl-findings.md`; the + evidence test is `tests/gateway_gap.rs` in `/workspace/lfs-poc`) — + a real `git-lfs` client served through an alkhttp extras door + mapping the LFS batch API onto alkblobs' `ops` (`blobs/stat`, + namespace-ACL-gated, External visibility). +- The ask ledger: alkgit's `substrate-recomposition.md` + §Upstream-push candidates #6 (the "alkhttp gateway ownership seam" + entry — filed here under the crate that owns the dispatch spine). + +--- + +## 1. The finding + +`GatewayDispatch::invoke`'s `OperationContext` builder hardwires +`ownership: None` when it assembles an external identity's context +(`src/gateway/dispatch.rs`, the builder around lines 215–249; the +`ownership: None` field assignment verified this session). Downstream +consequence, demonstrated live in POC-L: + +- `AccessControl::check` for a namespace-ACL-gated op (`resource_id` + extracted from the payload — alkblobs' `blobs/stat` with + `resource_id_path: $.namespace`, External visibility) falls back + to the **static** `Identity.resources` model when no ownership + provider is present in the context. +- Therefore a token-**carried static claim** — e.g. an LFS-style + bearer JWT embedding `{"blob-namespace": ["read"]}` in its + resources map — **is admitted through the gateway with no runtime + provider consulted**, while the same op served through the native + call protocol consults the dynamic (revocable) grant table and + admits only live grants. +- The dynamic grant table is *unreachable* from this dispatch spine: + no thread of the gateway path carries an ownership provider, so a + deployment whose authorization truth is a revocable table gets + stale-at-birth answers on the HTTP-gateway surface — revocation + and grant changes never apply there. + +The security frame is the family's standing invariant +(visible-surface = authorized-surface): an External op must not have +two authorization paths with different revocation semantics +depending on which door the caller walked through. The static +fallback is admission-by-claim at the exact point where the family's +posture wants admission-by-live-grant. + +## 2. What it means for the consumer (and what they did about it) + +The POC-L door cannot use the gateway dispatch spine for these ops. +Its compensation: build the `OperationContext` itself (identity from +the resolved HTTP identity + the ownership provider wired +explicitly) and call `OperationRegistry::invoke("blobs/stat", …)` +directly — after which the namespace ACL runs exactly as it would on +the native wire, against live grant sets (POC-L finding 2, the batch +binding section). The compensation is correct and verified, but it +is per-door hand-assembly of what the dispatch spine exists to do — +every future door (alkhttp's `git` feature, the LFS door proper, +alkssh's surfaces) inherits the same manual step and the same trap of +forgetting it (the spine *works* with the fallback, silently). + +## 3. Resolution shapes (recommendation only — own session) + +1. **Thread an ownership provider through the dispatch path** — an + `Option` on the gateway dispatch builder (per-dispatch config or + a connection-establishment property), `None` only where a + deployment genuinely has no ownership model. Preserves the + single-spine posture; needs an ADR if the context shape is + frozen-published (alkcall is 0.9, published — the context builder + surface's semver class decides). +2. **Bless the compensation**: document the door-side pattern (door + builds its own context with ownership wired; never route + External namespace-ACL ops through `GatewayDispatch::invoke` + without it) as a *door contract* line in the gateway docs, plus + a loud static-fallback warning at the hardwired site. Cheap, but + the trap stays structural — silent-by-default. +3. **Hybrid**: doc contract now (2), seam threading as the ADR'd + follow-up (1). Probably where the session lands, given 0.9 is + published and the ADR round for the context shape is not open. + +**Verification status**: the `ownership: None` anchor and the +builder's context assembly were re-read this session; the admission +behavior (static `Identity.resources` fallback, token-carried claim +admitted) is POC-L's `tests/gateway_gap.rs` result, witnessed in the +findings doc. Re-verify the behavior during resolution by re-running +that test against a current alkcall. + +## 4. Non-goal note + +Not asking for anything on the native call-protocol path: the +channel-path dispatch carries ownership correctly (the ops wire path +is what the whole family gates through). The finding is scoped to +the gateway dispatch spine used by HTTP-door routing. \ No newline at end of file