docs(arch): resolve OQ-45 — no application-level windowing; QUIC per-stream flow control suffices
The backpressure chain is complete by construction: QUIC flow control
→ bounded drainer channel → bounded stdout channel → OS pipe/PTY buffer
→ process write() blocks. Every link awaits its producer; no unbounded
buffer breaks the chain. The reversal path (an additive ControlMessage
variant, not a wire-format header change) is noted in ADR-052's
consequences as a two-way door, not the expected path.
Tightened ADR-052 assumption 1 from a hedge ('expected to work; a POC
would confirm') to a decided constraint. Updated all OQ-45 references
across the tty spec docs from 'open (low risk)' to 'resolved'.
This commit is contained in:
1 parent
df5d04af1d
commit
db4da1e6f0
7 files changed
+131
-59
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-07-06
|
||||
last_updated: 2026-07-07
|
||||
---
|
||||
|
||||
# alknet-tty
|
||||
@@ -48,7 +48,7 @@ the PTY (docker, SSH, local process) via a `TtyBackend` trait.
|
||||
|----|-------|--------|-----------|
|
||||
| OQ-43 | `TtyControl` trait object `Clone` constraint | resolved | `control: Option<Box<dyn TtyControl + Send + Unpin + Clone>>` via an `Arc`-backed `Clone` newtype; confirmed by the POC's concrete `PtyControl` |
|
||||
| OQ-44 | Terminal modes (TTY modes) | deferred(scope) | `TerminalParams.modes` reserved; default terminal modes suffice for current scope; blocked on a concrete mode-control use case |
|
||||
| OQ-45 | Flow control for high-throughput stdout | open (low risk) | QUIC per-stream flow control is expected to suffice; a high-volume POC would confirm |
|
||||
| OQ-45 | Flow control for high-throughput stdout | resolved | QUIC per-stream flow control is the backpressure mechanism (chain complete by construction); no application-level windowing. Reversal is an additive `ControlMessage` variant, not a wire-format change |
|
||||
| OQ-46 | Runner API surface | deferred(scope) | The runner mechanism (pipe mode) is in alknet-tty; runner policy (job management, log persistence, task graph) is a downstream crate, not in scope here |
|
||||
| OQ-47 | Stdin closure canonical signal | resolved | Either a zero-length stdin chunk or a `{"type":"eof"}` control chunk; both are accepted; the spec recommends `eof` for explicitness |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-07-06
|
||||
last_updated: 2026-07-07
|
||||
---
|
||||
|
||||
# alknet-tty — Overview
|
||||
@@ -263,7 +263,7 @@ See [open-questions.md](../../open-questions.md) for full details.
|
||||
|
||||
- **OQ-43** (resolved): `TtyControl` as a `Clone` trait object.
|
||||
- **OQ-44** (deferred(scope)): Terminal modes (TTY modes).
|
||||
- **OQ-45** (open, low risk): Flow control for high-throughput stdout.
|
||||
- **OQ-45** (resolved): Flow control for high-throughput stdout — no application-level windowing; QUIC per-stream flow control is the backpressure mechanism.
|
||||
- **OQ-46** (deferred(scope)): Runner API surface.
|
||||
- **OQ-47** (resolved): Stdin closure canonical signal.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-07-06
|
||||
last_updated: 2026-07-07
|
||||
---
|
||||
|
||||
# alknet-tty — TtyAdapter and Session Lifecycle
|
||||
@@ -204,16 +204,19 @@ revokes. The adapter's access control declares against the ADR-050 model:
|
||||
`identity.scopes` for the `tty:open` scope (or a deployment-configured
|
||||
scope) before allocating the session. A caller without the scope gets
|
||||
a negotiation error (`{"error":"forbidden"}`) and the stream closes.
|
||||
- **Resource ownership for backend-specific resources.** A docker
|
||||
backend's session targets a specific container (`BackendParams::Docker
|
||||
{ container }`); the adapter extracts the container ID via the
|
||||
`OperationSpec.resource_id_path` analog (here, a hardcoded field — the
|
||||
tty adapter is not an `OperationSpec`, so the extraction is direct
|
||||
from the negotiation frame's `container` field) and checks
|
||||
`OwnershipProvider::owns(identity, "container", id, "tty")` if an
|
||||
ownership provider is wired. A local backend's session has no
|
||||
pre-existing resource — the process is the resource, and the caller
|
||||
that opened the session owns it by construction.
|
||||
- **Resource ownership for backend-specific resources.** Some
|
||||
backends target a pre-existing resource (a docker backend targets a
|
||||
specific container); others create their own (a local backend's
|
||||
process, an SSH backend's channel). The adapter delegates the
|
||||
resource-id extraction to the backend via
|
||||
`TtyBackend::resource_id(¶ms)` (ADR-053), which returns
|
||||
`None` (no pre-existing resource — the session creates its own) or
|
||||
`Some((kind, id))` (the caller must own this resource). The adapter
|
||||
checks `OwnershipProvider::owns(identity, kind, id, "tty")` if an
|
||||
ownership provider is wired and the backend returns `Some`. The
|
||||
adapter does not parse backend-specific JSON itself — the extraction
|
||||
is backend-driven, so adding a backend with a new resource shape (e.g.,
|
||||
a Kubernetes backend targeting a pod) requires no adapter change.
|
||||
- **`forwarded_for` for proxied sessions.** A hub that proxies a
|
||||
terminal session to a worker carries the end user's identity as
|
||||
`forwarded_for` (ADR-032); the worker authorizes the hub (its direct
|
||||
@@ -222,22 +225,23 @@ revokes. The adapter's access control declares against the ADR-050 model:
|
||||
The tty adapter is a `ProtocolHandler`, not an `OperationSpec`-registered
|
||||
operation — it doesn't go through the call protocol's
|
||||
`OperationRegistry::invoke()`. The access-control shape is the adapter's
|
||||
own (scope-gate + ownership check at negotiation), declaring against the
|
||||
ADR-050 model but not consuming `OperationSpec.resource_id_path` (that
|
||||
field is for call-protocol operations; the tty adapter is its own
|
||||
ALPN). See ADR-050 §"Specifics" for the model this declares against.
|
||||
own (scope-gate + backend-driven ownership check at negotiation),
|
||||
declaring against the ADR-050 model but not consuming
|
||||
`OperationSpec.resource_id_path` (that field is for call-protocol
|
||||
operations; the tty adapter is its own ALPN, and the resource-id
|
||||
extraction is delegated to the backend via `resource_id()` rather than
|
||||
a path expression). See ADR-050 §"Specifics" for the model this declares
|
||||
against.
|
||||
|
||||
The concrete choices — the scope name (`tty:open`), the check-at-
|
||||
negotiation timing, and the hardcoded `container` field extraction for
|
||||
docker backend-specific resources (rather than a generic path like
|
||||
`OperationSpec.resource_id_path`) — are **two-way-door** choices within
|
||||
the one-way `TtyAdapter` shape. The scope name can be renamed (a
|
||||
deployment-configured scope, not a wire-format constant); the hardcoded
|
||||
field extraction can be generalized if a second backend with a different
|
||||
resource-id location appears (the adapter would branch by backend). No
|
||||
ADR is warranted for these until a second backend forces the
|
||||
generalization — they are reversible implementation choices, not
|
||||
architectural commitments.
|
||||
The concrete choice — the scope name (`tty:open`) and the
|
||||
check-at-negotiation timing — is a **two-way-door** choice within the
|
||||
one-way `TtyAdapter` shape. The scope name can be renamed (a
|
||||
deployment-configured scope, not a wire-format constant). The
|
||||
resource-id extraction is backend-driven via `resource_id()`, so new
|
||||
backends with new resource shapes require no adapter change — the
|
||||
generalization is already in place (ADR-053). No ADR is warranted for
|
||||
the scope name; it is a reversible implementation choice, not an
|
||||
architectural commitment.
|
||||
|
||||
### Connection and Stream Lifecycle
|
||||
|
||||
@@ -291,7 +295,7 @@ architectural commitments.
|
||||
|
||||
See [open-questions.md](../../open-questions.md) for full details.
|
||||
|
||||
- **OQ-45** (open, low risk): Flow control for high-throughput stdout.
|
||||
- **OQ-45** (resolved): Flow control for high-throughput stdout — no application-level windowing; QUIC per-stream flow control is the backpressure mechanism.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-07-06
|
||||
last_updated: 2026-07-07
|
||||
---
|
||||
|
||||
# alknet-tty — Wire Format
|
||||
@@ -96,9 +96,11 @@ Fields:
|
||||
- `env` — environment variables (empty = inherit).
|
||||
|
||||
Backend-specific selector fields ride alongside (e.g., `"container":
|
||||
"abc123"` for docker). The adapter parses the negotiation frame, extracts
|
||||
the `backend` string, and passes the backend-specific fields to the
|
||||
selected backend's `allocate()` as `BackendParams` (ADR-053).
|
||||
"abc123"` for docker). The adapter parses the negotiation frame,
|
||||
extracts the `backend` string, and passes the remaining backend-specific
|
||||
fields to the selected backend's `allocate()` as an opaque
|
||||
`serde_json::Map` (ADR-053) — the adapter does not interpret them; the
|
||||
backend deserializes its own strongly-typed params struct.
|
||||
|
||||
After the negotiation frame, the stream switches to raw chunks. There is
|
||||
no `call.responded`/`call.completed` — this is not the call protocol.
|
||||
@@ -239,8 +241,13 @@ a session — see [tty-adapter.md](tty-adapter.md).
|
||||
and servers parse. A 5th channel type requires a new ALPN
|
||||
(`alknet/tty/v2` per ADR-006), not a negotiated addition.
|
||||
- **No windowing.** The chunk format has no flow-control window; QUIC's
|
||||
per-stream flow control handles backpressure. See OQ-45 (high-throughput
|
||||
stdout; expected to suffice; a POC would confirm).
|
||||
per-stream flow control is the backpressure mechanism (OQ-45 resolved:
|
||||
the backpressure chain is complete by construction — QUIC flow control
|
||||
→ bounded drainer channel → bounded stdout channel → OS pipe/PTY
|
||||
buffer → process `write()` blocks; no unbounded buffer breaks the
|
||||
chain). The reversal path, if ever needed, is an additive
|
||||
`ControlMessage` variant on stream_type 3, not a wire-format header
|
||||
change.
|
||||
- **No negotiation round-trip.** The client writes the negotiation frame
|
||||
and starts sending chunks; the server reads the frame and starts
|
||||
pumping. There is no "the server acknowledges the negotiation before
|
||||
@@ -268,7 +275,9 @@ a session — see [tty-adapter.md](tty-adapter.md).
|
||||
See [open-questions.md](../../open-questions.md) for full details.
|
||||
|
||||
- **OQ-44** (deferred(scope)): Terminal modes.
|
||||
- **OQ-45** (open, low risk): Flow control for high-throughput stdout.
|
||||
- **OQ-45** (resolved): Flow control for high-throughput stdout — no
|
||||
application-level windowing; QUIC per-stream flow control is the
|
||||
backpressure mechanism.
|
||||
- **OQ-47** (resolved): Stdin closure canonical signal.
|
||||
|
||||
## References
|
||||
|
||||
Reference in new issue
Block a user