docs(arch): hub-wiring cluster — aggregated env, peer_operations, from_call cleanup, alknet-hub crate spec

Three ADRs addressing gaps surfaced by the first hub consumer (alkapi):

- ADR-067: Aggregated peer-env wiring — Dispatcher::with_aggregated_env hook
  so compose_root_env reads a shared PeerCompositeEnv across all calls,
  not a fresh per-call one. The hub-defining gap (alkapi OQ-08 / G.1).

- ADR-068: PeerCompositeEnv::peer_operations override — adds
  list_operation_names() to OperationEnv, overrides on OverlayOperationEnv
  and PeerCompositeEnv. Fixes services/list-peers returning empty operation
  lists for non-local peers (alkapi G.6).

- ADR-069: from_call is a manual free function, not auto-wired — reverses
  the aspirational OQ-27 resolution to match the implementation. Cleans
  the 'v1 default' hedging language in ADR-017 and client-and-adapters.md
  (alkapi G.4).

New crate spec: alknet-hub — reusable hub pattern (aggregated env,
connection lifecycle, worker supervision with backoff, service discovery).

Spec fixes: builder API drift (with_local/with_local_streaming separation),
ScopedOperationEnv → ScopedPeerEnv type name, OQ count 51→54.

Three new OQs: OQ-52 (wait_for_close), OQ-53 (backoff defaults),
OQ-54 (inbound hook placement).
This commit is contained in:
glm-5.2 committed 2026-07-09 16:01:30 +00:00
1 parent 2282647f8b
commit 87b2c2a5ef
15 files changed
+1052 -47

No files matched your search

@@ -1,15 +1,20 @@
# OQ-27: from_call Re-Import Trigger
- **Origin**: [client-and-adapters.md](crates/call/client-and-adapters.md), ADR-017 Assumption 4
- **Status**: **resolved** (2026-06-27)
- **Status**: **resolved** (2026-07-09 — amended from 2026-06-27 resolution)
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: The decision is **auto-re-import on connection
establishment**. The overlay is per-connection (Layer 2, ADR-024), so a
stale overlay dies with the connection; re-import on reconnect is
naturally scoped to the new connection. This is the right default for the
runner pattern (a worker reconnects → the hub re-discovers the worker's
ops automatically). An explicit `CallConnection::refresh()` method is a
genuine feature addition — non-breaking, additive — if a deployment
needs manual control.
- **Resolution**: `from_call` is a **manual free function**; the assembly layer
calls it after `connect()`. The overlay is per-connection (Layer 2, ADR-024),
so a stale overlay dies with the connection; re-import on reconnect is
naturally scoped to the new connection. A `CallConnection::refresh()` method
for mid-connection re-discovery is a genuine feature addition —
non-breaking, additive — if a deployment needs manual re-discovery without
drop-and-reconnect. See [ADR-069](../decisions/069-from-call-manual-free-function.md)
for the full rationale.
The original 2026-06-27 resolution ("auto-re-import on connection
establishment") was aspirational — written before the implementation
existed. The implementation made the right call (manual free function);
this amendment aligns the spec with the implementation.
- **Cross-references**: ADR-017, ADR-024, [client-and-adapters.md](crates/call/client-and-adapters.md)
@@ -0,0 +1,26 @@
# OQ-52: CallConnection::wait_for_close() for Supervision Loop
- **Origin**: [crates/hub/README.md](crates/hub/README.md)
- **Status**: open
- **Door type**: Two-way
- **Priority**: medium
- **Resolution**: Not yet decided.
The hub's worker supervision loop needs a way to await connection close so it
can call `detach_peer` and retry. Today `CallConnection` exposes `connection()`
(the underlying `Connection`) but not a "wait for run_loop exit" future.
Options:
- **(a)** Add a `closed()` method to `CallConnection` that returns a
`Future<Output = ()>` resolving when `run_loop` exits. The dispatcher
signals a `tokio::sync::Notify` on exit; `closed()` awaits it.
- **(b)** Use a `tokio::sync::oneshot` channel created by the caller and
passed to the dispatcher, signaled on `run_loop` exit.
- **(c)** Poll `connection().accept_bi()` in a loop until it returns
`ConnectionClosed` — works but is polling, not event-driven.
Option (a) is the cleanest: a method on `CallConnection` that any caller
(not just the hub) can use to await connection close. It is a small additive
change to `alknet-call`.
- **Cross-references**: ADR-067, [crates/hub/README.md](crates/hub/README.md)
@@ -0,0 +1,19 @@
# OQ-53: BackoffConfig Default Policy
- **Origin**: [crates/hub/README.md](crates/hub/README.md)
- **Status**: open
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: Not yet decided.
The `BackoffConfig` struct provides configurable backoff for worker
reconnection. The default policy (1s initial, 60s max, 2x multiplier) is a
starting point. Whether this is the right policy for production deployments
is an operational question, not an architectural one.
The `BackoffConfig` struct shape is committed; the defaults are a starting
point that can be changed without breaking the API. The question is whether
to change the defaults before the first release, based on operational
experience from the alkapi deployment.
- **Cross-references**: [crates/hub/README.md](crates/hub/README.md)
@@ -0,0 +1,26 @@
# OQ-54: Inbound Worker on_worker_connected Hook Placement
- **Origin**: [crates/hub/README.md](crates/hub/README.md)
- **Status**: open
- **Door type**: Two-way
- **Priority**: low
- **Resolution**: Not yet decided.
When a worker connects inbound (via `CallAdapter::handle`), the hub needs to
call `from_call`, register the bundles, and `attach_peer`. Two options:
- **(a) Explicit**: The assembly layer calls `hub.on_worker_connected()` after
`CallAdapter::handle` accepts the connection. The hub provides the method;
the assembly layer wires it.
- **(b) Wrapper**: The hub provides a `HubCallAdapter` wrapper that calls the
hook automatically inside `handle()`. The assembly layer uses the wrapper
instead of the raw `CallAdapter`.
Option (a) is simpler and more flexible — the assembly layer controls when
the hook fires and can add its own logic (authorization checks, logging)
before discovery. Option (b) is more convenient but hides the hook from the
assembly layer.
The explicit approach is the committed interim. A wrapper is additive.
- **Cross-references**: ADR-067, ADR-068, [crates/hub/README.md](crates/hub/README.md)