docs(review): 012 — gateway ownership seam, consumer input from alkgit's LFS POC
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.
This commit is contained in:
1 parent
1a493e9748
commit
03a6689c99
1 file changed
+105
@@ -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.
|
||||
Reference in new issue
Block a user