From e29672942cf3c64486117a31f370085094e6df52 Mon Sep 17 00:00:00 2001 From: "glm-5.2" Date: Sat, 4 Jul 2026 11:38:23 +0000 Subject: [PATCH] =?UTF-8?q?docs(arch):=20record=20OQ-42=20=E2=80=94=20dyna?= =?UTF-8?q?mic=20resource=20ownership=20for=20runtime-spawned=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alknet-docker POC research surfaced that containers are a natural AccessControl resource, but the resource set is dynamic (containers are created at runtime) and ownership is derived from creation — which the current static Identity.resources model (config-sourced via PeerEntry/CompositionAuthority) doesn't fit. The issue generalizes to every crate that spawns a thing at runtime and exposes it over the call protocol (docker, tty, opencode-runner wrapper, alknet-container fleet layer); solving it per-crate would diverge. Recording as OQ-42 in the centralized tracker with the generalized framing so the architecture workflow sees it: one-way door at the model level (core/call), two-way at the mechanism level, high priority, blocks the dependent crate specs. A Phase 0 research/POC pass is likely warranted before the ADR. --- docs/architecture/README.md | 5 +- docs/architecture/open-questions.md | 116 +++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 3 deletions(-) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index a0ef848..6bcc25a 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1,6 +1,6 @@ --- status: draft -last_updated: 2026-07-02 +last_updated: 2026-07-04 --- # Alknet Architecture @@ -155,6 +155,9 @@ See [open-questions.md](open-questions.md) for the full tracker. - **OQ-39**: ~~`to_openapi` published-spec versioning~~ — **resolved by ADR-045** (`info.version` semver tracks the gateway endpoint contract, not the operation set; per-caller operations discovered via `/search`) - **OQ-41**: Stream operators library — a handler-level utility library (filter, map, batch, dedupe, window, etc. on `BoxStream`), prior art in `@alkdev/pubsub/operators.ts`; feature extension, not an architectural decision (the architecture decision — stream composition is handler-level, not protocol-level — is made in ADR-049) +**Open (blocking, requires ADR before the dependent crate specs):** +- **OQ-42**: Dynamic resource ownership for runtime-spawned resources — surfaced by the alknet-docker POC (containers as `AccessControl` resources), generalized to every "spawn a thing at runtime and expose it over the call protocol" crate (docker, tty, opencode-runner wrapper, `alknet-container` fleet layer). The current `Identity.resources` → `AccessControl::check` model is static (config-sourced via `PeerEntry`/`CompositionAuthority`); runtime-spawned resources with derived ownership don't fit. One-way door at the model level (core/call), two-way at the mechanism level. High priority — blocks the docker/tty/runner/fleet crate specs. Likely warrants a Phase 0 research/POC pass before the ADR. + **Deferred (not active):** - **OQ-09**: WASM target boundaries — design constraint, not deliverable - **OQ-10**: Git adapter scope — start with smart protocol, add ERC721 later diff --git a/docs/architecture/open-questions.md b/docs/architecture/open-questions.md index afd1800..d834624 100644 --- a/docs/architecture/open-questions.md +++ b/docs/architecture/open-questions.md @@ -1,6 +1,6 @@ --- status: draft -last_updated: 2026-07-02 +last_updated: 2026-07-04 --- # Open Questions @@ -954,4 +954,116 @@ is a feature extension, not an unmade architecture decision. the utility library, which is scheduling work, not architecture work. - **Cross-references**: ADR-049, [operation-registry.md](crates/call/operation-registry.md) §"OperationEnv", - `/workspace/@alkdev/pubsub/src/operators.ts` (TS prior art) \ No newline at end of file + `/workspace/@alkdev/pubsub/src/operators.ts` (TS prior art) + +## Theme: Runtime-Spawned Resources and Ownership + +### OQ-42: Dynamic Resource Ownership for Runtime-Spawned Resources + +- **Origin**: [alknet-docker POC summary](../../research/alknet-docker/poc-summary.md) + §"Open Unknowns" #3 (container-as-resource identity model); generalized + during the Phase 1 review pass triggered by that research finding. +- **Status**: open +- **Door type**: One-way (the `Identity.resources` → `AccessControl::check` + model in core/call), two-way (the mechanism for supplementing it) +- **Priority**: high — blocks the alknet-docker, alknet-tty, opencode-runner + wrapper, and `alknet-container` (fleet normalization) crate specs. None of + those specs can declare their `AccessControl` shapes until this is resolved, + because the model available to them determines what ACL declarations are + even expressible. Permitting "docker picks a per-crate default and the + others follow" is the door-type-as-deferral anti-pattern (ADR-009 §"What + this framework is NOT"): each crate bakes in an ACL shape and downstream + crates build on whatever default was picked, making the "cheap reversal" + expensive. +- **Resolution**: Not yet made. This OQ records the issue and its scope so the + architecture workflow can see it; the resolution requires an ADR (and, + given the one-way door, likely a Phase 0 research/POC pass first). + + **The issue, generalized.** The alknet-docker POC research flagged that + containers are a natural resource for `AccessControl` (`resource_type: + "container"`, `resource_action: "exec"`), but containers are created at + runtime — the resource set is dynamic, and "who can exec into container C" + is a function of "who created C," not of a static `PeerEntry.resources` + entry an operator wrote. The POC agent's one-line summary — "the + `IdentityProvider` model in alknet-core is currently static (`PeerEntry` + set). Dynamic resource ownership needs a spec" — is accurate, and the + consequence it draws is the right one. + + The issue is broader than the docker case that surfaced it. **Every crate + that spawns a thing at runtime and exposes it over the call protocol hits + the same shape:** + + | Crate (some prospective) | Runtime-spawned resource | Ownership derivation | + |--------------------------|--------------------------|----------------------| + | alknet-docker | container ID | who created the container | + | alknet-tty | a TTY session (often bound to a container) | who opened the session | + | opencode-runner wrapper (a runner crate that starts an opencode instance in a container and wraps its OpenAPI surface via `alknet-http`'s `from_openapi`) | an opencode instance | who started the instance | + | `alknet-container` (fleet normalization, per the POC summary's §6 boundary) | a normalized container across multiple hosts | transitively, who created the underlying container | + + All share: (a) resources are runtime-spawned, not config-declared; (b) + ownership is derived from creation, not from a static ACL entry; (c) the + resource set churns continuously (instances start and stop constantly), + so any model requiring operator config edits per resource is a non-starter; + (d) the resource's lifecycle is bound to a process the call protocol + itself is managing, so ownership state and spawn/teardown must be coupled, + not two separate operator workflows. + + Solving this inside the docker crate spec would re-solve it identically in + each of those crates and they would diverge. The model has to live in + core/call, once. + + **What the current model does, and why it doesn't fit.** Today + `Identity.resources: HashMap>` is populated on two + paths, both static: from `PeerEntry.resources` (fingerprint/auth-token, + ADR-030) or from `CompositionAuthority.resources` (composition, ADR-015 / + ADR-022). `AccessControl::check` + (`crates/alknet-call/src/registry/spec.rs:59–108`) does a literal + `identity.resources.get(resource_type)` lookup and a string-equals match + on `resource_action`. There is no notion of "the resource set is dynamic" + in that function — it reads a map built when the identity was resolved. + `ConfigIdentityProvider` reads from `ArcSwap` (hot-reloadable + config), and `IdentityStore` (ADR-035) adds async `put_peer` / + `update_peer` / `remove_peer` — but those are *administrative* mutations + (operator-grade peer-record management), not "peer X just spawned resource + Y at runtime; record that Y is owned by X." The resource ownership path is + a different concern from peer-record management, even if they end up + sharing storage. + + **The one-way door.** Whether `Identity.resources` stays static-config- + sourced or gains a dynamic-ownership supplement (and what the trait shape + for that supplement is) determines what `AccessControl` declarations the + docker/tty/runner/fleet specs can make, what every `AccessControl::check` + call site reads, and what every consumer of `Identity` assumes. This is + core, not per-crate. The *mechanism* (a new `ResourceOwnershipProvider` + trait vs. extending `IdentityStore` vs. labels/ownership-tags on the + spawned resources themselves vs. a capability-style model) is two-way — + additive, reversible per the ADR-009 definition — but the model-level + decision is one-way. + + **Known consumers of the resolution.** Any spec that declares an + `AccessControl` with `resource_type`/`resource_action` against a + runtime-spawned resource set needs this resolved first. Today that's the + docker, tty, opencode-runner, and `alknet-container` specs; future + runner-shaped crates (any GPU-job runner, any "spawn a process and expose + it over the call protocol" crate) inherit the same requirement. The + resolution should make the model available in core/call so those specs + declare ACLs against it directly, rather than each crate inventing a + per-crate ownership layer. + + **Phase 0 may be warranted.** Given the one-way door at the model level + and the number of plausible mechanisms (each with different tradeoffs + around consistency, teardown coupling, fleet-scale state, and how a + remote spoke's spawned resources are represented on the hub), a targeted + research/POC pass before the ADR is likely the right sequencing — but + that's a decision to make once the candidate mechanisms are enumerated, + not something this OQ pre-commits. +- **Cross-references**: ADR-009 (door-type-as-deferral anti-pattern), + ADR-015, ADR-022 (the static `CompositionAuthority.resources` model this + questions), ADR-030, ADR-035 (`IdentityStore` — administrative peer + mutations, a different concern from runtime resource ownership), + [auth.md](crates/core/auth.md) (`Identity.resources`, `AccessControl::check` + interaction), + [operation-registry.md](crates/call/operation-registry.md) (`AccessControl`, + `OperationSpec`), + [alknet-docker POC summary](../../research/alknet-docker/poc-summary.md) + §"Open Unknowns" #3