docs(architecture): eliminate em dashes from the spec docs
Rewrote every prose em dash under docs/architecture/ with natural grammar (sentence splits, colons, semicolons, parentheses, commas); wire-format fences, JSON/BAST examples, and byte diagrams untouched. Verified: grep -rln '—' docs/architecture/ returns only overview.md's fenced dependency/module-map blocks (12 dashes), left verbatim per the fenced-code rule; all other files at zero.
This commit is contained in:
1 parent
6b20924f68
commit
cd0afe0497
15 files changed
+555
-552
No files matched your search
+25
-25
@@ -3,13 +3,13 @@ status: draft
|
|||||||
last_updated: 2026-09-16 (the graduation ADRs 007/008 + the amendments)
|
last_updated: 2026-09-16 (the graduation ADRs 007/008 + the amendments)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Architecture
|
# alktunnels: Architecture
|
||||||
|
|
||||||
Arbitrary bidirectional tunnels over alkcall channels: the
|
Arbitrary bidirectional tunnels over alkcall channels: the
|
||||||
`alk/tunnel`-family producer/consumer protocol crate. This directory
|
`alk/tunnel`-family producer/consumer protocol crate. This directory
|
||||||
holds the architecture spec docs and the ADRs. It is the sibling of
|
holds the architecture spec docs and the ADRs. It is the sibling of
|
||||||
alktty's architecture (`/workspace/@alkdev/alktty/docs/architecture/`)
|
alktty's architecture (`/workspace/@alkdev/alktty/docs/architecture/`):
|
||||||
— same substrate (alkcall channels), same shape (a producer
|
same substrate (alkcall channels), same shape (a producer
|
||||||
open-handler + a typed consumer session), different service (tunnels:
|
open-handler + a typed consumer session), different service (tunnels:
|
||||||
raw byte/datagram transport, not terminal sessions).
|
raw byte/datagram transport, not terminal sessions).
|
||||||
|
|
||||||
@@ -18,41 +18,41 @@ raw byte/datagram transport, not terminal sessions).
|
|||||||
| Document | Status | Description |
|
| Document | Status | Description |
|
||||||
|----------|--------|-------------|
|
|----------|--------|-------------|
|
||||||
| [overview.md](overview.md) | draft | Crate purpose, the resource model in brief, dependencies, ALPN, feature gates, module map |
|
| [overview.md](overview.md) | draft | Crate purpose, the resource model in brief, dependencies, ALPN, feature gates, module map |
|
||||||
| [wire.md](wire.md) | draft | The wire format: open-op params JSON (ADR-001; the direct op's `{substrate, target}` — ADR-007), the `bound` reply field + the forwarded op (ADR-008), the data-plane codec (raw pass-through for stream substrates; mandatory `[len: u16 BE]` framing for UDP — ADR-003), sentinels, half-close semantics |
|
| [wire.md](wire.md) | draft | The wire format: open-op params JSON (ADR-001; the direct op's `{substrate, target}` per ADR-007), the `bound` reply field + the forwarded op (ADR-008), the data-plane codec (raw pass-through for stream substrates; mandatory `[len: u16 BE]` framing for UDP per ADR-003), sentinels, half-close semantics |
|
||||||
| [producer.md](producer.md) | draft | The producer half: the `channels/tunnel/sub` + `channels/tunnel/direct` open ops, three establisher shapes (dial, accept-first listen, bind-first listen), the pump handler, the forwarded-op opener side, registration, scope gating |
|
| [producer.md](producer.md) | draft | The producer half: the `channels/tunnel/sub` + `channels/tunnel/direct` open ops, three establisher shapes (dial, accept-first listen, bind-first listen), the pump handler, the forwarded-op opener side, registration, scope gating |
|
||||||
| [consumer.md](consumer.md) | draft | The consumer half: `TunnelSession` (stream + datagram variants, `open`/`open_direct`), `TunnelListener` (the listen flow), the reverse-flow initiator, teardown ownership (ADR-005/008) |
|
| [consumer.md](consumer.md) | draft | The consumer half: `TunnelSession` (stream + datagram variants, `open`/`open_direct`), `TunnelListener` (the listen flow), the reverse-flow initiator, teardown ownership (ADR-005/008) |
|
||||||
| [bast.md](bast.md) | draft | The BAST (Binary Abstract Syntax Tree) document for the data-plane's binary framing (the UDP `[len: u16 BE]` codec — ADR-003); the stream pass-through has no binary framing to describe |
|
| [bast.md](bast.md) | draft | The BAST (Binary Abstract Syntax Tree) document for the data-plane's binary framing (the UDP `[len: u16 BE]` codec, ADR-003); the stream pass-through has no binary framing to describe |
|
||||||
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker (OQ-TN-01..16; the Phase 0 ledger promoted) |
|
| [open-questions.md](open-questions.md) | draft | Centralized OQ tracker (OQ-TN-01..16; the Phase 0 ledger promoted) |
|
||||||
|
|
||||||
## Applicable ADRs
|
## Applicable ADRs
|
||||||
|
|
||||||
alktunnels-native ADRs (001..008). Upstream ADRs (alkcall/alknet) are
|
alktunnels-native ADRs (001..008). Upstream ADRs (alkcall/alknet) are
|
||||||
referenced by their own numbers and are NOT ported — the alkcall
|
referenced by their own numbers and are NOT ported; the alkcall
|
||||||
originals at `/workspace/@alkdev/alkcall/docs/architecture/decisions/`
|
originals at `/workspace/@alkdev/alkcall/docs/architecture/decisions/`
|
||||||
remain authoritative for the shared surfaces (establishment, channels
|
remain authoritative for the shared surfaces (establishment, channels
|
||||||
wire format, `pump_bidi`, identity seam).
|
wire format, `pump_bidi`, identity seam).
|
||||||
|
|
||||||
| ADR | Title | Status |
|
| ADR | Title | Status |
|
||||||
|-----|-------|--------|
|
|-----|-------|--------|
|
||||||
| [001](decisions/001-open-params-layout.md) | Open-Op Params Layout: Resource + Substrate | Accepted; amended (params layout is per-op — the direct op's params ride ADR-007) |
|
| [001](decisions/001-open-params-layout.md) | Open-Op Params Layout: Resource + Substrate | Accepted; amended (params layout is per-op; the direct op's params ride ADR-007) |
|
||||||
| [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN (Option A) | Accepted; amended (flavor-form op ids: `…/sub`, `…/direct`, `…/forwarded` on the one ALPN) |
|
| [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN (Option A) | Accepted; amended (flavor-form op ids: `…/sub`, `…/direct`, `…/forwarded` on the one ALPN) |
|
||||||
| [003](decisions/003-codec-and-udp-framing.md) | Data-Plane Codec: Raw Pass-Through (Stream) / Mandatory Length Framing (UDP) | Accepted |
|
| [003](decisions/003-codec-and-udp-framing.md) | Data-Plane Codec: Raw Pass-Through (Stream) / Mandatory Length Framing (UDP) | Accepted |
|
||||||
| [004](decisions/004-no-backend-trait.md) | No TunnelBackend Trait — Halves Functions at the Assembly Layer | Accepted |
|
| [004](decisions/004-no-backend-trait.md) | No TunnelBackend Trait: Halves Functions at the Assembly Layer | Accepted |
|
||||||
| [005](decisions/005-consumer-session-owns-teardown.md) | The Consumer Session Type Owns Teardown | Accepted |
|
| [005](decisions/005-consumer-session-owns-teardown.md) | The Consumer Session Type Owns Teardown | Accepted |
|
||||||
| [006](decisions/006-access-control-posture.md) | Access-Control Posture — the Open Gate Is the Security Boundary | Accepted; amended (`tunnel:direct`/`tunnel:forwarded` scopes; the hub re-produce policy sentence) |
|
| [006](decisions/006-access-control-posture.md) | Access-Control Posture: the Open Gate Is the Security Boundary | Accepted; amended (`tunnel:direct`/`tunnel:forwarded` scopes; the hub re-produce policy sentence) |
|
||||||
| [007](decisions/007-direct-open-op.md) | The Direct Open Op — Dynamic-Target Egress | Accepted |
|
| [007](decisions/007-direct-open-op.md) | The Direct Open Op: Dynamic-Target Egress | Accepted |
|
||||||
| [008](decisions/008-listen-metadata-forwarded-op.md) | Listen Metadata — Bind-First Establisher and the Forwarded Op | Accepted |
|
| [008](decisions/008-listen-metadata-forwarded-op.md) | Listen Metadata: Bind-First Establisher and the Forwarded Op | Accepted |
|
||||||
|
|
||||||
## Key Design Principles
|
## Key Design Principles
|
||||||
|
|
||||||
1. **A tunnel is a resource, not an address — with one additive
|
1. **A tunnel is a resource, not an address (with one additive
|
||||||
exception.** Base params identify a produced resource + substrate
|
exception).** Base params identify a produced resource + substrate
|
||||||
discriminator; the producer owns the backing (local port, docker
|
discriminator; the producer owns the backing (local port, docker
|
||||||
container, in-process service, unix socket). Rich addressing
|
container, in-process service, unix socket). Rich addressing
|
||||||
enters through `-D`/dynamic composition, which speaks its protocol
|
enters through `-D`/dynamic composition, which speaks its protocol
|
||||||
inside the tunnel payload. The exception (ADR-007): the direct op
|
inside the tunnel payload. The exception (ADR-007): the direct op
|
||||||
lets a consumer ask the producer to dial an arbitrary target — a
|
lets a consumer ask the producer to dial an arbitrary target. That
|
||||||
different capability on its own op and scope (`tunnel:direct`),
|
is a different capability on its own op and scope (`tunnel:direct`),
|
||||||
never implied by `tunnel:open`. See
|
never implied by `tunnel:open`. See
|
||||||
[ADR-001](decisions/001-open-params-layout.md),
|
[ADR-001](decisions/001-open-params-layout.md),
|
||||||
[ADR-007](decisions/007-direct-open-op.md), and
|
[ADR-007](decisions/007-direct-open-op.md), and
|
||||||
@@ -61,7 +61,7 @@ wire format, `pump_bidi`, identity seam).
|
|||||||
2. **Role follows the resource; connection direction is independent.**
|
2. **Role follows the resource; connection direction is independent.**
|
||||||
Whoever can reach the target is the producer (registers openable
|
Whoever can reach the target is the producer (registers openable
|
||||||
channels); whoever wants the bytes is the consumer. `-L` and `-R`
|
channels); whoever wants the bytes is the consumer. `-L` and `-R`
|
||||||
are the same open op with the entry point on different machines —
|
are the same open op with the entry point on different machines;
|
||||||
no protocol-level direction (the hub-owns-the-connection model,
|
no protocol-level direction (the hub-owns-the-connection model,
|
||||||
OQ-TN-03). The composed `-R` flow rides the bind-first establisher
|
OQ-TN-03). The composed `-R` flow rides the bind-first establisher
|
||||||
+ the forwarded op (ADR-008). Validated by the reverse-flow POC:
|
+ the forwarded op (ADR-008). Validated by the reverse-flow POC:
|
||||||
@@ -70,15 +70,15 @@ wire format, `pump_bidi`, identity seam).
|
|||||||
[consumer.md](consumer.md).
|
[consumer.md](consumer.md).
|
||||||
|
|
||||||
3. **Raw pass-through for stream substrates; mandatory length framing
|
3. **Raw pass-through for stream substrates; mandatory length framing
|
||||||
for UDP.** A tunnel has one data stream per direction — no
|
for UDP.** A tunnel has one data stream per direction: no
|
||||||
sub-demux key, no tunnel-level header on stream substrates (0 B
|
sub-demux key, no tunnel-level header on stream substrates (0 B
|
||||||
tunnel overhead; the channels 8-byte header is the only cost).
|
tunnel overhead; the channels 8-byte header is the only cost).
|
||||||
UDP rides `[len: u16 BE]` framing — mandatory for correctness
|
UDP rides `[len: u16 BE]` framing, which is mandatory for correctness
|
||||||
(F-2; the rationale and the executable pin live in ADR-003). See
|
(F-2; the rationale and the executable pin live in ADR-003). See
|
||||||
[ADR-003](decisions/003-codec-and-udp-framing.md) and
|
[ADR-003](decisions/003-codec-and-udp-framing.md) and
|
||||||
[wire.md](wire.md).
|
[wire.md](wire.md).
|
||||||
|
|
||||||
4. **The two-pump shape is `alkcall::channels::pump_bidi` — never
|
4. **The two-pump shape is `alkcall::channels::pump_bidi`; it is never
|
||||||
hand-rolled.** The producer's pump handler and the consumer-side
|
hand-rolled.** The producer's pump handler and the consumer-side
|
||||||
pump both consume the upstream helper (ADR-050); each pump shuts
|
pump both consume the upstream helper (ADR-050); each pump shuts
|
||||||
down the opposite sink on completion (half-close semantics fall
|
down the opposite sink on completion (half-close semantics fall
|
||||||
@@ -87,7 +87,7 @@ wire format, `pump_bidi`, identity seam).
|
|||||||
[producer.md](producer.md).
|
[producer.md](producer.md).
|
||||||
|
|
||||||
5. **No backend trait.** Stream substrates need "produce boxed halves
|
5. **No backend trait.** Stream substrates need "produce boxed halves
|
||||||
for a resource" — a function, not a trait (the UDP POC's OQ-TN-05
|
for a resource"; that is a function, not a trait (the UDP POC's OQ-TN-05
|
||||||
finding). The inversion point is the assembly layer wiring
|
finding). The inversion point is the assembly layer wiring
|
||||||
substrate halves into the establisher/pump; substrate types stay
|
substrate halves into the establisher/pump; substrate types stay
|
||||||
in feature-gated backend modules. The listen variant (producer
|
in feature-gated backend modules. The listen variant (producer
|
||||||
@@ -96,7 +96,7 @@ wire format, `pump_bidi`, identity seam).
|
|||||||
|
|
||||||
6. **The consumer session type owns teardown.** One session = one
|
6. **The consumer session type owns teardown.** One session = one
|
||||||
channel = one tunnel (the channel ID is the flow key). The adopted
|
channel = one tunnel (the channel ID is the flow key). The adopted
|
||||||
side has no wrapper awaiting its pump (reverse POC W3) — the
|
side has no wrapper awaiting its pump (reverse POC W3): the
|
||||||
session must hold the pump handle and reap the channel on
|
session must hold the pump handle and reap the channel on
|
||||||
completion/close. See [ADR-005](decisions/005-consumer-session-owns-teardown.md)
|
completion/close. See [ADR-005](decisions/005-consumer-session-owns-teardown.md)
|
||||||
and [consumer.md](consumer.md).
|
and [consumer.md](consumer.md).
|
||||||
@@ -110,13 +110,13 @@ wire format, `pump_bidi`, identity seam).
|
|||||||
8. **Security posture: the open gate is the boundary.** Scope-gated
|
8. **Security posture: the open gate is the boundary.** Scope-gated
|
||||||
opens (`TUNNEL_OPEN_SCOPE = "tunnel:open"`,
|
opens (`TUNNEL_OPEN_SCOPE = "tunnel:open"`,
|
||||||
`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`,
|
`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`,
|
||||||
`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"` — each stable once
|
`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"` (each stable once
|
||||||
published; ADR-006 + Amendment 1), alkcall's op-level ACL via
|
published; ADR-006 + Amendment 1), alkcall's op-level ACL via
|
||||||
`register_openable`; caller identity resolves in the 0.7.0
|
`register_openable`; caller identity resolves in the 0.7.0
|
||||||
precedence order (token > `ServingConfig.identity` > transport);
|
precedence order (token > `ServingConfig.identity` > transport);
|
||||||
the establisher/pump handler see the per-call opener identity. No
|
the establisher/pump handler see the per-call opener identity. No
|
||||||
target allowlists in the protocol (an assembly establisher MAY
|
target allowlists in the protocol (an assembly establisher MAY
|
||||||
enforce them — policy, not protocol). Hub re-produce: the hub's
|
enforce them, but that is policy, not protocol). Hub re-produce: the hub's
|
||||||
ACL governs the final dial (the hub is the network boundary). See
|
ACL governs the final dial (the hub is the network boundary). See
|
||||||
[ADR-006](decisions/006-access-control-posture.md),
|
[ADR-006](decisions/006-access-control-posture.md),
|
||||||
[ADR-007](decisions/007-direct-open-op.md), and
|
[ADR-007](decisions/007-direct-open-op.md), and
|
||||||
@@ -155,10 +155,10 @@ Questions section.
|
|||||||
(CF-005/CF-006 = the identity seam, resolved in 0.7.0)
|
(CF-005/CF-006 = the identity seam, resolved in 0.7.0)
|
||||||
- The graduation research (the two new ADRs implement it):
|
- The graduation research (the two new ADRs implement it):
|
||||||
`docs/research/tunnels-graduation.md`; the upstream alkcall asks
|
`docs/research/tunnels-graduation.md`; the upstream alkcall asks
|
||||||
(review 008) **landed in alkcall 0.8.0** — flavor-form discovery
|
(review 008) **landed in alkcall 0.8.0**: flavor-form discovery
|
||||||
derivation + the establisher → reply `bound` projection, plus the
|
derivation + the establisher → reply `bound` projection, plus the
|
||||||
in-tree `ChannelRelay`/`HubLegTemplate` (alkcall ADR-051)
|
in-tree `ChannelRelay`/`HubLegTemplate` (alkcall ADR-051)
|
||||||
- Sibling precedent: alktty (`/workspace/@alkdev/alktty`) — the
|
- Sibling precedent: alktty (`/workspace/@alkdev/alktty`), i.e. the
|
||||||
producer/consumer-on-channels shape, wasm-clean default,
|
producer/consumer-on-channels shape, wasm-clean default,
|
||||||
feature-gated local backend, BAST convention
|
feature-gated local backend, BAST convention
|
||||||
- POC code: `/workspace/alktunnels-udp-poc` (forward),
|
- POC code: `/workspace/alktunnels-udp-poc` (forward),
|
||||||
|
|||||||
+17
-17
@@ -3,11 +3,11 @@ status: implemented
|
|||||||
last_updated: 2026-09-10
|
last_updated: 2026-09-10
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — BAST Document for the `alk/tunnel` Wire Format
|
# alktunnels: BAST Document for the `alk/tunnel` Wire Format
|
||||||
|
|
||||||
This document is the **BAST (Binary Abstract Syntax Tree)**
|
This document is the **BAST (Binary Abstract Syntax Tree)**
|
||||||
specification for the binary portions of the `alk/tunnel` wire format
|
specification for the binary portions of the `alk/tunnel` wire format.
|
||||||
— per AGENTS.md convention 12, the mandatory UDP length-framing codec
|
Per AGENTS.md convention 12, the mandatory UDP length-framing codec
|
||||||
([ADR-003](decisions/003-codec-and-udp-framing.md)) is binary framing
|
([ADR-003](decisions/003-codec-and-udp-framing.md)) is binary framing
|
||||||
beyond pass-through, so the BAST document exists even though the
|
beyond pass-through, so the BAST document exists even though the
|
||||||
format is trivial. It is a normative JSON document conforming to the
|
format is trivial. It is a normative JSON document conforming to the
|
||||||
@@ -24,31 +24,31 @@ an optional dep and this document is there to feed it.
|
|||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
The `alk/tunnel` wire format is: a JSON open op on the channels call
|
The `alk/tunnel` wire format is: a JSON open op on the channels call
|
||||||
plane (a UTF-8 JSON object inside the channels envelope — not a
|
plane (a UTF-8 JSON object inside the channels envelope, not a
|
||||||
binary layout; specified in [wire.md](wire.md) §The Open Op and
|
binary layout; specified in [wire.md](wire.md) §The Open Op and
|
||||||
implemented by `TunnelParams`/`tunnel_open_spec` in the Rust source),
|
implemented by `TunnelParams`/`tunnel_open_spec` in the Rust source),
|
||||||
and a binary data plane inside the channel `BiStream`.
|
and a binary data plane inside the channel `BiStream`.
|
||||||
|
|
||||||
Per the BAST format spec, a BAST document describes **binary data
|
Per the BAST format spec, a BAST document describes **binary data
|
||||||
layouts** — not JSON payloads
|
layouts**, not JSON payloads
|
||||||
([`validate_bytes` vs `validate_json`](https://alk.dev/bast/v1/spec)).
|
([`validate_bytes` vs `validate_json`](https://alk.dev/bast/v1/spec)).
|
||||||
This document therefore covers only the data plane's binary framing:
|
This document therefore covers only the data plane's binary framing:
|
||||||
|
|
||||||
1. **The datagram frame** (`[len: u16 BE][datagram bytes]`) — the
|
1. **The datagram frame** (`[len: u16 BE][datagram bytes]`), which is the
|
||||||
UDP-substrate codec (ADR-003; mandatory, F-2). Stream substrates
|
UDP-substrate codec (ADR-003; mandatory, F-2). Stream substrates
|
||||||
ride raw pass-through — zero tunnel-level framing, so there is
|
ride raw pass-through, i.e. zero tunnel-level framing, so there is
|
||||||
nothing for BAST to describe there.
|
nothing for BAST to describe there.
|
||||||
|
|
||||||
What this BAST deliberately does **not** cover:
|
What this BAST deliberately does **not** cover:
|
||||||
|
|
||||||
- **The open-op params JSON shape** (`{resource, substrate}`) — UTF-8
|
- **The open-op params JSON shape** (`{resource, substrate}`), which is UTF-8
|
||||||
JSON on the call plane; specified in
|
JSON on the call plane; specified in
|
||||||
[ADR-001](decisions/001-open-params-layout.md) and
|
[ADR-001](decisions/001-open-params-layout.md) and
|
||||||
[wire.md](wire.md), implemented by `TunnelParams`.
|
[wire.md](wire.md), implemented by `TunnelParams`.
|
||||||
- **The channels 8-byte chunk header** — the channels layer's own
|
- **The channels 8-byte chunk header**, the channels layer's own
|
||||||
framing (alknet ADR-071/093); stripped transparently before the
|
framing (alknet ADR-071/093); stripped transparently before the
|
||||||
tunnel data plane sees bytes. Specified upstream, not here.
|
tunnel data plane sees bytes. Specified upstream, not here.
|
||||||
- **The EOF sentinel** — the channels-level zero-length chunk
|
- **The EOF sentinel**, i.e. the channels-level zero-length chunk
|
||||||
(upstream); the tunnel codec never emits it (that is the F-2
|
(upstream); the tunnel codec never emits it (that is the F-2
|
||||||
invariant).
|
invariant).
|
||||||
|
|
||||||
@@ -72,12 +72,12 @@ What this BAST deliberately does **not** cover:
|
|||||||
|
|
||||||
## Annotations
|
## Annotations
|
||||||
|
|
||||||
### `DatagramFrame` — binary, exact
|
### `DatagramFrame`: binary, exact
|
||||||
|
|
||||||
Maps 1:1 to the on-wire bytes. `len` as `uint16` big-endian (2 bytes)
|
Maps 1:1 to the on-wire bytes. `len` as `uint16` big-endian (2 bytes)
|
||||||
followed by exactly `len` bytes of datagram payload. The zero-byte
|
followed by exactly `len` bytes of datagram payload. The zero-byte
|
||||||
payload (`len == 0`) is a legal empty datagram — a real datagram, NOT
|
payload (`len == 0`) is a legal empty datagram (a real datagram, NOT
|
||||||
EOF (the F-2 invariant; EOF is the channels-level sentinel, outside
|
EOF; the F-2 invariant; EOF is the channels-level sentinel, outside
|
||||||
this codec). The `maxLength: 65535` encodes the u16 bound; the codec
|
this codec). The `maxLength: 65535` encodes the u16 bound; the codec
|
||||||
rejects framing a larger datagram at frame time (`Oversize`) rather
|
rejects framing a larger datagram at frame time (`Oversize`) rather
|
||||||
than wrapping the length field.
|
than wrapping the length field.
|
||||||
@@ -85,13 +85,13 @@ than wrapping the length field.
|
|||||||
One frame per datagram, in both directions of the channel
|
One frame per datagram, in both directions of the channel
|
||||||
`BiStream`. Frames are self-delimiting on the byte stream; a decoder
|
`BiStream`. Frames are self-delimiting on the byte stream; a decoder
|
||||||
is an incremental state machine across chunk boundaries (chunk
|
is an incremental state machine across chunk boundaries (chunk
|
||||||
splitting/batching is transparent — forward POC, 7-byte chunk splits
|
splitting/batching is transparent, per the forward POC's 7-byte chunk splits
|
||||||
verified).
|
verified).
|
||||||
|
|
||||||
### Layering note
|
### Layering note
|
||||||
|
|
||||||
The codec sits at the substrate/pump boundary (the establisher wraps
|
The codec sits at the substrate/pump boundary (the establisher wraps
|
||||||
the dialed UDP half in the framed adapter — ADR-003's placement, so
|
the dialed UDP half in the framed adapter, per ADR-003's placement, so
|
||||||
the pump handler stays substrate-agnostic). The BAST describes the
|
the pump handler stays substrate-agnostic). The BAST describes the
|
||||||
wire inside the `BiStream` only; the pump/pump_bidi contract
|
wire inside the `BiStream` only; the pump/pump_bidi contract
|
||||||
(ADR-050) is upstream's, not re-specified here.
|
(ADR-050) is upstream's, not re-specified here.
|
||||||
@@ -100,9 +100,9 @@ wire inside the `BiStream` only; the pump/pump_bidi contract
|
|||||||
|
|
||||||
- [ADR-003](decisions/003-codec-and-udp-framing.md) (the codec
|
- [ADR-003](decisions/003-codec-and-udp-framing.md) (the codec
|
||||||
decision + F-2 mandate), [ADR-001](decisions/001-open-params-layout.md)
|
decision + F-2 mandate), [ADR-001](decisions/001-open-params-layout.md)
|
||||||
(the JSON open-op shape — outside BAST scope)
|
(the JSON open-op shape, outside BAST scope)
|
||||||
- [wire.md](wire.md) (the normative prose; §Normative byte diagrams)
|
- [wire.md](wire.md) (the normative prose; §Normative byte diagrams)
|
||||||
- alktty `tty-bast.md` (the BAST precedent — same meta-schema, same
|
- alktty `tty-bast.md` (the BAST precedent, with the same meta-schema and the same
|
||||||
"binary framing only" scope rule)
|
"binary framing only" scope rule)
|
||||||
- Forward POC `docs/research/poc-summary.md` §3 (the codec's
|
- Forward POC `docs/research/poc-summary.md` §3 (the codec's
|
||||||
validation matrix); reverse POC
|
validation matrix); reverse POC
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-09-18 (`open_direct`, `TunnelListener`, the forwarded-op serving side — ADR-007/008 as amended: the correlation token)
|
last_updated: 2026-09-18 (`open_direct`, `TunnelListener`, the forwarded-op serving side; ADR-007/008 as amended: the correlation token)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Consumer Half
|
# alktunnels: Consumer Half
|
||||||
|
|
||||||
The consumer half: `TunnelSession` — the typed client for tunnel
|
The consumer half: `TunnelSession`, the typed client for tunnel
|
||||||
channels — plus `TunnelListener` (the listen-flow session, ADR-008)
|
channels, plus `TunnelListener` (the listen-flow session, ADR-008)
|
||||||
and the forwarded-op serving side. The consumer is whoever wants the
|
and the forwarded-op serving side. The consumer is whoever wants the
|
||||||
bytes; it opens tunnel channels toward the producer (forward path), or
|
bytes; it opens tunnel channels toward the producer (forward path), or
|
||||||
per local accept toward a connect-side serving producer (the reverse
|
per local accept toward a connect-side serving producer (the reverse
|
||||||
flow). Both are the same session type — role follows the resource, not
|
flow). Both are the same session type; role follows the resource, not
|
||||||
the connection (OQ-TN-03). Design decisions:
|
the connection (OQ-TN-03). Design decisions:
|
||||||
[ADR-005](decisions/005-consumer-session-owns-teardown.md) (session
|
[ADR-005](decisions/005-consumer-session-owns-teardown.md) (session
|
||||||
owns teardown), [ADR-003](decisions/003-codec-and-udp-framing.md)
|
owns teardown), [ADR-003](decisions/003-codec-and-udp-framing.md)
|
||||||
@@ -20,24 +20,24 @@ metadata + the forwarded op); this document is the normative WHAT.
|
|||||||
|
|
||||||
## Opening a Tunnel
|
## Opening a Tunnel
|
||||||
|
|
||||||
### Forward path (`-L`/direct — the consumer dials the producer's transport)
|
### Forward path (`-L`/direct, the consumer dials the producer's transport)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let session = TunnelSession::open(&channel_client, params).await?;
|
let session = TunnelSession::open(&channel_client, params).await?;
|
||||||
```
|
```
|
||||||
|
|
||||||
- `params`: `{resource, substrate}` (ADR-001) — the produced resource
|
- `params`: `{resource, substrate}` (ADR-001), i.e. the produced resource
|
||||||
+ substrate discriminator.
|
+ substrate discriminator.
|
||||||
- Flow: `ChannelClient::open_channel("channels/tunnel/sub", params,
|
- Flow: `ChannelClient::open_channel("channels/tunnel/sub", params,
|
||||||
"alk/tunnel")` → adopt the returned channel ID → split the channel
|
"alk/tunnel")` → adopt the returned channel ID → split the channel
|
||||||
`BiStream` → present the substrate-shaped data plane.
|
`BiStream` → present the substrate-shaped data plane.
|
||||||
- Errors: a failed open resolves a typed error —
|
- Errors: a failed open resolves a typed error:
|
||||||
`ChannelOpenError::CallFailed` carrying the wire `CallError`;
|
`ChannelOpenError::CallFailed` carrying the wire `CallError`;
|
||||||
branch on `establishment_reason()` for `channel:open_failed`'s
|
branch on `establishment_reason()` for `channel:open_failed`'s
|
||||||
reason (dial_failed / unknown_resource / resource_shortage /
|
reason (dial_failed / unknown_resource / resource_shortage /
|
||||||
handler_error / timeout), or the pre-establishment codes
|
handler_error / timeout), or the pre-establishment codes
|
||||||
(`FORBIDDEN`, `channel:too_many_channels`). **A failed open never
|
(`FORBIDDEN`, `channel:too_many_channels`). **A failed open never
|
||||||
yields a session** — no phantom session, mirroring the
|
yields a session**; no phantom session, mirroring the
|
||||||
no-phantom-channel property (POC-verified).
|
no-phantom-channel property (POC-verified).
|
||||||
|
|
||||||
### Direct path (dynamic-target egress, ADR-007)
|
### Direct path (dynamic-target egress, ADR-007)
|
||||||
@@ -51,20 +51,20 @@ let session = TunnelSession::open_direct(&client, target, substrate).await?;
|
|||||||
caller states which one it holds. The session type, data planes,
|
caller states which one it holds. The session type, data planes,
|
||||||
and teardown API are identical.
|
and teardown API are identical.
|
||||||
- `target`: the substrate-shaped JSON object (`{host, port}` /
|
- `target`: the substrate-shaped JSON object (`{host, port}` /
|
||||||
`{path}` — ADR-007 §2); domain-form hosts stay unresolved (the
|
`{path}`; ADR-007 §2); domain-form hosts stay unresolved (the
|
||||||
producer resolves).
|
producer resolves).
|
||||||
- Flow: `open_channel("channels/tunnel/direct", {substrate, target},
|
- Flow: `open_channel("channels/tunnel/direct", {substrate, target},
|
||||||
"alk/tunnel")` → same adopt/split/data-plane flow as `open`.
|
"alk/tunnel")` → same adopt/split/data-plane flow as `open`.
|
||||||
- Errors: the same typed surface minus `unknown_resource` (no
|
- Errors: the same typed surface minus `unknown_resource` (no
|
||||||
registry lookup); malformed targets are the schema/
|
registry lookup); malformed targets are the schema/
|
||||||
`invalid_input`-class failure. An old producer rejects the unknown
|
`invalid_input`-class failure. An old producer rejects the unknown
|
||||||
op loudly (`NOT_FOUND` — the SSH posture).
|
op loudly (`NOT_FOUND`, the SSH posture).
|
||||||
|
|
||||||
### Reverse path (`-R` — the consumer initiates toward a connect-side serving producer)
|
### Reverse path (`-R`, the consumer initiates toward a connect-side serving producer)
|
||||||
|
|
||||||
The hub/accept-side shape (the reverse POC's `ReverseTunnel`, the
|
The hub/accept-side shape (the reverse POC's `ReverseTunnel`, the
|
||||||
seed of this path). Construction differs from the forward path only
|
seed of this path). Construction differs from the forward path only
|
||||||
in how the channel is obtained — the session type and teardown API
|
in how the channel is obtained; the session type and teardown API
|
||||||
are identical:
|
are identical:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -74,29 +74,29 @@ let session = TunnelSession::adopt(hub_manager, channel_id, substrate, TUNNEL_AL
|
|||||||
let pumped = session.pump_against(accepted_halves).await; // or drive the halves directly
|
let pumped = session.pump_against(accepted_halves).await; // or drive the halves directly
|
||||||
```
|
```
|
||||||
|
|
||||||
- `open_reverse_channel` — call the worker's open op on channel 0
|
- `open_reverse_channel`: call the worker's open op on channel 0
|
||||||
(`call_with_payload`; optional `auth_token`), extract the
|
(`call_with_payload`; optional `auth_token`), extract the
|
||||||
worker-allocated `channel_id`. Caller identity rides the transport
|
worker-allocated `channel_id`. Caller identity rides the transport
|
||||||
by default (alkcall 0.7.0 CF-005 (b): `Connection::set_identity`
|
by default (alkcall 0.7.0 CF-005 (b): `Connection::set_identity`
|
||||||
before dialing); the payload `auth_token` is the optional
|
before dialing); the payload `auth_token` is the optional
|
||||||
hub-forwarding path (precedence: token > `ServingConfig.identity` >
|
hub-forwarding path (precedence: token > `ServingConfig.identity` >
|
||||||
transport).
|
transport).
|
||||||
- `TunnelSession::adopt(manager, channel_id, substrate, alpn)` —
|
- `TunnelSession::adopt(manager, channel_id, substrate, alpn)`:
|
||||||
adopt the worker-allocated ID (connection-owner rule, ADR-047 §5 —
|
adopt the worker-allocated ID (connection-owner rule, ADR-047 §5:
|
||||||
the SERVING side allocates; the initiator adopts; early-arrival
|
the SERVING side allocates; the initiator adopts; early-arrival
|
||||||
parking covers the adoption race), install the session's data plane
|
parking covers the adoption race), install the session's data plane
|
||||||
from the adopted halves. The substrate parameter is required — a
|
from the adopted halves. The substrate parameter is required; a
|
||||||
datagram session cannot exist without it (the data plane variant is
|
datagram session cannot exist without it (the data plane variant is
|
||||||
substrate-shaped). `adopt` takes the same typed-error surface as
|
substrate-shaped). `adopt` takes the same typed-error surface as
|
||||||
`open` (an adopt failure is `AdoptFailed`-class, not an
|
`open` (an adopt failure is `AdoptFailed`-class, not an
|
||||||
establishment error).
|
establishment error).
|
||||||
- `pump_against(accepted_halves)` — spawn `pump_bidi` against the
|
- `pump_against(accepted_halves)`: spawn `pump_bidi` against the
|
||||||
accepted local connection and hold the pump handle (ADR-005).
|
accepted local connection and hold the pump handle (ADR-005).
|
||||||
|
|
||||||
The session type and its teardown API are identical on both paths —
|
The session type and its teardown API are identical on both paths:
|
||||||
one API, `-L` and `-R` alike.
|
one API, `-L` and `-R` alike.
|
||||||
|
|
||||||
## The Listen Flow — `TunnelListener` (ADR-008)
|
## The Listen Flow: `TunnelListener` (ADR-008)
|
||||||
|
|
||||||
The listen-flow sibling of `TunnelSession`: the session that OPENS a
|
The listen-flow sibling of `TunnelSession`: the session that OPENS a
|
||||||
bind-first listen tunnel and owns its teardown (ADR-005's ownership
|
bind-first listen tunnel and owns its teardown (ADR-005's ownership
|
||||||
@@ -106,25 +106,25 @@ opener's manager (`adopt` gains nothing).
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
let listener = TunnelListener::open(&client, params).await?; // bind-first: fast establishment
|
let listener = TunnelListener::open(&client, params).await?; // bind-first: fast establishment
|
||||||
let bound = listener.bound(); // the resolved OS-chosen address (Option — absent when unknown)
|
let bound = listener.bound(); // the resolved OS-chosen address (Option; absent when unknown)
|
||||||
let token = listener.listen(); // the correlation token (the producer's; from the `listen` reply field)
|
let token = listener.listen(); // the correlation token (the producer's; from the `listen` reply field)
|
||||||
let sessions = listener.forwarded(); // the forwarded sessions, as they arrive
|
let sessions = listener.forwarded(); // the forwarded sessions, as they arrive
|
||||||
listener.close(self); // or Drop — tears the listen channel + far listener down
|
listener.close(self); // or Drop; tears the listen channel + far listener down
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`TunnelListener::open`** — the same `channels/tunnel/sub` open
|
- **`TunnelListener::open`**: the same `channels/tunnel/sub` open
|
||||||
with `{resource, substrate}`; the establisher shape (bind-first vs
|
with `{resource, substrate}`; the establisher shape (bind-first vs
|
||||||
accept-first) is the producer's per-registration choice, invisible
|
accept-first) is the producer's per-registration choice, invisible
|
||||||
to the constructor. What differs: bind-first replies carry
|
to the constructor. What differs: bind-first replies carry
|
||||||
`"bound"` (ADR-008 §2 — the resolved address; informational, never
|
`"bound"` (ADR-008 §2: the resolved address; informational, never
|
||||||
a dial target) and `"listen"` (ADR-008 Amendment 1 — the
|
a dial target) and `"listen"` (ADR-008 Amendment 1: the
|
||||||
correlation token the producer minted).
|
correlation token the producer minted).
|
||||||
- **`bound()`** — `Option`-shaped: `Some` when the producer supplied
|
- **`bound()`**: `Option`-shaped: `Some` when the producer supplied
|
||||||
the field (bind-first), `None` otherwise (accept-first, dial
|
the field (bind-first), `None` otherwise (accept-first, dial
|
||||||
establishers, or old producers). reply#1-class fidelity (BND.ADDR).
|
establishers, or old producers). reply#1-class fidelity (BND.ADDR).
|
||||||
- **`listen()`** — `Option<u32>`: the correlation token (bind-first
|
- **`listen()`**: `Option<u32>`: the correlation token (bind-first
|
||||||
producers). The opener-side correlation registry — token → waiting
|
producers). The opener-side correlation registry, token → waiting
|
||||||
session — is assembly-side bookkeeping (consumer.md's posture:
|
session, is assembly-side bookkeeping (consumer.md's posture:
|
||||||
the protocol provides the op and params, not the wait state
|
the protocol provides the op and params, not the wait state
|
||||||
machine); `TunnelListener` hands the assembly the key.
|
machine); `TunnelListener` hands the assembly the key.
|
||||||
- **The forwarded side:** each accepted connection at the far
|
- **The forwarded side:** each accepted connection at the far
|
||||||
@@ -135,7 +135,7 @@ listener.close(self); // or Drop — tears the listen channel + far li
|
|||||||
session, adopts the allocated channel (`TunnelSession::adopt`),
|
session, adopts the allocated channel (`TunnelSession::adopt`),
|
||||||
and pumps against the local BIND client's halves.
|
and pumps against the local BIND client's halves.
|
||||||
- **`peer` fidelity:** the forwarded op's params carry the accepted
|
- **`peer` fidelity:** the forwarded op's params carry the accepted
|
||||||
connection's real remote address (reply#2-class fidelity — the
|
connection's real remote address (reply#2-class fidelity; the
|
||||||
alksocks POC's sentinel retires). Producer-asserted metadata, not
|
alksocks POC's sentinel retires). Producer-asserted metadata, not
|
||||||
transport truth.
|
transport truth.
|
||||||
- **Teardown:** dropping/`close`ing the listener EOFs the listen
|
- **Teardown:** dropping/`close`ing the listener EOFs the listen
|
||||||
@@ -145,18 +145,18 @@ listener.close(self); // or Drop — tears the listen channel + far li
|
|||||||
|
|
||||||
## The Data Plane (substrate-shaped)
|
## The Data Plane (substrate-shaped)
|
||||||
|
|
||||||
- **Stream variant (`tcp`, `unix`):** `stream_halves()` — raw
|
- **Stream variant (`tcp`, `unix`):** `stream_halves()`: raw
|
||||||
`AsyncRead`/`AsyncWrite` halves; the halves ARE the tunnel (raw
|
`AsyncRead`/`AsyncWrite` halves; the halves ARE the tunnel (raw
|
||||||
pass-through, ADR-003). For owned access (spawning local pumps),
|
pass-through, ADR-003). For owned access (spawning local pumps),
|
||||||
`take_halves()` consumes the session's halves.
|
`take_halves()` consumes the session's halves.
|
||||||
- **Datagram variant (`udp`):** `send_datagram(&[u8])` /
|
- **Datagram variant (`udp`):** `send_datagram(&[u8])` /
|
||||||
`recv_datagram() -> Option<Bytes>` over the mandatory
|
`recv_datagram() -> Option<Bytes>` over the mandatory
|
||||||
`[len: u16 BE]` codec (ADR-003). `recv_datagram` returns
|
`[len: u16 BE]` codec (ADR-003). `recv_datagram` returns
|
||||||
`Some(bytes)` per datagram — possibly empty (`len=0` is a legal
|
`Some(bytes)` per datagram (possibly empty; `len=0` is a legal
|
||||||
empty datagram) — and `None` only on stream EOF (the channels-level
|
empty datagram), and `None` only on stream EOF (the channels-level
|
||||||
sentinel; the codec never collides with it, F-2). A >65535-byte
|
sentinel; the codec never collides with it, F-2). A >65535-byte
|
||||||
send is rejected at frame time (`Oversize`). `take_halves()` on a
|
send is rejected at frame time (`Oversize`). `take_halves()` on a
|
||||||
datagram session yields the RAW halves — the wire bytes carry the
|
datagram session yields the RAW halves; the wire bytes carry the
|
||||||
codec framing (ADR-005's N-3 amendment: one `take_halves` semantic
|
codec framing (ADR-005's N-3 amendment: one `take_halves` semantic
|
||||||
for every substrate; keep the session for codec-applied I/O, or
|
for every substrate; keep the session for codec-applied I/O, or
|
||||||
compose the framed adapter at your own boundary).
|
compose the framed adapter at your own boundary).
|
||||||
@@ -165,27 +165,27 @@ listener.close(self); // or Drop — tears the listen channel + far li
|
|||||||
|
|
||||||
One session = one channel = one tunnel. The session owns:
|
One session = one channel = one tunnel. The session owns:
|
||||||
|
|
||||||
- **The adopted channel entry** — nothing upstream awaits the
|
- **The adopted channel entry**: nothing upstream awaits the
|
||||||
adopter's pump (the W3 gap); the session closes it structurally.
|
adopter's pump (the W3 gap); the session closes it structurally.
|
||||||
- **The session-owned pump handle** (when the session spawned one —
|
- **The session-owned pump handle** (when the session spawned one,
|
||||||
the reverse path's `pump_against`).
|
i.e. the reverse path's `pump_against`).
|
||||||
|
|
||||||
Teardown API:
|
Teardown API:
|
||||||
|
|
||||||
- `close(self)` — abort the pump (if session-owned), tear down the
|
- `close(self)`: abort the pump (if session-owned), tear down the
|
||||||
adopted channel (`ChannelManager::teardown_channel`), consume the
|
adopted channel (`ChannelManager::teardown_channel`), consume the
|
||||||
session. The ungraceful path.
|
session. The ungraceful path.
|
||||||
- `join(self)` — await pump completion (both directions finished;
|
- `join(self)`: await pump completion (both directions finished;
|
||||||
half-close semantics fall out of `pump_bidi` — one direction EOFs,
|
half-close semantics fall out of `pump_bidi`: one direction EOFs,
|
||||||
the opposite sink shuts down, the other keeps pumping until its own
|
the opposite sink shuts down, the other keeps pumping until its own
|
||||||
EOF; W4-validated), THEN reap the adopted channel. Returns the
|
EOF; W4-validated), THEN reap the adopted channel. Returns the
|
||||||
`(u64, u64)` copy counts for observability. The graceful path.
|
`(u64, u64)` copy counts for observability. The graceful path.
|
||||||
**Pump-less sessions** (after `take_halves`, or a datagram session
|
**Pump-less sessions** (after `take_halves`, or a datagram session
|
||||||
the caller drives directly): there is no session-owned pump —
|
the caller drives directly): there is no session-owned pump, so
|
||||||
`join` completes immediately (nothing to await) and reaps only;
|
`join` completes immediately (nothing to await) and reaps only;
|
||||||
the copy counts are `(0, 0)`.
|
the copy counts are `(0, 0)`.
|
||||||
- `Drop` — aborts the pump and sync-reaps the channel (teardown
|
- `Drop`: aborts the pump and sync-reaps the channel (teardown
|
||||||
semantics of `close`, best-effort — `Drop` cannot await). Dropping
|
semantics of `close`; best-effort, since `Drop` cannot await). Dropping
|
||||||
without close/join never leaks the channel entry.
|
without close/join never leaks the channel entry.
|
||||||
- **`TunnelSession` does not implement `Clone`.** Aliasing a session
|
- **`TunnelSession` does not implement `Clone`.** Aliasing a session
|
||||||
would alias its teardown; multi-channel consumers hold a
|
would alias its teardown; multi-channel consumers hold a
|
||||||
@@ -195,7 +195,7 @@ Teardown API:
|
|||||||
|
|
||||||
A peer-initiated `channel/close` (the generic channel op on the
|
A peer-initiated `channel/close` (the generic channel op on the
|
||||||
producer's serving registry) tears the SERVING side down; the
|
producer's serving registry) tears the SERVING side down; the
|
||||||
consumer's session still owns its adopted entry — `close`/`join` reaps
|
consumer's session still owns its adopted entry, so `close`/`join` reaps
|
||||||
it. The reverse POC pinned this split (the close ran on the worker;
|
it. The reverse POC pinned this split (the close ran on the worker;
|
||||||
the hub reaped its own entry, W3). EOF propagation is symmetric:
|
the hub reaped its own entry, W3). EOF propagation is symmetric:
|
||||||
either side's teardown reaches the other as the channels-level
|
either side's teardown reaches the other as the channels-level
|
||||||
@@ -208,20 +208,20 @@ roles (alkcall ADR-022 §2): a worker dials the hub (connect side),
|
|||||||
serves its open ops (serving side), and its own outbound calls still
|
serves its open ops (serving side), and its own outbound calls still
|
||||||
resolve while serving (reverse POC: worker_outbound_calls_still_
|
resolve while serving (reverse POC: worker_outbound_calls_still_
|
||||||
resolve_while_serving). The consumer half adds nothing to the
|
resolve_while_serving). The consumer half adds nothing to the
|
||||||
connection's serving posture — a pure consumer (`from_connection`)
|
connection's serving posture: a pure consumer (`from_connection`)
|
||||||
never dispatches inbound requests; a reverse-flow initiator needs no
|
never dispatches inbound requests; a reverse-flow initiator needs no
|
||||||
serving registry of its own.
|
serving registry of its own.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- **OQ-TN-12**: hub re-produce composition (deferred — a hub proxy
|
- **OQ-TN-12**: hub re-produce composition (deferred; a hub proxy
|
||||||
re-exporting a consumed resource would add a
|
re-exporting a consumed resource would add a
|
||||||
`register_openable_with_establisher` whose establisher opens a
|
`register_openable_with_establisher` whose establisher opens a
|
||||||
consumer channel; no concrete consumer yet. The re-produce policy
|
consumer channel; no concrete consumer yet. The re-produce policy
|
||||||
sentence is pinned: the hub's ACL governs the final dial — ADR-006
|
sentence is pinned: the hub's ACL governs the final dial, per ADR-006
|
||||||
Amendment 1)
|
Amendment 1)
|
||||||
- **OQ-TN-13**: UDP truncation (resolved — fail-loud, ADR-003)
|
- **OQ-TN-13**: UDP truncation (resolved, fail-loud, ADR-003)
|
||||||
- **OQ-TN-15/16**: resolved (ADR-007/008 — `open_direct`,
|
- **OQ-TN-15/16**: resolved (ADR-007/008: `open_direct`,
|
||||||
`TunnelListener`, the forwarded op)
|
`TunnelListener`, the forwarded op)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -232,8 +232,8 @@ serving registry of its own.
|
|||||||
`ReverseTunnel` open_and_pump / close / join_and_reap; W3, W4)
|
`ReverseTunnel` open_and_pump / close / join_and_reap; W3, W4)
|
||||||
- `docs/research/tunnels-graduation.md` (the graduation research)
|
- `docs/research/tunnels-graduation.md` (the graduation research)
|
||||||
- Upstream asks (review 008): `alkcall/docs/reviews/
|
- Upstream asks (review 008): `alkcall/docs/reviews/
|
||||||
008-tunnels-graduation-upstream-review.md` — landed in alkcall
|
008-tunnels-graduation-upstream-review.md`; landed in alkcall
|
||||||
0.8.0
|
0.8.0
|
||||||
- alkcall ADR-047 §5 (allocation — the initiator adopts), ADR-050
|
- alkcall ADR-047 §5 (allocation; the initiator adopts), ADR-050
|
||||||
(`pump_bidi`), ledger
|
(`pump_bidi`), ledger
|
||||||
CF-005/CF-006 (identity on the reverse path)
|
CF-005/CF-006 (identity on the reverse path)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# ADR-001: Open-Op Params Layout — Resource + Substrate
|
# ADR-001: Open-Op Params Layout (Resource + Substrate)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (2026-09-07); amended 2026-09-16 (params layout is per-op —
|
Accepted (2026-09-07); amended 2026-09-16 (params layout is per-op:
|
||||||
the direct op pins its own params, see Amendment 1)
|
the direct op pins its own params, see Amendment 1)
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
@@ -13,7 +13,7 @@ carrier of "what to open." alknet ADR-071 §ALPN table noted
|
|||||||
`alknet/tunnel` as `[0, 1]` data in/out only; the addressing scheme
|
`alknet/tunnel` as `[0, 1]` data in/out only; the addressing scheme
|
||||||
was never decided there. Phase 0 resolved the framing of the problem
|
was never decided there. Phase 0 resolved the framing of the problem
|
||||||
(the OQ-TN-01 reframe, 2026-09-05): the rich "remote addressing"
|
(the OQ-TN-01 reframe, 2026-09-05): the rich "remote addressing"
|
||||||
framing was an XY problem — except in the `-D`/dynamic case (which
|
framing was an XY problem. Except in the `-D`/dynamic case (which
|
||||||
composes at the assembly layer), a tunnel is either TCP or UDP, and
|
composes at the assembly layer), a tunnel is either TCP or UDP, and
|
||||||
`params` need only *identify a produced resource*, not carry a
|
`params` need only *identify a produced resource*, not carry a
|
||||||
general-purpose address.
|
general-purpose address.
|
||||||
@@ -21,7 +21,7 @@ general-purpose address.
|
|||||||
Constraints that shaped the decision:
|
Constraints that shaped the decision:
|
||||||
|
|
||||||
- `params` is ALPN-specific JSON, interpreted by the open op's
|
- `params` is ALPN-specific JSON, interpreted by the open op's
|
||||||
establisher/handler — never by the channels layer (alkcall
|
establisher/handler, and never by the channels layer (alkcall
|
||||||
ADR-039). The layout is a one-way door once a consumer exists
|
ADR-039). The layout is a one-way door once a consumer exists
|
||||||
(wire-stable once published; additive changes only).
|
(wire-stable once published; additive changes only).
|
||||||
- The identifier must be substrate-extensible without format changes
|
- The identifier must be substrate-extensible without format changes
|
||||||
@@ -34,7 +34,7 @@ Constraints that shaped the decision:
|
|||||||
address. A `host:port` in params would leak the producer's
|
address. A `host:port` in params would leak the producer's
|
||||||
topology into the wire and pin the wire to address-shaped targets.
|
topology into the wire and pin the wire to address-shaped targets.
|
||||||
- alkcall's registry schema-validates the open op's input
|
- alkcall's registry schema-validates the open op's input
|
||||||
(`input_schema` runs before the establisher — review 007 Part B),
|
(`input_schema` runs before the establisher, per review 007 Part B),
|
||||||
so the layout must be schema-describable (no per-substrate
|
so the layout must be schema-describable (no per-substrate
|
||||||
polymorphic payloads in v1).
|
polymorphic payloads in v1).
|
||||||
- Both POCs used exactly this shape (`TunnelParams {resource,
|
- Both POCs used exactly this shape (`TunnelParams {resource,
|
||||||
@@ -52,21 +52,21 @@ JSON object:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`resource`** (string, required) — the produced resource
|
- **`resource`** (string, required): the produced resource
|
||||||
identifier: the producer's stable name for the tunnel target. NOT
|
identifier: the producer's stable name for the tunnel target. NOT
|
||||||
an address; the producer's registry (an assembly-side construct,
|
an address; the producer's registry (an assembly-side construct,
|
||||||
OQ-TN-11) maps the name to its backing (a local port, a docker
|
OQ-TN-11) maps the name to its backing (a local port, a docker
|
||||||
container's port, an in-process service, a unix socket path).
|
container's port, an in-process service, a unix socket path).
|
||||||
- **`substrate`** (string, required) — the extensible discriminator:
|
- **`substrate`** (string, required): the extensible discriminator:
|
||||||
`"tcp"` | `"udp"` | `"unix"` in v1. A new substrate is a new value,
|
`"tcp"` | `"udp"` | `"unix"` in v1. A new substrate is a new value,
|
||||||
not a format change — the same additive property SSH gets from
|
not a format change, the same additive property SSH gets from
|
||||||
channel-type strings and SOCKS5 gets from ATYP values. The
|
channel-type strings and SOCKS5 gets from ATYP values. The
|
||||||
discriminator also selects the data-plane framing (ADR-003): the
|
discriminator also selects the data-plane framing (ADR-003): the
|
||||||
establisher and both pumps must agree on it, so it rides the open
|
establisher and both pumps must agree on it, so it rides the open
|
||||||
op rather than being inferred.
|
op rather than being inferred.
|
||||||
- The open op's `input_schema` pins both fields as required strings;
|
- The open op's `input_schema` pins both fields as required strings;
|
||||||
`substrate` is enum-validated against the v1 set. Unknown
|
`substrate` is enum-validated against the v1 set. Unknown
|
||||||
substrate values are a schema failure (typed open error) — a
|
substrate values are a schema failure (typed open error): a
|
||||||
consumer speaking a newer substrate to an older producer fails
|
consumer speaking a newer substrate to an older producer fails
|
||||||
loudly, not silently.
|
loudly, not silently.
|
||||||
- Substrate-specific detail (the path of a unix socket, the UDP
|
- Substrate-specific detail (the path of a unix socket, the UDP
|
||||||
@@ -75,12 +75,12 @@ JSON object:
|
|||||||
- The open-failure error path rides alkcall ADR-049 (typed
|
- The open-failure error path rides alkcall ADR-049 (typed
|
||||||
`channel:open_failed` with `details.reason` ∈ `dial_failed` /
|
`channel:open_failed` with `details.reason` ∈ `dial_failed` /
|
||||||
`unknown_resource` / `resource_shortage` / `handler_error` /
|
`unknown_resource` / `resource_shortage` / `handler_error` /
|
||||||
`timeout`): an unknown resource name is `unknown_resource` — no
|
`timeout`): an unknown resource name is `unknown_resource`; there is no
|
||||||
params-level error field, no establishment frame (the pre-ADR-049
|
params-level error field, no establishment frame (the pre-ADR-049
|
||||||
OQ-TN-09 hunch is superseded).
|
OQ-TN-09 hunch is superseded).
|
||||||
|
|
||||||
Rich in-band addressing (SOCKS5 ATYP, per-datagram remote addresses)
|
Rich in-band addressing (SOCKS5 ATYP, per-datagram remote addresses)
|
||||||
enters only through the `-D`/dynamic-target composition path — it is
|
enters only through the `-D`/dynamic-target composition path; it is
|
||||||
carried INSIDE the tunnel payload by whatever protocol the tunnel
|
carried INSIDE the tunnel payload by whatever protocol the tunnel
|
||||||
carries (socks5, udpgw-style framing), never in base params.
|
carries (socks5, udpgw-style framing), never in base params.
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ carries (socks5, udpgw-style framing), never in base params.
|
|||||||
renames are a breaking wire change (do not). Adding an optional
|
renames are a breaking wire change (do not). Adding an optional
|
||||||
field is additive-safe; adding a `substrate` value is
|
field is additive-safe; adding a `substrate` value is
|
||||||
consumer-progressive (an older producer rejects with
|
consumer-progressive (an older producer rejects with
|
||||||
`invalid_input`-class schema error — the SSH "unknown channel type"
|
`invalid_input`-class schema error, in the SSH "unknown channel type"
|
||||||
posture, loud not silent).
|
posture, loud not silent).
|
||||||
- **The BAST question is moot for params.** Params is JSON inside the
|
- **The BAST question is moot for params.** Params is JSON inside the
|
||||||
channels open op (which is already a JSON envelope); the data
|
channels open op (which is already a JSON envelope); the data
|
||||||
@@ -101,7 +101,7 @@ carries (socks5, udpgw-style framing), never in base params.
|
|||||||
- **Producers own a naming discipline.** Resource ids are
|
- **Producers own a naming discipline.** Resource ids are
|
||||||
producer-scoped names (OQ-TN-11: collision domain = the producer's
|
producer-scoped names (OQ-TN-11: collision domain = the producer's
|
||||||
registry). Consumers learn available resources through the
|
registry). Consumers learn available resources through the
|
||||||
ACL-filtered ops listing (OQ-TN-08) — the spec's
|
ACL-filtered ops listing (OQ-TN-08), so the spec's
|
||||||
`OperationSpec.description` SHOULD carry a human-readable hint
|
`OperationSpec.description` SHOULD carry a human-readable hint
|
||||||
(e.g. "postgres on the worker's tailnet") since it round-trips
|
(e.g. "postgres on the worker's tailnet") since it round-trips
|
||||||
through discovery.
|
through discovery.
|
||||||
@@ -125,10 +125,10 @@ carries (socks5, udpgw-style framing), never in base params.
|
|||||||
The `{resource, substrate}` layout pins the `channels/tunnel/sub` open
|
The `{resource, substrate}` layout pins the `channels/tunnel/sub` open
|
||||||
op. It is **per-op, not per-ALPN**: ADR-007 adds the
|
op. It is **per-op, not per-ALPN**: ADR-007 adds the
|
||||||
`channels/tunnel/direct` op on the same `alk/tunnel` ALPN with its own
|
`channels/tunnel/direct` op on the same `alk/tunnel` ALPN with its own
|
||||||
params (`{substrate, target}` — a dynamic target, a different
|
params (`{substrate, target}`, i.e. a dynamic target, a different
|
||||||
capability with its own scope; the resource-not-address invariant is
|
capability with its own scope; the resource-not-address invariant is
|
||||||
unchanged because a dynamic target is not a produced resource). The
|
unchanged because a dynamic target is not a produced resource). The
|
||||||
per-op reading was always latent in the layout's basis — params are
|
per-op reading was always latent in the layout's basis, since params are
|
||||||
"the `input` of the operation" (alkcall ADR-047), and ops are
|
"the `input` of the operation" (alkcall ADR-047), and ops are
|
||||||
per-flavor once flavor-form op ids exist (ADR-002 Amendment 1).
|
per-flavor once flavor-form op ids exist (ADR-002 Amendment 1).
|
||||||
Additive-only evolution per op stands: this amendment adds nothing to
|
Additive-only evolution per op stands: this amendment adds nothing to
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ enum) and the ALPN namespace (a published string is wire-stable).
|
|||||||
|
|
||||||
Prior art surveyed in Phase 0 (`ssh-socks5-survey.md`):
|
Prior art surveyed in Phase 0 (`ssh-socks5-survey.md`):
|
||||||
|
|
||||||
- SSH uses ONE channel mechanism for all forwarding flavors — the
|
- SSH uses ONE channel mechanism for all forwarding flavors; the
|
||||||
channel type string (`direct-tcpip`, `forwarded-tcpip`,
|
channel type string (`direct-tcpip`, `forwarded-tcpip`,
|
||||||
`direct-streamlocal`) is per-open metadata on one transport, not a
|
`direct-streamlocal`) is per-open metadata on one transport, not a
|
||||||
separate transport per flavor.
|
separate transport per flavor.
|
||||||
@@ -35,7 +35,7 @@ Prior art surveyed in Phase 0 (`ssh-socks5-survey.md`):
|
|||||||
stream (length-framed datagrams inside a TCP tunnel).
|
stream (length-framed datagrams inside a TCP tunnel).
|
||||||
- With OQ-TN-02 resolved as endpoint-at-open for base UDP resources,
|
- With OQ-TN-02 resolved as endpoint-at-open for base UDP resources,
|
||||||
the substrate discriminator in `params` tells the establisher and
|
the substrate discriminator in `params` tells the establisher and
|
||||||
pumps which framing to expect — exactly option A's shape. The
|
pumps which framing to expect, exactly option A's shape. The
|
||||||
discriminator is load-bearing (it selects the framing, ADR-003),
|
discriminator is load-bearing (it selects the framing, ADR-003),
|
||||||
so it must ride the open op; the ALPN carries no substrate
|
so it must ride the open op; the ALPN carries no substrate
|
||||||
information at all.
|
information at all.
|
||||||
@@ -49,16 +49,16 @@ pump handler, and consumer session type are shared across substrates
|
|||||||
session types).
|
session types).
|
||||||
|
|
||||||
- The open op id is `channels/tunnel/sub` (the `channels/<alpn>/sub`
|
- The open op id is `channels/tunnel/sub` (the `channels/<alpn>/sub`
|
||||||
convention of alkcall ADR-047 — the open op is a `Sub`-typed
|
convention of alkcall ADR-047 (the open op is a `Sub`-typed
|
||||||
operation: the reply carries `channel_id` once, the data plane
|
operation: the reply carries `channel_id` once, the data plane
|
||||||
flows on the channel's `BiStream`).
|
flows on the channel's `BiStream`).
|
||||||
- The channel's ALPN marker on the open-op spec is `alk/tunnel`
|
- The channel's ALPN marker on the open-op spec is `alk/tunnel`
|
||||||
(`ChannelOpenSpec::new("alk/tunnel")`) — the value the
|
(`ChannelOpenSpec::new("alk/tunnel")`, which is the value the
|
||||||
per-ALPN dispatch and the adopted-side manager record for
|
per-ALPN dispatch and the adopted-side manager record for
|
||||||
observability.
|
observability.
|
||||||
- If a future tunnel flavor needs structurally different framing from
|
- If a future tunnel flavor needs structurally different framing from
|
||||||
byte zero with no params-dependent dispatch, that is a NEW ALPN
|
byte zero with no params-dependent dispatch, that is a NEW ALPN
|
||||||
decided by a new ADR before its first consumer — never a change to
|
decided by a new ADR before its first consumer, and never a change to
|
||||||
`alk/tunnel`'s meaning. ALPN strings are wire-stable once
|
`alk/tunnel`'s meaning. ALPN strings are wire-stable once
|
||||||
published.
|
published.
|
||||||
|
|
||||||
@@ -71,12 +71,12 @@ session types).
|
|||||||
`(resource, substrate)`.
|
`(resource, substrate)`.
|
||||||
- **Framing is params-dependent, not ALPN-dependent.** The establisher
|
- **Framing is params-dependent, not ALPN-dependent.** The establisher
|
||||||
validates `substrate` semantically (the registry lookup); the pump
|
validates `substrate` semantically (the registry lookup); the pump
|
||||||
handler is framing-agnostic (raw pass-through halves — the codec
|
handler is framing-agnostic (raw pass-through halves; the codec
|
||||||
lives on the consumer/producer edge for UDP, ADR-003). A mismatched
|
lives on the consumer/producer edge for UDP, ADR-003). A mismatched
|
||||||
`substrate` value fails at schema/establisher time, loudly.
|
`substrate` value fails at schema/establisher time, loudly.
|
||||||
- **ALPN namespace hygiene:** `alk/tunnel` is published here. The
|
- **ALPN namespace hygiene:** `alk/tunnel` is published here. The
|
||||||
`alknet/tunnel` spelling in the alknet ADR-071 table predates the
|
`alknet/tunnel` spelling in the alknet ADR-071 table predates the
|
||||||
`alknet/` → `alk/` prefix swap (alkcall v0.1.1) — docs and code
|
`alknet/` → `alk/` prefix swap (alkcall v0.1.1), so docs and code
|
||||||
must not perpetuate the old prefix.
|
must not perpetuate the old prefix.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -88,20 +88,20 @@ session types).
|
|||||||
|
|
||||||
## Amendment 1 (flavor-form op ids, 2026-09-16)
|
## Amendment 1 (flavor-form op ids, 2026-09-16)
|
||||||
|
|
||||||
The decision stands — one ALPN, no ALPN split per flavor. What
|
The decision stands: one ALPN, no ALPN split per flavor. What
|
||||||
changes: the one-ALPN rule is realized as **one ALPN, several
|
changes: the one-ALPN rule is realized as **one ALPN, several
|
||||||
`Sub`-typed open ops**, because ADR-007 (the direct op) and ADR-008
|
`Sub`-typed open ops**, because ADR-007 (the direct op) and ADR-008
|
||||||
(the forwarded op) are different capabilities sharing the tunnel
|
(the forwarded op) are different capabilities sharing the tunnel
|
||||||
transport — ssh's one-transport-many-channel-types shape.
|
transport, ssh's one-transport-many-channel-types shape.
|
||||||
|
|
||||||
- The op-id convention extends to flavor form: `channels/<alpn>/sub`
|
- The op-id convention extends to flavor form: `channels/<alpn>/sub`
|
||||||
(the original resource op, reserved and unchanged),
|
(the original resource op, reserved and unchanged),
|
||||||
`channels/<alpn>/direct` (dial an arbitrary target), and
|
`channels/<alpn>/direct` (dial an arbitrary target), and
|
||||||
`channels/<alpn>/forwarded` (accept-as-open toward the listen
|
`channels/<alpn>/forwarded` (accept-as-open toward the listen
|
||||||
opener). New flavors are new op ids — additive; the `…/sub` op is
|
opener). New flavors are new op ids, which is additive; the `…/sub` op is
|
||||||
never reused for a different meaning.
|
never reused for a different meaning.
|
||||||
- The alternative — sibling ALPNs (`alk/tunnel-direct`,
|
- The alternative, sibling ALPNs (`alk/tunnel-direct`,
|
||||||
`alk/tunnel-forwarded`) with standard `…/sub` op ids — was rejected:
|
`alk/tunnel-forwarded`) with standard `…/sub` op ids, was rejected:
|
||||||
it fragments the tunnel namespace per flavor, forces hub relay to
|
it fragments the tunnel namespace per flavor, forces hub relay to
|
||||||
track three ALPNs, and amends this ADR's own new-ALPN justification
|
track three ALPNs, and amends this ADR's own new-ALPN justification
|
||||||
clause (the flavors share framing; none needs structurally different
|
clause (the flavors share framing; none needs structurally different
|
||||||
@@ -109,7 +109,7 @@ transport — ssh's one-transport-many-channel-types shape.
|
|||||||
- Upstream coordination: alkcall ADR-047 pins discovery's ALPN
|
- Upstream coordination: alkcall ADR-047 pins discovery's ALPN
|
||||||
derivation to exactly the `…/sub`/`…/pub` suffix shapes
|
derivation to exactly the `…/sub`/`…/pub` suffix shapes
|
||||||
(`derive_alpn_from_op_name`); flavor-form op ids need that
|
(`derive_alpn_from_op_name`); flavor-form op ids need that
|
||||||
derivation (and the hub-relay reconstruction riding it) extended —
|
derivation (and the hub-relay reconstruction riding it) extended; it was
|
||||||
filed upstream
|
filed upstream
|
||||||
(`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`,
|
(`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`,
|
||||||
ask #1) before the first consumer. Runtime was never blocked:
|
ask #1) before the first consumer. Runtime was never blocked:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# ADR-003: Data-Plane Codec — Raw Pass-Through (Stream) / Mandatory Length Framing (UDP)
|
# ADR-003: Data-Plane Codec, Raw Pass-Through (Stream) / Mandatory Length Framing (UDP)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (2026-09-07; supersedes the Phase 0 "codec direction" hunch
|
Accepted (2026-09-07; supersedes the Phase 0 "codec direction" hunch
|
||||||
by making the UDP framing mandatory — reverse POC finding F-2).
|
by making the UDP framing mandatory, per reverse POC finding F-2).
|
||||||
Amended (2026-09-08, review 001 U-1 remediation): the substrate-
|
Amended (2026-09-08, review 001 U-1 remediation): the substrate-
|
||||||
boundary placement is pinned mechanically — the `local` feature's
|
boundary placement is pinned mechanically, so the `local` feature's
|
||||||
`UdpHalf` composes the codec at the boundary (channel side framed,
|
`UdpHalf` composes the codec at the boundary (channel side framed,
|
||||||
target side raw), with the pump-window and recv-scratch disciplines
|
target side raw), with the pump-window and recv-scratch disciplines
|
||||||
the composition requires. See §Amendment (2026-09-08).
|
the composition requires. See §Amendment (2026-09-08).
|
||||||
@@ -14,10 +14,10 @@ the composition requires. See §Amendment (2026-09-08).
|
|||||||
|
|
||||||
The tunnel data plane rides a channels data channel: raw bytes inside
|
The tunnel data plane rides a channels data channel: raw bytes inside
|
||||||
the channel `BiStream` (the channels layer strips its 8-byte header
|
the channel `BiStream` (the channels layer strips its 8-byte header
|
||||||
transparently — alknet ADR-093/ADR-071; AGENTS.md convention 5). The
|
transparently, per alknet ADR-093/ADR-071 and AGENTS.md convention 5). The
|
||||||
tunnel protocol owns whatever framing it puts inside the `BiStream`.
|
tunnel protocol owns whatever framing it puts inside the `BiStream`.
|
||||||
A tunnel has one data stream per direction, so there is no
|
A tunnel has one data stream per direction, so there is no
|
||||||
sub-demux key — a 5-byte header like alktty's (with `stream_type`)
|
sub-demux key (a 5-byte header like alktty's, with `stream_type`,
|
||||||
would be pure overhead here.
|
would be pure overhead here.
|
||||||
|
|
||||||
The Phase 0 direction (`phase-0-findings.md` checklist, 2026-09-06):
|
The Phase 0 direction (`phase-0-findings.md` checklist, 2026-09-06):
|
||||||
@@ -29,14 +29,14 @@ Two validation passes sharpened this:
|
|||||||
|
|
||||||
1. **Forward POC (2026-09-06, 17 tests):** the codec round-trips
|
1. **Forward POC (2026-09-06, 17 tests):** the codec round-trips
|
||||||
datagrams split across chunks, batched in one chunk, partial
|
datagrams split across chunks, batched in one chunk, partial
|
||||||
headers, and empty datagrams (`len=0` is a legal payload, NOT EOF
|
headers, and empty datagrams (`len=0` is a legal payload, NOT EOF;
|
||||||
— EOF is the channels-level `length=0` chunk sentinel; the two
|
EOF is the channels-level `length=0` chunk sentinel; the two
|
||||||
coexist at different layers). The 1400-byte (max ethernet MTU
|
coexist at different layers). The 1400-byte (max ethernet MTU
|
||||||
payload) datagram rides the bounded-buffer path; >65535 is
|
payload) datagram rides the bounded-buffer path; >65535 is
|
||||||
rejected at frame time.
|
rejected at frame time.
|
||||||
2. **Reverse POC finding F-2 (2026-09-07, executable-pinned):** in
|
2. **Reverse POC finding F-2 (2026-09-07, executable-pinned):** in
|
||||||
the RAW pass-through pump shape, an empty UDP datagram is a
|
the RAW pass-through pump shape, an empty UDP datagram is a
|
||||||
zero-byte read from the substrate adapter — `tokio::io::copy`
|
zero-byte read from the substrate adapter, and `tokio::io::copy`
|
||||||
treats `Ok(0)` as end-of-stream and shuts the pump down. An empty
|
treats `Ok(0)` as end-of-stream and shuts the pump down. An empty
|
||||||
datagram and the zero-length EOF sentinel are the SAME wire shape
|
datagram and the zero-length EOF sentinel are the SAME wire shape
|
||||||
at the pump level. The test pins the non-round-trip so the
|
at the pump level. The test pins the non-round-trip so the
|
||||||
@@ -49,7 +49,7 @@ The data plane inside a `alk/tunnel` channel is:
|
|||||||
- **Stream substrates (`tcp`, `unix`): raw pass-through.** The halves
|
- **Stream substrates (`tcp`, `unix`): raw pass-through.** The halves
|
||||||
are the tunnel; zero tunnel-level framing. The only wire overhead
|
are the tunnel; zero tunnel-level framing. The only wire overhead
|
||||||
is the channels 8-byte chunk header. A zero-length read on a stream
|
is the channels 8-byte chunk header. A zero-length read on a stream
|
||||||
substrate is genuinely EOF (stream semantics) — no collision. Both
|
substrate is genuinely EOF (stream semantics), so there is no collision. Both
|
||||||
pumps run `alkcall::channels::pump_bidi` (ADR-050) over the raw
|
pumps run `alkcall::channels::pump_bidi` (ADR-050) over the raw
|
||||||
halves.
|
halves.
|
||||||
- **Datagram substrate (`udp`): mandatory `[len: u16 BE][datagram]`
|
- **Datagram substrate (`udp`): mandatory `[len: u16 BE][datagram]`
|
||||||
@@ -57,7 +57,7 @@ The data plane inside a `alk/tunnel` channel is:
|
|||||||
length-prefixed frame, on BOTH directions:
|
length-prefixed frame, on BOTH directions:
|
||||||
- The **establisher** wraps the dialed UDP socket in the framed
|
- The **establisher** wraps the dialed UDP socket in the framed
|
||||||
adapter BEFORE returning the plan (the plan payload is the
|
adapter BEFORE returning the plan (the plan payload is the
|
||||||
adapter, not a raw socket) — the pump handler stays
|
adapter, not a raw socket), so the pump handler stays
|
||||||
substrate-agnostic (ADR-004's invariant; producer.md's placement).
|
substrate-agnostic (ADR-004's invariant; producer.md's placement).
|
||||||
- Consumer side: the `TunnelSession`'s datagram variant does the
|
- Consumer side: the `TunnelSession`'s datagram variant does the
|
||||||
same over the adopted halves.
|
same over the adopted halves.
|
||||||
@@ -66,29 +66,29 @@ The data plane inside a `alk/tunnel` channel is:
|
|||||||
POC). EOF is exclusively the channels-level sentinel; the codec
|
POC). EOF is exclusively the channels-level sentinel; the codec
|
||||||
never emits zero-length reads, so the layers never collide. This
|
never emits zero-length reads, so the layers never collide. This
|
||||||
is the load-bearing property F-2 validated from a second angle:
|
is the load-bearing property F-2 validated from a second angle:
|
||||||
the length prefix makes an empty datagram two bytes — unambiguous
|
the length prefix makes an empty datagram two bytes, which is unambiguous
|
||||||
with EOF.
|
with EOF.
|
||||||
- **MTU discipline:** the codec rejects framing a >65535-byte
|
- **MTU discipline:** the codec rejects framing a >65535-byte
|
||||||
datagram (`Oversize` error at frame time, never a wire overflow —
|
datagram (`Oversize` error at frame time, never a wire overflow;
|
||||||
the u16 length field would wrap). A truncated receive (caller
|
the u16 length field would wrap). A truncated receive (caller
|
||||||
buffer smaller than the datagram) fails loudly at the adapter
|
buffer smaller than the datagram) fails loudly at the adapter
|
||||||
level — resolved 2026-09-07 (OQ-TN-13, fail-loud): silent
|
level, resolved 2026-09-07 (OQ-TN-13, fail-loud): silent
|
||||||
truncation would corrupt the framing invariants (the same
|
truncation would corrupt the framing invariants (the same
|
||||||
invariant class F-2 protects).
|
invariant class F-2 protects).
|
||||||
- **Chunking is transparent.** Datagrams split across channel chunks,
|
- **Chunking is transparent.** Datagrams split across channel chunks,
|
||||||
batch into single chunks, and survive partial headers — the
|
batch into single chunks, and survive partial headers; the
|
||||||
incremental decoder is the only consumer-visible decode path
|
incremental decoder is the only consumer-visible decode path
|
||||||
(forward POC: maximally awkward 7-byte chunk splits verified).
|
(forward POC: maximally awkward 7-byte chunk splits verified).
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
### Amendment (2026-09-08 — the boundary mechanics, from review 001 U-1's remediation)
|
### Amendment (2026-09-08, the boundary mechanics from review 001 U-1's remediation)
|
||||||
|
|
||||||
The placement sentence ("the establisher wraps the dialed UDP socket
|
The placement sentence ("the establisher wraps the dialed UDP socket
|
||||||
in the framed adapter before returning the plan") leaves the adapter's
|
in the framed adapter before returning the plan") leaves the adapter's
|
||||||
*mechanics* open; review 001 found `UdpHalf` shipping as a raw
|
*mechanics* open; review 001 found `UdpHalf` shipping as a raw
|
||||||
boundary-preserving adapter (the codec on one end only — real targets
|
boundary-preserving adapter (the codec on one end only, so real targets
|
||||||
saw the tunnel's length prefixes; empty datagrams collapsed to EOF at
|
saw the tunnel's length prefixes; empty datagrams collapsed to EOF at
|
||||||
the pump's zero-read check; >8 KiB datagrams split at the copy
|
the pump's zero-read check; >8 KiB datagrams split at the copy
|
||||||
buffer). The remediation pins the mechanics:
|
buffer). The remediation pins the mechanics:
|
||||||
|
|
||||||
@@ -98,29 +98,29 @@ buffer). The remediation pins the mechanics:
|
|||||||
sends each decoded payload as one raw datagram; `poll_read` **frames**
|
sends each decoded payload as one raw datagram; `poll_read` **frames**
|
||||||
(`frame_datagram`) each raw target datagram and hands frame bytes to
|
(`frame_datagram`) each raw target datagram and hands frame bytes to
|
||||||
the pump. "Frame on write, de-frame on read" describes the TARGET's
|
the pump. "Frame on write, de-frame on read" describes the TARGET's
|
||||||
face and is the inverted (wrong) reading — the review's original
|
face and is the inverted (wrong) reading; the review's original
|
||||||
fix sketch had it inverted and was corrected in re-validation.
|
fix sketch had it inverted and was corrected in re-validation.
|
||||||
- **The write half accepts all offered bytes and reports them
|
- **The write half accepts all offered bytes and reports them
|
||||||
consumed.** The pump's copy engine re-offers a *full* window
|
consumed.** The pump's copy engine re-offers a *full* window
|
||||||
unsatisfied until it drains (tokio `copy`'s `read_done` path never
|
unsatisfied until it drains (tokio `copy`'s `read_done` path never
|
||||||
tops the window up), so a partial-frame tail could sit forever in a
|
tops the window up), so a partial-frame tail could sit forever in a
|
||||||
full window if the adapter accepted only complete frames — the
|
full window if the adapter accepted only complete frames; the
|
||||||
>8 KiB frame would deadlock the pump. Completed datagrams queue for
|
>8 KiB frame would deadlock the pump. Completed datagrams queue for
|
||||||
the socket; a trailing partial frame waits in the `DatagramReader`
|
the socket; a trailing partial frame waits in the `DatagramReader`
|
||||||
(its state is bounded by construction — the u16 cap, review 001
|
(its state is bounded by construction, by the u16 cap, review 001
|
||||||
N-2). Backpressure is an accepted-byte high-water mark on
|
N-2). Backpressure is an accepted-byte high-water mark on
|
||||||
`poll_write`, and the socket drain pumps opportunistically on
|
`poll_write`, and the socket drain pumps opportunistically on
|
||||||
write/flush/shutdown (tokio's copy polls flush after every window
|
write/flush/shutdown (tokio's copy polls flush after every window
|
||||||
drain).
|
drain).
|
||||||
- **The recv scratch is full-size (65535) and lazy.** `try_recv`
|
- **The recv scratch is full-size (65535) and lazy.** `try_recv`
|
||||||
truncates SILENTLY at the buffer size — a smaller scratch corrupts
|
truncates SILENTLY at the buffer size, and a smaller scratch corrupts
|
||||||
the framing before any size check could reject it (the review's
|
the framing before any size check could reject it (the review's
|
||||||
"size to the MTU" suggestion is unsafe as written). The 64 KiB
|
"size to the MTU" suggestion is unsafe as written). The 64 KiB
|
||||||
eager allocation is instead lazy (first recv), the U-3 mitigation.
|
eager allocation is instead lazy (first recv), the U-3 mitigation.
|
||||||
- **A >caller-buffer frame is staged, not an error.** The frame's
|
- **A >caller-buffer frame is staged, not an error.** The frame's
|
||||||
length prefix reaches the pump on the first read; the remainder
|
length prefix reaches the pump on the first read; the remainder
|
||||||
hands out across reads. The pump's far-side de-framer reassembles
|
hands out across reads. The pump's far-side de-framer reassembles
|
||||||
the true length — the raw adapter's read-level `InvalidData` is
|
the true length; the raw adapter's read-level `InvalidData` is
|
||||||
replaced by staging (the fail-loud posture now protects the *frame
|
replaced by staging (the fail-loud posture now protects the *frame
|
||||||
stream*, and a wire-level over-bound datagram remains a loud
|
stream*, and a wire-level over-bound datagram remains a loud
|
||||||
`Oversize`-class error at the frame path).
|
`Oversize`-class error at the frame path).
|
||||||
@@ -131,9 +131,9 @@ recording-target suite).
|
|||||||
|
|
||||||
## Consequences (original)
|
## Consequences (original)
|
||||||
|
|
||||||
- **TCP/unix tunnels have 0 B tunnel overhead** — the SSH
|
- **TCP/unix tunnels have 0 B tunnel overhead**, matching the SSH
|
||||||
`direct-tcpip` experience (the payload is the bytes).
|
`direct-tcpip` experience (the payload is the bytes).
|
||||||
- **UDP correctness requires the codec** — raw pass-through over UDP
|
- **UDP correctness requires the codec**, since raw pass-through over UDP
|
||||||
is structurally broken for empty datagrams (F-2). The substrate
|
is structurally broken for empty datagrams (F-2). The substrate
|
||||||
discriminator (ADR-001) is what tells both sides to engage the
|
discriminator (ADR-001) is what tells both sides to engage the
|
||||||
codec; a consumer bypassing it for UDP is a spec violation, not a
|
codec; a consumer bypassing it for UDP is a spec violation, not a
|
||||||
@@ -141,8 +141,8 @@ recording-target suite).
|
|||||||
- **`tokio::io::copy`-shaped pumps fit UDP only through the framed
|
- **`tokio::io::copy`-shaped pumps fit UDP only through the framed
|
||||||
adapter** (the `UdpHalf` + codec wrapper presents
|
adapter** (the `UdpHalf` + codec wrapper presents
|
||||||
`AsyncRead`/`AsyncWrite` whose zero-length read never occurs for a
|
`AsyncRead`/`AsyncWrite` whose zero-length read never occurs for a
|
||||||
well-formed stream — `len=0` yields a 2-byte wire frame).
|
well-formed stream; `len=0` yields a 2-byte wire frame).
|
||||||
- **The BAST document exists** (AGENTS.md convention 12 — the
|
- **The BAST document exists** (AGENTS.md convention 12; the
|
||||||
mandatory framing IS binary framing beyond pass-through, so the
|
mandatory framing IS binary framing beyond pass-through, so the
|
||||||
trigger fired): `docs/architecture/bast.md` describes the
|
trigger fired): `docs/architecture/bast.md` describes the
|
||||||
`[len: u16 BE]` datagram frame per the BAST meta-schema; the
|
`[len: u16 BE]` datagram frame per the BAST meta-schema; the
|
||||||
@@ -160,6 +160,6 @@ recording-target suite).
|
|||||||
sentinel layering + MTU); reverse POC:
|
sentinel layering + MTU); reverse POC:
|
||||||
`docs/research/reverse-poc-summary.md` §F-2 (the mandate)
|
`docs/research/reverse-poc-summary.md` §F-2 (the mandate)
|
||||||
- `docs/research/ssh-socks5-survey.md` §UDP (tun2proxy udpgw framing
|
- `docs/research/ssh-socks5-survey.md` §UDP (tun2proxy udpgw framing
|
||||||
precedent — no CONN_ID, no FLAGS; boundary framing only)
|
precedent (no CONN_ID, no FLAGS; boundary framing only)
|
||||||
- alknet ADR-071/093 (channels wire format; the zero-length sentinel)
|
- alknet ADR-071/093 (channels wire format; the zero-length sentinel)
|
||||||
- alkcall `channels::pump_bidi` (ADR-050)
|
- alkcall `channels::pump_bidi` (ADR-050)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# ADR-004: No TunnelBackend Trait — Halves Functions at the Assembly Layer
|
# ADR-004: No TunnelBackend Trait (Halves Functions at the Assembly Layer)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ re-produce thread)
|
|||||||
## Context
|
## Context
|
||||||
|
|
||||||
alktty has a `TtyBackend` trait because backends (local PTY, docker,
|
alktty has a `TtyBackend` trait because backends (local PTY, docker,
|
||||||
SSH) produce complex handles — `TtyHandle` carries
|
SSH) produce complex handles: `TtyHandle` carries
|
||||||
stdin/stdout/stderr/exit-code quadruple + control — and the adapter
|
stdin/stdout/stderr/exit-code quadruple + control, and the adapter
|
||||||
pumps them. The Phase 0 question (OQ-TN-05): does alktunnels need a
|
pumps them. The Phase 0 question (OQ-TN-05): does alktunnels need a
|
||||||
`TunnelBackend` trait, or is the two-pump handler + feature-gated
|
`TunnelBackend` trait, or is the two-pump handler + feature-gated
|
||||||
substrate modules the whole story?
|
substrate modules the whole story?
|
||||||
@@ -24,7 +24,7 @@ backend's:
|
|||||||
connection is the same boxed-halves shape.
|
connection is the same boxed-halves shape.
|
||||||
|
|
||||||
The UDP POC gave the executable answer for dial flows (2026-09-06):
|
The UDP POC gave the executable answer for dial flows (2026-09-06):
|
||||||
`pump_halves` is generic over boxed `(AsyncRead, AsyncWrite)` halves —
|
`pump_halves` is generic over boxed `(AsyncRead, AsyncWrite)` halves:
|
||||||
TCP contributes `into_split()` halves, UDP contributes the `UdpHalf`
|
TCP contributes `into_split()` halves, UDP contributes the `UdpHalf`
|
||||||
socket adapter, the pump never knows which. "Produce boxed halves for
|
socket adapter, the pump never knows which. "Produce boxed halves for
|
||||||
a resource" is a function, not a trait.
|
a resource" is a function, not a trait.
|
||||||
@@ -33,7 +33,7 @@ The one remaining thread was the hub re-produce composition (does a
|
|||||||
hub proxy that re-produces a consumed resource need a trait or a
|
hub proxy that re-produces a consumed resource need a trait or a
|
||||||
composition helper?). The reverse POC (2026-09-07) resolved the
|
composition helper?). The reverse POC (2026-09-07) resolved the
|
||||||
mechanics: a hub proxy is a consumer + producer composed at the
|
mechanics: a hub proxy is a consumer + producer composed at the
|
||||||
assembly layer — the reverse POC's hub is exactly this shape (it
|
assembly layer; the reverse POC's hub is exactly this shape (it
|
||||||
consumes a channels connection and produces reverse-tunnel opens
|
consumes a channels connection and produces reverse-tunnel opens
|
||||||
toward the worker); re-exporting (registering an openable for a
|
toward the worker); re-exporting (registering an openable for a
|
||||||
resource the hub itself consumes) would add a
|
resource the hub itself consumes) would add a
|
||||||
@@ -51,7 +51,7 @@ assembly layer:
|
|||||||
|
|
||||||
- **The produce function.** The producer's establisher resolves
|
- **The produce function.** The producer's establisher resolves
|
||||||
`(resource, substrate)` → boxed halves
|
`(resource, substrate)` → boxed halves
|
||||||
(`Box<dyn AsyncRead + Send + Sync + Unpin>` ×2 — the `+ Sync` is
|
(`Box<dyn AsyncRead + Send + Sync + Unpin>` ×2; the `+ Sync` is
|
||||||
the `ChannelPlan` bound, reverse POC F-1) or a framed UDP adapter.
|
the `ChannelPlan` bound, reverse POC F-1) or a framed UDP adapter.
|
||||||
The dial itself is substrate code living in feature-gated backend
|
The dial itself is substrate code living in feature-gated backend
|
||||||
modules (`local` feature: real sockets), injected at assembly:
|
modules (`local` feature: real sockets), injected at assembly:
|
||||||
@@ -64,12 +64,12 @@ assembly layer:
|
|||||||
open op; its establisher pops the next accepted connection from an
|
open op; its establisher pops the next accepted connection from an
|
||||||
assembly-owned listener queue and returns it as the plan payload.
|
assembly-owned listener queue and returns it as the plan payload.
|
||||||
The listener lifecycle (bind, accept loop, cancel) is assembly
|
The listener lifecycle (bind, accept loop, cancel) is assembly
|
||||||
code — the protocol never binds (OQ-TN-04). The reverse POC's
|
code; the protocol never binds (OQ-TN-04). The reverse POC's
|
||||||
worker (dial) and the forward POC's producer (dial) both rode this
|
worker (dial) and the forward POC's producer (dial) both rode this
|
||||||
shape; the listen establisher is the same plan-flow with a
|
shape; the listen establisher is the same plan-flow with a
|
||||||
different halves source.
|
different halves source.
|
||||||
- **The pump handler is substrate-agnostic by construction:**
|
- **The pump handler is substrate-agnostic by construction:**
|
||||||
`pump_bidi(bidi, t_read, t_write)` over the downcast plan payload —
|
`pump_bidi(bidi, t_read, t_write)` over the downcast plan payload;
|
||||||
it cannot know whether the halves came from TCP, UDP, a unix
|
it cannot know whether the halves came from TCP, UDP, a unix
|
||||||
socket, or an in-process pipe, and must not.
|
socket, or an in-process pipe, and must not.
|
||||||
- **Datagram substrates** contribute a framed adapter (ADR-003)
|
- **Datagram substrates** contribute a framed adapter (ADR-003)
|
||||||
@@ -78,9 +78,9 @@ assembly layer:
|
|||||||
every assembly layer re-implements the same glue (registry + dial +
|
every assembly layer re-implements the same glue (registry + dial +
|
||||||
establisher construction + pump wiring) three or more times, extract
|
establisher construction + pump wiring) three or more times, extract
|
||||||
a `TunnelBackend`-shaped trait THEN (the alknet ADR-078
|
a `TunnelBackend`-shaped trait THEN (the alknet ADR-078
|
||||||
convergence-test threshold — extract when the shapes have converged
|
convergence-test threshold: extract when the shapes have converged
|
||||||
across consumers, not before)
|
across consumers, not before)
|
||||||
— as an additive layer, not a v1 wire/ABI commitment. Until then
|
and do it as an additive layer, not a v1 wire/ABI commitment. Until then
|
||||||
the function-plus-closure shape keeps the crate dependency-light
|
the function-plus-closure shape keeps the crate dependency-light
|
||||||
and wasm-clean.
|
and wasm-clean.
|
||||||
|
|
||||||
@@ -89,14 +89,14 @@ assembly layer:
|
|||||||
- **The default crate stays wasm-clean** with no socket/platform
|
- **The default crate stays wasm-clean** with no socket/platform
|
||||||
deps; `local` (sockets) and future backends (docker, process) are
|
deps; `local` (sockets) and future backends (docker, process) are
|
||||||
feature-gated modules implementing dial/listen functions.
|
feature-gated modules implementing dial/listen functions.
|
||||||
- **Assembly layers write more glue than a trait would save** — the
|
- **Assembly layers write more glue than a trait would save**, which is
|
||||||
accepted cost, validated twice (both POCs' producers are ~200 lines
|
the accepted cost, validated twice (both POCs' producers are ~200 lines
|
||||||
including the UDP adapter).
|
including the UDP adapter).
|
||||||
- **Plan payloads are `Send + Sync`** (reverse POC F-1, documented on
|
- **Plan payloads are `Send + Sync`** (reverse POC F-1, documented on
|
||||||
alkcall's `ChannelPlan` type in 0.7.0): socket-backed handles carry
|
alkcall's `ChannelPlan` type in 0.7.0): socket-backed handles carry
|
||||||
`+ Sync` naturally; non-Sync handles (process pipes as boxed trait
|
`+ Sync` naturally; non-Sync handles (process pipes as boxed trait
|
||||||
objects) need a wrapper. The spec documents the constraint.
|
objects) need a wrapper. The spec documents the constraint.
|
||||||
- **The listen variant needs no new wire surface** — same open op,
|
- **The listen variant needs no new wire surface**: same open op,
|
||||||
same params, same typed errors (an empty listener queue during
|
same params, same typed errors (an empty listener queue during
|
||||||
establishment is `resource_shortage`; a closed listener is
|
establishment is `resource_shortage`; a closed listener is
|
||||||
`dial_failed`-class).
|
`dial_failed`-class).
|
||||||
@@ -108,8 +108,8 @@ assembly layer:
|
|||||||
- Forward POC: `docs/research/poc-summary.md` §5 (halves-not-trait);
|
- Forward POC: `docs/research/poc-summary.md` §5 (halves-not-trait);
|
||||||
reverse POC: `docs/research/reverse-poc-summary.md` (the hub shape,
|
reverse POC: `docs/research/reverse-poc-summary.md` (the hub shape,
|
||||||
F-1)
|
F-1)
|
||||||
- alktty ADR-002 (`TtyBackend` — the contrast case: when a trait IS
|
- alktty ADR-002 (`TtyBackend`, the contrast case: when a trait IS
|
||||||
warranted), alktty ADR-003 (backend placement — the feature-gate
|
warranted), alktty ADR-003 (backend placement, the feature-gate
|
||||||
pattern)
|
pattern)
|
||||||
- AGENTS.md conventions 4/7/14 (wasm-clean, substrate-agnostic,
|
- AGENTS.md conventions 4/7/14 (wasm-clean, substrate-agnostic,
|
||||||
feature flags)
|
feature flags)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
Accepted (2026-09-07; resolves the reverse POC W3 finding).
|
Accepted (2026-09-07; resolves the reverse POC W3 finding).
|
||||||
Amended (2026-09-08, review 001 N-3 decision): the datagram
|
Amended (2026-09-08, review 001 N-3 decision): the datagram
|
||||||
`take_halves` posture is pinned — raw framed halves stay the only
|
`take_halves` posture is pinned: raw framed halves stay the only
|
||||||
owned-halves shape (`take_halves` keeps its semantics for every
|
owned-halves shape (`take_halves` keeps its semantics for every
|
||||||
substrate), the datagram accessors remain the codec path, and a
|
substrate), the datagram accessors remain the codec path, and a
|
||||||
framed-halves convenience is deferred to the first consumer that
|
framed-halves convenience is deferred to the first consumer that
|
||||||
@@ -17,23 +17,23 @@ A tunnel channel's two ends have asymmetric lifecycle machinery:
|
|||||||
- **The serving side (the side the open op ran on)** is
|
- **The serving side (the side the open op ran on)** is
|
||||||
wrapper-managed: the open wrapper awaits the pump handler's
|
wrapper-managed: the open wrapper awaits the pump handler's
|
||||||
`JoinHandle`, and its completion triggers channel teardown (drop of
|
`JoinHandle`, and its completion triggers channel teardown (drop of
|
||||||
the demux sender = EOF to the handler's read half — alkcall ADR-049
|
the demux sender = EOF to the handler's read half, per alkcall ADR-049
|
||||||
+ review 007 R-02). Out-of-band `channel/close` also tears it down.
|
+ review 007 R-02). Out-of-band `channel/close` also tears it down.
|
||||||
Nothing leaks.
|
Nothing leaks.
|
||||||
- **The adopting side (the side that called the open op and adopted
|
- **The adopting side (the side that called the open op and adopted
|
||||||
the channel ID — the consumer)** has NO such machinery:
|
the channel ID, i.e. the consumer)** has NO such machinery:
|
||||||
`ChannelManager::adopt_channel` installs routing state nothing
|
`ChannelManager::adopt_channel` installs routing state nothing
|
||||||
awaits. The consumer-side pump (`pump_bidi` spawned locally) is
|
awaits. The consumer-side pump (`pump_bidi` spawned locally) is
|
||||||
somebody's `JoinHandle`; the adopted channel state is nobody's to
|
somebody's `JoinHandle`; the adopted channel state is nobody's to
|
||||||
reap. If the assembly layer drops both, the channel entry leaks in
|
reap. If the assembly layer drops both, the channel entry leaks in
|
||||||
the manager until transport EOF (`clear_all`) — and worse, a
|
the manager until transport EOF (`clear_all`); and worse, a
|
||||||
consumer that drops the pump handle mid-flight aborts the pump
|
consumer that drops the pump handle mid-flight aborts the pump
|
||||||
without reaping, leaving half-open state on the peer.
|
without reaping, leaving half-open state on the peer.
|
||||||
|
|
||||||
The reverse POC found this by construction (W3, 2026-09-07): the hub
|
The reverse POC found this by construction (W3, 2026-09-07): the hub
|
||||||
must hold its pump handle and reap (`teardown_channel`) itself —
|
must hold its pump handle and reap (`teardown_channel`) itself.
|
||||||
`ReverseTunnel::join_and_reap`/`close` in the POC. The lifecycle is
|
`ReverseTunnel::join_and_reap`/`close` in the POC. The lifecycle is
|
||||||
assembly-layer by design (OQ-TN-04 — no forced binding means no
|
assembly-layer by design (OQ-TN-04: no forced binding means no
|
||||||
protocol-owned listener lifecycle either), but the POC demonstrated
|
protocol-owned listener lifecycle either), but the POC demonstrated
|
||||||
that leaving teardown discipline to each assembly layer's memory is
|
that leaving teardown discipline to each assembly layer's memory is
|
||||||
exactly the kind of gap the spec must close with a type, not a doc
|
exactly the kind of gap the spec must close with a type, not a doc
|
||||||
@@ -49,31 +49,31 @@ plane (raw halves vs datagram codec, ADR-003).
|
|||||||
The consumer half is a typed session, **`TunnelSession`**, and it owns
|
The consumer half is a typed session, **`TunnelSession`**, and it owns
|
||||||
its teardown:
|
its teardown:
|
||||||
|
|
||||||
- **Construction:** `TunnelSession::open(client, params)` — calls the
|
- **Construction:** `TunnelSession::open(client, params)`: calls the
|
||||||
open op (`ChannelClient::open_channel` on the forward path; the
|
open op (`ChannelClient::open_channel` on the forward path; the
|
||||||
reverse path's `open_reverse_channel` + `adopt_channel` equivalent),
|
reverse path's `open_reverse_channel` + `adopt_channel` equivalent),
|
||||||
adopts the returned channel ID, splits the channel `BiStream`, and
|
adopts the returned channel ID, splits the channel `BiStream`, and
|
||||||
presents the substrate-shaped data plane:
|
presents the substrate-shaped data plane:
|
||||||
- **Stream variant:** raw halves (`AsyncRead`/`AsyncWrite`) — the
|
- **Stream variant:** raw halves (`AsyncRead`/`AsyncWrite`); the
|
||||||
halves ARE the tunnel.
|
halves ARE the tunnel.
|
||||||
- **Datagram variant:** `send_datagram`/`recv_datagram` over the
|
- **Datagram variant:** `send_datagram`/`recv_datagram` over the
|
||||||
mandatory codec (ADR-003) — boundary-preserving, `len=0` is an
|
mandatory codec (ADR-003), which is boundary-preserving; `len=0` is an
|
||||||
empty datagram, `recv_datagram` returns `None` only on stream EOF.
|
empty datagram, `recv_datagram` returns `None` only on stream EOF.
|
||||||
- **Pump ownership:** the session's `pump_against(accepted_halves)`
|
- **Pump ownership:** the session's `pump_against(accepted_halves)`
|
||||||
(the reverse-flow use) spawns `pump_bidi` and holds the returned
|
(the reverse-flow use) spawns `pump_bidi` and holds the returned
|
||||||
`JoinHandle`. For the pure consumer (no local pump — the session
|
`JoinHandle`. For the pure consumer (no local pump; the session
|
||||||
halves ARE handed to the caller), the session does not spawn; the
|
halves ARE handed to the caller), the session does not spawn; the
|
||||||
caller drives the halves and the session still owns the channel
|
caller drives the halves and the session still owns the channel
|
||||||
entry.
|
entry.
|
||||||
- **Teardown API (the point of the ADR):**
|
- **Teardown API (the point of the ADR):**
|
||||||
- `close(self)` — abort the pump (if session-owned), tear down the
|
- `close(self)`: abort the pump (if session-owned), tear down the
|
||||||
adopted channel (`teardown_channel`), consume the session. The
|
adopted channel (`teardown_channel`), consume the session. The
|
||||||
abort path (ungraceful).
|
abort path (ungraceful).
|
||||||
- `join(self)` — await pump completion (both directions finished),
|
- `join(self)`: await pump completion (both directions finished),
|
||||||
THEN reap the adopted channel, return the `(u64, u64)` copy
|
THEN reap the adopted channel, return the `(u64, u64)` copy
|
||||||
counts from `pump_bidi` for observability. The graceful path.
|
counts from `pump_bidi` for observability. The graceful path.
|
||||||
- Dropping the session without either: the `Drop` impl tears the
|
- Dropping the session without either: the `Drop` impl tears the
|
||||||
channel down (the panic-free fallback — never leak the entry).
|
channel down (the panic-free fallback, never leaking the entry).
|
||||||
`Drop` cannot await, so it aborts the pump and calls the
|
`Drop` cannot await, so it aborts the pump and calls the
|
||||||
sync `teardown_channel`; this is semantically `close`.
|
sync `teardown_channel`; this is semantically `close`.
|
||||||
- **Half-open semantics fall out of `pump_bidi`** (ADR-050): one
|
- **Half-open semantics fall out of `pump_bidi`** (ADR-050): one
|
||||||
@@ -81,7 +81,7 @@ its teardown:
|
|||||||
crosses the mux) → the other pump keeps running until its own EOF.
|
crosses the mux) → the other pump keeps running until its own EOF.
|
||||||
The session's `join` completes when BOTH pumps finish. Half-close
|
The session's `join` completes when BOTH pumps finish. Half-close
|
||||||
semantics validated end-to-end (reverse POC W4).
|
semantics validated end-to-end (reverse POC W4).
|
||||||
- **Error surface:** a failed open resolves a typed error —
|
- **Error surface:** a failed open resolves a typed error:
|
||||||
`ChannelOpenError::CallFailed` carrying the wire `CallError`;
|
`ChannelOpenError::CallFailed` carrying the wire `CallError`;
|
||||||
branch on `establishment_reason()` for `channel:open_failed`'s
|
branch on `establishment_reason()` for `channel:open_failed`'s
|
||||||
reason (ADR-049 §4). A failed open never yields a session (no
|
reason (ADR-049 §4). A failed open never yields a session (no
|
||||||
@@ -92,19 +92,19 @@ its teardown:
|
|||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
### Amendment (2026-09-08 — the N-3 datagram `take_halves` posture, decided before the first external consumer)
|
### Amendment (2026-09-08, the N-3 datagram `take_halves` posture, decided before the first external consumer)
|
||||||
|
|
||||||
Review 001 N-3: a datagram session exposes codec-applied accessors
|
Review 001 N-3: a datagram session exposes codec-applied accessors
|
||||||
(`send_datagram`/`recv_datagram`) until `take_halves` — then the
|
(`send_datagram`/`recv_datagram`) until `take_halves`, at which point the
|
||||||
caller owns RAW halves whose wire bytes carry the `[len: u16 BE]`
|
caller owns RAW halves whose wire bytes carry the `[len: u16 BE]`
|
||||||
framing. Documented, but a footgun asymmetry the API window
|
framing. Documented, but a footgun asymmetry the API window
|
||||||
(this ADR's surface becomes ABI-stable at the first external
|
(this ADR's surface becomes ABI-stable at the first external
|
||||||
consumer) forced a decision on. Decided: **option 1 — keep as-is,
|
consumer) forced a decision on. Decided: **option 1 (keep as-is,
|
||||||
carried by docs**, with the rationale:
|
carried by docs)**, with the rationale:
|
||||||
|
|
||||||
- **No external consumer exists** (verified 2026-09-08 — no crate in
|
- **No external consumer exists** (verified 2026-09-08; no crate in
|
||||||
the workspace depends on alktunnels); the window is open, so a
|
the workspace depends on alktunnels); the window is open, so a
|
||||||
breaking reshape was *available* — but nothing yet demonstrates a
|
breaking reshape was *available*, but nothing yet demonstrates a
|
||||||
need for a different shape.
|
need for a different shape.
|
||||||
- **The framing asymmetry is ADR-003's spec, not drift.** The
|
- **The framing asymmetry is ADR-003's spec, not drift.** The
|
||||||
datagram data plane is mandatory-framing by substrate
|
datagram data plane is mandatory-framing by substrate
|
||||||
@@ -113,23 +113,23 @@ carried by docs**, with the rationale:
|
|||||||
boundary." `take_halves` yielding raw halves keeps ONE semantic
|
boundary." `take_halves` yielding raw halves keeps ONE semantic
|
||||||
across substrates (the tunnel's wire bytes, uninterpreted); a
|
across substrates (the tunnel's wire bytes, uninterpreted); a
|
||||||
per-substrate `take_halves` (option 3) diverges semantics by
|
per-substrate `take_halves` (option 3) diverges semantics by
|
||||||
substrate — the worse confusion at the type level even as it
|
substrate (the worse confusion at the type level even as it
|
||||||
removes the byte-level footgun.
|
removes the byte-level footgun.
|
||||||
- **`pump_against` needs the framed adapter, not framed halves.** The
|
- **`pump_against` needs the framed adapter, not framed halves.** The
|
||||||
adjacent seam (N-11, resolved in-review with the doc note): a UDP
|
adjacent seam (N-11, resolved in-review with the doc note): a UDP
|
||||||
session pumped against an accepted local half requires the FRAMED
|
session pumped against an accepted local half requires the FRAMED
|
||||||
adapter on the accepted side — the `local` feature's `UdpHalf` (or
|
adapter on the accepted side, namely the `local` feature's `UdpHalf` (or
|
||||||
an assembly equivalent), not a raw socket. What composes there is
|
an assembly equivalent), not a raw socket. What composes there is
|
||||||
the adapter AT the boundary, not a session-side wrapper pair; a
|
the adapter AT the boundary, not a session-side wrapper pair; a
|
||||||
`take_framed_halves` convenience would not feed this use.
|
`take_framed_halves` convenience would not feed this use.
|
||||||
- **The convenience is additive and can land later.** Option 2's
|
- **The convenience is additive and can land later.** Option 2's
|
||||||
`take_framed_halves` breaks nothing when added after the first
|
`take_framed_halves` breaks nothing when added after the first
|
||||||
consumer (unlike a `take_halves` reshape), so its timing is
|
consumer (unlike a `take_halves` reshape), so its timing is
|
||||||
decoupled from this window — deferring it costs nothing now, and
|
decoupled from this window, so deferring it costs nothing now, and
|
||||||
the first real consumer's reach defines its shape (a
|
the first real consumer's reach defines its shape (a
|
||||||
`DatagramReader`-wrapped pair has an open design question: an
|
`DatagramReader`-wrapped pair has an open design question: an
|
||||||
`AsyncRead` face re-collides with the F-2 empty-datagram/EOF
|
`AsyncRead` face re-collides with the F-2 empty-datagram/EOF
|
||||||
ambiguity the codec exists to prevent — the codec-side framing
|
ambiguity the codec exists to prevent: the codec-side framing
|
||||||
wants accessor-shaped surface, which the session already has).
|
wants accessor-shaped surface, which the session already has).
|
||||||
- **The docs carry the contract.** `take_halves`'s doc states the
|
- **The docs carry the contract.** `take_halves`'s doc states the
|
||||||
framing (landed with the implementation); consumer.md's datagram
|
framing (landed with the implementation); consumer.md's datagram
|
||||||
@@ -138,14 +138,14 @@ carried by docs**, with the rationale:
|
|||||||
**Consequence:** datagram callers wanting codec-applied I/O after
|
**Consequence:** datagram callers wanting codec-applied I/O after
|
||||||
taking ownership should keep the session (drive `send_datagram`/
|
taking ownership should keep the session (drive `send_datagram`/
|
||||||
`recv_datagram`; the session IS the owner) or compose `UdpHalf` at
|
`recv_datagram`; the session IS the owner) or compose `UdpHalf` at
|
||||||
their own boundary — `take_halves` is the raw-wire escape hatch and
|
their own boundary; `take_halves` is the raw-wire escape hatch and
|
||||||
stays documented as such. Revisit only on a demonstrated consumer
|
stays documented as such. Revisit only on a demonstrated consumer
|
||||||
need (file an OQ at that point).
|
need (file an OQ at that point).
|
||||||
|
|
||||||
### Consequences (original)
|
### Consequences (original)
|
||||||
|
|
||||||
- **Assembly layers cannot leak adopted channels** by forgetting to
|
- **Assembly layers cannot leak adopted channels** by forgetting to
|
||||||
reap — the type is the discipline (the W3 gap closes structurally).
|
reap; the type is the discipline (the W3 gap closes structurally).
|
||||||
- **The reverse-flow initiator gets the same session shape** as the
|
- **The reverse-flow initiator gets the same session shape** as the
|
||||||
forward consumer: the reverse POC's hub-side `ReverseTunnel` is the
|
forward consumer: the reverse POC's hub-side `ReverseTunnel` is the
|
||||||
seed; the spec generalizes it so `-L` and `-R` consumers share one
|
seed; the spec generalizes it so `-L` and `-R` consumers share one
|
||||||
@@ -156,7 +156,7 @@ need (file an OQ at that point).
|
|||||||
already wrapper-managed (R-02); the session only fixes the adopter
|
already wrapper-managed (R-02); the session only fixes the adopter
|
||||||
asymmetry.
|
asymmetry.
|
||||||
- **`Drop`-based teardown is best-effort** (abort + sync reap, no
|
- **`Drop`-based teardown is best-effort** (abort + sync reap, no
|
||||||
await) — the documented contract; graceful flows call `join` or
|
await): the documented contract; graceful flows call `join` or
|
||||||
`close` explicitly.
|
`close` explicitly.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -165,7 +165,7 @@ need (file an OQ at that point).
|
|||||||
finding), §W4 (half-close validation)
|
finding), §W4 (half-close validation)
|
||||||
- OQ-TN-03, OQ-TN-04, OQ-TN-09 (promoted)
|
- OQ-TN-03, OQ-TN-04, OQ-TN-09 (promoted)
|
||||||
- alkcall ADR-049 + review 007 R-02 (wrapper-managed serving side),
|
- alkcall ADR-049 + review 007 R-02 (wrapper-managed serving side),
|
||||||
ADR-050 (`pump_bidi` + copy counts), ADR-047 §5 (allocation — the
|
ADR-050 (`pump_bidi` + copy counts), ADR-047 §5 (allocation; the
|
||||||
initiator adopts)
|
initiator adopts)
|
||||||
- AGENTS.md convention 10 (limits inherited — the session adds no
|
- AGENTS.md convention 10 (limits inherited; the session adds no
|
||||||
second bookkeeping layer; it owns exactly one channel's lifecycle)
|
second bookkeeping layer; it owns exactly one channel's lifecycle)
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# ADR-006: Access-Control Posture — the Open Gate Is the Security Boundary
|
# ADR-006: Access-Control Posture: the Open Gate Is the Security Boundary
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (2026-09-07; records the OQ-TN-08 resolution as a decision —
|
Accepted (2026-09-07; records the OQ-TN-08 resolution as a decision;
|
||||||
the posture was resolved in Phase 0 but the consumer-visible constants
|
the posture was resolved in Phase 0 but the consumer-visible constants
|
||||||
had no ADR backing); amended 2026-09-16 (scope separation for the
|
had no ADR backing); amended 2026-09-16 (scope separation for the
|
||||||
direct/forwarded ops + the hub re-produce policy sentence, see
|
direct/forwarded ops + the hub re-produce policy sentence, see
|
||||||
@@ -11,7 +11,7 @@ Amendment 1)
|
|||||||
## Context
|
## Context
|
||||||
|
|
||||||
Tunnels reach local networks: opening a tunnel channel instructs the
|
Tunnels reach local networks: opening a tunnel channel instructs the
|
||||||
producer to dial (or accept for) a target — potentially anything the
|
producer to dial (or accept for) a target, potentially anything the
|
||||||
producer's host can reach. The open gate is therefore the security
|
producer's host can reach. The open gate is therefore the security
|
||||||
boundary of this crate, in the same sense that SSH's
|
boundary of this crate, in the same sense that SSH's
|
||||||
`AllowTcpForwarding`/`PermitOpen` govern `direct-tcpip` opens. The
|
`AllowTcpForwarding`/`PermitOpen` govern `direct-tcpip` opens. The
|
||||||
@@ -20,13 +20,13 @@ this crate vs alkcall's op-level ACL vs the assembly layer.
|
|||||||
|
|
||||||
Phase 0 resolved the conceptual tangle (2026-09-05, the OQ-TN-08
|
Phase 0 resolved the conceptual tangle (2026-09-05, the OQ-TN-08
|
||||||
resolution): at protocol-crate level, a produced resource belongs to
|
resolution): at protocol-crate level, a produced resource belongs to
|
||||||
the far side of the connection — the protocol works under that
|
the far side of the connection, and the protocol works under that
|
||||||
assumption, so the ACL story is exactly alkcall's existing op-level
|
assumption, so the ACL story is exactly alkcall's existing op-level
|
||||||
ACL. No new policy layer. The hub/overlay mechanism (workers
|
ACL. No new policy layer. The hub/overlay mechanism (workers
|
||||||
connecting in, the hub re-exposing resources per ITS policy) is a
|
connecting in, the hub re-exposing resources per ITS policy) is a
|
||||||
downstream, assembly-layer concern (OQ-TN-12's scope).
|
downstream, assembly-layer concern (OQ-TN-12's scope).
|
||||||
|
|
||||||
What was left implicit — and needs pinning, because it is
|
What was left implicit, and needs pinning because it is
|
||||||
consumer-visible and stable-once-published:
|
consumer-visible and stable-once-published:
|
||||||
|
|
||||||
- The scope constant governing tunnel opens (`tunnel:open`) is
|
- The scope constant governing tunnel opens (`tunnel:open`) is
|
||||||
@@ -39,10 +39,10 @@ consumer-visible and stable-once-published:
|
|||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
1. **The open gate is the boundary, and it is alkcall's ACL.** The
|
1. **The open gate is the boundary, and it is alkcall's ACL.** The
|
||||||
open op's `AccessControl` rides `ChannelCore::register_openable` —
|
open op's `AccessControl` rides `ChannelCore::register_openable`;
|
||||||
the registry runs the ACL before the establisher. The base gate is
|
the registry runs the ACL before the establisher. The base gate is
|
||||||
the scope constant **`TUNNEL_OPEN_SCOPE = "tunnel:open"`**
|
the scope constant **`TUNNEL_OPEN_SCOPE = "tunnel:open"`**
|
||||||
(required scope, exact string — stable once published; consumers
|
(required scope, exact string; stable once published, and consumers
|
||||||
will request grants by it). An identity without the scope is
|
will request grants by it). An identity without the scope is
|
||||||
`FORBIDDEN` before any establisher code runs.
|
`FORBIDDEN` before any establisher code runs.
|
||||||
2. **The scope string is wire/ACL-stable** from the first consumer:
|
2. **The scope string is wire/ACL-stable** from the first consumer:
|
||||||
@@ -51,7 +51,7 @@ consumer-visible and stable-once-published:
|
|||||||
3. **No target allowlists and no tunnel-specific policy machinery in
|
3. **No target allowlists and no tunnel-specific policy machinery in
|
||||||
v1.** The resource is owned by the far side (the Phase 0
|
v1.** The resource is owned by the far side (the Phase 0
|
||||||
resolution); a producer that wants finer granularity wires
|
resolution); a producer that wants finer granularity wires
|
||||||
`OwnershipProvider` checks into ITS establisher — the protocol
|
`OwnershipProvider` checks into ITS establisher; the protocol
|
||||||
provides the seam (the establisher sees the per-call opener
|
provides the seam (the establisher sees the per-call opener
|
||||||
identity, alkcall 0.7.0 CF-006), not the policy. This mirrors
|
identity, alkcall 0.7.0 CF-006), not the policy. This mirrors
|
||||||
alktty's posture (`TTY_OPEN_SCOPE` + op-level ACL, no
|
alktty's posture (`TTY_OPEN_SCOPE` + op-level ACL, no
|
||||||
@@ -80,17 +80,17 @@ consumer-visible and stable-once-published:
|
|||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- **One security knob per producer deployment** (the op-level ACL +
|
- **One security knob per producer deployment** (the op-level ACL +
|
||||||
scope grants) instead of a tunnel-specific policy language — the
|
scope grants) instead of a tunnel-specific policy language, which is
|
||||||
accepted simplicity; per-resource granularity is an establisher
|
the accepted simplicity; per-resource granularity is an establisher
|
||||||
customization (the seam), not a v1 surface.
|
customization (the seam), not a v1 surface.
|
||||||
- **The scope constant is a compatibility commitment:** treat
|
- **The scope constant is a compatibility commitment:** treat
|
||||||
`tunnel:open` like the ALPN string. Changing it after consumers
|
`tunnel:open` like the ALPN string. Changing it after consumers
|
||||||
request grants is a breaking ACL change.
|
request grants is a breaking ACL change.
|
||||||
- **No allowlist concept** — a wildcard-egress producer
|
- **No allowlist concept**: a wildcard-egress producer
|
||||||
(`0.0.0.0/0`-style egress) is expressed by registering a resource
|
(`0.0.0.0/0`-style egress) is expressed by registering a resource
|
||||||
whose backing is that egress; gating it is the assembly layer's
|
whose backing is that egress; gating it is the assembly layer's
|
||||||
ownership check at the seam. The protocol stays out of the business.
|
ownership check at the seam. The protocol stays out of the business.
|
||||||
- **Tunnels reach local networks** — producers MUST treat the gate as
|
- **Tunnels reach local networks**, so producers MUST treat the gate as
|
||||||
the security boundary; the spec docs state this posture (producer.md).
|
the security boundary; the spec docs state this posture (producer.md).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -99,7 +99,7 @@ consumer-visible and stable-once-published:
|
|||||||
- alkcall ADR-047 (open ops + ACL wiring), ADR-017 §7 (the
|
- alkcall ADR-047 (open ops + ACL wiring), ADR-017 §7 (the
|
||||||
`auth_token` path), ledger CF-005/CF-006 (identity precedence,
|
`auth_token` path), ledger CF-005/CF-006 (identity precedence,
|
||||||
per-call opener); alknet ADR-024/ADR-050 (registry layering,
|
per-call opener); alknet ADR-024/ADR-050 (registry layering,
|
||||||
ownership — the alkcall ports ADR-019/ADR-011)
|
ownership; the alkcall ports ADR-019/ADR-011)
|
||||||
- alktty's `TTY_OPEN_SCOPE` posture (the sibling precedent)
|
- alktty's `TTY_OPEN_SCOPE` posture (the sibling precedent)
|
||||||
- AGENTS.md convention 13 (access control)
|
- AGENTS.md convention 13 (access control)
|
||||||
|
|
||||||
@@ -109,19 +109,19 @@ The posture stands; the scope space grows with the ops (ADR-007,
|
|||||||
ADR-008):
|
ADR-008):
|
||||||
|
|
||||||
- **`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`** gates
|
- **`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`** gates
|
||||||
`channels/tunnel/direct` — dynamic-target egress. **Never implied by
|
`channels/tunnel/direct` for dynamic-target egress. **Never implied by
|
||||||
`tunnel:open`**: "dial `postgres-primary`" and "dial anything from
|
`tunnel:open`**: "dial `postgres-primary`" and "dial anything from
|
||||||
my network" are different capabilities, and the separation is
|
my network" are different capabilities, and the separation is
|
||||||
structural (scopes attach to op specs, not params). A deployment
|
structural (scopes attach to op specs, not params). A deployment
|
||||||
granting both grants both explicitly.
|
granting both grants both explicitly.
|
||||||
- **`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"`** gates
|
- **`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"`** gates
|
||||||
`channels/tunnel/forwarded` on the opener side — the grant for
|
`channels/tunnel/forwarded` on the opener side; it is the grant for
|
||||||
RECEIVING producer-initiated forwarded connections (ingress).
|
RECEIVING producer-initiated forwarded connections (ingress).
|
||||||
Ingress and egress are different grants (the alksocks POC #5
|
Ingress and egress are different grants (the alksocks POC #5
|
||||||
`socks5:bind` gate proved the distinction matters in practice).
|
`socks5:bind` gate proved the distinction matters in practice).
|
||||||
- **Hub re-produce policy sentence (the security model):** a hub that
|
- **Hub re-produce policy sentence (the security model):** a hub that
|
||||||
re-produces the direct op applies ITS OWN target policy at the
|
re-produces the direct op applies ITS OWN target policy at the
|
||||||
re-produce hop — the hub's ACL, not the original opener's, governs
|
re-produce hop; the hub's ACL, not the original opener's, governs
|
||||||
the final dial. The hub is the network boundary; the original
|
the final dial. The hub is the network boundary; the original
|
||||||
opener's grant gets it to the hub, the hub's own grant decides what
|
opener's grant gets it to the hub, the hub's own grant decides what
|
||||||
it will dial on the far side. Correct-by-default; pinned here
|
it will dial on the far side. Correct-by-default; pinned here
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# ADR-007: The Direct Open Op — Dynamic-Target Egress
|
# ADR-007: The Direct Open Op (Dynamic-Target Egress)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ the backing; rich addressing rides the `-D`/dynamic composition path,
|
|||||||
never the wire. That decision stands. The use case that justifies an
|
never the wire. That decision stands. The use case that justifies an
|
||||||
*additive* revisit is the one the alksocks graduation research
|
*additive* revisit is the one the alksocks graduation research
|
||||||
(`docs/research/tunnels-graduation.md`) named: a consumer asking the
|
(`docs/research/tunnels-graduation.md`) named: a consumer asking the
|
||||||
producer to dial an arbitrary target chosen at session time — ssh's
|
producer to dial an arbitrary target chosen at session time, i.e. ssh's
|
||||||
`direct-tcpip` shape, SOCKS5 CONNECT's egress, and the vpn-like
|
`direct-tcpip` shape, SOCKS5 CONNECT's egress, and the vpn-like
|
||||||
dial-callback translation all need it. A dynamic target is a different
|
dial-callback translation all need it. A dynamic target is a different
|
||||||
capability from opening a named resource, and ssh carries it as its own
|
capability from opening a named resource, and ssh carries it as its own
|
||||||
@@ -19,7 +19,7 @@ channel type precisely because the two capabilities must not share one
|
|||||||
grant.
|
grant.
|
||||||
|
|
||||||
The capability is dangerous in exactly the way ADR-001's "resource, not
|
The capability is dangerous in exactly the way ADR-001's "resource, not
|
||||||
address" framing protects against — arbitrary egress from the
|
address" framing protects against: arbitrary egress from the
|
||||||
producer's network. The mitigation is not refusing the capability (a
|
producer's network. The mitigation is not refusing the capability (a
|
||||||
SOCKS5 service IS the capability) but separating it at the ACL: "dial
|
SOCKS5 service IS the capability) but separating it at the ACL: "dial
|
||||||
`postgres-primary`" and "dial anything" must not be the same grant.
|
`postgres-primary`" and "dial anything" must not be the same grant.
|
||||||
@@ -27,7 +27,7 @@ The alksocks POC #5 scope gate (`socks5:bind`) proved the pattern
|
|||||||
matters in practice.
|
matters in practice.
|
||||||
|
|
||||||
Timing: alksocks is the first real consumer and nothing is published
|
Timing: alksocks is the first real consumer and nothing is published
|
||||||
against the direct shape yet — this is the correct moment to pin the
|
against the direct shape yet, so this is the correct moment to pin the
|
||||||
wire-stable surface.
|
wire-stable surface.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
@@ -35,19 +35,19 @@ wire-stable surface.
|
|||||||
### 1. One ALPN, a second open op
|
### 1. One ALPN, a second open op
|
||||||
|
|
||||||
The direct op rides the same `alk/tunnel` ALPN (ADR-002 stands: one
|
The direct op rides the same `alk/tunnel` ALPN (ADR-002 stands: one
|
||||||
ALPN, one protocol identity — SSH's one-transport-many-channel-types
|
ALPN, one protocol identity, i.e. SSH's one-transport-many-channel-types
|
||||||
shape). The op id is **`channels/tunnel/direct`** — the flavor-form
|
shape). The op id is **`channels/tunnel/direct`**, the flavor-form
|
||||||
op-id convention (ADR-002 Amendment 1): `channels/<alpn>/<flavor>`,
|
op-id convention (ADR-002 Amendment 1): `channels/<alpn>/<flavor>`,
|
||||||
`Sub`-typed, same establishment phase and wrapper as `…/sub`. An older
|
`Sub`-typed, same establishment phase and wrapper as `…/sub`. An older
|
||||||
producer rejects the unknown op loudly (registry lookup fails — the
|
producer rejects the unknown op loudly (registry lookup fails, which is the
|
||||||
SSH "unknown channel type" posture, `NOT_FOUND`).
|
SSH "unknown channel type" posture, `NOT_FOUND`).
|
||||||
|
|
||||||
The op-id convention itself needed an upstream alkcall amendment
|
The op-id convention itself needed an upstream alkcall amendment
|
||||||
(ADR-047 amendment: discovery's ALPN derivation and the
|
(ADR-047 amendment: discovery's ALPN derivation and the
|
||||||
`rebuild_spec_for` marker path recognize flavor-form op ids; hub relay
|
`rebuild_spec_for` marker path recognize flavor-form op ids; hub relay
|
||||||
reconstructs them). Runtime was never blocked — registration sets the
|
reconstructs them). Runtime was never blocked, since registration sets the
|
||||||
ALPN explicitly and `open_channel(op_id, params, alpn)` takes the ALPN
|
ALPN explicitly and `open_channel(op_id, params, alpn)` takes the ALPN
|
||||||
as an argument — but discovery + hub relay are the re-produce path
|
as an argument; discovery + hub relay are the re-produce path
|
||||||
(alknodes, OQ-TN-12), so the convention is pinned upstream before the
|
(alknodes, OQ-TN-12), so the convention is pinned upstream before the
|
||||||
first consumer. **Landed in alkcall 0.8.0** (review 008 U-1, ADR-047
|
first consumer. **Landed in alkcall 0.8.0** (review 008 U-1, ADR-047
|
||||||
amendment 3).
|
amendment 3).
|
||||||
@@ -61,27 +61,27 @@ amendment 3).
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`substrate`** (string, required) — same discriminator semantics as
|
- **`substrate`** (string, required): same discriminator semantics as
|
||||||
ADR-001: `"tcp"` | `"udp"` | `"unix"` in v1; selects the data-plane
|
ADR-001: `"tcp"` | `"udp"` | `"unix"` in v1; selects the data-plane
|
||||||
framing (ADR-003). Enum-validated; unknown values fail schema
|
framing (ADR-003). Enum-validated; unknown values fail schema
|
||||||
validation loudly.
|
validation loudly.
|
||||||
- **`target`** (object, required) — a self-describing JSON object keyed
|
- **`target`** (object, required): a self-describing JSON object keyed
|
||||||
by the sibling `substrate` field:
|
by the sibling `substrate` field:
|
||||||
- `tcp` → `{ "host": <string>, "port": <u16> }` — `host` is a
|
- `tcp` → `{ "host": <string>, "port": <u16> }`, where `host` is a
|
||||||
hostname, IPv4/IPv6 literal, or the JSON form of
|
hostname, IPv4/IPv6 literal, or the JSON form of
|
||||||
`SocketAddr`-shaped values; **domain-form targets stay unresolved
|
`SocketAddr`-shaped values; **domain-form targets stay unresolved
|
||||||
on the wire** (producer-side resolution — the ssh `-D` semantic;
|
on the wire** (producer-side resolution, the ssh `-D` semantic;
|
||||||
both POCs validated egress-side resolution). No DNS dependency on
|
both POCs validated egress-side resolution). No DNS dependency on
|
||||||
the consumer.
|
the consumer.
|
||||||
- `udp` → `{ "host": <string>, "port": <u16> }` — the connected-UDP
|
- `udp` → `{ "host": <string>, "port": <u16> }`: the connected-UDP
|
||||||
destination (endpoint-at-open; ADR-003 framing applies as for base
|
destination (endpoint-at-open; ADR-003 framing applies as for base
|
||||||
udp).
|
udp).
|
||||||
- `unix` → `{ "path": <string> }` — the socket path on the
|
- `unix` → `{ "path": <string> }`: the socket path on the
|
||||||
producer's filesystem.
|
producer's filesystem.
|
||||||
- The schema pins both fields required; the target sub-schema is
|
- The schema pins both fields required; the target sub-schema is
|
||||||
per-substrate (an `allOf` of two `if`/`then` branches keyed on the
|
per-substrate (an `allOf` of two `if`/`then` branches keyed on the
|
||||||
sibling `substrate` value, validated **registry-side** — the schema
|
sibling `substrate` value, validated **registry-side**: the schema
|
||||||
gate runs before the establisher; the establisher's serde parse
|
gate runs before the establisher; the establisher's serde parse
|
||||||
re-checks as the backstop; see the Erratum below and Consequences).
|
re-checks as the backstop; see the Erratum below and Consequences).
|
||||||
Malformed targets are the typed `invalid_input`-class failure;
|
Malformed targets are the typed `invalid_input`-class failure;
|
||||||
**`unknown_resource` can never fire on the direct op** (there is no
|
**`unknown_resource` can never fire on the direct op** (there is no
|
||||||
@@ -97,16 +97,16 @@ amendment 3).
|
|||||||
*wire* encodes.
|
*wire* encodes.
|
||||||
- **Dial-only: no listen/direct-BIND form.** The reverse shape
|
- **Dial-only: no listen/direct-BIND form.** The reverse shape
|
||||||
(`-R`-style ingress) is the listen-tunnel + the forwarded op
|
(`-R`-style ingress) is the listen-tunnel + the forwarded op
|
||||||
(ADR-008) — a direct-BIND form would duplicate its capability with
|
(ADR-008); a direct-BIND form would duplicate its capability with
|
||||||
none of its metadata fidelity.
|
none of its metadata fidelity.
|
||||||
|
|
||||||
### 3. Scope: `TUNNEL_DIRECT_SCOPE = "tunnel:direct"`
|
### 3. Scope: `TUNNEL_DIRECT_SCOPE = "tunnel:direct"`
|
||||||
|
|
||||||
The op's `AccessControl.required_scopes` is `["tunnel:direct"]` — a
|
The op's `AccessControl.required_scopes` is `["tunnel:direct"]`, a
|
||||||
separate grant from `tunnel:open`, never implied by it. "Dial
|
separate grant from `tunnel:open`, never implied by it. "Dial
|
||||||
`postgres-primary`" and "dial anything from my network" are different
|
`postgres-primary`" and "dial anything from my network" are different
|
||||||
capabilities; the ACL separation is structural (scopes attach to op
|
capabilities; the ACL separation is structural (scopes attach to op
|
||||||
specs, not params — ADR-006 §2's additivity rule). Scope strings are
|
specs, not params, per ADR-006 §2's additivity rule). Scope strings are
|
||||||
ACL-stable once published (ADR-006 §2): renames are breaking; new
|
ACL-stable once published (ADR-006 §2): renames are breaking; new
|
||||||
gates are new scopes on new ops.
|
gates are new scopes on new ops.
|
||||||
|
|
||||||
@@ -115,13 +115,13 @@ gates are new scopes on new ops.
|
|||||||
Same establisher contract as the dial shape (producer.md), minus the
|
Same establisher contract as the dial shape (producer.md), minus the
|
||||||
lookup:
|
lookup:
|
||||||
|
|
||||||
1. Parse and validate `target` against `substrate` (a mismatch —
|
1. Parse and validate `target` against `substrate` (a mismatch, such as
|
||||||
`unix` without `path`, `tcp` with a bad port — is `invalid_input`
|
`unix` without `path` or `tcp` with a bad port, is `invalid_input`
|
||||||
class).
|
class).
|
||||||
2. Inject the parsed `TunnelTarget` into the assembly-provided
|
2. Inject the parsed `TunnelTarget` into the assembly-provided
|
||||||
`DialFn` (the same halves function the base op's registry backings
|
`DialFn` (the same halves function the base op's registry backings
|
||||||
resolve to; ADR-004 — the protocol never sees a socket type).
|
resolve to; ADR-004, which is why the protocol never sees a socket type).
|
||||||
3. Return the dialed handle via `Establishment::new(plan)` — unchanged
|
3. Return the dialed handle via `Establishment::new(plan)`: unchanged
|
||||||
plan-flow.
|
plan-flow.
|
||||||
|
|
||||||
Error mapping (the dial table, minus the registry row):
|
Error mapping (the dial table, minus the registry row):
|
||||||
@@ -163,12 +163,12 @@ sentence (also ADR-006 amendment).
|
|||||||
a target object must be optional-with-default.
|
a target object must be optional-with-default.
|
||||||
- **`SubstrateAddr`'s untagged leniency is schema-gated** (review 002
|
- **`SubstrateAddr`'s untagged leniency is schema-gated** (review 002
|
||||||
N-4): untagged serde variants cannot `deny_unknown_fields`, so a
|
N-4): untagged serde variants cannot `deny_unknown_fields`, so a
|
||||||
cross-shape target (e.g. `{path, host}`) parses at the serde layer —
|
cross-shape target (e.g. `{path, host}`) parses at the serde layer;
|
||||||
the SCHEMA's `additionalProperties: false` per branch is the real
|
the SCHEMA's `additionalProperties: false` per branch is the real
|
||||||
shape-vs-substrate gate. One caution for future registry variants: a
|
shape-vs-substrate gate. One caution for future registry variants: a
|
||||||
schema-less registry would leave the establisher's serde backstop as
|
schema-less registry would leave the establisher's serde backstop as
|
||||||
the only gate, and it keys `render_target` off the sibling
|
the only gate, and it keys `render_target` off the sibling
|
||||||
`substrate` — a cross-shape target that sneaked past would render
|
`substrate`; a cross-shape target that sneaked past would render
|
||||||
*something* (`/p` for the unix case) rather than fail; the serde
|
*something* (`/p` for the unix case) rather than fail; the serde
|
||||||
backstop renders the sibling-keyed target, so cross-shape garbage is
|
backstop renders the sibling-keyed target, so cross-shape garbage is
|
||||||
the schema's absence, not a serde failure. If this crate is ever
|
the schema's absence, not a serde failure. If this crate is ever
|
||||||
@@ -177,15 +177,15 @@ sentence (also ADR-006 amendment).
|
|||||||
- **`SubstrateAddr` must not grow overlapping untagged variants.** If
|
- **`SubstrateAddr` must not grow overlapping untagged variants.** If
|
||||||
a future variant's shapes overlap (e.g. a `{host, port, flowlabel}`
|
a future variant's shapes overlap (e.g. a `{host, port, flowlabel}`
|
||||||
form), untagged disambiguation starts ordering (first-match wins
|
form), untagged disambiguation starts ordering (first-match wins
|
||||||
becomes load-bearing and silent) — switch to an internally-tagged
|
becomes load-bearing and silent), which means switching to an internally-tagged
|
||||||
or untagged-with-content form at that point, deliberately (an ADR
|
or untagged-with-content form at that point, deliberately (an ADR
|
||||||
line), never by accident. Pre-consumer and additive until then.
|
line), never by accident. Pre-consumer and additive until then.
|
||||||
- **The BAST question is moot** — params is JSON inside the channels
|
- **The BAST question is moot**: params is JSON inside the channels
|
||||||
open op; the data plane is unchanged (raw pass-through for tcp/unix,
|
open op; the data plane is unchanged (raw pass-through for tcp/unix,
|
||||||
the `[len: u16 BE]` codec for udp — ADR-003).
|
the `[len: u16 BE]` codec for udp per ADR-003).
|
||||||
- **Discovery exposure**: the direct op's `input_schema` carries
|
- **Discovery exposure**: the direct op's `input_schema` carries
|
||||||
`{substrate, target}`, so a discovery listing reveals that dynamic
|
`{substrate, target}`, so a discovery listing reveals that dynamic
|
||||||
egress exists and of which substrates — acceptable; the target
|
egress exists and of which substrates; that is acceptable, since the target
|
||||||
values (what was dialed) never enter discovery (they ride per-call
|
values (what was dialed) never enter discovery (they ride per-call
|
||||||
params). A producer wanting to hide the capability entirely simply
|
params). A producer wanting to hide the capability entirely simply
|
||||||
does not register the op (and old producers reject it loudly anyway).
|
does not register the op (and old producers reject it loudly anyway).
|
||||||
@@ -196,7 +196,7 @@ sentence (also ADR-006 amendment).
|
|||||||
direct op itself is protocol-layer (default crate, wasm-clean).
|
direct op itself is protocol-layer (default crate, wasm-clean).
|
||||||
- **SOCKS5 CONNECT composes directly:** the alksocks adapter's
|
- **SOCKS5 CONNECT composes directly:** the alksocks adapter's
|
||||||
`DialFn` seam maps the SOCKS5 CONNECT target (ATYP-decoded) onto
|
`DialFn` seam maps the SOCKS5 CONNECT target (ATYP-decoded) onto
|
||||||
`open_direct` — the graduation's §4 table, now with the stand-in
|
`open_direct`, which is the graduation's §4 table with the stand-in
|
||||||
dissolved.
|
dissolved.
|
||||||
|
|
||||||
## Erratum (2026-09-20, review 002 D-2)
|
## Erratum (2026-09-20, review 002 D-2)
|
||||||
@@ -204,7 +204,7 @@ sentence (also ADR-006 amendment).
|
|||||||
§2's original wording said the target sub-schema is "`oneOf` on the
|
§2's original wording said the target sub-schema is "`oneOf` on the
|
||||||
sibling `substrate` value, validated at establisher time". Both
|
sibling `substrate` value, validated at establisher time". Both
|
||||||
halves of that sentence were stale against the shipped schema
|
halves of that sentence were stale against the shipped schema
|
||||||
(the decision itself was not — the code is the wire-stable side and
|
(the decision itself was not; the code is the wire-stable side and
|
||||||
is correct):
|
is correct):
|
||||||
|
|
||||||
1. **The shipped structure is an `allOf` of two `if`/`then`
|
1. **The shipped structure is an `allOf` of two `if`/`then`
|
||||||
@@ -225,14 +225,14 @@ is correct):
|
|||||||
## References
|
## References
|
||||||
|
|
||||||
- `docs/research/tunnels-graduation.md` §2 (the ask, options, and the
|
- `docs/research/tunnels-graduation.md` §2 (the ask, options, and the
|
||||||
lean) — the design discussion this ADR records
|
lean): the design discussion this ADR records
|
||||||
- alksocks `poc-bind-findings.md` (the scope-gate precedent) and
|
- alksocks `poc-bind-findings.md` (the scope-gate precedent) and
|
||||||
`poc-connect-wasm-findings.md` (the dial-at-command-read layering)
|
`poc-connect-wasm-findings.md` (the dial-at-command-read layering)
|
||||||
- alkcall ADR-047 (open ops + `input_schema` + discovery marker),
|
- alkcall ADR-047 (open ops + `input_schema` + discovery marker),
|
||||||
ADR-049 (establishment/typed errors), Gap F (op-name → ALPN
|
ADR-049 (establishment/typed errors), Gap F (op-name → ALPN
|
||||||
derivation — the flavor-form amendment target)
|
derivation, the flavor-form amendment target)
|
||||||
- alktunnels ADR-001 (the resource-params invariant the direct op
|
- alktunnels ADR-001 (the resource-params invariant the direct op
|
||||||
extends), ADR-002 Amendment 1 (flavor-form op ids), ADR-006
|
extends), ADR-002 Amendment 1 (flavor-form op ids), ADR-006
|
||||||
(scope posture + the hub-policy amendment)
|
(scope posture + the hub-policy amendment)
|
||||||
- AGENTS.md conventions 9 (no forced binding — dial-only, no listen
|
- AGENTS.md conventions 9 (no forced binding; dial-only, no listen
|
||||||
form), 13 (access control), 17 (ALPN naming)
|
form), 13 (access control), 17 (ALPN naming)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# ADR-008: Listen Metadata — Bind-First Establisher and the Forwarded Op
|
# ADR-008: Listen Metadata: Bind-First Establisher and the Forwarded Op
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted (2026-09-16); amended 2026-09-18 (the correlation token —
|
Accepted (2026-09-16); amended 2026-09-18 (the correlation token,
|
||||||
`listen` reply field + forwarded-params key, see Amendment 1); amended
|
the `listen` reply field + forwarded-params key, see Amendment 1); amended
|
||||||
2026-11-03 (the forwarded op's serving-side establishment phase + the
|
2026-11-03 (the forwarded op's serving-side establishment phase + the
|
||||||
correlation-miss vocabulary, see Amendment 2)
|
correlation-miss vocabulary, see Amendment 2)
|
||||||
|
|
||||||
@@ -12,12 +12,12 @@ correlation-miss vocabulary, see Amendment 2)
|
|||||||
alksocks' BIND POC (`poc-bind-findings.md` in the alksocks repo) pinned
|
alksocks' BIND POC (`poc-bind-findings.md` in the alksocks repo) pinned
|
||||||
two upstream asks for composed (`-R`-style) BIND fidelity:
|
two upstream asks for composed (`-R`-style) BIND fidelity:
|
||||||
|
|
||||||
1. **Bound-addr discovery without consuming an accept** — reply#1's
|
1. **Bound-addr discovery without consuming an accept**: reply#1's
|
||||||
`BND.ADDR` must precede the app server's dial, but the listen
|
`BND.ADDR` must precede the app server's dial, but the listen
|
||||||
establisher is accept-first: its establishment phase pops an
|
establisher is accept-first: its establishment phase pops an
|
||||||
already-accepted connection, so the open cannot complete (and
|
already-accepted connection, so the open cannot complete (and
|
||||||
cannot report a bound address) until the app server dials.
|
cannot report a bound address) until the app server dials.
|
||||||
2. **Peer metadata on accepted handles** — reply#2's peer address is
|
2. **Peer metadata on accepted handles**: reply#2's peer address is
|
||||||
lost through the composed path; `TargetHandle` carries boxed halves
|
lost through the composed path; `TargetHandle` carries boxed halves
|
||||||
only, so the composed variant reports the sentinel.
|
only, so the composed variant reports the sentinel.
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ The graduation research (`docs/research/tunnels-graduation.md` §3)
|
|||||||
framed the option space: per-ask carriers (a reply-metadata projection
|
framed the option space: per-ask carriers (a reply-metadata projection
|
||||||
plus one of three addr-discovery shapes) versus ssh's
|
plus one of three addr-discovery shapes) versus ssh's
|
||||||
`forwarded-tcpip` shape as the combined alternative. Decision: the
|
`forwarded-tcpip` shape as the combined alternative. Decision: the
|
||||||
`forwarded-tcpip` shape — **bind-first establisher + accept-as-open**
|
`forwarded-tcpip` shape (**bind-first establisher + accept-as-open**),
|
||||||
— because it resolves both asks *and* two structural problems the POC
|
because it resolves both asks *and* two structural problems the POC
|
||||||
recorded, with zero special carriers:
|
recorded, with zero special carriers:
|
||||||
|
|
||||||
- The address-first-vs-accept-first **deadlock dissolves** instead of
|
- The address-first-vs-accept-first **deadlock dissolves** instead of
|
||||||
@@ -35,9 +35,9 @@ recorded, with zero special carriers:
|
|||||||
OS-chosen bound address (ask #1 solved natively).
|
OS-chosen bound address (ask #1 solved natively).
|
||||||
- The **10s establishment-bound problem dissolves**: the accept wait
|
- The **10s establishment-bound problem dissolves**: the accept wait
|
||||||
moves out of the establishment phase into the pump-handler phase
|
moves out of the establishment phase into the pump-handler phase
|
||||||
(whose lifetime is teardown-governed, not establishment-bounded) —
|
(whose lifetime is teardown-governed, not establishment-bounded), so
|
||||||
FTP-style long BIND waits stop resolving as spurious refusals.
|
FTP-style long BIND waits stop resolving as spurious refusals.
|
||||||
- Ask #2 (peer metadata) rides the reverse channel's open-op params —
|
- Ask #2 (peer metadata) rides the reverse channel's open-op params:
|
||||||
the wire's native "this connection is for X" moment.
|
the wire's native "this connection is for X" moment.
|
||||||
|
|
||||||
All the machinery is POC-validated: `open_reverse_channel` +
|
All the machinery is POC-validated: `open_reverse_channel` +
|
||||||
@@ -50,18 +50,18 @@ over a serving connection (the reverse POC's hub shape).
|
|||||||
### 1. Third establisher shape: bind-first
|
### 1. Third establisher shape: bind-first
|
||||||
|
|
||||||
The listen establisher gains a bind-first variant alongside the
|
The listen establisher gains a bind-first variant alongside the
|
||||||
accept-first shape (which stays — see §5):
|
accept-first shape (which stays; see §5):
|
||||||
|
|
||||||
- The establisher **binds the listener** (via the assembly-provided
|
- The establisher **binds the listener** (via the assembly-provided
|
||||||
listen function) and returns `Establishment::new(plan)` carrying the
|
listen function) and returns `Establishment::new(plan)` carrying the
|
||||||
listener handle. Establishment is fast — bounded by the bind, not by
|
listener handle. Establishment is fast, bounded by the bind, not by
|
||||||
any app-server dial.
|
any app-server dial.
|
||||||
- **The establisher mints a correlation token** (Amendment 1): a fresh
|
- **The establisher mints a correlation token** (Amendment 1): a fresh
|
||||||
u32 unique per open on this producer, carried twice — inside the
|
u32 unique per open on this producer, carried twice: inside the
|
||||||
plan (the pump handler needs it to correlate per-accept forwarded
|
plan (the pump handler needs it to correlate per-accept forwarded
|
||||||
opens) and as the additive `listen` reply field (§2). The plan also
|
opens) and as the additive `listen` reply field (§2). The plan also
|
||||||
carries the listen tunnel's substrate, stamped at establisher time
|
carries the listen tunnel's substrate, stamped at establisher time
|
||||||
from the one successful params parse — the accept dispatcher reads
|
from the one successful params parse; the accept dispatcher reads
|
||||||
it from the plan and stamps it into each forwarded open's
|
it from the plan and stamps it into each forwarded open's
|
||||||
`substrate` param; it never re-parses wire input (review 002 M-1:
|
`substrate` param; it never re-parses wire input (review 002 M-1:
|
||||||
the removed silent `Tcp` fallback). The original
|
the removed silent `Tcp` fallback). The original
|
||||||
@@ -71,24 +71,25 @@ accept-first shape (which stays — see §5):
|
|||||||
a generic hub relay forwards params untouched (it strips/rewrites
|
a generic hub relay forwards params untouched (it strips/rewrites
|
||||||
only the reply's `channel_id`), so a spoke's forwarded open would
|
only the reply's `channel_id`), so a spoke's forwarded open would
|
||||||
carry an id the end opener never allocated. A producer-minted token
|
carry an id the end opener never allocated. A producer-minted token
|
||||||
rides both hops untouched — reply fields pass through relays
|
rides both hops untouched, since reply fields pass through relays
|
||||||
per-hop-truthful (alkcall ADR-051), and the same minted token
|
per-hop-truthful (alkcall ADR-051), and the same minted token
|
||||||
appears in every hop's reply.
|
appears in every hop's reply.
|
||||||
- The establisher MAY contribute the observed bound address to the
|
- The establisher MAY contribute the observed bound address to the
|
||||||
open-op reply (§2): `"bound": { ... }` — the real OS-chosen address
|
open-op reply (§2): `"bound": { ... }`, which is the real OS-chosen address
|
||||||
(port `0` resolved), substrate-shaped like the direct op's target
|
(port `0` resolved), substrate-shaped like the direct op's target
|
||||||
(`{host, port}` for tcp/udp, `{path}` for unix).
|
(`{host, port}` for tcp/udp, `{path}` for unix).
|
||||||
- The pump handler for this shape is an **accept dispatcher**, not
|
- The pump handler for this shape is an **accept dispatcher**, not
|
||||||
`pump_bidi`: it accepts the channel's `BiStream` (the R-02 contract)
|
`pump_bidi`: it accepts the channel's `BiStream` (the R-02 contract)
|
||||||
and runs the assembly-provided accept loop concurrently — each
|
and runs the assembly-provided accept loop concurrently; each
|
||||||
accepted connection produces a forwarded channel (§3); the channel's
|
accepted connection produces a forwarded channel (§3); the channel's
|
||||||
own `BiStream` stays idle and its EOF (the opener dropping the
|
own `BiStream` stays idle and its EOF (the opener dropping the
|
||||||
listener session) cancels the loop and tears the listener down. The
|
listener session) cancels the loop and tears the listener down. The
|
||||||
idle-read check is deliberately WIDER than a bare EOF comparison
|
idle-read check is deliberately WIDER than a bare EOF comparison
|
||||||
(review 002 N-2): the watch treats ANY read as teardown — `Ok(0)`
|
(review 002 N-2): the watch treats ANY read as teardown, whether
|
||||||
(the EOF sentinel), an error, **and** any non-empty read (a peer
|
`Ok(0)`
|
||||||
that writes to the idle listen channel is misbehaving; the channel
|
(the EOF sentinel), an error, **or** any non-empty read (a peer
|
||||||
is not a data plane — fail closed). An implementer reading only
|
that writes to the idle listen channel is misbehaving; the channel
|
||||||
|
is not a data plane, so fail closed). An implementer reading only
|
||||||
"its EOF cancels the loop" and checking `Ok(0)` alone would hang on
|
"its EOF cancels the loop" and checking `Ok(0)` alone would hang on
|
||||||
a write-happy peer; the shipped posture is the any-byte one,
|
a write-happy peer; the shipped posture is the any-byte one,
|
||||||
pinned by the write-happy-peer test.
|
pinned by the write-happy-peer test.
|
||||||
@@ -102,42 +103,42 @@ The open-op reply gains an **optional, additive** field:
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Present only when the establisher supplies it (bind-first listeners);
|
- Present only when the establisher supplies it (bind-first listeners);
|
||||||
absent for dial establishers — old consumers ignore unknown reply
|
absent for dial establishers; old consumers ignore unknown reply
|
||||||
fields (plain JSON, alkcall `ResponseEnvelope`), so the change is
|
fields (plain JSON, alkcall `ResponseEnvelope`), so the change is
|
||||||
wire-additive.
|
wire-additive.
|
||||||
- The carrier is an upstream alkcall ask (ADR-049 amendment 3:
|
- The carrier is an upstream alkcall ask (ADR-049 amendment 3:
|
||||||
establisher → reply projection — `Establishment` gains optional
|
establisher → reply projection, with `Establishment` gaining optional
|
||||||
reply fields the wrapper merges into the success output; the
|
reply fields the wrapper merges into the success output; the
|
||||||
`channel_id` key is wrapper-reserved). Filed in
|
`channel_id` key is wrapper-reserved). Filed in
|
||||||
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`.
|
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`.
|
||||||
**Landed in alkcall 0.8.0** (review 008 U-2, ADR-049 amendment 3:
|
**Landed in alkcall 0.8.0** (review 008 U-2, ADR-049 amendment 3:
|
||||||
`Establishment::with_reply_field` + the wrapper merge with the
|
`Establishment::with_reply_field` + the wrapper merge with the
|
||||||
reserved-key guard).
|
reserved-key guard).
|
||||||
- **`listen` — the correlation token (Amendment 1).** A bind-first
|
- **`listen`: the correlation token (Amendment 1).** A bind-first
|
||||||
establisher also contributes `"listen": <u32>`: the token the
|
establisher also contributes `"listen": <u32>`: the token the
|
||||||
forwarded op's params key on (§3, as amended). Same projection
|
forwarded op's params key on (§3, as amended). Same projection
|
||||||
carrier, same additive posture — a second reply field on the same
|
carrier, same additive posture: a second reply field on the
|
||||||
opens that carry `bound`. `bound` and `listen` are independent
|
same opens that carry `bound`. `bound` and `listen` are independent
|
||||||
fields (an accept-first establisher could supply `listen`-shaped
|
fields (an accept-first establisher could supply `listen`-shaped
|
||||||
correlation too — the accept queue's establisher mints its own —
|
correlation too, since the accept queue's establisher mints its
|
||||||
but v1 pins the pair on bind-first only).
|
own, but v1 pins the pair on bind-first only).
|
||||||
- `bound` is informational surface, not addressing: a consumer MUST
|
- `bound` is informational surface, not addressing: a consumer MUST
|
||||||
NOT treat it as a dial target (the producer owns the backing —
|
NOT treat it as a dial target (the producer owns the backing,
|
||||||
ADR-001's invariant). It exists for RFC-1928-reply-class fidelity
|
per ADR-001's invariant). It exists for RFC-1928-reply-class fidelity
|
||||||
(BND.ADDR) and observability.
|
(BND.ADDR) and observability.
|
||||||
|
|
||||||
### 3. The forwarded op: `channels/tunnel/forwarded`
|
### 3. The forwarded op: `channels/tunnel/forwarded`
|
||||||
|
|
||||||
When the listener accepts, the **producer opens a channel back toward
|
When the listener accepts, the **producer opens a channel back toward
|
||||||
the listen-opener** — accept-as-open, the `forwarded-tcpip` shape:
|
the listen-opener**: accept-as-open, the `forwarded-tcpip` shape:
|
||||||
|
|
||||||
- Op id **`channels/tunnel/forwarded`** (flavor-form, ADR-002
|
- Op id **`channels/tunnel/forwarded`** (flavor-form, ADR-002
|
||||||
Amendment 1), `Sub`-typed, ALPN marker `alk/tunnel`. It is served by
|
Amendment 1), `Sub`-typed, ALPN marker `alk/tunnel`. It is served by
|
||||||
the **listen-opener side** (the initiator of the listen-tunnel
|
the **listen-opener side** (the initiator of the listen-tunnel
|
||||||
open): the producer *calls* it per accept
|
open): the producer *calls* it per accept
|
||||||
(`open_reverse_channel`-shaped — the call plane on channel 0), the
|
(`open_reverse_channel`-shaped; the call plane on channel 0), the
|
||||||
opener's registry allocates the `channel_id`, the producer adopts it
|
opener's registry allocates the `channel_id`, the producer adopts it
|
||||||
(the ADR-047 §5 connection-owner rule — the serving side of the
|
(the ADR-047 §5 connection-owner rule, i.e. the serving side of the
|
||||||
forwarded call allocates; the producer-side initiator adopts) and
|
forwarded call allocates; the producer-side initiator adopts) and
|
||||||
pumps the accepted connection's halves against the adopted channel.
|
pumps the accepted connection's halves against the adopted channel.
|
||||||
- Params:
|
- Params:
|
||||||
@@ -150,19 +151,19 @@ the listen-opener** — accept-as-open, the `forwarded-tcpip` shape:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **`listen`** (u32, required) — the correlation token the
|
- **`listen`** (u32, required): the correlation token the
|
||||||
bind-first establisher minted and returned in the listen open's
|
bind-first establisher minted and returned in the listen open's
|
||||||
reply (§2, as amended). The opener matches it against the
|
reply (§2, as amended). The opener matches it against the
|
||||||
`listen` field of its open listener sessions — a `HashMap<u32, ..>`
|
`listen` field of its open listener sessions, a `HashMap<u32, ..>`
|
||||||
on the opener side; no registry op, no extra wire field. The
|
on the opener side; no registry op, no extra wire field. The
|
||||||
token space is the producer's: fresh per open, unique on that
|
token space is the producer's: fresh per open, unique on that
|
||||||
producer (the opener-side map is keyed by the producer's token,
|
producer (the opener-side map is keyed by the producer's token,
|
||||||
and one opener's map may hold tokens from several producers — a
|
and one opener's map may hold tokens from several producers; a
|
||||||
hash collision across producers is the opener's bookkeeping
|
hash collision across producers is the opener's bookkeeping
|
||||||
choice, e.g. keying `(token)` uniquely or `(producer, token)`;
|
choice, e.g. keying `(token)` uniquely or `(producer, token)`;
|
||||||
v1 leaves it assembly-side). **Amends the original `channel`
|
v1 leaves it assembly-side). **Amends the original `channel`
|
||||||
key** (the listen channel's ID): that semantic is unimplementable
|
key** (the listen channel's ID): that semantic is unimplementable
|
||||||
as pinned — the serving-side wrapper never exposes the allocated
|
as pinned, since the serving-side wrapper never exposes the allocated
|
||||||
channel ID to the establisher or handler (alkcall 0.8.0's
|
channel ID to the establisher or handler (alkcall 0.8.0's
|
||||||
`OpenHandler` surface), and generic hub relays forward params
|
`OpenHandler` surface), and generic hub relays forward params
|
||||||
untouched, so a spoke's forwarded open would carry an id the end
|
untouched, so a spoke's forwarded open would carry an id the end
|
||||||
@@ -171,34 +172,35 @@ the listen-opener** — accept-as-open, the `forwarded-tcpip` shape:
|
|||||||
the reply field passes through per-hop truthful (alkcall ADR-051),
|
the reply field passes through per-hop truthful (alkcall ADR-051),
|
||||||
and the forwarded open carries the same minted value at every
|
and the forwarded open carries the same minted value at every
|
||||||
hop.
|
hop.
|
||||||
- **`substrate`** (string, required) — the listen tunnel's
|
- **`substrate`** (string, required): the listen tunnel's
|
||||||
substrate; shapes the forwarded channel's data-plane framing
|
substrate; shapes the forwarded channel's data-plane framing
|
||||||
(ADR-003), same discriminator semantics as everywhere else.
|
(ADR-003), same discriminator semantics as everywhere else.
|
||||||
- **`peer`** (object, required) — the accepted connection's remote
|
- **`peer`** (object, required): the accepted connection's remote
|
||||||
address, substrate-shaped (`{host, port}` / `{path}`). This is ask
|
address, substrate-shaped (`{host, port}` / `{path}`). This is ask
|
||||||
#2's carrier: reply#2-class fidelity rides the open op, the wire's
|
#2's carrier: reply#2-class fidelity rides the open op, the wire's
|
||||||
native metadata moment. Optional in kind, required in v1 — a
|
native metadata moment. Optional in kind, required in v1: a
|
||||||
producer that cannot know the peer (rare; all POC'd listeners
|
producer that cannot know the peer (rare; all POC'd listeners
|
||||||
can) omits... no: **required**; a producer that cannot determine
|
can) omits... no: **required**; a producer that cannot determine
|
||||||
the peer does not serve a bind-first listener over that substrate.
|
the peer does not serve a bind-first listener over that substrate.
|
||||||
- Scope: **`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"`** — the
|
- Scope: **`TUNNEL_FORWARDED_SCOPE = "tunnel:forwarded"`**, the
|
||||||
opener-side grant for RECEIVING forwarded connections (ingress).
|
opener-side grant for RECEIVING forwarded connections (ingress).
|
||||||
Separate from `tunnel:open` and `tunnel:direct` (ADR-006 amendment):
|
Separate from `tunnel:open` and `tunnel:direct` (ADR-006 amendment):
|
||||||
exposing a listener and dialing egress are different capabilities.
|
exposing a listener and dialing egress are different capabilities.
|
||||||
- The opener-side serving shape: `register_openable_with_establisher`
|
- The opener-side serving shape: `register_openable_with_establisher`
|
||||||
as-is (no establisher — the forwarded channel needs no
|
as-is (no establisher, because the forwarded channel needs no
|
||||||
establishment; its data plane starts immediately — **amended by
|
establishment; its data plane starts immediately; this clause was
|
||||||
Amendment 2**: the served shape gains an establisher variant, the
|
**amended by Amendment 2**: the served shape gains an establisher
|
||||||
injected correlation gate; this sentence records the pre-amendment
|
variant, the injected correlation gate; this sentence records the
|
||||||
posture, which stays a valid shape — see Amendment 2), ACL-gated by
|
pre-amendment posture, which stays a valid shape per Amendment 2),
|
||||||
|
ACL-gated by
|
||||||
the scope, handler = correlate the open (via `listen`, the
|
the scope, handler = correlate the open (via `listen`, the
|
||||||
correlation token) to the waiting
|
correlation token) to the waiting
|
||||||
session, accept the `BiStream`, pump against the local BIND client's
|
session, accept the `BiStream`, pump against the local BIND client's
|
||||||
halves. The correlation registry is assembly-side bookkeeping —
|
halves. The correlation registry is assembly-side bookkeeping;
|
||||||
the protocol provides the op and the params, not the
|
the protocol provides the op and the params, not the
|
||||||
wait/quiet-state machine.
|
wait/quiet-state machine.
|
||||||
- An older opener that does not serve the forwarded op rejects the
|
- An older opener that does not serve the forwarded op rejects the
|
||||||
producer's per-accept call loudly (`NOT_FOUND`) — the SSH posture.
|
producer's per-accept call loudly (`NOT_FOUND`), the SSH posture.
|
||||||
A producer serving bind-first SHOULD prefer openers that advertise
|
A producer serving bind-first SHOULD prefer openers that advertise
|
||||||
the forwarded op (discovery, alkcall ADR-047).
|
the forwarded op (discovery, alkcall ADR-047).
|
||||||
|
|
||||||
@@ -210,7 +212,7 @@ let bound = listener.bound(); // the resolved ad
|
|||||||
// ... per forwarded accept (opener side, served op):
|
// ... per forwarded accept (opener side, served op):
|
||||||
// correlate by `listen`, accept the BiStream, pump against the
|
// correlate by `listen`, accept the BiStream, pump against the
|
||||||
// local session's halves
|
// local session's halves
|
||||||
listener.close(self); // or Drop — tears the listen channel + far listener down
|
listener.close(self); // or Drop; tears the listen channel + far listener down
|
||||||
```
|
```
|
||||||
|
|
||||||
`TunnelListener` is the listen-flow sibling of `TunnelSession`: it owns
|
`TunnelListener` is the listen-flow sibling of `TunnelSession`: it owns
|
||||||
@@ -218,7 +220,7 @@ the listen channel's teardown (ADR-005's ownership posture extended to
|
|||||||
the listen flow); it presents `bound()` and the teardown API, and the
|
the listen flow); it presents `bound()` and the teardown API, and the
|
||||||
forwarded channels themselves arrive as separate adopted-side
|
forwarded channels themselves arrive as separate adopted-side
|
||||||
`TunnelSession`s on the opener's manager. `TunnelSession::adopt` gains
|
`TunnelSession`s on the opener's manager. `TunnelSession::adopt` gains
|
||||||
nothing — forwarded channels are ordinary adopted channels. The
|
nothing; forwarded channels are ordinary adopted channels. The
|
||||||
opener-side correlation registry (token → waiting listener session)
|
opener-side correlation registry (token → waiting listener session)
|
||||||
is assembly-side bookkeeping (consumer.md's posture); the crate's
|
is assembly-side bookkeeping (consumer.md's posture); the crate's
|
||||||
`TunnelListener` type provides the `listen` token it needs to key it.
|
`TunnelListener` type provides the `listen` token it needs to key it.
|
||||||
@@ -230,7 +232,7 @@ remains a valid establisher shape for producers whose resource backing
|
|||||||
IS an accept queue (an externally-fed or pre-bound listener shared
|
IS an accept queue (an externally-fed or pre-bound listener shared
|
||||||
across opens). Bind-first is the shape for producers that own the
|
across opens). Bind-first is the shape for producers that own the
|
||||||
bind decision (the common hub case; alksocks' composed BIND). The two
|
bind decision (the common hub case; alksocks' composed BIND). The two
|
||||||
shapes are per-registration assembly choices over the same op — no
|
shapes are per-registration assembly choices over the same op; there is no
|
||||||
wire distinction beyond `bound`'s presence.
|
wire distinction beyond `bound`'s presence.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
@@ -243,7 +245,7 @@ wire distinction beyond `bound`'s presence.
|
|||||||
(ADR-049 amendment 3) and the flavor-form discovery derivation
|
(ADR-049 amendment 3) and the flavor-form discovery derivation
|
||||||
(ADR-047 amendment) **landed in alkcall 0.8.0** (review 008, Units
|
(ADR-047 amendment) **landed in alkcall 0.8.0** (review 008, Units
|
||||||
1–2); the in-tree `ChannelRelay` + `HubLegTemplate` (ADR-051) came
|
1–2); the in-tree `ChannelRelay` + `HubLegTemplate` (ADR-051) came
|
||||||
with them — the hub re-produce machinery §6 assumed assembly-layer
|
with them, so the hub re-produce machinery §6 assumed assembly-layer
|
||||||
is now upstream, and the relay passes reply fields through per-hop
|
is now upstream, and the relay passes reply fields through per-hop
|
||||||
truthful (which is what makes the Amendment 1 token compose through
|
truthful (which is what makes the Amendment 1 token compose through
|
||||||
hubs with zero tunnel-specific relay behavior). The bind-first
|
hubs with zero tunnel-specific relay behavior). The bind-first
|
||||||
@@ -253,21 +255,21 @@ wire distinction beyond `bound`'s presence.
|
|||||||
carrier from the graduation doc is superseded: with accept-as-open,
|
carrier from the graduation doc is superseded: with accept-as-open,
|
||||||
the peer rides the forwarded op's params; no process-local metadata
|
the peer rides the forwarded op's params; no process-local metadata
|
||||||
field, no sentinel. (If a future non-forwarded accept shape needs
|
field, no sentinel. (If a future non-forwarded accept shape needs
|
||||||
metadata, revisit then — additive.)
|
metadata, revisit then, and the change is additive.)
|
||||||
- **The BIND reply#2 fidelity is end-to-end:** the opener-side
|
- **The BIND reply#2 fidelity is end-to-end:** the opener-side
|
||||||
correlation writes reply#2 with the REAL peer from the forwarded
|
correlation writes reply#2 with the REAL peer from the forwarded
|
||||||
op's params. reply#1's `BND.ADDR` is the `bound` reply field. Both
|
op's params. reply#1's `BND.ADDR` is the `bound` reply field. Both
|
||||||
POC stand-ins (carried `bind_addr`; sentinel `0.0.0.0:0`) retire.
|
POC stand-ins (carried `bind_addr`; sentinel `0.0.0.0:0`) retire.
|
||||||
- **Hub re-produce:** a hub re-producing a listen resource binds its
|
- **Hub re-produce:** a hub re-producing a listen resource binds its
|
||||||
own listener (bind-first, its own `bound` + minted `listen` token)
|
own listener (bind-first, its own `bound` + minted `listen` token)
|
||||||
and re-opens forwarded channels with the same token — the spoke's
|
and re-opens forwarded channels with the same token, so the spoke's
|
||||||
forwarded opens carry the hub's token untouched (params are not
|
forwarded opens carry the hub's token untouched (params are not
|
||||||
rewritten by the generic relay), and the metadata is per-hop
|
rewritten by the generic relay), and the metadata is per-hop
|
||||||
truthful, matching alkcall ADR-042's terminate-and-re-produce
|
truthful, matching alkcall ADR-042's terminate-and-re-produce
|
||||||
posture. The end opener correlates on the hub's minted token — the
|
posture. The end opener correlates on the hub's minted token, the
|
||||||
value it read from its own open's reply.
|
value it read from its own open's reply.
|
||||||
- **Local-variant BIND needs none of this** (the listener and the
|
- **Local-variant BIND needs none of this** (the listener and the
|
||||||
accept are in-process — POC #5 variant 1, unchanged); the asks and
|
accept are in-process; POC #5 variant 1, unchanged); the asks and
|
||||||
this ADR are scoped to the composed variant.
|
this ADR are scoped to the composed variant.
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -278,18 +280,18 @@ wire distinction beyond `bound`'s presence.
|
|||||||
deadlock and establishment-bound findings this shape dissolves)
|
deadlock and establishment-bound findings this shape dissolves)
|
||||||
- alkcall ADR-047 (open ops; connection-owner allocation; the
|
- alkcall ADR-047 (open ops; connection-owner allocation; the
|
||||||
flavor-form amendment), ADR-049 (establishment; the reply-projection
|
flavor-form amendment), ADR-049 (establishment; the reply-projection
|
||||||
amendment), ADR-042 (hub relay — re-produce id semantics)
|
amendment), ADR-042 (hub relay; re-produce id semantics)
|
||||||
- alktunnels ADR-001 (params per-op amendment), ADR-002 Amendment 1
|
- alktunnels ADR-001 (params per-op amendment), ADR-002 Amendment 1
|
||||||
(flavor-form op ids), ADR-005 (teardown ownership — extended to
|
(flavor-form op ids), ADR-005 (teardown ownership, extended to
|
||||||
`TunnelListener`), ADR-006 (scope posture amendment)
|
`TunnelListener`), ADR-006 (scope posture amendment)
|
||||||
- AGENTS.md conventions 9 (no forced binding — the bind decision is
|
- AGENTS.md conventions 9 (no forced binding, so the bind decision is
|
||||||
the producer's), 13 (access control), 8 (two-pump contract — the
|
the producer's), 13 (access control), 8 (two-pump contract, where the
|
||||||
accept dispatcher is a per-accept `pump_bidi`, not a hand-rolled
|
accept dispatcher is a per-accept `pump_bidi`, not a hand-rolled
|
||||||
loop over one stream)
|
loop over one stream)
|
||||||
|
|
||||||
## Amendment 1 (the correlation token, 2026-09-18)
|
## Amendment 1 (the correlation token, 2026-09-18)
|
||||||
|
|
||||||
The decision stands — bind-first establisher, accept-as-open, the
|
The decision stands: bind-first establisher, accept-as-open, the
|
||||||
`bound` reply field, the forwarded op. What changes: the forwarded
|
`bound` reply field, the forwarded op. What changes: the forwarded
|
||||||
op's correlation key (§3's `channel` param) and the bind-first
|
op's correlation key (§3's `channel` param) and the bind-first
|
||||||
establisher's reply surface (§2 gains `listen`).
|
establisher's reply surface (§2 gains `listen`).
|
||||||
@@ -301,18 +303,18 @@ establisher's reply surface (§2 gains `listen`).
|
|||||||
1. **The serving side never learns its own channel ID.** The
|
1. **The serving side never learns its own channel ID.** The
|
||||||
open-op wrapper allocates the channel and replies with its ID,
|
open-op wrapper allocates the channel and replies with its ID,
|
||||||
but the `OpenHandler`/establisher surface is
|
but the `OpenHandler`/establisher surface is
|
||||||
`Fn(Value, Option<ChannelPlan>, Connection, AuthContext)` — no
|
`Fn(Value, Option<ChannelPlan>, Connection, AuthContext)`, with no
|
||||||
ID anywhere. The §3 semantic ("the opener knows … without a
|
ID anywhere. The §3 semantic ("the opener knows … without a
|
||||||
registry op") required the producer's accept dispatcher to know
|
registry op") required the producer's accept dispatcher to know
|
||||||
its own listen channel's ID to stamp into forwarded opens;
|
its own listen channel's ID to stamp into forwarded opens;
|
||||||
unimplementable without a new upstream surface. (Filing one —
|
unimplementable without a new upstream surface. (Filing one,
|
||||||
exposing the allocated ID to handlers — was considered and
|
which would expose the allocated ID to handlers, was considered and
|
||||||
rejected: it is an alkcall-surface change for a correlation
|
rejected: it is an alkcall-surface change for a correlation
|
||||||
need this crate can solve in-protocol, and it would still not
|
need this crate can solve in-protocol, and it would still not
|
||||||
fix (2).)
|
fix (2).)
|
||||||
2. **Generic hub relays do not rewrite params.** The in-tree
|
2. **Generic hub relays do not rewrite params.** The in-tree
|
||||||
`ChannelRelay` (ADR-051) forwards the open-op input untouched —
|
`ChannelRelay` (ADR-051) forwards the open-op input untouched;
|
||||||
it strips/rewrites only the reply's `channel_id`. A spoke's
|
it strips/rewrites only the reply's `channel_id`. A spoke's
|
||||||
forwarded opens would carry the spoke-side listen channel's ID,
|
forwarded opens would carry the spoke-side listen channel's ID,
|
||||||
which the end opener never allocated and cannot correlate ("hubs
|
which the end opener never allocated and cannot correlate ("hubs
|
||||||
re-map the id naturally" assumed tunnel-specific relay behavior
|
re-map the id naturally" assumed tunnel-specific relay behavior
|
||||||
@@ -323,23 +325,23 @@ establisher's reply surface (§2 gains `listen`).
|
|||||||
reply field via the same `Establishment::with_reply_field` carrier
|
reply field via the same `Establishment::with_reply_field` carrier
|
||||||
`bound` rides. Both fields pass through relays untouched
|
`bound` rides. Both fields pass through relays untouched
|
||||||
(reply fields ride per-hop-truthful, ADR-051), and the forwarded
|
(reply fields ride per-hop-truthful, ADR-051), and the forwarded
|
||||||
open's `listen` param carries the minted token at every hop — the
|
open's `listen` param carries the minted token at every hop; the
|
||||||
end opener correlates on the value from its own open's reply.
|
end opener correlates on the value from its own open's reply.
|
||||||
- **Why not a consumer-minted token:** the opener choosing the token
|
- **Why not a consumer-minted token:** the opener choosing the token
|
||||||
and passing it in the listen open's params would work on the direct
|
and passing it in the listen open's params would work on the direct
|
||||||
path, but fails hub re-produce the same way (the hub's own
|
path, but fails hub re-produce the same way (the hub's own
|
||||||
bind-first open is a NEW open — it cannot adopt the end opener's
|
bind-first open is a NEW open, and it cannot adopt the end opener's
|
||||||
token without a pass-through mechanism, which is exactly the
|
token without a pass-through mechanism, which is exactly the
|
||||||
params-rewriting relays don't do). Producer-minted is the direction
|
params-rewriting relays don't do). Producer-minted is the direction
|
||||||
information already flows (producer → opener via reply fields);
|
information already flows (producer → opener via reply fields);
|
||||||
no new flow is needed.
|
no new flow is needed.
|
||||||
- **Token lifecycle:** minted at establisher time (one per open —
|
- **Token lifecycle:** minted at establisher time (one per open;
|
||||||
two opens never share a token, so the opener's correlation map
|
two opens never share a token, so the opener's correlation map
|
||||||
needs no session disambiguation beyond the token); retired with
|
needs no session disambiguation beyond the token); retired with
|
||||||
the listener session (the token's only consumer is the per-accept
|
the listener session (the token's only consumer is the per-accept
|
||||||
forwarded opens of that listener). Uniqueness is per-producer;
|
forwarded opens of that listener). Uniqueness is per-producer;
|
||||||
the opener-side map keys on the token as read from its own reply.
|
the opener-side map keys on the token as read from its own reply.
|
||||||
- **Scope names, op ids, `bound`, `peer` — unchanged.** The wire
|
- **Scope names, op ids, `bound`, and `peer` are unchanged.** The wire
|
||||||
delta vs the pre-amendment ADR is exactly: the reply gains the
|
delta vs the pre-amendment ADR is exactly: the reply gains the
|
||||||
optional `listen` field, the forwarded params rename `channel` →
|
optional `listen` field, the forwarded params rename `channel` →
|
||||||
`listen` with token semantics. Nothing else shipped against either
|
`listen` with token semantics. Nothing else shipped against either
|
||||||
@@ -349,17 +351,17 @@ establisher's reply surface (§2 gains `listen`).
|
|||||||
|
|
||||||
Resolves review 003 (`docs/reviews/003-correlation-miss-arrival-class-alkssh-input.md`)
|
Resolves review 003 (`docs/reviews/003-correlation-miss-arrival-class-alkssh-input.md`)
|
||||||
via `docs/plans/003-correlation-miss-resolve.md` §Decisions 1–4. The
|
via `docs/plans/003-correlation-miss-resolve.md` §Decisions 1–4. The
|
||||||
decision stands — bind-first establisher, accept-as-open, the
|
decision stands: bind-first establisher, accept-as-open, the
|
||||||
`bound`/`listen` reply fields, the forwarded op, `tunnel:forwarded`.
|
`bound`/`listen` reply fields, the forwarded op, `tunnel:forwarded`.
|
||||||
What changes: **the serving side of the forwarded op (§3) gains an
|
What changes: **the serving side of the forwarded op (§3) gains an
|
||||||
establishment phase** — §3's sentence "no establisher — the forwarded
|
establishment phase**: §3's sentence, "no establisher; the forwarded
|
||||||
channel needs no establishment; its data plane starts immediately"
|
channel needs no establishment; its data plane starts immediately"
|
||||||
(marked inline above) is the text this amendment amends.
|
(marked inline above) is the text this amendment amends.
|
||||||
|
|
||||||
### The miss class this closes (the tri-vocabulary history)
|
### The miss class this closes (the tri-vocabulary history)
|
||||||
|
|
||||||
A forwarded open whose `listen` token no longer routes — the
|
A forwarded open whose `listen` token no longer routes, i.e. the
|
||||||
stale/missing-token arrival class — is currently served
|
stale/missing-token arrival class, is currently served
|
||||||
establisher-less everywhere (alkssh's door, alksocks' composed BIND,
|
establisher-less everywhere (alkssh's door, alksocks' composed BIND,
|
||||||
and this crate's built-in shape). That is the one posture exception
|
and this crate's built-in shape). That is the one posture exception
|
||||||
to ADR-049's "a refused open is a typed call error, never a phantom
|
to ADR-049's "a refused open is a typed call error, never a phantom
|
||||||
@@ -367,16 +369,16 @@ channel": the open succeeds at the call plane, declines post-open in
|
|||||||
the assembly, and the producer-side pump EOFs with no signal. The
|
the assembly, and the producer-side pump EOFs with no signal. The
|
||||||
family has named the class three ways:
|
family has named the class three ways:
|
||||||
|
|
||||||
- **alkssh** — `correlation-miss` at `debug`:
|
- **alkssh** uses `correlation-miss` at `debug`:
|
||||||
`observe::decline_arrival(row, "correlation-miss", listen)` +
|
`observe::decline_arrival(row, "correlation-miss", listen)` +
|
||||||
teardown-at-birth (`src/tunnel_map.rs`; tokens removed at session
|
teardown-at-birth (`src/tunnel_map.rs`; tokens removed at session
|
||||||
teardown by the shared per-fabric-leg `ListenRouter`).
|
teardown by the shared per-fabric-leg `ListenRouter`).
|
||||||
- **alksocks** — prose at `warn`: "forwarded open with no waiting
|
- **alksocks** uses prose at `warn`: "forwarded open with no waiting
|
||||||
session" (`src/composed/mod.rs:497`).
|
session" (`src/composed/mod.rs:497`).
|
||||||
- **alktunnels** — the `handler_error`-class generic arm at `warn`
|
- **alktunnels** has the `handler_error`-class generic arm at `warn`
|
||||||
(the bind-first accept loop's failed-open branch,
|
(the bind-first accept loop's failed-open branch,
|
||||||
`src/producer.rs:838-854`), which fires only for the
|
`src/producer.rs:838-854`), which fires only for the
|
||||||
connection-gone/`NOT_FOUND`/ACL-refused subclass — an in-session
|
connection-gone/`NOT_FOUND`/ACL-refused subclass; an in-session
|
||||||
miss (route removed while the fabric connection lives) succeeds at
|
miss (route removed while the fabric connection lives) succeeds at
|
||||||
the call plane and logs nothing at all.
|
the call plane and logs nothing at all.
|
||||||
|
|
||||||
@@ -384,7 +386,7 @@ Amendment 2 retires the prose variant and the fabric-side silence.
|
|||||||
|
|
||||||
### The decisions
|
### The decisions
|
||||||
|
|
||||||
1. **The serving side gains an establisher variant — the gate is
|
1. **The serving side gains an establisher variant, and the gate is
|
||||||
injected.** The protocol crate cannot hold the assembly's
|
injected.** The protocol crate cannot hold the assembly's
|
||||||
correlation map, so the establisher's check is an
|
correlation map, so the establisher's check is an
|
||||||
assembly-provided gate closure over the forwarded params
|
assembly-provided gate closure over the forwarded params
|
||||||
@@ -392,38 +394,39 @@ Amendment 2 retires the prose variant and the fabric-side silence.
|
|||||||
alkcall `OpenEstablisher` dance and registers via
|
alkcall `OpenEstablisher` dance and registers via
|
||||||
`register_openable_with_establisher` (alkcall 0.10.x, verified
|
`register_openable_with_establisher` (alkcall 0.10.x, verified
|
||||||
present). Assemblies must NOT hand-roll the alkcall establisher
|
present). Assemblies must NOT hand-roll the alkcall establisher
|
||||||
plumbing — the crate-owned registration surface is the only
|
plumbing; the crate-owned registration surface is the only
|
||||||
supported path. The gate is a lookup, not a dial: establishment
|
supported path. The gate is a lookup, not a dial: establishment
|
||||||
stays fast, bounded by the usual establisher rules only.
|
stays fast, bounded by the usual establisher rules only.
|
||||||
2. **A stale/missing token fails the open as a typed
|
2. **A stale/missing token fails the open as a typed
|
||||||
`channel:open_failed`.** The crate maps the gate's failure to
|
`channel:open_failed`.** The crate maps the gate's failure to
|
||||||
alkcall's `EstablishmentError::UnknownResource`, so the reply is
|
alkcall's `EstablishmentError::UnknownResource`, so the reply is
|
||||||
`channel:open_failed` with `details.reason = "unknown_resource"` —
|
`channel:open_failed` with `details.reason = "unknown_resource"`,
|
||||||
no phantom channel is ever born (ADR-049's posture, extended here
|
and no phantom channel is ever born (ADR-049's posture, extended here
|
||||||
to the forwarded op's serving side). The waiting session IS a
|
to the forwarded op's serving side). The waiting session IS a
|
||||||
resource that is gone; **`unknown_resource` is reused as-is and
|
resource that is gone; **`unknown_resource` is reused as-is and
|
||||||
the wire vocabulary is NOT extended**: `wire.md`'s open-op sections
|
the wire vocabulary is NOT extended**: `wire.md`'s open-op sections
|
||||||
(the pinned `details.reason ∈ dial_failed / unknown_resource /
|
(the pinned `details.reason ∈ dial_failed / unknown_resource /
|
||||||
resource_shortage / handler_error / timeout` list and its
|
resource_shortage / handler_error / timeout` list and its
|
||||||
§The Forwarded Op) are untouched by this amendment — there is no
|
§The Forwarded Op) are untouched by this amendment; there is no
|
||||||
reason-class ADR, no new wire value. (The one serving-shape prose
|
reason-class ADR, no new wire value. (The one serving-shape prose
|
||||||
line in `wire.md` — "no establisher" under §The Forwarded Op —
|
line in `wire.md`, "no establisher" under §The Forwarded Op,
|
||||||
syncs with the API task's in-code doc updates, plan 003 W1; this
|
syncs with the API task's in-code doc updates, plan 003 W1; this
|
||||||
ADR does not touch `wire.md`.)
|
ADR does not touch `wire.md`.)
|
||||||
3. **Gate order is pinned: the scope gate precedes the establisher.**
|
3. **Gate order is pinned: the scope gate precedes the establisher.**
|
||||||
The registry runs `tunnel:forwarded` ACL before the open op's
|
The registry runs `tunnel:forwarded` ACL before the open op's
|
||||||
establishment phase (alkcall's open-op wrapper order: registry
|
establishment phase (alkcall's open-op wrapper order: registry
|
||||||
gates — schema, then ACL — then the wrapper's allocate →
|
gates (schema, then ACL), then the wrapper's allocate →
|
||||||
establisher → handler). A scope-less producer is `FORBIDDEN`
|
establisher → handler). A scope-less producer is `FORBIDDEN`
|
||||||
before any gate lookup; the gate never sees an unauthorized open.
|
before any gate lookup; the gate never sees an unauthorized open.
|
||||||
4. **The residual post-open race keeps teardown-at-birth, named
|
4. **The residual post-open race keeps teardown-at-birth, named
|
||||||
`correlation-miss` at `debug`.** A token that vanishes between
|
`correlation-miss` at `debug`.** A token that vanishes between
|
||||||
establisher pass and handler run — the narrow race the gate cannot
|
establisher pass and handler run, the narrow race the gate cannot
|
||||||
close — keeps §3's opener-half posture, unchanged: the assembly
|
close, keeps §3's opener-half posture, unchanged: the assembly
|
||||||
handler declines, its early return reaps the channel
|
handler declines, its early return reaps the channel
|
||||||
teardown-at-birth (R-02). Its event name is **`correlation-miss`**
|
teardown-at-birth (R-02). Its event name is **`correlation-miss`**
|
||||||
(alkssh's shipped vocabulary wins — it is published; alksocks'
|
(alkssh's shipped vocabulary wins; it is published, and alksocks'
|
||||||
prose variant retires), at `debug` **assembly-side** — the
|
prose variant retires), at `debug` **assembly-side**:
|
||||||
|
the
|
||||||
protocol crate's built-in handler holds no map, so this is the
|
protocol crate's built-in handler holds no map, so this is the
|
||||||
assemblies' convention this ADR records, not code this crate runs.
|
assemblies' convention this ADR records, not code this crate runs.
|
||||||
The producer-side generic arm (the accept loop's failed-open
|
The producer-side generic arm (the accept loop's failed-open
|
||||||
@@ -436,7 +439,7 @@ The base `register_tunnel_forwarded_openable` (establisher-less, the
|
|||||||
post-open shape) stays for compatibility; the establisher variant
|
post-open shape) stays for compatibility; the establisher variant
|
||||||
(`register_tunnel_forwarded_openable_with_establisher`) is the
|
(`register_tunnel_forwarded_openable_with_establisher`) is the
|
||||||
recommended posture, and new assemblies take it by default. Both ride
|
recommended posture, and new assemblies take it by default. Both ride
|
||||||
the same op id, params, scope, and reply surface — the wire delta of
|
the same op id, params, scope, and reply surface; the wire delta of
|
||||||
this amendment is empty.
|
this amendment is empty.
|
||||||
|
|
||||||
### References
|
### References
|
||||||
@@ -452,5 +455,5 @@ this amendment is empty.
|
|||||||
`EstablishmentError::UnknownResource → unknown_resource` (verified)
|
`EstablishmentError::UnknownResource → unknown_resource` (verified)
|
||||||
- alkssh `src/tunnel_map.rs` (`ListenRouter`, `decline_arrival`);
|
- alkssh `src/tunnel_map.rs` (`ListenRouter`, `decline_arrival`);
|
||||||
alksocks `src/composed/mod.rs:497`; alktunnels
|
alksocks `src/composed/mod.rs:497`; alktunnels
|
||||||
`src/producer.rs:838-854` — the three miss sites this amendment
|
`src/producer.rs:838-854`: the three miss sites this amendment
|
||||||
converges (source for §The miss class above)
|
converges (source for §The miss class above)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-09-18 (OQ-TN-16 updated — ADR-008 Amendment 1: the correlation token)
|
last_updated: 2026-09-18 (OQ-TN-16 updated per ADR-008 Amendment 1: the correlation token)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Open Questions
|
# alktunnels: Open Questions
|
||||||
|
|
||||||
Centralized tracker. The Phase 0 OQ ledger (`docs/research/
|
Centralized tracker. The Phase 0 OQ ledger (`docs/research/
|
||||||
phase-0-findings.md`, OQ-TN-01..10) is promoted here with statuses
|
phase-0-findings.md`, OQ-TN-01..10) is promoted here with statuses
|
||||||
@@ -15,7 +15,7 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
### OQ-TN-01: Target addressing format (open-op `params` shape)
|
### OQ-TN-01: Target addressing format (open-op `params` shape)
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-01)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-01)
|
||||||
- **Status**: resolved — [ADR-001](decisions/001-open-params-layout.md)
|
- **Status**: resolved (see [ADR-001](decisions/001-open-params-layout.md))
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Resolution**: 2026-09-07 (Phase 1). `params` = self-contained
|
- **Resolution**: 2026-09-07 (Phase 1). `params` = self-contained
|
||||||
JSON object: `{ "resource": <string>, "substrate": <string> }`.
|
JSON object: `{ "resource": <string>, "substrate": <string> }`.
|
||||||
@@ -26,17 +26,17 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
+ survey input (ssh-socks5-survey §OQ-TN-01 residue) + both POCs.
|
+ survey input (ssh-socks5-survey §OQ-TN-01 residue) + both POCs.
|
||||||
- **Cross-references**: OQ-TN-02, OQ-TN-03, ADR-001, ADR-002
|
- **Cross-references**: OQ-TN-02, OQ-TN-03, ADR-001, ADR-002
|
||||||
|
|
||||||
### OQ-TN-02: Datagram substrates (UDP) — boundary preservation
|
### OQ-TN-02: Datagram substrates (UDP), boundary preservation
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-02)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-02)
|
||||||
- **Status**: resolved — [ADR-003](decisions/003-codec-and-udp-framing.md)
|
- **Status**: resolved; see [ADR-003](decisions/003-codec-and-udp-framing.md)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Resolution**: split by path (2026-09-06 survey) + F-2 mandate
|
- **Resolution**: split by path (2026-09-06 survey) + F-2 mandate
|
||||||
(2026-09-07 reverse POC). Base open-op UDP resources:
|
(2026-09-07 reverse POC). Base open-op UDP resources:
|
||||||
endpoint-at-open (one channel = one flow). Dynamic/`-D` UDP:
|
endpoint-at-open (one channel = one flow). Dynamic/`-D` UDP:
|
||||||
per-datagram addressing inside the tunnel payload, assembly-layer.
|
per-datagram addressing inside the tunnel payload, assembly-layer.
|
||||||
Boundary preservation: mandatory `[len: u16 BE]` per-datagram
|
Boundary preservation: mandatory `[len: u16 BE]` per-datagram
|
||||||
framing — mandatory for correctness, not cosmetics (F-2; the full
|
framing; this is mandatory for correctness, not cosmetics (F-2; the full
|
||||||
rationale and the executable pin live in ADR-003). Under the codec,
|
rationale and the executable pin live in ADR-003). Under the codec,
|
||||||
`len=0` is a legal empty datagram (forward POC validated); EOF is
|
`len=0` is a legal empty datagram (forward POC validated); EOF is
|
||||||
the channels-level sentinel; the layers never collide.
|
the channels-level sentinel; the layers never collide.
|
||||||
@@ -46,7 +46,7 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-03)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-03)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Status**: resolved — hub-owns-the-connection model (2026-09-05) +
|
- **Status**: resolved: the hub-owns-the-connection model (2026-09-05) +
|
||||||
the reverse-flow POC (2026-09-07, `reverse-poc-summary.md`).
|
the reverse-flow POC (2026-09-07, `reverse-poc-summary.md`).
|
||||||
No protocol-level direction: role follows the resource. `-L`/`-R`
|
No protocol-level direction: role follows the resource. `-L`/`-R`
|
||||||
are the same open op with the entry point on different machines;
|
are the same open op with the entry point on different machines;
|
||||||
@@ -60,37 +60,37 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-04)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-04)
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Status**: resolved — binding is always assembly-layer and
|
- **Status**: resolved: binding is always assembly-layer and
|
||||||
optional, on either side; the protocol never binds. API surface:
|
optional, on either side; the protocol never binds. API surface:
|
||||||
dial flows with no local bind (both POCs), listen flows where the
|
dial flows with no local bind (both POCs), listen flows where the
|
||||||
binding is the producer's establisher (listen variant, ADR-004),
|
binding is the producer's establisher (listen variant, ADR-004),
|
||||||
unbound/abstract flows (in-process pipes — the pump is generic over
|
unbound/abstract flows (in-process pipes; the pump is generic over
|
||||||
boxed halves). The concrete produce/consume surface is spec'd in
|
boxed halves). The concrete produce/consume surface is spec'd in
|
||||||
producer.md / consumer.md (ADR-005).
|
producer.md / consumer.md (ADR-005).
|
||||||
- **Cross-references**: OQ-TN-03, ADR-004, ADR-005
|
- **Cross-references**: OQ-TN-03, ADR-004, ADR-005
|
||||||
|
|
||||||
## Theme 2: Crate architecture
|
## Theme 2: Crate architecture
|
||||||
|
|
||||||
### OQ-TN-05: Backend inversion point — `TunnelBackend` trait?
|
### OQ-TN-05: Backend inversion point (`TunnelBackend` trait?)
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-05)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-05)
|
||||||
- **Status**: resolved — [ADR-004](decisions/004-no-backend-trait.md)
|
- **Status**: resolved: [ADR-004](decisions/004-no-backend-trait.md)
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Resolution**: 2026-09-07 (Phase 1). No trait for substrate access:
|
- **Resolution**: 2026-09-07 (Phase 1). No trait for substrate access:
|
||||||
"produce boxed halves for a resource" is a function (UDP POC,
|
"produce boxed halves for a resource" is a function (UDP POC,
|
||||||
OQ-TN-05 executable input). The only remaining thread — the hub
|
OQ-TN-05 executable input). The only remaining thread, the hub
|
||||||
re-produce composition — resolves the same way: a hub proxy is a
|
re-produce composition, resolves the same way: a hub proxy is a
|
||||||
consumer + producer composed at the assembly layer (the reverse
|
consumer + producer composed at the assembly layer (the reverse
|
||||||
POC's hub is exactly this shape minus re-export); a composition
|
POC's hub is exactly this shape minus re-export); a composition
|
||||||
helper is deferred(scope) until a concrete hub-proxy consumer
|
helper is deferred(scope) until a concrete hub-proxy consumer
|
||||||
exists (OQ-TN-12).
|
exists (OQ-TN-12).
|
||||||
- **Cross-references**: OQ-TN-12, ADR-004
|
- **Cross-references**: OQ-TN-12, ADR-004
|
||||||
|
|
||||||
### OQ-TN-06: The two-pump helper — extract now?
|
### OQ-TN-06: The two-pump helper (extract now?)
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-06)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-06)
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Status**: resolved (upstream) — `alkcall::channels::pump_bidi`
|
- **Status**: resolved (upstream): `alkcall::channels::pump_bidi`
|
||||||
landed in alkcall 0.6.0 (ADR-050, review 007 R-03), shape as this
|
landed in alkcall 0.6.0 (ADR-050, review 007 R-03), shape as this
|
||||||
OQ sketched it; this crate is cited as convergence input. alktunnels
|
OQ sketched it; this crate is cited as convergence input. alktunnels
|
||||||
consumes the helper; hand-rolling is out.
|
consumes the helper; hand-rolling is out.
|
||||||
@@ -101,7 +101,7 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
### OQ-TN-07: ALPN strategy
|
### OQ-TN-07: ALPN strategy
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-07)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-07)
|
||||||
- **Status**: resolved — [ADR-002](decisions/002-alpn-strategy.md)
|
- **Status**: resolved: [ADR-002](decisions/002-alpn-strategy.md)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Resolution**: 2026-09-07 (Phase 1). Option A: single `alk/tunnel`
|
- **Resolution**: 2026-09-07 (Phase 1). Option A: single `alk/tunnel`
|
||||||
ALPN; the substrate discriminator in `params` selects the framing.
|
ALPN; the substrate discriminator in `params` selects the framing.
|
||||||
@@ -113,8 +113,8 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
### OQ-TN-08: Access control and ownership scope
|
### OQ-TN-08: Access control and ownership scope
|
||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-08)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-08)
|
||||||
- **Status**: resolved — [ADR-006](decisions/006-access-control-posture.md)
|
- **Status**: resolved: [ADR-006](decisions/006-access-control-posture.md)
|
||||||
(2026-09-07 — the posture promoted to a decision). The resource is
|
(2026-09-07; the posture promoted to a decision). The resource is
|
||||||
owned by the far side (alkcall op-level ACL applies as-is; no
|
owned by the far side (alkcall op-level ACL applies as-is; no
|
||||||
tunnel-specific policy layer, no allowlists in v1; the ownership
|
tunnel-specific policy layer, no allowlists in v1; the ownership
|
||||||
seam is the establisher seeing the per-call opener identity).
|
seam is the establisher seeing the per-call opener identity).
|
||||||
@@ -134,7 +134,7 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-09)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-09)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Status**: resolved (superseded by alkcall ADR-049, 0.5.0; narrowed
|
- **Status**: resolved (superseded by alkcall ADR-049, 0.5.0; narrowed
|
||||||
through 0.6.0/0.7.0) — establishment is the open op's awaited phase;
|
through 0.6.0/0.7.0): establishment is the open op's awaited phase;
|
||||||
dial failure is a typed `channel:open_failed` call error (reason ∈
|
dial failure is a typed `channel:open_failed` call error (reason ∈
|
||||||
`dial_failed` / `unknown_resource` / `resource_shortage` /
|
`dial_failed` / `unknown_resource` / `resource_shortage` /
|
||||||
`handler_error` / `timeout`), never a phantom channel. Residual
|
`handler_error` / `timeout`), never a phantom channel. Residual
|
||||||
@@ -148,35 +148,35 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
|
|
||||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-10)
|
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-10)
|
||||||
- **Priority**: low
|
- **Priority**: low
|
||||||
- **Status**: resolved — UDP POC DONE (2026-09-06, 17 tests),
|
- **Status**: resolved: the UDP POC is DONE (2026-09-06, 17 tests),
|
||||||
reverse-flow POC DONE (2026-09-07, 14 tests; re-validated over
|
reverse-flow POC DONE (2026-09-07, 14 tests; re-validated over
|
||||||
alkcall 0.7.0, 16 tests). #3 split (OQ-TN-14): unix ships with the
|
alkcall 0.7.0, 16 tests). #3 split (OQ-TN-14): unix ships with the
|
||||||
`local` feature; stdio is OUT — alktty's pipe mode owns process
|
`local` feature; stdio is OUT, since alktty's pipe mode owns process
|
||||||
stdio (exit codes + signals are tty semantics). #4 (helper spike)
|
stdio (exit codes + signals are tty semantics). #4 (helper spike)
|
||||||
resolved by alkcall ADR-050. Findings F-1 (plan payloads
|
resolved by alkcall ADR-050. Findings F-1 (plan payloads
|
||||||
`Send + Sync` — documented upstream) and F-2 (UDP codec mandatory)
|
`Send + Sync`, documented upstream) and F-2 (UDP codec mandatory)
|
||||||
recorded; W1 filed upstream and RESOLVED (CF-005/CF-006).
|
recorded; W1 filed upstream and RESOLVED (CF-005/CF-006).
|
||||||
- **Cross-references**: ADR-003, OQ-TN-05, OQ-TN-06
|
- **Cross-references**: ADR-003, OQ-TN-05, OQ-TN-06
|
||||||
|
|
||||||
## Theme 4: Phase 1 residues (new)
|
## Theme 4: Phase 1 residues (new)
|
||||||
|
|
||||||
### OQ-TN-11: Resource naming — collision domain and lifecycle of a resource id
|
### OQ-TN-11: Resource naming (collision domain and lifecycle of a resource id)
|
||||||
|
|
||||||
- **Origin**: OQ-TN-01's residue (stable resource name vs target
|
- **Origin**: OQ-TN-01's residue (stable resource name vs target
|
||||||
address), carried into Phase 1
|
address), carried into Phase 1
|
||||||
- **Status**: partially resolved — the collision-domain half is
|
- **Status**: partially resolved: the collision-domain half is
|
||||||
**resolved by [ADR-001](decisions/001-open-params-layout.md)**: the
|
**resolved by [ADR-001](decisions/001-open-params-layout.md)**: the
|
||||||
domain is the producer's registry (per-producer; two producers on
|
domain is the producer's registry (per-producer; two producers on
|
||||||
the same connection may expose the same resource name independently
|
the same connection may expose the same resource name independently;
|
||||||
— the open op resolves against the SERVING side's registry). The
|
the open op resolves against the SERVING side's registry). The
|
||||||
lifecycle half stays open: is a resource registry a producer-side
|
lifecycle half stays open: is a resource registry a producer-side
|
||||||
assembly construct (the POCs' `HashMap` — the leading candidate),
|
assembly construct (the POCs' `HashMap`, the leading candidate),
|
||||||
or does it need protocol-level register/rename/retire ops?
|
or does it need protocol-level register/rename/retire ops?
|
||||||
- **Priority**: medium
|
- **Priority**: medium
|
||||||
- **Impacts**: producer.md's `ResourceRegistry` shape and the
|
- **Impacts**: producer.md's `ResourceRegistry` shape and the
|
||||||
discovery story (OQ-TN-08); a protocol-level registry op would be a
|
discovery story (OQ-TN-08); a protocol-level registry op would be a
|
||||||
new wire surface (one-way door).
|
new wire surface (one-way door).
|
||||||
- **Resolution**: lifecycle half pending — no prior-art pressure
|
- **Resolution**: lifecycle half pending; no prior-art pressure
|
||||||
(SSH lists no forwards in-band; discovery is the ops listing);
|
(SSH lists no forwards in-band; discovery is the ops listing);
|
||||||
decide when a consumer needs shared/lifecycle-managed resources.
|
decide when a consumer needs shared/lifecycle-managed resources.
|
||||||
- **Cross-references**: ADR-001, OQ-TN-08
|
- **Cross-references**: ADR-001, OQ-TN-08
|
||||||
@@ -184,7 +184,7 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
### OQ-TN-12: Hub re-produce composition helper
|
### OQ-TN-12: Hub re-produce composition helper
|
||||||
|
|
||||||
- **Origin**: OQ-TN-05's remaining thread (2026-09-05 hub model)
|
- **Origin**: OQ-TN-05's remaining thread (2026-09-05 hub model)
|
||||||
- **Status**: resolved (upstream, 2026-09-18) — alkcall 0.8.0 ships
|
- **Status**: resolved (upstream, 2026-09-18): alkcall 0.8.0 ships
|
||||||
the in-tree `ChannelRelay` + `HubLegTemplate`/`HubLegImports`
|
the in-tree `ChannelRelay` + `HubLegTemplate`/`HubLegImports`
|
||||||
(alkcall ADR-051, review 008 Unit 3): the relay openable wraps
|
(alkcall ADR-051, review 008 Unit 3): the relay openable wraps
|
||||||
from_call-imported marked specs on the consumer-leg registry via
|
from_call-imported marked specs on the consumer-leg registry via
|
||||||
@@ -192,10 +192,10 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
teardown), the translate hop calls the producer leg with
|
teardown), the translate hop calls the producer leg with
|
||||||
`forwarded_for` from the consumer's per-call identity, the
|
`forwarded_for` from the consumer's per-call identity, the
|
||||||
byte-forward hop is `pump_bidi` awaited inline, the spoke reply's
|
byte-forward hop is `pump_bidi` awaited inline, the spoke reply's
|
||||||
`channel_id` is stripped/replaced (other fields ride through —
|
`channel_id` is stripped/replaced (other fields ride through, so
|
||||||
`bound` flows end-to-end per-hop truthful), and the adopted
|
`bound` flows end-to-end per-hop truthful), and the adopted
|
||||||
producer-leg entry reclaims via a `Drop` guard on every plan path.
|
producer-leg entry reclaims via a `Drop` guard on every plan path.
|
||||||
The composition helper a hub proxy needed is upstream — this crate
|
The composition helper a hub proxy needed is upstream, and this crate
|
||||||
composes it (OQ-TN-05's resolution stands: no tunnel-side trait).
|
composes it (OQ-TN-05's resolution stands: no tunnel-side trait).
|
||||||
Tracker task: `architecture/oq-tn-12-tracker` (tasks/architecture/).
|
Tracker task: `architecture/oq-tn-12-tracker` (tasks/architecture/).
|
||||||
- **Priority**: low
|
- **Priority**: low
|
||||||
@@ -205,9 +205,9 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
### OQ-TN-13: UDP truncation semantics at the codec boundary
|
### OQ-TN-13: UDP truncation semantics at the codec boundary
|
||||||
|
|
||||||
- **Origin**: forward POC Issues Surfaced #3
|
- **Origin**: forward POC Issues Surfaced #3
|
||||||
- **Status**: resolved — [ADR-003](decisions/003-codec-and-udp-framing.md)
|
- **Status**: resolved: [ADR-003](decisions/003-codec-and-udp-framing.md)
|
||||||
(2026-09-07): **fail-loud at the adapter level.** A receive buffer
|
(2026-09-07): **fail-loud at the adapter level.** A receive buffer
|
||||||
smaller than the datagram is an error, never a silent truncation —
|
smaller than the datagram is an error, never a silent truncation;
|
||||||
a truncated datagram would corrupt the length-framed stream's
|
a truncated datagram would corrupt the length-framed stream's
|
||||||
framing invariants (the reverse POC's F-2 rationale: the codec's
|
framing invariants (the reverse POC's F-2 rationale: the codec's
|
||||||
wire discipline is what keeps empty datagrams unambiguous; silent
|
wire discipline is what keeps empty datagrams unambiguous; silent
|
||||||
@@ -226,20 +226,20 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
- **Origin**: OQ-TN-10 #3 (the cheap un-POC'd item)
|
- **Origin**: OQ-TN-10 #3 (the cheap un-POC'd item)
|
||||||
- **Priority**: low
|
- **Priority**: low
|
||||||
- **Status**: **resolved 2026-09-07 (split by substrate):**
|
- **Status**: **resolved 2026-09-07 (split by substrate):**
|
||||||
- **Unix: in scope for the `local` feature v1** — same halves shape
|
- **Unix: in scope for the `local` feature v1**: same halves shape
|
||||||
as TCP (`dial_unix`), cheap, wire enum already carries `unix`
|
as TCP (`dial_unix`), cheap, wire enum already carries `unix`
|
||||||
(ADR-001). Implemented by `tunnels/local-socket-halves`.
|
(ADR-001). Implemented by `tunnels/local-socket-halves`.
|
||||||
- **Stdio: OUT of scope — alktty owns it.** A spawned process's
|
- **Stdio: OUT of scope; alktty owns it.** A spawned process's
|
||||||
stdin/stdout/stderr IS a terminal session in alktty's model:
|
stdin/stdout/stderr IS a terminal session in alktty's model:
|
||||||
`LocalTtyBackend`'s pipe mode (`tokio::process::Command` +
|
`LocalTtyBackend`'s pipe mode (`tokio::process::Command` +
|
||||||
`Stdio::piped()`, alktty `tty-local.md`) is exactly the stdio
|
`Stdio::piped()`, alktty `tty-local.md`) is exactly the stdio
|
||||||
case — three multiplexed logical streams + the exit-code
|
case, with three multiplexed logical streams + the exit-code
|
||||||
control chunk (alktty ADR-004) + signal forwarding (REQ-TTY-02).
|
control chunk (alktty ADR-004) + signal forwarding (REQ-TTY-02).
|
||||||
A "stdio bridge" here would be alktty's runner mode with the
|
A "stdio bridge" here would be alktty's runner mode with the
|
||||||
terminal stripped out: a strictly worse duplicate that also
|
terminal stripped out: a strictly worse duplicate that also
|
||||||
drops the semantics that matter (exit code, signals — a byte
|
drops the semantics that matter (exit code, signals; a byte
|
||||||
tunnel has neither). Consumers needing to run commands remotely
|
tunnel has neither). Consumers needing to run commands remotely
|
||||||
compose: alkcall dial → alktty session (pipe mode) — the two
|
compose: alkcall dial → alktty session (pipe mode); the two
|
||||||
crates are siblings on the same channels substrate, not
|
crates are siblings on the same channels substrate, not
|
||||||
competitors. (The OQ-TN-10 #3 "cheap item" was scoped before the
|
competitors. (The OQ-TN-10 #3 "cheap item" was scoped before the
|
||||||
sibling relationship was this clear.)
|
sibling relationship was this clear.)
|
||||||
@@ -256,16 +256,16 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
- **Origin**: `docs/research/tunnels-graduation.md` §2 (the alksocks
|
- **Origin**: `docs/research/tunnels-graduation.md` §2 (the alksocks
|
||||||
graduation discussion, 2026-09-15)
|
graduation discussion, 2026-09-15)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Status**: resolved — [ADR-007](decisions/007-direct-open-op.md)
|
- **Status**: resolved: [ADR-007](decisions/007-direct-open-op.md)
|
||||||
(2026-09-16). Option (a): a second `Sub`-typed open op
|
(2026-09-16). Option (a): a second `Sub`-typed open op
|
||||||
`channels/tunnel/direct` on the same `alk/tunnel` ALPN, params
|
`channels/tunnel/direct` on the same `alk/tunnel` ALPN, params
|
||||||
`{substrate, target}` (self-describing JSON target keyed by the
|
`{substrate, target}` (self-describing JSON target keyed by the
|
||||||
sibling substrate — ATYP bytes rejected: not schema-describable, no
|
sibling substrate; ATYP bytes rejected: not schema-describable, no
|
||||||
unix path form), scope `tunnel:direct` (never implied by
|
unix path form), scope `tunnel:direct` (never implied by
|
||||||
`tunnel:open` — structural ACL separation), dial-only (no
|
`tunnel:open`, which is the structural ACL separation), dial-only (no
|
||||||
listen/direct-BIND form — ingress is the listen + forwarded
|
listen/direct-BIND form; ingress is the listen + forwarded
|
||||||
surface). The op-id convention needed an upstream alkcall amendment
|
surface). The op-id convention needed an upstream alkcall amendment
|
||||||
(ADR-047's discovery derivation was `/sub`-shaped) — filed in
|
(ADR-047's discovery derivation was `/sub`-shaped); it was filed in
|
||||||
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`
|
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`
|
||||||
(ask #1) and **landed in alkcall 0.8.0** (ADR-047 amendment 3:
|
(ask #1) and **landed in alkcall 0.8.0** (ADR-047 amendment 3:
|
||||||
flavor-form op ids + the explicit `channel_open_alpn` discovery
|
flavor-form op ids + the explicit `channel_open_alpn` discovery
|
||||||
@@ -279,23 +279,23 @@ OQ-TN numbering. Format per `docs/sdd_process.md`.
|
|||||||
BIND POC's two asks: bound-addr without consuming an accept; peer
|
BIND POC's two asks: bound-addr without consuming an accept; peer
|
||||||
metadata on accepted handles)
|
metadata on accepted handles)
|
||||||
- **Priority**: high
|
- **Priority**: high
|
||||||
- **Status**: resolved — [ADR-008](decisions/008-listen-metadata-forwarded-op.md)
|
- **Status**: resolved: [ADR-008](decisions/008-listen-metadata-forwarded-op.md)
|
||||||
(2026-09-16; **amended 2026-09-18** — Amendment 1). The
|
(2026-09-16; **amended 2026-09-18**, Amendment 1). The
|
||||||
`forwarded-tcpip` shape adopted over per-ask carriers: (1) a
|
`forwarded-tcpip` shape adopted over per-ask carriers: (1) a
|
||||||
bind-first listen establisher — establishment ends at bind time, so
|
bind-first listen establisher: establishment ends at bind time, so
|
||||||
the open-op reply carries the real OS-chosen address via an
|
the open-op reply carries the real OS-chosen address via an
|
||||||
additive `bound` field (ask #1; the carrier is the establisher →
|
additive `bound` field (ask #1; the carrier is the establisher →
|
||||||
reply projection — filed as the review's ask #2 and **landed in
|
reply projection, filed as the review's ask #2 and **landed in
|
||||||
alkcall 0.8.0** as ADR-049 amendment 3,
|
alkcall 0.8.0** as ADR-049 amendment 3,
|
||||||
`Establishment::with_reply_field` + `open_channel_with_reply`),
|
`Establishment::with_reply_field` + `open_channel_with_reply`),
|
||||||
and the address-first-vs-accept-first BIND deadlock + the 10s
|
and the address-first-vs-accept-first BIND deadlock + the 10s
|
||||||
establishment-bound problem dissolve with the shape; (2) the
|
establishment-bound problem dissolve with the shape; (2) the
|
||||||
forwarded op `channels/tunnel/forwarded` (accept-as-open) — the
|
forwarded op `channels/tunnel/forwarded` (accept-as-open): the
|
||||||
producer opens a channel per accept with `{listen, substrate,
|
producer opens a channel per accept with `{listen, substrate,
|
||||||
peer}` in params (ask #2's carrier; the `TargetHandle.peer` option
|
peer}` in params (ask #2's carrier; the `TargetHandle.peer` option
|
||||||
is superseded). Amendment 1: the correlation key is a
|
is superseded). Amendment 1: the correlation key is a
|
||||||
**producer-minted token** (`listen` reply field + forwarded
|
**producer-minted token** (`listen` reply field + forwarded
|
||||||
param), not the listen channel's ID — the serving-side handler
|
param), not the listen channel's ID; the serving-side handler
|
||||||
never sees its own channel ID (alkcall 0.8.0's `OpenHandler`
|
never sees its own channel ID (alkcall 0.8.0's `OpenHandler`
|
||||||
surface) and generic relays don't rewrite params, so a
|
surface) and generic relays don't rewrite params, so a
|
||||||
producer-minted token (which rides reply fields and params through
|
producer-minted token (which rides reply fields and params through
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-09-18 (the graduation: the direct op, listen metadata, the forwarded op — ADR-007/008 as amended: the correlation token)
|
last_updated: 2026-09-18 (the graduation: the direct op, listen metadata, the forwarded op; ADR-007/008 as amended: the correlation token)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Overview
|
# alktunnels: Overview
|
||||||
|
|
||||||
Arbitrary bidirectional tunnels over alkcall channels: TCP, UDP, unix
|
Arbitrary bidirectional tunnels over alkcall channels: TCP, UDP, unix
|
||||||
sockets, and other stream or datagram substrates — in the `ssh -L` /
|
sockets, and other stream or datagram substrates, all in the `ssh -L` /
|
||||||
`ssh -D` / `ssh -R` sense. A producer/consumer protocol crate riding
|
`ssh -D` / `ssh -R` sense. A producer/consumer protocol crate riding
|
||||||
alkcall channels the same way alktty does (`alk/tty` is the sibling
|
alkcall channels the same way alktty does (`alk/tty` is the sibling
|
||||||
precedent). This document covers the crate's purpose, the resource
|
precedent). This document covers the crate's purpose, the resource
|
||||||
@@ -28,8 +28,8 @@ channels and provides the typed consumer sessions:
|
|||||||
halves (ADR-050, R-02's lifetime contract). Three ops on the one
|
halves (ADR-050, R-02's lifetime contract). Three ops on the one
|
||||||
ALPN (ADR-002 Amendment 1): `channels/tunnel/sub` (the resource
|
ALPN (ADR-002 Amendment 1): `channels/tunnel/sub` (the resource
|
||||||
op), `channels/tunnel/direct` (dynamic-target egress, ADR-007), and
|
op), `channels/tunnel/direct` (dynamic-target egress, ADR-007), and
|
||||||
— served by the listen-opener side — `channels/tunnel/forwarded`
|
`channels/tunnel/forwarded` (served by the listen-opener side;
|
||||||
(accept-as-open, ADR-008). Three establisher shapes: dial, listen
|
accept-as-open, ADR-008). Three establisher shapes: dial, listen
|
||||||
(accept-first), and bind-first listen.
|
(accept-first), and bind-first listen.
|
||||||
- **The consumer half** (`TunnelSession` / `TunnelListener`,
|
- **The consumer half** (`TunnelSession` / `TunnelListener`,
|
||||||
ADR-005/008): opens tunnel channels (`ChannelClient::open_channel`
|
ADR-005/008): opens tunnel channels (`ChannelClient::open_channel`
|
||||||
@@ -44,11 +44,11 @@ The guiding insight (the OQ-TN-01 reframe):
|
|||||||
> A tunnel is a resource, not an address. Base params identify a
|
> A tunnel is a resource, not an address. Base params identify a
|
||||||
> produced resource + substrate; the producer owns the backing. The
|
> produced resource + substrate; the producer owns the backing. The
|
||||||
> one additive exception is the direct op (ADR-007): a consumer may
|
> one additive exception is the direct op (ADR-007): a consumer may
|
||||||
> ask the producer to dial an arbitrary target — a different
|
> ask the producer to dial an arbitrary target, and that is a
|
||||||
> capability, its own op and its own scope (`tunnel:direct`), never
|
> different capability, its own op and its own scope (`tunnel:direct`), never
|
||||||
> implied by `tunnel:open`. Rich per-datagram addressing (SOCKS5 ATYP,
|
> implied by `tunnel:open`. Rich per-datagram addressing (SOCKS5 ATYP,
|
||||||
> udpgw-style remotes) still enters only through the `-D`/dynamic
|
> udpgw-style remotes) still enters only through the `-D`/dynamic
|
||||||
> composition path — inside the tunnel payload, never in the wire.
|
> composition path, i.e. inside the tunnel payload, never in the wire.
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
@@ -57,15 +57,15 @@ consumers, the same role alktty plays for terminal sessions:
|
|||||||
|
|
||||||
- A hub that runs agent workspaces exposes workers' services
|
- A hub that runs agent workspaces exposes workers' services
|
||||||
(postgres, redis, gitea HTTP) as named resources; a coordinator
|
(postgres, redis, gitea HTTP) as named resources; a coordinator
|
||||||
opens tunnels to them over alkcall channels — the VPN/SSH-tunnel
|
opens tunnels to them over alkcall channels (the VPN/SSH-tunnel
|
||||||
service pattern without SSH.
|
service pattern without SSH).
|
||||||
- A worker behind NAT dials the hub and serves its own open ops
|
- A worker behind NAT dials the hub and serves its own open ops
|
||||||
(the reverse flow, `ssh -R`); the hub opens tunnel channels toward
|
(the reverse flow, `ssh -R`); the hub opens tunnel channels toward
|
||||||
it per local accept. Both POCs validated this end-to-end.
|
it per local accept. Both POCs validated this end-to-end.
|
||||||
- A `-D`-style consumer tunnels a SOCKS5 connection; the socks5
|
- A `-D`-style consumer tunnels a SOCKS5 connection; the socks5
|
||||||
server (assembly layer) does dynamic target selection inside the
|
server (assembly layer) does dynamic target selection inside the
|
||||||
tunnel payload.
|
tunnel payload.
|
||||||
- The protocol layer is substrate-agnostic by construction — the
|
- The protocol layer is substrate-agnostic by construction: the
|
||||||
bookkeeping (target addressing, direction, lifecycle) never
|
bookkeeping (target addressing, direction, lifecycle) never
|
||||||
hardcodes a substrate (AGENTS.md convention 7). TCP, UDP, unix
|
hardcodes a substrate (AGENTS.md convention 7). TCP, UDP, unix
|
||||||
sockets, and in-process pipes ride the same open op, pump shape,
|
sockets, and in-process pipes ride the same open op, pump shape,
|
||||||
@@ -77,10 +77,10 @@ A tunnel channel's life (full detail in [wire.md](wire.md)):
|
|||||||
|
|
||||||
1. **Open (the establishment phase).** The consumer calls
|
1. **Open (the establishment phase).** The consumer calls
|
||||||
`channels/tunnel/sub` with `params = {resource, substrate}`
|
`channels/tunnel/sub` with `params = {resource, substrate}`
|
||||||
(ADR-001) — or `channels/tunnel/direct` with
|
(ADR-001), or `channels/tunnel/direct` with
|
||||||
`params = {substrate, target}` (ADR-007, dynamic egress). The
|
`params = {substrate, target}` (ADR-007, dynamic egress). The
|
||||||
producer's establisher — the open op's awaited phase (alkcall
|
producer's establisher (the open op's awaited phase, alkcall
|
||||||
ADR-049) — resolves the resource (or parses the target), dials the
|
ADR-049) resolves the resource (or parses the target), dials the
|
||||||
substrate, and returns the handle in the plan. Failure is a typed
|
substrate, and returns the handle in the plan. Failure is a typed
|
||||||
`channel:open_failed` (`dial_failed` / `unknown_resource` /
|
`channel:open_failed` (`dial_failed` / `unknown_resource` /
|
||||||
`resource_shortage` / `handler_error` / `timeout`); the channel
|
`resource_shortage` / `handler_error` / `timeout`); the channel
|
||||||
@@ -124,15 +124,15 @@ alktunnels (local feature) — non-wasm by design
|
|||||||
Same posture as alktty: the default crate is protocol-only and
|
Same posture as alktty: the default crate is protocol-only and
|
||||||
wasm-clean (AGENTS.md convention 4); substrate I/O is feature-gated
|
wasm-clean (AGENTS.md convention 4); substrate I/O is feature-gated
|
||||||
(ADR-004). The wire format carries no alkcall-internal types
|
(ADR-004). The wire format carries no alkcall-internal types
|
||||||
(alktty ADR-006's self-containment rule applies to the tunnel codec —
|
(alktty ADR-006's self-containment rule applies to the tunnel codec,
|
||||||
trivial here, since the codec is a 2-byte length prefix).
|
though it is trivial here, since the codec is a 2-byte length prefix).
|
||||||
|
|
||||||
## Feature Gates
|
## Feature Gates
|
||||||
|
|
||||||
| Feature | Contents | Wasm |
|
| Feature | Contents | Wasm |
|
||||||
|---------|----------|------|
|
|---------|----------|------|
|
||||||
| *(default)* | params, wire codec, open-op spec, establisher shapes, `TunnelSession` — protocol only | yes |
|
| *(default)* | params, wire codec, open-op spec, establisher shapes, `TunnelSession` (protocol only) | yes |
|
||||||
| `local` | TCP/UDP/unix dial + listen helpers — halves-producing dial/listen functions (ADR-004). Unix ships (OQ-TN-14); stdio does NOT — a spawned process's stdio is alktty's pipe mode (exit codes, signals), not a tunnel | no |
|
| `local` | TCP/UDP/unix dial + listen helpers, i.e. halves-producing dial/listen functions (ADR-004). Unix ships (OQ-TN-14); stdio does NOT: a spawned process's stdio is alktty's pipe mode (exit codes, signals), not a tunnel | no |
|
||||||
|
|
||||||
## Module Map
|
## Module Map
|
||||||
|
|
||||||
@@ -164,14 +164,14 @@ All design decisions are documented as ADRs in [decisions/](decisions/).
|
|||||||
|
|
||||||
| ADR | Decision | Summary |
|
| ADR | Decision | Summary |
|
||||||
|-----|----------|---------|
|
|-----|----------|---------|
|
||||||
| [001](decisions/001-open-params-layout.md) | Open-op params: resource + substrate | `{resource, substrate}` — the producer's stable name, not an address; wire-stable from the first consumer |
|
| [001](decisions/001-open-params-layout.md) | Open-op params: resource + substrate | `{resource, substrate}`, the producer's stable name (not an address); wire-stable from the first consumer |
|
||||||
| [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN | Substrate in params selects the framing; no ALPN split, no API bifurcation |
|
| [002](decisions/002-alpn-strategy.md) | Single `alk/tunnel` ALPN | Substrate in params selects the framing; no ALPN split, no API bifurcation |
|
||||||
| [003](decisions/003-codec-and-udp-framing.md) | Data-plane codec | Raw pass-through (stream) / mandatory `[len: u16 BE]` (UDP — F-2 mandate); `len=0` is a legal empty datagram |
|
| [003](decisions/003-codec-and-udp-framing.md) | Data-plane codec | Raw pass-through (stream) / mandatory `[len: u16 BE]` (UDP; F-2 mandate); `len=0` is a legal empty datagram |
|
||||||
| [004](decisions/004-no-backend-trait.md) | No TunnelBackend trait | Halves functions at the assembly layer; listen is an establisher shape; trait re-evaluated at the 3-consumer threshold |
|
| [004](decisions/004-no-backend-trait.md) | No TunnelBackend trait | Halves functions at the assembly layer; listen is an establisher shape; trait re-evaluated at the 3-consumer threshold |
|
||||||
| [005](decisions/005-consumer-session-owns-teardown.md) | Consumer session owns teardown | `TunnelSession` with close/join/Drop — the W3 gap closes structurally |
|
| [005](decisions/005-consumer-session-owns-teardown.md) | Consumer session owns teardown | `TunnelSession` with close/join/Drop; the W3 gap closes structurally |
|
||||||
| [006](decisions/006-access-control-posture.md) | Access-control posture | The open gate is the boundary: `tunnel:open` scope (stable once published), op-level ACL, ownership seam, no allowlists in v1; amended — `tunnel:direct`/`tunnel:forwarded` scopes + the hub re-produce policy sentence |
|
| [006](decisions/006-access-control-posture.md) | Access-control posture | The open gate is the boundary: `tunnel:open` scope (stable once published), op-level ACL, ownership seam, no allowlists in v1; amended with the `tunnel:direct`/`tunnel:forwarded` scopes + the hub re-produce policy sentence |
|
||||||
| [007](decisions/007-direct-open-op.md) | The direct open op | `channels/tunnel/direct`: dynamic-target egress, `{substrate, target}` params, `tunnel:direct` scope, dial-only — the ssh `direct-tcpip` capability with structural ACL separation |
|
| [007](decisions/007-direct-open-op.md) | The direct open op | `channels/tunnel/direct`: dynamic-target egress, `{substrate, target}` params, `tunnel:direct` scope, dial-only (the ssh `direct-tcpip` capability with structural ACL separation) |
|
||||||
| [008](decisions/008-listen-metadata-forwarded-op.md) | Listen metadata + the forwarded op | Bind-first establisher (reply `bound` field = BND.ADDR fidelity; `listen` = the correlation token), `channels/tunnel/forwarded` accept-as-open (params carry `{listen, substrate, peer}` = reply#2 fidelity) — the `forwarded-tcpip` shape; amended — producer-minted correlation token supersedes the channel-ID key |
|
| [008](decisions/008-listen-metadata-forwarded-op.md) | Listen metadata + the forwarded op | Bind-first establisher (reply `bound` field = BND.ADDR fidelity; `listen` = the correlation token), `channels/tunnel/forwarded` accept-as-open (params carry `{listen, substrate, peer}` = reply#2 fidelity); this is the `forwarded-tcpip` shape; amended so that a producer-minted correlation token supersedes the channel-ID key |
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -179,13 +179,13 @@ Open questions are tracked in [open-questions.md](open-questions.md).
|
|||||||
Key questions affecting this document:
|
Key questions affecting this document:
|
||||||
|
|
||||||
- **OQ-TN-11**: resource naming collision domain + lifecycle (partially resolved)
|
- **OQ-TN-11**: resource naming collision domain + lifecycle (partially resolved)
|
||||||
- **OQ-TN-12**: hub re-produce composition helper (deferred(scope) —
|
- **OQ-TN-12**: hub re-produce composition helper (deferred(scope);
|
||||||
the policy sentence is pinned: the hub's ACL governs the final dial)
|
the policy sentence is pinned: the hub's ACL governs the final dial)
|
||||||
- **OQ-TN-13**: UDP truncation semantics (resolved — fail-loud, ADR-003)
|
- **OQ-TN-13**: UDP truncation semantics (resolved, fail-loud, ADR-003)
|
||||||
- **OQ-TN-14**: unix/stdio placement (resolved — unix in via
|
- **OQ-TN-14**: unix/stdio placement (resolved; unix in via
|
||||||
`local`, stdio out: alktty's pipe mode owns process stdio)
|
`local`, stdio out: alktty's pipe mode owns process stdio)
|
||||||
- **OQ-TN-15**: the direct op (resolved — ADR-007)
|
- **OQ-TN-15**: the direct op (resolved; ADR-007)
|
||||||
- **OQ-TN-16**: listen metadata (resolved — ADR-008)
|
- **OQ-TN-16**: listen metadata (resolved; ADR-008)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
status: draft
|
status: draft
|
||||||
last_updated: 2026-11-03 (the forwarded op's serving-side establishment
|
last_updated: 2026-11-03 (the forwarded op's serving-side establishment
|
||||||
phase + the correlation-miss vocabulary — ADR-008 Amendment 2)
|
phase + the correlation-miss vocabulary, ADR-008 Amendment 2)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Producer Half
|
# alktunnels: Producer Half
|
||||||
|
|
||||||
The producer half: the `channels/tunnel/sub` open op — its spec, its
|
The producer half: the `channels/tunnel/sub` open op, its spec, its
|
||||||
establishers (dial + listen shapes), its pump handler, and its
|
establishers (dial + listen shapes), its pump handler, and its
|
||||||
registration — plus the sibling ops on the same ALPN: the direct op
|
registration, plus the sibling ops on the same ALPN: the direct op
|
||||||
(`channels/tunnel/direct`, dynamic-target egress) and the forwarded op
|
(`channels/tunnel/direct`, dynamic-target egress) and the forwarded op
|
||||||
(`channels/tunnel/forwarded`, accept-as-open). The producer is whoever
|
(`channels/tunnel/forwarded`, accept-as-open). The producer is whoever
|
||||||
can reach the tunnel target; connection direction is irrelevant
|
can reach the tunnel target; connection direction is irrelevant
|
||||||
@@ -44,16 +44,16 @@ ACL, allocates the channel (connection-owner rule, ADR-047 §5), runs
|
|||||||
the establisher as the awaited bounded establishment phase, then
|
the establisher as the awaited bounded establishment phase, then
|
||||||
spawns the pump handler. The generic channel ops
|
spawns the pump handler. The generic channel ops
|
||||||
(`channel/close`, `channel/control`, `channel/resources/subscribe`)
|
(`channel/close`, `channel/control`, `channel/resources/subscribe`)
|
||||||
register separately via `ChannelOperations::register_on` — the
|
register separately via `ChannelOperations::register_on`; the
|
||||||
producer (or its assembly layer) MUST register them on the serving
|
producer (or its assembly layer) MUST register them on the serving
|
||||||
registry so peers can manage channels out-of-band (the recipe alkcall
|
registry so peers can manage channels out-of-band (the recipe alkcall
|
||||||
review 007 §Part B verified — `alkcall/docs/reviews/
|
review 007 §Part B verified; see `alkcall/docs/reviews/
|
||||||
007-establishment-follow-ups-review.md`; R-01 = the `Establishment`
|
007-establishment-follow-ups-review.md`; R-01 = the `Establishment`
|
||||||
plan payload, R-02 = the `OpenHandler` lifetime contract).
|
plan payload, R-02 = the `OpenHandler` lifetime contract).
|
||||||
|
|
||||||
**Registration timing:** the openable may register AFTER
|
**Registration timing:** the openable may register AFTER
|
||||||
`from_connection_with_serving` returns (the connect-side serving
|
`from_connection_with_serving` returns (the connect-side serving
|
||||||
posture — the reverse POC's W2): the dispatcher reads through the
|
posture; the reverse POC's W2): the dispatcher reads through the
|
||||||
shared `Arc<OperationRegistry>` per dispatch. Late registration is a
|
shared `Arc<OperationRegistry>` per dispatch. Late registration is a
|
||||||
supported assembly shape, not a race.
|
supported assembly shape, not a race.
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ supported assembly shape, not a race.
|
|||||||
|
|
||||||
Dynamic-target egress (ADR-007): the same ALPN, a second `Sub`-typed
|
Dynamic-target egress (ADR-007): the same ALPN, a second `Sub`-typed
|
||||||
open op whose establisher dials a per-call target instead of resolving
|
open op whose establisher dials a per-call target instead of resolving
|
||||||
a resource. One ALPN, two ops, two scopes — dial-what-the-producer-
|
a resource. One ALPN, two ops, two scopes: dial-what-the-producer-
|
||||||
registered (`tunnel:open`) and dial-anything (`tunnel:direct`) are
|
registered (`tunnel:open`) and dial-anything (`tunnel:direct`) are
|
||||||
different capabilities.
|
different capabilities.
|
||||||
|
|
||||||
@@ -77,11 +77,11 @@ access control: required_scopes: ["tunnel:direct"]
|
|||||||
description: SHOULD carry a human-readable hint (dynamic-target egress)
|
description: SHOULD carry a human-readable hint (dynamic-target egress)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **No registry lookup** — the establisher parses `target` against
|
- **No registry lookup**: the establisher parses `target` against
|
||||||
`substrate` (malformed → `invalid_input`-class; `unknown_resource`
|
`substrate` (malformed → `invalid_input`-class; `unknown_resource`
|
||||||
can never fire) and injects the parsed `TunnelTarget` into the same
|
can never fire) and injects the parsed `TunnelTarget` into the same
|
||||||
assembly-provided `DialFn` the base op's backings resolve to.
|
assembly-provided `DialFn` the base op's backings resolve to.
|
||||||
- **Dial-only** — no listen/direct-BIND form; the ingress capability
|
- **Dial-only**: no listen/direct-BIND form; the ingress capability
|
||||||
is the listen + forwarded surface (ADR-008).
|
is the listen + forwarded surface (ADR-008).
|
||||||
- Registration: `register_tunnel_direct_openable` (same wrapper, same
|
- Registration: `register_tunnel_direct_openable` (same wrapper, same
|
||||||
plan-flow; the spec builder is public for assembly variants).
|
plan-flow; the spec builder is public for assembly variants).
|
||||||
@@ -91,9 +91,9 @@ description: SHOULD carry a human-readable hint (dynamic-target egress)
|
|||||||
The establisher is the open op's awaited, bounded establishment phase
|
The establisher is the open op's awaited, bounded establishment phase
|
||||||
(alkcall ADR-049 §1/§2): validate params semantically, consult
|
(alkcall ADR-049 §1/§2): validate params semantically, consult
|
||||||
ownership if wired, dial/accept the substrate, and return
|
ownership if wired, dial/accept the substrate, and return
|
||||||
`Establishment::new(plan)` — the dialed handle rides the typed-opaque
|
`Establishment::new(plan)`; the dialed handle rides the typed-opaque
|
||||||
plan (`ChannelPlan = Arc<dyn Any + Send + Sync>`; the `+ Sync` bound
|
plan (`ChannelPlan = Arc<dyn Any + Send + Sync>`; the `+ Sync` bound
|
||||||
constrains plan payloads — socket-backed handles carry it naturally,
|
constrains plan payloads; socket-backed handles carry it naturally,
|
||||||
reverse POC F-1, documented upstream). On failure:
|
reverse POC F-1, documented upstream). On failure:
|
||||||
`EstablishmentError` (the typed reason codes) → the wrapper tears the
|
`EstablishmentError` (the typed reason codes) → the wrapper tears the
|
||||||
channel down and replies `channel:open_failed`.
|
channel down and replies `channel:open_failed`.
|
||||||
@@ -126,15 +126,15 @@ listener; a hub exposing its own port to a third party): the same
|
|||||||
open op, the same params; the establisher pops the next accepted
|
open op, the same params; the establisher pops the next accepted
|
||||||
connection from an assembly-owned listener queue and returns it as
|
connection from an assembly-owned listener queue and returns it as
|
||||||
the plan payload. The listener lifecycle (bind, accept loop, cancel)
|
the plan payload. The listener lifecycle (bind, accept loop, cancel)
|
||||||
is assembly code — the protocol never binds (OQ-TN-04). Error
|
is assembly code; the protocol never binds (OQ-TN-04). Error
|
||||||
mapping: an empty queue during establishment is `resource_shortage`;
|
mapping: an empty queue during establishment is `resource_shortage`;
|
||||||
a closed listener is `dial_failed`-class. No new wire surface — the
|
a closed listener is `dial_failed`-class. No new wire surface: the
|
||||||
listen variant is plan-flow with a different halves source.
|
listen variant is plan-flow with a different halves source.
|
||||||
|
|
||||||
**The accept-first shape stays** (ADR-008 §5): for producers whose
|
**The accept-first shape stays** (ADR-008 §5): for producers whose
|
||||||
resource backing IS an accept queue (an externally-fed or pre-bound
|
resource backing IS an accept queue (an externally-fed or pre-bound
|
||||||
listener shared across opens), this remains the correct shape — the
|
listener shared across opens), this remains the correct shape: the
|
||||||
POC #5 composed-BIND shape. Its limits (no `bound` in the reply — the
|
POC #5 composed-BIND shape. Its limits (no `bound` in the reply: the
|
||||||
accept hasn't happened at establishment; the POC's
|
accept hasn't happened at establishment; the POC's
|
||||||
establishment-bound-vs-wait problem) are inherent to accepting-first.
|
establishment-bound-vs-wait problem) are inherent to accepting-first.
|
||||||
|
|
||||||
@@ -142,32 +142,32 @@ establishment-bound-vs-wait problem) are inherent to accepting-first.
|
|||||||
|
|
||||||
For a producer that OWNS the bind decision (the common hub case;
|
For a producer that OWNS the bind decision (the common hub case;
|
||||||
alksocks' composed BIND): the establisher **binds the listener** (via
|
alksocks' composed BIND): the establisher **binds the listener** (via
|
||||||
the assembly-provided listen function — the protocol never binds,
|
the assembly-provided listen function; the protocol never binds,
|
||||||
OQ-TN-04) and returns the listener handle as the plan payload.
|
OQ-TN-04) and returns the listener handle as the plan payload.
|
||||||
Establishment is fast — bounded by the bind, not by any app-server
|
Establishment is fast (bounded by the bind, not by any app-server
|
||||||
dial:
|
dial):
|
||||||
|
|
||||||
- **The establishment phase ends at bind time**, so the open-op reply
|
- **The establishment phase ends at bind time**, so the open-op reply
|
||||||
can carry the REAL OS-chosen address: the establisher contributes
|
can carry the REAL OS-chosen address: the establisher contributes
|
||||||
`"bound": {host, port}` (or `{path}`) to the reply — the
|
`"bound": {host, port}` (or `{path}`) to the reply: the
|
||||||
establisher → reply projection (ADR-049 amendment 3,
|
establisher → reply projection (ADR-049 amendment 3,
|
||||||
`Establishment::with_reply_field`; the `channel_id` key is
|
`Establishment::with_reply_field`; the `channel_id` key is
|
||||||
wrapper-reserved). **Landed in alkcall 0.8.0** (review 008 U-2);
|
wrapper-reserved). **Landed in alkcall 0.8.0** (review 008 U-2);
|
||||||
consumers read it via `ChannelClient::open_channel_with_reply`.
|
consumers read it via `ChannelClient::open_channel_with_reply`.
|
||||||
- **The establisher mints the correlation token** (ADR-008
|
- **The establisher mints the correlation token** (ADR-008
|
||||||
Amendment 1): a fresh u32 per open, contributed as the `"listen"`
|
Amendment 1): a fresh u32 per open, contributed as the `"listen"`
|
||||||
reply field (the same projection) and carried in the plan — the
|
reply field (the same projection) and carried in the plan; the
|
||||||
accept dispatcher reads it from the plan and stamps it into each
|
accept dispatcher reads it from the plan and stamps it into each
|
||||||
forwarded open's `listen` param. The plan carries
|
forwarded open's `listen` param. The plan carries
|
||||||
`{listener, token, substrate}` (a bind-first plan type; the
|
`{listener, token, substrate}` (a bind-first plan type; the
|
||||||
substrate is stamped at establisher time — the dispatcher reads it
|
substrate is stamped at establisher time, and the dispatcher reads it
|
||||||
from the plan, never re-parses wire input); the token is
|
from the plan, never re-parses wire input); the token is
|
||||||
relay-transparent by construction (reply fields and params both
|
relay-transparent by construction (reply fields and params both
|
||||||
pass hubs untouched — alkcall ADR-051; ADR-008 Amendment 1 has the
|
pass hubs untouched per alkcall ADR-051; ADR-008 Amendment 1 has the
|
||||||
why-superseded of the channel-ID key).
|
why-superseded of the channel-ID key).
|
||||||
- **The pump handler for this shape is an accept dispatcher**, not
|
- **The pump handler for this shape is an accept dispatcher**, not
|
||||||
`pump_bidi`: it accepts the channel's `BiStream` (the R-02
|
`pump_bidi`: it accepts the channel's `BiStream` (the R-02
|
||||||
contract), then runs the assembly-provided accept loop — each
|
contract), then runs the assembly-provided accept loop; each
|
||||||
accepted connection produces a forwarded-channel open toward the
|
accepted connection produces a forwarded-channel open toward the
|
||||||
listen opener (§The Forwarded Op below); the accepted halves pump
|
listen opener (§The Forwarded Op below); the accepted halves pump
|
||||||
against the adopted channel. The listen channel's own `BiStream`
|
against the adopted channel. The listen channel's own `BiStream`
|
||||||
@@ -179,22 +179,22 @@ dial:
|
|||||||
- The address-first-vs-accept-first BIND deadlock (the alksocks POC's
|
- The address-first-vs-accept-first BIND deadlock (the alksocks POC's
|
||||||
one real discovery) DISSOLVES in this shape: the open completes at
|
one real discovery) DISSOLVES in this shape: the open completes at
|
||||||
bind time, reply#1-class metadata (the `bound` field) precedes any
|
bind time, reply#1-class metadata (the `bound` field) precedes any
|
||||||
app-server dial, and the accept wait lives in the pump phase —
|
app-server dial, and the accept wait lives in the pump phase:
|
||||||
teardown-governed, not establishment-bounded (the 10s bound problem
|
teardown-governed, not establishment-bounded (the 10s bound problem
|
||||||
dissolves with it).
|
dissolves with it).
|
||||||
|
|
||||||
**Identity:** the establisher receives the **per-call opener
|
**Identity:** the establisher receives the **per-call opener
|
||||||
identity** (the dispatch-resolved identity overlaid onto the
|
identity** (the dispatch-resolved identity overlaid onto the
|
||||||
install-time context — alkcall 0.7.0 CF-006). On hub-forwarded opens
|
install-time context, alkcall 0.7.0 CF-006). On hub-forwarded opens
|
||||||
it sees the end client, not the hub. `auth` carries the opener's
|
it sees the end client, not the hub. `auth` carries the opener's
|
||||||
identity plus the transport-truthful fields (`alpn`, `remote_addr`,
|
identity plus the transport-truthful fields (`alpn`, `remote_addr`,
|
||||||
`tls_client_fingerprint`) — never rewritten. Consult it for
|
`tls_client_fingerprint`); these are never rewritten. Consult it for
|
||||||
ownership-scoped resources if the assembly layer wants
|
ownership-scoped resources if the assembly layer wants
|
||||||
`OwnershipProvider` checks here (OQ-TN-08's optional half).
|
`OwnershipProvider` checks here (OQ-TN-08's optional half).
|
||||||
|
|
||||||
**Concurrency:** each open's establisher result flows to ITS pump
|
**Concurrency:** each open's establisher result flows to ITS pump
|
||||||
handler — concurrent opens of the same resource are independent
|
handler; concurrent opens of the same resource are independent
|
||||||
(the R-01 plan flow — alkcall review 007 R-01, the
|
(the R-01 plan flow per alkcall review 007 R-01, the
|
||||||
`Establishment::new(plan)` payload; the forward POC's resource-keyed
|
`Establishment::new(plan)` payload; the forward POC's resource-keyed
|
||||||
handoff race is structurally gone, confirmed under concurrency by the
|
handoff race is structurally gone, confirmed under concurrency by the
|
||||||
reverse POC).
|
reverse POC).
|
||||||
@@ -206,27 +206,27 @@ The `OpenHandler` (alkcall ADR-049/050 shape:
|
|||||||
JoinHandle<()>`):
|
JoinHandle<()>`):
|
||||||
|
|
||||||
1. Downcast the plan to the concrete handle type
|
1. Downcast the plan to the concrete handle type
|
||||||
(`Arc::downcast::<TargetHandle>` — establisher and handler agree
|
(`Arc::downcast::<TargetHandle>`, so establisher and handler agree
|
||||||
on the type; alkcall never inspects it).
|
on the type; alkcall never inspects it).
|
||||||
2. `conn.accept_bi()` — the channel's yield-once `BiStream`.
|
2. `conn.accept_bi()`: the channel's yield-once `BiStream`.
|
||||||
3. `pump_bidi(bidi, handle.read, handle.write)` — **awaited inline**
|
3. `pump_bidi(bidi, handle.read, handle.write)`: **awaited inline**
|
||||||
inside the handler's task. The returned `JoinHandle` MUST track
|
inside the handler's task. The returned `JoinHandle` MUST track
|
||||||
the data-plane lifetime (R-02): the wrapper awaits it and its
|
the data-plane lifetime (R-02): the wrapper awaits it and its
|
||||||
completion triggers channel teardown. **Early return =
|
completion triggers channel teardown. **Early return =
|
||||||
teardown-at-birth** — both pumps see instant EOF with zero bytes;
|
teardown-at-birth**: both pumps see instant EOF with zero bytes;
|
||||||
the "spawn-and-forget" shape is the one bug class the R-02
|
the "spawn-and-forget" shape is the one bug class the R-02
|
||||||
telemetry (yield-once flag + birth-teardown debug hint) diagnoses
|
telemetry (yield-once flag + birth-teardown debug hint) diagnoses
|
||||||
at runtime. Hand-rolling the two-pump shape is out (ADR-050).
|
at runtime. Hand-rolling the two-pump shape is out (ADR-050).
|
||||||
4. UDP: the plan payload is the FRAMED adapter (ADR-003) — the pump
|
4. UDP: the plan payload is the FRAMED adapter (ADR-003), so the pump
|
||||||
is unchanged; the codec lives at the substrate/pump boundary.
|
is unchanged; the codec lives at the substrate/pump boundary.
|
||||||
5. **Bind-first listen handler (the accept dispatcher, ADR-008 §1):**
|
5. **Bind-first listen handler (the accept dispatcher, ADR-008 §1):**
|
||||||
the plan payload is the listener; after `accept_bi()`, the handler
|
the plan payload is the listener; after `accept_bi()`, the handler
|
||||||
runs the accept loop (assembly-provided) concurrently with holding
|
runs the accept loop (assembly-provided) concurrently with holding
|
||||||
the channel's `BiStream` — each accepted connection produces a
|
the channel's `BiStream`; each accepted connection produces a
|
||||||
forwarded-channel open toward the listen opener. The listen
|
forwarded-channel open toward the listen opener. The listen
|
||||||
channel's own stream stays idle (no pumps on it); its EOF cancels
|
channel's own stream stays idle (no pumps on it); its EOF cancels
|
||||||
the loop (the listener tears down with the session). Per-accept
|
the loop (the listener tears down with the session). Per-accept
|
||||||
data planes are still `pump_bidi` (convention 8 — per-channel,
|
data planes are still `pump_bidi` (convention 8, per-channel,
|
||||||
never a hand-rolled loop over one stream).
|
never a hand-rolled loop over one stream).
|
||||||
|
|
||||||
**The handler is substrate-agnostic by construction:** it cannot know
|
**The handler is substrate-agnostic by construction:** it cannot know
|
||||||
@@ -237,18 +237,18 @@ in-process pipe, and must not (AGENTS.md convention 7).
|
|||||||
|
|
||||||
Accept-as-open: when the bind-first listener accepts, the PRODUCER
|
Accept-as-open: when the bind-first listener accepts, the PRODUCER
|
||||||
opens a channel back toward the listen opener. The opener SERVES the
|
opens a channel back toward the listen opener. The opener SERVES the
|
||||||
op (`channels/tunnel/forwarded`, scope `tunnel:forwarded` — ingress;
|
op (`channels/tunnel/forwarded`, scope `tunnel:forwarded`, ingress;
|
||||||
Amendment 2's two serving shapes: the recommended correlation-gate
|
Amendment 2's two serving shapes: the recommended correlation-gate
|
||||||
establisher variant — a stale/missing token fails the open as a
|
establisher variant, where a stale/missing token fails the open as a
|
||||||
typed `channel:open_failed`/`unknown_resource` before the channel
|
typed `channel:open_failed`/`unknown_resource` before the channel
|
||||||
is born — or the base establisher-less shape, the data plane starts
|
is born, or the base establisher-less shape, where the data plane starts
|
||||||
immediately and the assembly's post-open decline owns the miss):
|
immediately and the assembly's post-open decline owns the miss):
|
||||||
|
|
||||||
1. The producer's per-accept open carries `{listen, substrate, peer}`
|
1. The producer's per-accept open carries `{listen, substrate, peer}`:
|
||||||
— the correlation token from the plan (the value the establisher
|
the correlation token from the plan (the value the establisher
|
||||||
minted; the opener matches it against the `listen` field of its
|
minted; the opener matches it against the `listen` field of its
|
||||||
open listener sessions — ADR-008 Amendment 1), the substrate
|
open listener sessions per ADR-008 Amendment 1), the substrate
|
||||||
(also from the plan — stamped at establisher time from the one
|
(also from the plan, stamped at establisher time from the one
|
||||||
successful params parse; the dispatcher never re-parses wire
|
successful params parse; the dispatcher never re-parses wire
|
||||||
input), the accepted connection's remote address (reply#2-class
|
input), the accepted connection's remote address (reply#2-class
|
||||||
fidelity's carrier).
|
fidelity's carrier).
|
||||||
@@ -256,19 +256,19 @@ immediately and the assembly's post-open decline owns the miss):
|
|||||||
adopts it (`TunnelSession::adopt`) and pumps the accepted halves
|
adopts it (`TunnelSession::adopt`) and pumps the accepted halves
|
||||||
against it (`pump_against`).
|
against it (`pump_against`).
|
||||||
3. The opener-side handler correlates the open (via `listen`) to the
|
3. The opener-side handler correlates the open (via `listen`) to the
|
||||||
waiting session (assembly-side bookkeeping — the protocol provides
|
waiting session (assembly-side bookkeeping; the protocol provides
|
||||||
the op and params, not the wait state machine), accepts the
|
the op and params, not the wait state machine), accepts the
|
||||||
`BiStream`, and pumps against the local session's halves. Under
|
`BiStream`, and pumps against the local session's halves. Under
|
||||||
the establisher variant (Amendment 2) that correlation lookup
|
the establisher variant (Amendment 2) that correlation lookup
|
||||||
rides the gate at establisher time instead — the handler-side
|
rides the gate at establisher time instead; the handler-side
|
||||||
correlation remains only for the narrow residual race (a token
|
correlation remains only for the narrow residual race (a token
|
||||||
vanishing between gate pass and handler run; the miss class is
|
vanishing between gate pass and handler run; the miss class is
|
||||||
named `correlation-miss` at `debug`, assembly-side).
|
named `correlation-miss` at `debug`, assembly-side).
|
||||||
4. Identity: the forwarded op's opener-side gate sees the PRODUCER's
|
4. Identity: the forwarded op's opener-side gate sees the PRODUCER's
|
||||||
per-call identity (CF-005 precedence — the producer may carry an
|
per-call identity (CF-005 precedence; the producer may carry an
|
||||||
`auth_token` when it is itself forwarding); the peer address in
|
`auth_token` when it is itself forwarding); the peer address in
|
||||||
params is producer-asserted data, not transport truth — the ACL
|
params is producer-asserted data, not transport truth; the ACL
|
||||||
trusts the producer's grant, the application treats `peer` as
|
trusts the producer's grant, and the application treats `peer` as
|
||||||
metadata.
|
metadata.
|
||||||
|
|
||||||
## Registration API (the spec surface)
|
## Registration API (the spec surface)
|
||||||
@@ -279,7 +279,7 @@ pub fn register_tunnel_openable(
|
|||||||
registry: &ResourceRegistry, // assembly-owned: (resource, substrate) -> backing
|
registry: &ResourceRegistry, // assembly-owned: (resource, substrate) -> backing
|
||||||
on_registry: &Arc<OperationRegistry>, // the session's dispatch registry
|
on_registry: &Arc<OperationRegistry>, // the session's dispatch registry
|
||||||
// (the per-session fork per alkcall ADR-047 §4
|
// (the per-session fork per alkcall ADR-047 §4
|
||||||
// amendment, 2026-09-03 — NOT the connection
|
// amendment, 2026-09-03, NOT the connection
|
||||||
// overlay; Layer-2-registered open ops resolve
|
// overlay; Layer-2-registered open ops resolve
|
||||||
// NOT_FOUND, review 004 F-01)
|
// NOT_FOUND, review 004 F-01)
|
||||||
auth: AuthContext, // the install-time context (per-call overlay is upstream's)
|
auth: AuthContext, // the install-time context (per-call overlay is upstream's)
|
||||||
@@ -310,7 +310,7 @@ pub fn register_tunnel_bindfirst_openable( // bind-first (shape 3, ADR-008)
|
|||||||
- `ResourceRegistry` (assembly construct, OQ-TN-11): maps
|
- `ResourceRegistry` (assembly construct, OQ-TN-11): maps
|
||||||
`(resource, substrate)` → backing. The POCs' `HashMap` shape is the
|
`(resource, substrate)` → backing. The POCs' `HashMap` shape is the
|
||||||
candidate; the collision domain is the producer's registry
|
candidate; the collision domain is the producer's registry
|
||||||
(ADR-001 — two producers on one connection may expose the same
|
(per ADR-001, two producers on one connection may expose the same
|
||||||
resource name independently).
|
resource name independently).
|
||||||
- The scope constants `TUNNEL_OPEN_SCOPE = "tunnel:open"`,
|
- The scope constants `TUNNEL_OPEN_SCOPE = "tunnel:open"`,
|
||||||
`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`, and
|
`TUNNEL_DIRECT_SCOPE = "tunnel:direct"`, and
|
||||||
@@ -321,7 +321,7 @@ pub fn register_tunnel_bindfirst_openable( // bind-first (shape 3, ADR-008)
|
|||||||
|
|
||||||
## Access control posture
|
## Access control posture
|
||||||
|
|
||||||
Tunnels reach local networks — the open gate is the security boundary
|
Tunnels reach local networks; the open gate is the security boundary
|
||||||
(OQ-TN-08, ADR-006 + Amendment 1):
|
(OQ-TN-08, ADR-006 + Amendment 1):
|
||||||
|
|
||||||
- The registry runs `AccessControl` before the establisher (the ACL
|
- The registry runs `AccessControl` before the establisher (the ACL
|
||||||
@@ -331,26 +331,26 @@ Tunnels reach local networks — the open gate is the security boundary
|
|||||||
`TUNNEL_FORWARDED_SCOPE` gates ingress (receiving forwarded
|
`TUNNEL_FORWARDED_SCOPE` gates ingress (receiving forwarded
|
||||||
connections) on the opener side. Three scopes, three capabilities.
|
connections) on the opener side. Three scopes, three capabilities.
|
||||||
- The assembly layer MAY add resource-scoped checks via the
|
- The assembly layer MAY add resource-scoped checks via the
|
||||||
establisher (ownership on the resource name) — the protocol
|
establisher (ownership on the resource name); the protocol
|
||||||
provides the seam, not the policy. For the direct op the seam is
|
provides the seam, not the policy. For the direct op the seam is
|
||||||
the parsed `TunnelTarget` (a deployment MAY enforce target
|
the parsed `TunnelTarget` (a deployment MAY enforce target
|
||||||
allowlists in its establisher — policy, not protocol).
|
allowlists in its establisher; policy, not protocol).
|
||||||
- No target allowlists in the PROTOCOL, no tunnel-specific policy
|
- No target allowlists in the PROTOCOL, no tunnel-specific policy
|
||||||
machinery in v1 — the far side owns the resource (OQ-TN-08
|
machinery in v1, as the far side owns the resource (OQ-TN-08
|
||||||
resolution); discovery is the ACL-filtered ops listing.
|
resolution); discovery is the ACL-filtered ops listing.
|
||||||
- **Hub re-produce policy (the security model sentence, ADR-006
|
- **Hub re-produce policy (the security model sentence, ADR-006
|
||||||
Amendment 1):** a hub re-producing the direct op applies ITS OWN
|
Amendment 1):** a hub re-producing the direct op applies ITS OWN
|
||||||
target policy at the re-produce hop — the hub's ACL governs the
|
target policy at the re-produce hop; the hub's ACL governs the
|
||||||
final dial. The hub is the network boundary.
|
final dial. The hub is the network boundary.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- **OQ-TN-11**: resource naming collision domain + lifecycle
|
- **OQ-TN-11**: resource naming collision domain + lifecycle
|
||||||
(partially resolved — the per-producer registry is the leading
|
(partially resolved; the per-producer registry is the leading
|
||||||
candidate)
|
candidate)
|
||||||
- **OQ-TN-14**: resolved (split: unix in, stdio out — alktty's pipe
|
- **OQ-TN-14**: resolved (split: unix in, stdio out, with alktty's pipe
|
||||||
mode owns process stdio; see open-questions.md)
|
mode owning process stdio; see open-questions.md)
|
||||||
- **OQ-TN-15/16**: resolved (ADR-007/008 — the direct op, listen
|
- **OQ-TN-15/16**: resolved (ADR-007/008: the direct op, listen
|
||||||
metadata, the forwarded op)
|
metadata, the forwarded op)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -364,7 +364,7 @@ Tunnels reach local networks — the open gate is the security boundary
|
|||||||
- alkcall ADR-047/049 (open ops + establishment), ADR-050 (`pump_bidi`),
|
- alkcall ADR-047/049 (open ops + establishment), ADR-050 (`pump_bidi`),
|
||||||
ledger CF-005/CF-006 (identity precedence + per-call opener)
|
ledger CF-005/CF-006 (identity precedence + per-call opener)
|
||||||
- Upstream asks (review 008): `alkcall/docs/reviews/
|
- Upstream asks (review 008): `alkcall/docs/reviews/
|
||||||
008-tunnels-graduation-upstream-review.md` — both landed in
|
008-tunnels-graduation-upstream-review.md`; both landed in
|
||||||
alkcall 0.8.0 (flavor-form discovery derivation, ADR-047
|
alkcall 0.8.0 (flavor-form discovery derivation, ADR-047
|
||||||
amendment 3; the establisher → reply `bound` projection, ADR-049
|
amendment 3; the establisher → reply `bound` projection, ADR-049
|
||||||
amendment 3), plus the in-tree `ChannelRelay`/`HubLegTemplate`
|
amendment 3), plus the in-tree `ChannelRelay`/`HubLegTemplate`
|
||||||
|
|||||||
+58
-58
@@ -3,12 +3,12 @@ status: draft
|
|||||||
last_updated: 2026-09-20 (review 002 D-1/D-2 sync: the params schemas' port bounds, `additionalProperties: false` gates, `listen` `minimum: 1`, and the registry-side gate ordering recorded; ADR-007's oneOf wording corrected to the shipped allOf/if-then)
|
last_updated: 2026-09-20 (review 002 D-1/D-2 sync: the params schemas' port bounds, `additionalProperties: false` gates, `listen` `minimum: 1`, and the registry-side gate ordering recorded; ADR-007's oneOf wording corrected to the shipped allOf/if-then)
|
||||||
---
|
---
|
||||||
|
|
||||||
# alktunnels — Wire Format
|
# alktunnels: Wire Format
|
||||||
|
|
||||||
The `alk/tunnel` wire surface. Two layers: the **open op** (JSON
|
The `alk/tunnel` wire surface. Two layers: the **open op** (JSON
|
||||||
params on the channels call plane — structured, schema-validated,
|
params on the channels call plane: structured, schema-validated,
|
||||||
typed errors) and the **data plane** (bytes/datagrams inside the
|
typed errors) and the **data plane** (bytes/datagrams inside the
|
||||||
channel `BiStream` — the codec, ADR-003). The channels layer strips
|
channel `BiStream`, the codec, ADR-003). The channels layer strips
|
||||||
its own 8-byte header transparently (alknet ADR-093); the tunnel
|
its own 8-byte header transparently (alknet ADR-093); the tunnel
|
||||||
protocol owns everything inside the `BiStream`.
|
protocol owns everything inside the `BiStream`.
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ this document is the normative WHAT.
|
|||||||
|
|
||||||
`Sub`-typed operation (alkcall ADR-047): the reply carries
|
`Sub`-typed operation (alkcall ADR-047): the reply carries
|
||||||
`{ "channel_id": <u32> }` once; the data plane flows on the channel's
|
`{ "channel_id": <u32> }` once; the data plane flows on the channel's
|
||||||
`BiStream` — never in the call response stream.
|
`BiStream`, never in the call response stream.
|
||||||
|
|
||||||
### Input (params)
|
### Input (params)
|
||||||
|
|
||||||
@@ -32,33 +32,33 @@ this document is the normative WHAT.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `resource`: string, required — the producer's stable resource name
|
- `resource`: string, required: the producer's stable resource name
|
||||||
(NOT an address; the producer's registry maps it to its backing).
|
(NOT an address; the producer's registry maps it to its backing).
|
||||||
- `substrate`: string, required, enum `["tcp", "udp", "unix"]` — the
|
- `substrate`: string, required, enum `["tcp", "udp", "unix"]`: the
|
||||||
extensible discriminator (ADR-001); selects the data-plane framing.
|
extensible discriminator (ADR-001); selects the data-plane framing.
|
||||||
The schema ships with all three values in v1 (unix has an
|
The schema ships with all three values in v1 (unix has an
|
||||||
implementation path — OQ-TN-14). An older producer rejecting a
|
implementation path, OQ-TN-14). An older producer rejecting a
|
||||||
newer substrate value is the SSH "unknown channel type" posture —
|
newer substrate value is the SSH "unknown channel type" posture:
|
||||||
loud, not silent.
|
loud, not silent.
|
||||||
- `input_schema` pins both as required strings; unknown substrate
|
- `input_schema` pins both as required strings; unknown substrate
|
||||||
values fail schema validation (typed error — loud, not silent).
|
values fail schema validation (typed error, loud, not silent).
|
||||||
- Extensibility: a new substrate is a new enum value, not a format
|
- Extensibility: a new substrate is a new enum value, not a format
|
||||||
change. An older producer rejecting a newer substrate is the SSH
|
change. An older producer rejecting a newer substrate is the SSH
|
||||||
"unknown channel type" posture.
|
"unknown channel type" posture.
|
||||||
|
|
||||||
### Reply / errors
|
### Reply / errors
|
||||||
|
|
||||||
- Success: `{ "channel_id": <u32> }` — the channel is open, the
|
- Success: `{ "channel_id": <u32> }`: the channel is open, the
|
||||||
establishment phase (dial) succeeded, the pump handler is running.
|
establishment phase (dial) succeeded, the pump handler is running.
|
||||||
Bind-first listen opens MAY add `"bound"` (§The Listen Open Op's
|
Bind-first listen opens MAY add `"bound"` (§The Listen Open Op's
|
||||||
Reply below).
|
Reply below).
|
||||||
- Failure: a typed `channel:open_failed` call error with
|
- Failure: a typed `channel:open_failed` call error with
|
||||||
`details: { reason, message }` — `reason ∈ dial_failed` /
|
`details: { reason, message }` with `reason ∈ dial_failed` /
|
||||||
`unknown_resource` / `resource_shortage` / `handler_error` /
|
`unknown_resource` / `resource_shortage` / `handler_error` /
|
||||||
`timeout` (alkcall ADR-049 §3). The registry's ACL gate runs before
|
`timeout` (alkcall ADR-049 §3). The registry's ACL gate runs before
|
||||||
the wrapper (`FORBIDDEN` for a scope-less identity); the 256-channel
|
the wrapper (`FORBIDDEN` for a scope-less identity); the 256-channel
|
||||||
cap is `channel:too_many_channels`. **A failed open never returns a
|
cap is `channel:too_many_channels`. **A failed open never returns a
|
||||||
`channel_id`** — no phantom channel (the SSH contract, POC-verified
|
`channel_id`**; no phantom channel (the SSH contract, POC-verified
|
||||||
from the consumer side).
|
from the consumer side).
|
||||||
- The scope gate: the open op's `AccessControl.required_scopes` is
|
- The scope gate: the open op's `AccessControl.required_scopes` is
|
||||||
`["tunnel:open"]` (the `TUNNEL_OPEN_SCOPE` constant). Caller
|
`["tunnel:open"]` (the `TUNNEL_OPEN_SCOPE` constant). Caller
|
||||||
@@ -69,10 +69,10 @@ this document is the normative WHAT.
|
|||||||
|
|
||||||
## The Direct Op (`channels/tunnel/direct`)
|
## The Direct Op (`channels/tunnel/direct`)
|
||||||
|
|
||||||
Dynamic-target egress (ADR-007) — "dial what I name," not "dial what
|
Dynamic-target egress (ADR-007): "dial what I name," not "dial what
|
||||||
the producer registered." `Sub`-typed, ALPN marker `alk/tunnel`; the
|
the producer registered." `Sub`-typed, ALPN marker `alk/tunnel`; the
|
||||||
same establishment phase and wrapper as `…/sub`. Scope gate:
|
same establishment phase and wrapper as `…/sub`. Scope gate:
|
||||||
`["tunnel:direct"]` (`TUNNEL_DIRECT_SCOPE`) — **never implied by
|
`["tunnel:direct"]` (`TUNNEL_DIRECT_SCOPE`), **never implied by
|
||||||
`tunnel:open`** (ADR-006 Amendment 1).
|
`tunnel:open`** (ADR-006 Amendment 1).
|
||||||
|
|
||||||
### Input (params)
|
### Input (params)
|
||||||
@@ -84,42 +84,42 @@ same establishment phase and wrapper as `…/sub`. Scope gate:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `substrate`: string, required, enum `["tcp", "udp", "unix"]` — the
|
- `substrate`: string, required, enum `["tcp", "udp", "unix"]`: the
|
||||||
same discriminator semantics as `…/sub` (ADR-001); selects the
|
same discriminator semantics as `…/sub` (ADR-001); selects the
|
||||||
data-plane framing.
|
data-plane framing.
|
||||||
- `target`: object, required, keyed by the sibling `substrate` —
|
- `target`: object, required, keyed by the sibling `substrate`:
|
||||||
- `tcp`/`udp` → `{ "host": <string>, "port": <u16> }` (domain-form
|
- `tcp`/`udp` → `{ "host": <string>, "port": <u16> }` (domain-form
|
||||||
hosts stay unresolved on the wire — producer-side resolution, the
|
hosts stay unresolved on the wire; producer-side resolution, the
|
||||||
ssh `-D` semantic);
|
ssh `-D` semantic);
|
||||||
- `unix` → `{ "path": <string> }`.
|
- `unix` → `{ "path": <string> }`.
|
||||||
- `input_schema` pins both required; the target sub-schema is
|
- `input_schema` pins both required; the target sub-schema is
|
||||||
per-substrate — an `allOf` of two `if`/`then` branches keyed on the
|
per-substrate: an `allOf` of two `if`/`then` branches keyed on the
|
||||||
sibling `substrate` value, validated **registry-side** (the schema
|
sibling `substrate` value, validated **registry-side** (the schema
|
||||||
gate runs before the establisher — the behavior
|
gate runs before the establisher, as the behavior
|
||||||
`malformed_target_is_schema_class_and_leaves_no_channel` pins; the
|
`malformed_target_is_schema_class_and_leaves_no_channel` pins; the
|
||||||
establisher's serde parse re-checks as the backstop). Malformed
|
establisher's serde parse re-checks as the backstop). Malformed
|
||||||
targets are the `invalid_input`-class failure; **`unknown_resource`
|
targets are the `invalid_input`-class failure; **`unknown_resource`
|
||||||
can never fire on the direct op** (no registry lookup).
|
can never fire on the direct op** (no registry lookup).
|
||||||
- Schema bounds (wire-stable — the target sub-schema is part of the
|
- Schema bounds (wire-stable; the target sub-schema is part of the
|
||||||
one-way door, ADR-007 §"Wire-stable"): each branch's target object
|
one-way door, ADR-007 §"Wire-stable"): each branch's target object
|
||||||
carries `"type": "object"` and `"additionalProperties": false` —
|
carries `"type": "object"` and `"additionalProperties": false`:
|
||||||
- `tcp`/`udp` → `{ "host": {"type": "string"}, "port": {"type":
|
- `tcp`/`udp` → `{ "host": {"type": "string"}, "port": {"type":
|
||||||
"integer", "minimum": 0, "maximum": 65535} }`, both required;
|
"integer", "minimum": 0, "maximum": 65535} }`, both required;
|
||||||
- `unix` → `{ "path": {"type": "string"} }`, required.
|
- `unix` → `{ "path": {"type": "string"} }`, required.
|
||||||
|
|
||||||
The `additionalProperties: false` gate is what makes a cross-shape
|
The `additionalProperties: false` gate is what makes a cross-shape
|
||||||
target (a `unix` open carrying `host`) fail the registry's
|
target (a `unix` open carrying `host`) fail the registry's
|
||||||
`INVALID_INPUT` class *before* the establisher — not a serde-level
|
`INVALID_INPUT` class *before* the establisher, not a serde-level
|
||||||
quirk.
|
quirk.
|
||||||
|
|
||||||
### Reply / errors
|
### Reply / errors
|
||||||
|
|
||||||
- Success: `{ "channel_id": <u32> }` — identical semantics to
|
- Success: `{ "channel_id": <u32> }`: identical semantics to
|
||||||
`…/sub`; no `bound` field (dial establishers do not bind).
|
`…/sub`; no `bound` field (dial establishers do not bind).
|
||||||
- Failure: the same typed surface as `…/sub` minus `unknown_resource`,
|
- Failure: the same typed surface as `…/sub` minus `unknown_resource`,
|
||||||
plus the schema/`invalid_input` class for malformed targets.
|
plus the schema/`invalid_input` class for malformed targets.
|
||||||
|
|
||||||
## The Listen Open Op's Reply — the `bound` and `listen` Fields
|
## The Listen Open Op's Reply: the `bound` and `listen` Fields
|
||||||
|
|
||||||
A bind-first listen open (ADR-008 §1, served by `…/sub` with the
|
A bind-first listen open (ADR-008 §1, served by `…/sub` with the
|
||||||
bind-first establisher) MAY add optional, additive reply fields:
|
bind-first establisher) MAY add optional, additive reply fields:
|
||||||
@@ -133,16 +133,16 @@ bind-first establisher) MAY add optional, additive reply fields:
|
|||||||
(`{host, port}` / `{path}`); the REAL OS-chosen address (port `0`
|
(`{host, port}` / `{path}`); the REAL OS-chosen address (port `0`
|
||||||
resolved).
|
resolved).
|
||||||
- `listen` is the correlation token (ADR-008 Amendment 1): a u32 the
|
- `listen` is the correlation token (ADR-008 Amendment 1): a u32 the
|
||||||
establisher minted, unique per open on that producer — the key the
|
establisher minted, unique per open on that producer; the key the
|
||||||
forwarded op's params carry (§The Forwarded Op below). Riding the
|
forwarded op's params carry (§The Forwarded Op below). Riding the
|
||||||
reply is what makes it relay-transparent: reply fields pass through
|
reply is what makes it relay-transparent: reply fields pass through
|
||||||
hub re-produce untouched (alkcall ADR-051), and the same minted
|
hub re-produce untouched (alkcall ADR-051), and the same minted
|
||||||
token reappears in every hop's reply.
|
token reappears in every hop's reply.
|
||||||
- Present only when the establisher supplies them; absent for dial
|
- Present only when the establisher supplies them; absent for dial
|
||||||
establishers and accept-first listen opens. Old consumers ignore
|
establishers and accept-first listen opens. Old consumers ignore
|
||||||
unknown reply fields (plain JSON) — wire-additive.
|
unknown reply fields (plain JSON), which is wire-additive.
|
||||||
- `bound` is informational surface, not addressing: a consumer MUST
|
- `bound` is informational surface, not addressing: a consumer MUST
|
||||||
NOT treat `bound` as a dial target (the producer owns the backing —
|
NOT treat `bound` as a dial target (the producer owns the backing,
|
||||||
ADR-001). It exists for RFC-1928-reply-class fidelity (BND.ADDR)
|
ADR-001). It exists for RFC-1928-reply-class fidelity (BND.ADDR)
|
||||||
and observability.
|
and observability.
|
||||||
|
|
||||||
@@ -150,17 +150,17 @@ bind-first establisher) MAY add optional, additive reply fields:
|
|||||||
|
|
||||||
Accept-as-open (ADR-008 §3): when a bind-first listener accepts, the
|
Accept-as-open (ADR-008 §3): when a bind-first listener accepts, the
|
||||||
PRODUCER opens a channel back toward the listen-opener side. The
|
PRODUCER opens a channel back toward the listen-opener side. The
|
||||||
producer *calls* the op (the opener SERVES it — the ADR-047 §5
|
producer *calls* the op (the opener SERVES it, per the ADR-047 §5
|
||||||
connection-owner rule: the serving side allocates the `channel_id`,
|
connection-owner rule: the serving side allocates the `channel_id`,
|
||||||
the producer-side initiator adopts). `Sub`-typed, ALPN marker
|
the producer-side initiator adopts). `Sub`-typed, ALPN marker
|
||||||
`alk/tunnel`; the serving side registers establisher-less by base
|
`alk/tunnel`; the serving side registers establisher-less by base
|
||||||
shape (the data plane starts immediately) or — the recommended shape,
|
shape (the data plane starts immediately) or (the recommended shape,
|
||||||
ADR-008 Amendment 2 — with the correlation-gate establisher variant
|
ADR-008 Amendment 2) with the correlation-gate establisher variant
|
||||||
(a stale/missing token then fails the op as a typed
|
(a stale/missing token then fails the op as a typed
|
||||||
`channel:open_failed` with `details.reason = "unknown_resource"`; the
|
`channel:open_failed` with `details.reason = "unknown_resource"`; the
|
||||||
wire vocabulary is unchanged by the amendment).
|
wire vocabulary is unchanged by the amendment).
|
||||||
Scope gate (opener side): `["tunnel:forwarded"]`
|
Scope gate (opener side): `["tunnel:forwarded"]`
|
||||||
(`TUNNEL_FORWARDED_SCOPE`) — ingress, a separate grant from
|
(`TUNNEL_FORWARDED_SCOPE`): ingress, a separate grant from
|
||||||
`tunnel:open`/`tunnel:direct` (ADR-006 Amendment 1).
|
`tunnel:open`/`tunnel:direct` (ADR-006 Amendment 1).
|
||||||
|
|
||||||
### Input (params)
|
### Input (params)
|
||||||
@@ -173,23 +173,23 @@ Scope gate (opener side): `["tunnel:forwarded"]`
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `listen`: integer > 0, required — the correlation token from the
|
- `listen`: integer > 0, required: the correlation token from the
|
||||||
listen open's `listen` reply field (ADR-008 Amendment 1; the
|
listen open's `listen` reply field (ADR-008 Amendment 1; the
|
||||||
producer-minted token, relay-transparent). The opener correlates
|
producer-minted token, relay-transparent). The opener correlates
|
||||||
the accept to its waiting listener session by matching it. The
|
the accept to its waiting listener session by matching it. The
|
||||||
schema pins `"minimum": 1` and the serde type pins it as the
|
schema pins `"minimum": 1` and the serde type pins it as the
|
||||||
backstop (a `listen: 0` on the wire is a malformed open, rejected
|
backstop (a `listen: 0` on the wire is a malformed open, rejected
|
||||||
at parse time — never silently accepted).
|
at parse time, never silently accepted).
|
||||||
- `substrate`: string, required, enum — the listen tunnel's
|
- `substrate`: string, required, enum: the listen tunnel's
|
||||||
substrate; shapes the forwarded channel's data-plane framing.
|
substrate; shapes the forwarded channel's data-plane framing.
|
||||||
- `peer`: object, required — the accepted connection's remote address,
|
- `peer`: object, required: the accepted connection's remote address,
|
||||||
substrate-shaped (`{host, port}` / `{path}`). Reply#2-class
|
substrate-shaped (`{host, port}` / `{path}`). Reply#2-class
|
||||||
fidelity's carrier (ask #2).
|
fidelity's carrier (ask #2).
|
||||||
- Schema bounds (wire-stable, mirroring the direct op's target
|
- Schema bounds (wire-stable, mirroring the direct op's target
|
||||||
sub-schema): the per-substrate `peer` sub-schema is an `allOf` of
|
sub-schema): the per-substrate `peer` sub-schema is an `allOf` of
|
||||||
two `if`/`then` branches keyed on the sibling `substrate`, each
|
two `if`/`then` branches keyed on the sibling `substrate`, each
|
||||||
branch's object carrying `"type": "object"` and
|
branch's object carrying `"type": "object"` and
|
||||||
`"additionalProperties": false` —
|
`"additionalProperties": false`:
|
||||||
- `tcp`/`udp` → `{ "host": {"type": "string"}, "port": {"type":
|
- `tcp`/`udp` → `{ "host": {"type": "string"}, "port": {"type":
|
||||||
"integer", "minimum": 0, "maximum": 65535} }`, both required;
|
"integer", "minimum": 0, "maximum": 65535} }`, both required;
|
||||||
- `unix` → `{ "path": {"type": "string"} }`, required.
|
- `unix` → `{ "path": {"type": "string"} }`, required.
|
||||||
@@ -199,22 +199,22 @@ Scope gate (opener side): `["tunnel:forwarded"]`
|
|||||||
|
|
||||||
### Reply / errors
|
### Reply / errors
|
||||||
|
|
||||||
- Success: `{ "channel_id": <u32> }` — the opener-allocated ID the
|
- Success: `{ "channel_id": <u32> }`: the opener-allocated ID the
|
||||||
producer adopts, then pumps the accepted halves against.
|
producer adopts, then pumps the accepted halves against.
|
||||||
- Failure: a stale/missing correlation token is the typed
|
- Failure: a stale/missing correlation token is the typed
|
||||||
`channel:open_failed` with `details.reason = "unknown_resource"`
|
`channel:open_failed` with `details.reason = "unknown_resource"`
|
||||||
(the establisher variant, ADR-008 Amendment 2 — the recommended
|
(the establisher variant, ADR-008 Amendment 2, which is the
|
||||||
serving shape; the miss never births a channel). An opener without
|
recommended serving shape; the miss never births a channel). An opener without
|
||||||
the scope is `FORBIDDEN`; an old opener that does not serve the op
|
the scope is `FORBIDDEN`; an old opener that does not serve the op
|
||||||
rejects loudly (`NOT_FOUND` — the SSH posture). Other open failures
|
rejects loudly (`NOT_FOUND`: the SSH posture). Other open failures
|
||||||
ride the existing typed vocabulary. Under the base (establisher-
|
ride the existing typed vocabulary. Under the base (establisher-
|
||||||
less) serving shape an in-session miss produces no call error — it
|
less) serving shape an in-session miss produces no call error; it
|
||||||
is the assembly-side post-open decline (the channel reaps at
|
is the assembly-side post-open decline (the channel reaps at
|
||||||
birth, `correlation-miss` at `debug`).
|
birth, `correlation-miss` at `debug`).
|
||||||
|
|
||||||
## The Data Plane
|
## The Data Plane
|
||||||
|
|
||||||
One data stream per direction on one channel — the channel ID is the
|
One data stream per direction on one channel: the channel ID is the
|
||||||
flow key (no conn-id, no sub-demux). Two codecs by substrate
|
flow key (no conn-id, no sub-demux). Two codecs by substrate
|
||||||
(ADR-003):
|
(ADR-003):
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ overhead is the channels 8-byte chunk header. A zero-length read is
|
|||||||
genuinely EOF (stream semantics).
|
genuinely EOF (stream semantics).
|
||||||
|
|
||||||
Both pumps run `alkcall::channels::pump_bidi(channel, peer_read,
|
Both pumps run `alkcall::channels::pump_bidi(channel, peer_read,
|
||||||
peer_write)` — the two-pump contract (alknet ADR-078, pinned upstream
|
peer_write)`, the two-pump contract (alknet ADR-078, pinned upstream
|
||||||
ADR-050): when one direction's source EOFs, the pump shuts the
|
ADR-050): when one direction's source EOFs, the pump shuts the
|
||||||
OPPOSITE sink down (the EOF sentinel crosses the mux; the peer sees a
|
OPPOSITE sink down (the EOF sentinel crosses the mux; the peer sees a
|
||||||
clean half-close), and the helper completes when both pumps finish,
|
clean half-close), and the helper completes when both pumps finish,
|
||||||
@@ -234,15 +234,15 @@ returning `(u64, u64)` copy counts. Copy errors are EOF-shaped
|
|||||||
|
|
||||||
### Datagram substrate (`udp`): mandatory length framing
|
### Datagram substrate (`udp`): mandatory length framing
|
||||||
|
|
||||||
Every datagram rides `[len: u16 BE][datagram]` on BOTH directions —
|
Every datagram rides `[len: u16 BE][datagram]` on BOTH directions;
|
||||||
mandatory for correctness (the F-2 rationale lives in ADR-003; the
|
this is mandatory for correctness (the F-2 rationale lives in ADR-003; the
|
||||||
short form: raw pass-through cannot carry an empty datagram). Under
|
short form: raw pass-through cannot carry an empty datagram). Under
|
||||||
the codec:
|
the codec:
|
||||||
|
|
||||||
- **Framing (`frame_datagram`):** `len: u16 BE` + payload. Datagrams
|
- **Framing (`frame_datagram`):** `len: u16 BE` + payload. Datagrams
|
||||||
> 65535 bytes are rejected at frame time (`Oversize`) — never a
|
> 65535 bytes are rejected at frame time (`Oversize`), never a
|
||||||
wire overflow (the u16 length field would wrap).
|
wire overflow (the u16 length field would wrap).
|
||||||
- **Decoding (`DatagramReader`):** incremental — datagrams split
|
- **Decoding (`DatagramReader`):** incremental; datagrams split
|
||||||
across channel chunks, batch into single chunks, and survive
|
across channel chunks, batch into single chunks, and survive
|
||||||
partial headers. The decoder buffers state across reads; it is the
|
partial headers. The decoder buffers state across reads; it is the
|
||||||
only consumer-visible decode path.
|
only consumer-visible decode path.
|
||||||
@@ -253,7 +253,7 @@ the codec:
|
|||||||
returns `Some(bytes)` per datagram (possibly empty), `None` only on
|
returns `Some(bytes)` per datagram (possibly empty), `None` only on
|
||||||
stream EOF.
|
stream EOF.
|
||||||
- **Chunk-size independence:** datagram boundaries survive chunk
|
- **Chunk-size independence:** datagram boundaries survive chunk
|
||||||
splitting, batching, and any channels-layer re-chunking — verified
|
splitting, batching, and any channels-layer re-chunking, verified
|
||||||
end-to-end at 7-byte chunk splits (forward POC).
|
end-to-end at 7-byte chunk splits (forward POC).
|
||||||
- **MTU discipline:** 1400-byte datagrams (a conservative MTU-safe
|
- **MTU discipline:** 1400-byte datagrams (a conservative MTU-safe
|
||||||
payload, not the theoretical 1472 max) ride the bounded-buffer path;
|
payload, not the theoretical 1472 max) ride the bounded-buffer path;
|
||||||
@@ -262,10 +262,10 @@ the codec:
|
|||||||
- **Truncation:** silent truncation never happens (OQ-TN-13, resolved
|
- **Truncation:** silent truncation never happens (OQ-TN-13, resolved
|
||||||
fail-loud per ADR-003): the `local` adapter recvs on a full
|
fail-loud per ADR-003): the `local` adapter recvs on a full
|
||||||
65535-byte scratch (UDP `try_recv` truncates silently at the buffer
|
65535-byte scratch (UDP `try_recv` truncates silently at the buffer
|
||||||
size — a smaller scratch would corrupt the framing) and a
|
size; a smaller scratch would corrupt the framing) and a
|
||||||
>caller-buffer frame is staged across reads, the length prefix
|
>caller-buffer frame is staged across reads, the length prefix
|
||||||
intact, so the far-side de-framer reassembles the true length. A
|
intact, so the far-side de-framer reassembles the true length. A
|
||||||
wire-level over-bound datagram (>65535 — impossible for a
|
wire-level over-bound datagram (>65535, impossible for a
|
||||||
well-formed peer) is a loud error at the frame path.
|
well-formed peer) is a loud error at the frame path.
|
||||||
|
|
||||||
### Sentinels and lifecycle (both codecs)
|
### Sentinels and lifecycle (both codecs)
|
||||||
@@ -282,10 +282,10 @@ the codec:
|
|||||||
idle-expiry mapping is a producer-registry concern, not wire).
|
idle-expiry mapping is a producer-registry concern, not wire).
|
||||||
- **No establishment frame.** The open op's reply IS the
|
- **No establishment frame.** The open op's reply IS the
|
||||||
establishment result (ADR-049); the pre-ADR-049
|
establishment result (ADR-049); the pre-ADR-049
|
||||||
"JSON ack before data" hunch is superseded — the wire carries zero
|
"JSON ack before data" hunch is superseded; the wire carries zero
|
||||||
tunnel-level control framing in v1.
|
tunnel-level control framing in v1.
|
||||||
- **BAST:** the binary framing (the UDP codec) carries its BAST
|
- **BAST:** the binary framing (the UDP codec) carries its BAST
|
||||||
document — [bast.md](bast.md) (AGENTS.md convention 12; the
|
document in [bast.md](bast.md) (AGENTS.md convention 12; the
|
||||||
stream pass-through has no binary framing to describe).
|
stream pass-through has no binary framing to describe).
|
||||||
|
|
||||||
## Normative byte diagrams
|
## Normative byte diagrams
|
||||||
@@ -307,11 +307,11 @@ UDP substrate, one direction's wire view:
|
|||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
- **OQ-TN-13**: resolved (fail-loud, ADR-003) — see the Truncation
|
- **OQ-TN-13**: resolved (fail-loud, ADR-003); see the Truncation
|
||||||
bullet
|
bullet
|
||||||
- **OQ-TN-02**: resolved — see the ADR-003 mandate
|
- **OQ-TN-02**: resolved; see the ADR-003 mandate
|
||||||
- **OQ-TN-15**: resolved — see ADR-007 (the direct op)
|
- **OQ-TN-15**: resolved; see ADR-007 (the direct op)
|
||||||
- **OQ-TN-16**: resolved — see ADR-008 (the `bound`/`listen` fields +
|
- **OQ-TN-16**: resolved; see ADR-008 (the `bound`/`listen` fields +
|
||||||
the forwarded op)
|
the forwarded op)
|
||||||
|
|
||||||
## References
|
## References
|
||||||
@@ -333,5 +333,5 @@ UDP substrate, one direction's wire view:
|
|||||||
ADR-050 (`pump_bidi`); alknet ADR-071/093 (channels wire)
|
ADR-050 (`pump_bidi`); alknet ADR-071/093 (channels wire)
|
||||||
- `docs/research/tunnels-graduation.md` (the graduation research the
|
- `docs/research/tunnels-graduation.md` (the graduation research the
|
||||||
new ops implement); the upstream asks (review 008) landed in
|
new ops implement); the upstream asks (review 008) landed in
|
||||||
alkcall 0.8.0 — see
|
alkcall 0.8.0; see
|
||||||
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`
|
`alkcall/docs/reviews/008-tunnels-graduation-upstream-review.md`
|
||||||
Reference in new issue
Block a user