docs: Phase 1 architecture spec — ADRs 001..006, spec docs, OQ promotion
The Phase 0 OQ ledger (OQ-TN-01..10) promoted into
docs/architecture/open-questions.md (now OQ-TN-01..14, with four
new Phase 1 residues). Six ADRs and four spec docs, all Draft, all
decision-shaped per the SDD process (ADRs carry the WHY; specs carry
the WHAT and reference by number):
ADRs:
- 001 open-params-layout: params = {resource, substrate} — the
producer's stable name, not an address; wire-stable from the first
consumer; unknown substrates fail loudly at schema time
- 002 alpn-strategy: single alk/tunnel ALPN (option A); substrate in
params selects the framing; prior art (SSH/SOCKS5/udpgw) gives no
reason for the split
- 003 codec-and-udp-framing: raw pass-through (stream) / mandatory
[len: u16 BE] (UDP) — F-2 mandate recorded (the codec is mandatory
for correctness, not cosmetics); len=0 = legal empty datagram;
OQ-TN-13 resolved fail-loud (truncation)
- 004 no-backend-trait: halves functions at the assembly layer;
listen is an establisher shape; hub re-produce deferred(OQ-TN-12);
trait re-evaluated at the alknet ADR-078 convergence threshold
- 005 consumer-session-owns-teardown: TunnelSession with
open/adopt/stream_halves/take_halves/pump_against/close/join/Drop —
the W3 adopter gap closes structurally; Drop is the best-effort
fallback
- 006 access-control-posture: the open gate is the boundary;
TUNNEL_OPEN_SCOPE = tunnel:open (stable once published); op-level
ACL; ownership seam; no allowlists in v1; identity = 0.7.0's
precedence chain
Spec docs:
- overview.md: purpose, resource model, deps (alkcall 0.7.0,
wasm-clean default, local feature), module map
- wire.md: the open op (params/reply/typed errors), the data plane
by substrate, sentinels + half-close, byte diagrams
- producer.md: spec, establisher (dial + listen shapes), pump
handler (pump_bidi inline, R-02), registration API, ACL posture
- consumer.md: TunnelSession (forward open + reverse adopt
construction), data plane, teardown API (close/join/Drop incl.
pump-less join semantics)
- bast.md: the BAST doc for the UDP codec (convention 12's trigger
fired — the framing IS binary beyond pass-through)
- open-questions.md: OQ-TN-01..14; 01..10 promoted (faithful to the
phase-0 ledger's final states), 11 partially resolved (collision
domain = per-producer registry per ADR-001; lifecycle open),
12 deferred(scope), 13 resolved fail-loud (ADR-003), 14 open
Verified by an architecture-reviewer pass (2 criticals, 6 majors,
10 minors — all fixed: ADR-001/003-vs-OQ decision-state contradictions
resolved; the Layer-2 mislabel corrected to the ADR-047 §4 per-session
fork; alktty/alknet ADR misattributions fixed; codec placement pinned
to the establisher (pump stays substrate-agnostic); reverse-path
construction named (TunnelSession::adopt + pump_against); security
posture promoted to ADR-006; BAST doc written; F-2 rationale deduped;
README tables completed; channel_id > 0; MTU wording fixed; ADR-047 §5
attributions corrected; impacts lines added to unresolved OQs; review/
ledger pointer paths added to README references).
Verification: doc set internal cross-refs all resolve; cargo test,
fmt --check, doc --no-deps clean
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-09-07
|
||||
---
|
||||
|
||||
# alktunnels — Open Questions
|
||||
|
||||
Centralized tracker. The Phase 0 OQ ledger (`docs/research/
|
||||
phase-0-findings.md`, OQ-TN-01..10) is promoted here with statuses
|
||||
current as of the Phase 1 spec; new Phase 1 questions continue the
|
||||
OQ-TN numbering. Format per `docs/sdd_process.md`.
|
||||
|
||||
## Theme 1: Wire format and addressing
|
||||
|
||||
### OQ-TN-01: Target addressing format (open-op `params` shape)
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-01)
|
||||
- **Status**: resolved — [ADR-001](decisions/001-open-params-layout.md)
|
||||
- **Priority**: high
|
||||
- **Resolution**: 2026-09-07 (Phase 1). `params` = self-contained
|
||||
JSON object: `{ "resource": <string>, "substrate": <string> }`.
|
||||
The resource identifier is the producer's stable name (a registered
|
||||
resource id, not an address); substrate is the extensible
|
||||
discriminator (`tcp` | `udp` | `unix` extensible, additive). The
|
||||
producer owns the backing; no URL-style general addressing. Reframe
|
||||
+ survey input (ssh-socks5-survey §OQ-TN-01 residue) + both POCs.
|
||||
- **Cross-references**: OQ-TN-02, OQ-TN-03, ADR-001, ADR-002
|
||||
|
||||
### OQ-TN-02: Datagram substrates (UDP) — boundary preservation
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-02)
|
||||
- **Status**: resolved — [ADR-003](decisions/003-codec-and-udp-framing.md)
|
||||
- **Priority**: high
|
||||
- **Resolution**: split by path (2026-09-06 survey) + F-2 mandate
|
||||
(2026-09-07 reverse POC). Base open-op UDP resources:
|
||||
endpoint-at-open (one channel = one flow). Dynamic/`-D` UDP:
|
||||
per-datagram addressing inside the tunnel payload, assembly-layer.
|
||||
Boundary preservation: mandatory `[len: u16 BE]` per-datagram
|
||||
framing — mandatory for correctness, not cosmetics (F-2; the full
|
||||
rationale and the executable pin live in ADR-003). Under the codec,
|
||||
`len=0` is a legal empty datagram (forward POC validated); EOF is
|
||||
the channels-level sentinel; the layers never collide.
|
||||
- **Cross-references**: OQ-TN-01, OQ-TN-07, ADR-003
|
||||
|
||||
### OQ-TN-03: Direction semantics (`-L` / `-R` / dynamic)
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-03)
|
||||
- **Priority**: high
|
||||
- **Status**: resolved — hub-owns-the-connection model (2026-09-05) +
|
||||
the reverse-flow POC (2026-09-07, `reverse-poc-summary.md`).
|
||||
No protocol-level direction: role follows the resource. `-L`/`-R`
|
||||
are the same open op with the entry point on different machines;
|
||||
no advertisement op is needed (the listener is the initiator's own
|
||||
local resource; a far-side listener is a producer-side listen
|
||||
establisher over the same open op). `-D` composes at the assembly
|
||||
layer ("tunnel a socks5 connection"), out of base-crate scope.
|
||||
- **Cross-references**: OQ-TN-04, OQ-TN-08, ADR-004, ADR-005
|
||||
|
||||
### OQ-TN-04: No forced local binding
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-04)
|
||||
- **Priority**: medium
|
||||
- **Status**: resolved — binding is always assembly-layer and
|
||||
optional, on either side; the protocol never binds. API surface:
|
||||
dial flows with no local bind (both POCs), listen flows where the
|
||||
binding is the producer's establisher (listen variant, ADR-004),
|
||||
unbound/abstract flows (in-process pipes — the pump is generic over
|
||||
boxed halves). The concrete produce/consume surface is spec'd in
|
||||
producer.md / consumer.md (ADR-005).
|
||||
- **Cross-references**: OQ-TN-03, ADR-004, ADR-005
|
||||
|
||||
## Theme 2: Crate architecture
|
||||
|
||||
### OQ-TN-05: Backend inversion point — `TunnelBackend` trait?
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-05)
|
||||
- **Status**: resolved — [ADR-004](decisions/004-no-backend-trait.md)
|
||||
- **Priority**: medium
|
||||
- **Resolution**: 2026-09-07 (Phase 1). No trait for substrate access:
|
||||
"produce boxed halves for a resource" is a function (UDP POC,
|
||||
OQ-TN-05 executable input). The only remaining thread — the hub
|
||||
re-produce composition — resolves the same way: a hub proxy is a
|
||||
consumer + producer composed at the assembly layer (the reverse
|
||||
POC's hub is exactly this shape minus re-export); a composition
|
||||
helper is deferred(scope) until a concrete hub-proxy consumer
|
||||
exists (OQ-TN-12).
|
||||
- **Cross-references**: OQ-TN-12, ADR-004
|
||||
|
||||
### OQ-TN-06: The two-pump helper — extract now?
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-06)
|
||||
- **Priority**: medium
|
||||
- **Status**: resolved (upstream) — `alkcall::channels::pump_bidi`
|
||||
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
|
||||
consumes the helper; hand-rolling is out.
|
||||
- **Cross-references**: ADR-005 (consumer pump), ADR-050 (upstream)
|
||||
|
||||
## Theme 3: Protocol surfaces
|
||||
|
||||
### OQ-TN-07: ALPN strategy
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-07)
|
||||
- **Status**: resolved — [ADR-002](decisions/002-alpn-strategy.md)
|
||||
- **Priority**: high
|
||||
- **Resolution**: 2026-09-07 (Phase 1). Option A: single `alk/tunnel`
|
||||
ALPN; the substrate discriminator in `params` selects the framing.
|
||||
Prior art (SSH one channel mechanism; SOCKS5 one control
|
||||
connection; udpgw self-describing framing) gives no reason for the
|
||||
split; option B's consumer-side API bifurcation buys nothing.
|
||||
- **Cross-references**: OQ-TN-01, OQ-TN-02, ADR-001, ADR-002
|
||||
|
||||
### OQ-TN-08: Access control and ownership scope
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-08)
|
||||
- **Status**: resolved — [ADR-006](decisions/006-access-control-posture.md)
|
||||
(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
|
||||
tunnel-specific policy layer, no allowlists in v1; the ownership
|
||||
seam is the establisher seeing the per-call opener identity).
|
||||
Discovery = the ACL-filtered ops listing (openable channels are
|
||||
operations, ADR-047); `OperationSpec.description` round-trips
|
||||
through discovery (review 006 E-02). The live resource-enumeration
|
||||
half (alkcall OQ-40) stays deferred upstream. Identity: 0.7.0's
|
||||
precedence chain (token > `ServingConfig.identity` > transport),
|
||||
per-call opener identity on the open-op hooks (CF-005/CF-006).
|
||||
The `tunnel:open` scope constant is stable once published (ADR-006
|
||||
§2). `-D` = "tunnel a socks5 connection" (target selection in the
|
||||
socks5 protocol).
|
||||
- **Cross-references**: OQ-TN-03, ADR-006, producer.md (scope gating)
|
||||
|
||||
### OQ-TN-09: Lifecycle, teardown, and error reporting
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-09)
|
||||
- **Priority**: high
|
||||
- **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;
|
||||
dial failure is a typed `channel:open_failed` call error (reason ∈
|
||||
`dial_failed` / `unknown_resource` / `resource_shortage` /
|
||||
`handler_error` / `timeout`), never a phantom channel. Residual
|
||||
resolved by the POCs: half-open semantics = the two-pump contract
|
||||
(W4 validated); the consumer session owns teardown (W3, ADR-005);
|
||||
v1 needs no mid-stream control frame (pump-phase failures are
|
||||
EOF-shaped by design, ADR-049 §6).
|
||||
- **Cross-references**: ADR-003 (sentinels), ADR-005, alkcall ADR-049
|
||||
|
||||
### OQ-TN-10: POC scope (Phase 0 closure)
|
||||
|
||||
- **Origin**: [phase-0-findings.md](../research/phase-0-findings.md) (OQ-TN-10)
|
||||
- **Priority**: low
|
||||
- **Status**: resolved — UDP POC DONE (2026-09-06, 17 tests),
|
||||
reverse-flow POC DONE (2026-09-07, 14 tests; re-validated over
|
||||
alkcall 0.7.0, 16 tests). #3 (unix/stdio) folds into the real
|
||||
crate's test suite (pump agnosticism confirmed twice); #4 (helper
|
||||
spike) resolved by alkcall ADR-050. Findings F-1 (plan payloads
|
||||
`Send + Sync` — documented upstream) and F-2 (UDP codec mandatory)
|
||||
recorded; W1 filed upstream and RESOLVED (CF-005/CF-006).
|
||||
- **Cross-references**: ADR-003, OQ-TN-05, OQ-TN-06
|
||||
|
||||
## Theme 4: Phase 1 residues (new)
|
||||
|
||||
### OQ-TN-11: Resource naming — collision domain and lifecycle of a resource id
|
||||
|
||||
- **Origin**: OQ-TN-01's residue (stable resource name vs target
|
||||
address), carried into Phase 1
|
||||
- **Status**: partially resolved — the collision-domain half is
|
||||
**resolved by [ADR-001](decisions/001-open-params-layout.md)**: the
|
||||
domain is the producer's registry (per-producer; two producers on
|
||||
the same connection may expose the same resource name independently
|
||||
— the open op resolves against the SERVING side's registry). The
|
||||
lifecycle half stays open: is a resource registry a producer-side
|
||||
assembly construct (the POCs' `HashMap` — the leading candidate),
|
||||
or does it need protocol-level register/rename/retire ops?
|
||||
- **Priority**: medium
|
||||
- **Impacts**: producer.md's `ResourceRegistry` shape and the
|
||||
discovery story (OQ-TN-08); a protocol-level registry op would be a
|
||||
new wire surface (one-way door).
|
||||
- **Resolution**: lifecycle half pending — no prior-art pressure
|
||||
(SSH lists no forwards in-band; discovery is the ops listing);
|
||||
decide when a consumer needs shared/lifecycle-managed resources.
|
||||
- **Cross-references**: ADR-001, OQ-TN-08
|
||||
|
||||
### OQ-TN-12: Hub re-produce composition helper
|
||||
|
||||
- **Origin**: OQ-TN-05's remaining thread (2026-09-05 hub model)
|
||||
- **Status**: deferred(scope)
|
||||
- **Priority**: low
|
||||
- **Blocked on**: a concrete hub-proxy consumer (a crate that
|
||||
re-produces a consumed tunnel resource) — the reverse POC's hub is
|
||||
the seed shape (consumer + `register_openable` on the same
|
||||
connection) but does not exercise re-export; if one materializes,
|
||||
decide whether a composition helper belongs in this crate or is
|
||||
assembly-layer wiring of the existing surface.
|
||||
- **Cross-references**: OQ-TN-05, ADR-004, alkcall ADR-042 (relay)
|
||||
|
||||
### OQ-TN-13: UDP truncation semantics at the codec boundary
|
||||
|
||||
- **Origin**: forward POC Issues Surfaced #3
|
||||
- **Status**: resolved — [ADR-003](decisions/003-codec-and-udp-framing.md)
|
||||
(2026-09-07): **fail-loud at the adapter level.** A receive buffer
|
||||
smaller than the datagram is an error, never a silent truncation —
|
||||
a truncated datagram would corrupt the length-framed stream's
|
||||
framing invariants (the reverse POC's F-2 rationale: the codec's
|
||||
wire discipline is what keeps empty datagrams unambiguous; silent
|
||||
truncation is the same class of invariant break). The codec's MTU
|
||||
bound (65535) makes truncation unreachable for well-formed peers;
|
||||
failing loud turns a spec violation into a typed error.
|
||||
- **Priority**: medium
|
||||
- **Impacts**: the `local` feature's UDP adapter and
|
||||
`TunnelSession::recv_datagram`'s error surface; consumer-visible
|
||||
(an error vs silent truncation is a behavioral difference), so the
|
||||
decision rides an Accepted ADR before the first consumer.
|
||||
- **Cross-references**: ADR-003, wire.md
|
||||
|
||||
### OQ-TN-14: Unix-socket and stdio substrate placement
|
||||
|
||||
- **Origin**: OQ-TN-10 #3 (the cheap un-POC'd item)
|
||||
- **Status**: open
|
||||
- **Priority**: low
|
||||
- **Impacts**: the `local` feature's module surface (whether unix
|
||||
dial/listen helpers ship in v1); not a wire/ADR question (the wire
|
||||
enum already carries `unix` per ADR-001).
|
||||
- **Resolution**: pending — both POCs confirm pump agnosticism; unix
|
||||
is cheap (same halves shape as TCP); stdio bridging is a different
|
||||
lifecycle (no dial — the pipe IS the resource). Fold into the
|
||||
backend module task; unix leans yes for v1, stdio leans deferred.
|
||||
- **Cross-references**: ADR-003, ADR-004, overview.md
|
||||
Reference in New Issue
Block a user