tasks: Phase 2 decomposition — 12-task dependency graph for v1
tasks/architecture/: - oq-promotion-sync (planning): back-pointers from the phase-0 ledger + AGENTS.md to the promoted OQ tracker (the convergence checklist's final half) - oq-tn-14-tracker: the Safe-Exit external-trigger tracker task for OQ-TN-14 (unix/stdio placement; [external-trigger, deferred-oq], risk trivial, level research per the two-halves rule) tasks/tunnels/ (the implementation graph, 8 generations): - crate-init: module skeleton per overview.md's module map - params: TunnelParams + open-op spec (ADR-001 wire-stable surface) - wire-codec: frame_datagram/DatagramReader + the 8 POC-pinned test families (ADR-003) - producer-open-op: establisher (dial, plan flow R-01) + pump handler (pump_bidi inline R-02) + registration; POC-ported integration tests - consumer-session: TunnelSession (open/adopt, data planes, teardown matrix — ADR-005); generalizes the reverse POC's ReverseTunnel - producer-listen: the listen establisher + AcceptQueue contract (ADR-004 shape 2) - local-socket-halves: the local feature (TCP/UDP/unix halves functions; truncation fail-loud per OQ-TN-13; unix ships per OQ-TN-14's lean-yes, stdio deferred) - review-core-crates: review-injection point before the downstream tasks build on the high-risk producer/consumer shapes - end-to-end-suite: 6 suites / >=20 tests consolidating both POC suites against the public API (the spec's executable form) - review-impl: the phase-gate review (wire/API/conventions/docs sync; findings doc per the alkhttp/alkcall house pattern) Graph verified with taskgraph: 12 tasks valid, no cycles, 8 generations; critical path = oq-promotion-sync -> crate-init -> params -> wire-codec -> producer-open-op -> consumer-session -> review-core-crates -> review-impl; risk concentrated in the two session tasks (both POC-validated); parallel groups available at generations 1 and 6
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
---
|
||||||
|
id: architecture/oq-promotion-sync
|
||||||
|
name: Sync AGENTS.md + phase-0 ledger pointers to the promoted OQ ledger (planning)
|
||||||
|
status: pending
|
||||||
|
depends_on: []
|
||||||
|
scope: single
|
||||||
|
risk: trivial
|
||||||
|
impact: project
|
||||||
|
level: planning
|
||||||
|
tags: [architecture, phase-1, oq]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Phase 1 promoted the Phase 0 OQ ledger into `docs/architecture/open-questions.md`
|
||||||
|
(OQ-TN-01..14) and split OQ-TN-11/13 into resolved halves (ADR-001/ADR-003).
|
||||||
|
The older ledgers still describe pre-Phase-1 states in two places:
|
||||||
|
|
||||||
|
1. `docs/research/phase-0-findings.md` — the OQ sections (OQ-TN-01..10) should
|
||||||
|
carry one-line "promoted to `docs/architecture/open-questions.md` as OQ-TN-NN"
|
||||||
|
pointers so a reader landing in the research doc finds the live tracker (the
|
||||||
|
convergence checklist's last unchecked item says "promoted ... with statuses" —
|
||||||
|
the promotion exists; the back-pointer is the missing half).
|
||||||
|
2. `AGENTS.md` — the "Architecture Context" section says `docs/architecture/`
|
||||||
|
"will hold the authoritative spec (Phase 1 of the SDD process)... It does not
|
||||||
|
exist yet". That is now stale: the spec exists (ADR-001..006 + 4 spec docs +
|
||||||
|
BAST + OQ tracker). Update the paragraph to point at the real directory and
|
||||||
|
adjust the "read it before non-trivial changes once it exists" line.
|
||||||
|
|
||||||
|
## Work
|
||||||
|
|
||||||
|
- phase-0-findings.md: add a promotion note at the top of the Open Questions
|
||||||
|
section (and one line under the convergence checklist's final item) pointing
|
||||||
|
at `docs/architecture/open-questions.md`; mark the checklist item done.
|
||||||
|
- AGENTS.md: rewrite the `docs/architecture/` bullet to the present tense
|
||||||
|
(spec exists; read it before non-trivial changes; ADRs 001..006 are
|
||||||
|
Accepted; the OQ tracker is authoritative for open questions).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- [ ] phase-0-findings.md has the promotion pointer; the checklist's final
|
||||||
|
item is checked
|
||||||
|
- [ ] AGENTS.md architecture-context paragraph reflects the existing spec
|
||||||
|
- [ ] No other stale "does not exist yet" language anywhere in AGENTS.md or
|
||||||
|
docs/
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Any change to the OQ statuses themselves (that is the tracker's job now)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/open-questions.md (the promoted ledger)
|
||||||
|
- docs/sdd_process.md §Safe Exit (the two-halves rule for deferrals)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
id: architecture/oq-tn-14-tracker
|
||||||
|
name: "[external-trigger, deferred-oq] OQ-TN-14 unix/stdio placement tracker"
|
||||||
|
status: pending
|
||||||
|
depends_on: []
|
||||||
|
scope: single
|
||||||
|
risk: trivial
|
||||||
|
impact: isolated
|
||||||
|
level: research
|
||||||
|
tags: [architecture, deferred-oq, external-trigger, unix, stdio]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
External-trigger tracker for OQ-TN-14 (`docs/architecture/open-questions.md`
|
||||||
|
— unix-socket and stdio substrate placement, currently `open` leaning
|
||||||
|
unix-yes-v1). This task is NOT actionable implementation work; it tracks
|
||||||
|
whether the unblocking condition has arrived. If the condition arrives, this
|
||||||
|
task flips the OQ (and the implementation task `tunnels/local-unix-halves`
|
||||||
|
below) from its deferred/open posture to actionable.
|
||||||
|
|
||||||
|
Unblocking conditions (any one):
|
||||||
|
|
||||||
|
1. A consumer needs unix-socket tunneling through alktunnels (a hub exposing
|
||||||
|
a docker container's unix socket, or a local daemon bridge).
|
||||||
|
2. The `tunnels/crate-init` task lands and the `local` feature module needs
|
||||||
|
its v1 surface finalized (the decision point baked into that task).
|
||||||
|
|
||||||
|
If neither arrives by the end of the v1 implementation phase, the OQ stays
|
||||||
|
open and `local` ships TCP+UDP only (the lean posture ADR-004 endorses).
|
||||||
|
|
||||||
|
## Work
|
||||||
|
|
||||||
|
- Re-read OQ-TN-14 + `docs/architecture/overview.md` §Feature Gates when
|
||||||
|
triaging; confirm whether the unix helper ships with `local` v1.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- [ ] Tracked (this file existing and being reviewed at triage time IS the
|
||||||
|
tracking mechanism — no code or doc change is expected until the
|
||||||
|
condition arrives)
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- Implementing the unix/stdio helpers (see `tunnels/local-unix-halves`)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/open-questions.md OQ-TN-14
|
||||||
|
- docs/sdd_process.md §Deferred OQs and the blocker-task half
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/consumer-session
|
||||||
|
name: Consumer half — TunnelSession (open/adopt, data planes, teardown)
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/params, tunnels/wire-codec, tunnels/producer-open-op]
|
||||||
|
scope: broad
|
||||||
|
risk: high
|
||||||
|
impact: phase
|
||||||
|
level: implementation
|
||||||
|
tags: [consumer, session, teardown]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `src/consumer.rs` per consumer.md + ADR-005: `TunnelSession` —
|
||||||
|
the typed client, both construction paths, the substrate-shaped data
|
||||||
|
plane, and teardown ownership. The reverse POC's consumer
|
||||||
|
(`/workspace/alktunnels-reverse-poc/src/consumer.rs` — `ReverseTunnel`)
|
||||||
|
is the seed; this task generalizes it to the spec's session type.
|
||||||
|
|
||||||
|
### Construction
|
||||||
|
|
||||||
|
- **Forward path:** `TunnelSession::open(client:
|
||||||
|
&ChannelClient, params: TunnelParams) -> Result<Self,
|
||||||
|
TunnelOpenError>` — `client.open_channel(OP_TUNNEL_OPEN, params,
|
||||||
|
TUNNEL_ALPN)` → adopt → split the channel `BiStream` → data plane by
|
||||||
|
substrate. Typed errors (ADR-049 §4; never a phantom session).
|
||||||
|
- **Reverse path:** `TunnelSession::adopt(manager: &ChannelManager,
|
||||||
|
channel_id: u32, alpn: impl Into<String>) -> Result<Self,
|
||||||
|
TunnelOpenError>` — adopt a worker-allocated ID (early arrivals
|
||||||
|
parked), install the data plane from the adopted halves.
|
||||||
|
`open_reverse_channel(hub_call, params, auth_token) -> Result<u32,
|
||||||
|
ReverseOpenError>` as the free function the assembly layer calls
|
||||||
|
before `adopt` (the POC's shape — the hub's call surface is a
|
||||||
|
`CallConnection`, not a `ChannelClient`, so the two-step is the
|
||||||
|
honest API).
|
||||||
|
|
||||||
|
### Data plane
|
||||||
|
|
||||||
|
- **Stream variant:** `stream_halves()` →
|
||||||
|
`(&mut dyn AsyncRead, &mut dyn AsyncWrite)` (borrowed access);
|
||||||
|
`take_halves(self)` → owned boxed halves (the session's halves ARE
|
||||||
|
the tunnel — raw pass-through).
|
||||||
|
- **Datagram variant:** `send_datagram(&[u8]) -> Result<(),
|
||||||
|
TunnelIoError>` (frame → write → flush; `Oversize` >65535);
|
||||||
|
`recv_datagram() -> Result<Option<Bytes>, TunnelIoError>` —
|
||||||
|
`Some(bytes)` per datagram (possibly empty, `len=0` legal), `None`
|
||||||
|
only on stream EOF (the F-2 layering; the POC's
|
||||||
|
`read_one_datagram` incremental loop).
|
||||||
|
- Wrong-substrate operations are `WrongSubstrate` errors (the POC's
|
||||||
|
shape).
|
||||||
|
|
||||||
|
### Pump ownership + teardown (ADR-005 — the point)
|
||||||
|
|
||||||
|
- `pump_against(self, accepted: impl AsyncRead + AsyncWrite + ...)` —
|
||||||
|
for the reverse path: spawn `pump_bidi(channel_bistream,
|
||||||
|
accepted_read, accepted_write)`, hold the returned handle. Returns
|
||||||
|
the session (builder-style) or takes self and returns the handle —
|
||||||
|
pick the shape that makes holding easy; document it.
|
||||||
|
- `close(self) -> bool` — abort the pump (if session-owned) +
|
||||||
|
`teardown_channel` (ungraceful path).
|
||||||
|
- `join(self) -> (u64, u64, bool)` — await pump completion, then reap;
|
||||||
|
copy counts for observability. **Pump-less sessions** (after
|
||||||
|
`take_halves`): completes immediately, reaps only, `(0, 0, reaped)`
|
||||||
|
(consumer.md's pinned semantics).
|
||||||
|
- `Drop` — abort + sync `teardown_channel` (best-effort; never leak
|
||||||
|
the entry). No `Clone`.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Extend the producer task's integration suite: forward open (session
|
||||||
|
halves drive a duplex), reverse open+adopt+pump_against (the POC's
|
||||||
|
`ReverseTunnel` tests: round-trip, half-close W4, join_and_reap copy
|
||||||
|
counts, out-of-band close + self-reaping, pump-less join), datagram
|
||||||
|
variant (round-trip incl. empty datagram via the codec), teardown
|
||||||
|
matrix (close/join/Drop paths — no leaked channel entries asserted via
|
||||||
|
`channel_ids()`).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `open` + `adopt` construction both present; typed error surfaces
|
||||||
|
exact
|
||||||
|
- [ ] `stream_halves`/`take_halves`/`send_datagram`/`recv_datagram`
|
||||||
|
per consumer.md
|
||||||
|
- [ ] Teardown matrix: close (ungraceful), join (graceful + copy
|
||||||
|
counts), pump-less join `(0, 0, reaped)`, Drop (no leak)
|
||||||
|
- [ ] Half-close semantics test (W4's shape) passes
|
||||||
|
- [ ] No `Clone` on `TunnelSession` (compile-asserted)
|
||||||
|
- [ ] Clippy/fmt clean; wasm32 check passes; `cargo test` green
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/consumer.md (the normative API)
|
||||||
|
- docs/architecture/decisions/005-consumer-session-owns-teardown.md
|
||||||
|
- POC reference: `/workspace/alktunnels-reverse-poc/src/consumer.rs`
|
||||||
|
(the seed shape to generalize)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/crate-init
|
||||||
|
name: Initialize the alktunnels module skeleton (params, wire, producer, consumer, error)
|
||||||
|
status: pending
|
||||||
|
depends_on: [architecture/oq-promotion-sync]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: project
|
||||||
|
level: implementation
|
||||||
|
tags: [scaffold, crate-init]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Convert the alktunnels scaffold crate into the real module skeleton per
|
||||||
|
`docs/architecture/overview.md` §Module Map. The crate already exists
|
||||||
|
(Cargo.toml with alkcall 0.7.0, the wasm-clean tokio subset, licenses,
|
||||||
|
`lib.rs` stub); this task adds the module structure the subsequent tasks
|
||||||
|
fill in.
|
||||||
|
|
||||||
|
### Module skeleton
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/lib.rs
|
||||||
|
//! alktunnels — arbitrary bidirectional tunnels over alkcall channels.
|
||||||
|
//! (crate docs per overview.md §What — the two-paragraph shape)
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod params;
|
||||||
|
pub mod wire;
|
||||||
|
|
||||||
|
pub mod producer;
|
||||||
|
pub mod consumer;
|
||||||
|
|
||||||
|
// Public API surface: re-exports filled in by subsequent tasks (convention 16).
|
||||||
|
```
|
||||||
|
|
||||||
|
- `src/error.rs` — `TunnelError` (thiserror; convention 2) + the typed
|
||||||
|
open-error surface re-exported from alkcall (`ChannelOpenError`,
|
||||||
|
`establishment_reason`-shaped helper). Skeleton only; filled by
|
||||||
|
`tunnels/params` and `tunnels/consumer-session`.
|
||||||
|
- `src/params.rs` — `TunnelParams {resource, substrate}` +
|
||||||
|
`Substrate` enum (`tcp | udp | unix`) + `tunnel_open_spec()` builder +
|
||||||
|
`TUNNEL_OPEN_SCOPE`/`OP_TUNNEL_OPEN`/`TUNNEL_ALPN` constants. Skeleton
|
||||||
|
with type definitions; the schema builder + tests land in
|
||||||
|
`tunnels/params`.
|
||||||
|
- `src/wire.rs` — the codec module doc (ADR-003 summary + bast.md
|
||||||
|
pointer); skeleton for `frame_datagram`/`DatagramReader`/
|
||||||
|
`DatagramCodecError`, filled by `tunnels/wire-codec`.
|
||||||
|
- `src/producer.rs` — module doc (producer.md summary); skeleton for
|
||||||
|
`tunnel_open_spec` wiring, `register_tunnel_openable`, the establisher
|
||||||
|
shape, the pump handler — filled by `tunnels/producer-open-op` +
|
||||||
|
`tunnels/producer-listen`.
|
||||||
|
- `src/consumer.rs` — module doc (consumer.md summary); skeleton for
|
||||||
|
`TunnelSession` — filled by `tunnels/consumer-session`.
|
||||||
|
|
||||||
|
### Keep in place / verify
|
||||||
|
|
||||||
|
- Cargo.toml: alkcall 0.7.0 pin, the wasm-clean tokio subset, the empty
|
||||||
|
`[features]` table (the `local` feature is added by `tunnels/local-socket-halves`).
|
||||||
|
- `bytes`, `serde`, `serde_json`, `thiserror`, `tracing`, `futures` stay;
|
||||||
|
drop `async-trait` IF the skeleton confirms no trait is needed (ADR-004 —
|
||||||
|
it likely is not; note the decision in Summary if dropped).
|
||||||
|
- The default crate stays wasm-clean: `cargo check --target
|
||||||
|
wasm32-unknown-unknown` must pass at every task in this graph
|
||||||
|
(convention 4; the structural guard).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `src/lib.rs` declares `error`, `params`, `wire`, `producer`,
|
||||||
|
`consumer` with doc comments; public API re-exports listed (empty
|
||||||
|
bodies fine)
|
||||||
|
- [ ] Every skeleton module compiles (`cargo check` clean)
|
||||||
|
- [ ] `cargo clippy --all-targets -- -D warnings` clean
|
||||||
|
- [ ] `cargo fmt --check` clean
|
||||||
|
- [ ] `cargo check --target wasm32-unknown-unknown` passes
|
||||||
|
- [ ] No comments in code beyond doc comments (convention 1)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/overview.md (module map, feature gates)
|
||||||
|
- docs/architecture/decisions/004-no-backend-trait.md (module placement)
|
||||||
|
- AGENTS.md conventions 1/2/4/15/16
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/end-to-end-suite
|
||||||
|
name: End-to-end suite — forward, reverse, listen, datagram, teardown matrix
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/consumer-session, tunnels/producer-listen, tunnels/local-socket-halves]
|
||||||
|
scope: broad
|
||||||
|
risk: medium
|
||||||
|
impact: phase
|
||||||
|
level: implementation
|
||||||
|
tags: [tests, integration, phase-gate]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Consolidate the integration coverage into `tests/` as the crate's
|
||||||
|
end-to-end suite: every validated POC behavior, re-expressed against the
|
||||||
|
crate's public API (not the POCs' internals). This is the quality gate
|
||||||
|
before the implementation review — the suite is the spec's executable
|
||||||
|
form.
|
||||||
|
|
||||||
|
### Suites
|
||||||
|
|
||||||
|
1. **Forward (`-L`)**: consumer `open` → producer establisher dials →
|
||||||
|
`pump_bidi` → round-trip; 1 MiB backpressure; typed errors
|
||||||
|
(unknown_resource / dial_failed / FORBIDDEN / timeout-adjacent);
|
||||||
|
per-call opener identity witness (CF-006).
|
||||||
|
2. **Reverse (`-R`)**: hub initiator — `open_reverse_channel` +
|
||||||
|
`adopt` + `pump_against`; odd-ID allocation asserted (ADR-047 §5);
|
||||||
|
identity precedence chain (transport alone / `ServingConfig.
|
||||||
|
identity` override / token override / identity-less fail-closed);
|
||||||
|
half-close (W4); out-of-band `channel/close` + self-reaping (W3);
|
||||||
|
concurrent same-resource opens (R-01, no handoff race).
|
||||||
|
3. **Listen producer**: the accept-queue flow end-to-end + its typed
|
||||||
|
errors (from `tunnels/producer-listen`).
|
||||||
|
4. **Datagram (`udp`)**: forward + reverse datagram sessions via the
|
||||||
|
codec — round-trip, empty datagram (the F-2 layering), chunk-split
|
||||||
|
survival, TCP+UDP concurrent channels on one connection.
|
||||||
|
5. **Teardown matrix** (ADR-005): close / join (with + without pump) /
|
||||||
|
Drop — `channel_ids()` asserts no leaks on either side after each.
|
||||||
|
6. **`local` feature suite**: real sockets end-to-end (from
|
||||||
|
`tunnels/local-socket-halves` — runs after that task lands; the
|
||||||
|
duplex-transport suites above do not need it).
|
||||||
|
|
||||||
|
Transport stand-in: `tokio::io::duplex` (both POCs' harness; the
|
||||||
|
alkcall layer owns real transports — unchanged scope). The suite may
|
||||||
|
share one harness module (`tests/common/`); the POC harnesses
|
||||||
|
(`/workspace/alktunnels-udp-poc/src/harness.rs`,
|
||||||
|
`/workspace/alktunnels-reverse-poc/src/harness.rs`) are the two
|
||||||
|
topologies to unify.
|
||||||
|
|
||||||
|
### The spec-conformance assertions to keep visible
|
||||||
|
|
||||||
|
- A failed open never returns a `channel_id` (no phantom channel —
|
||||||
|
both sides' `channel_ids()` hold only 0 afterward).
|
||||||
|
- A failed adopt never leaks the session (Drop reaps).
|
||||||
|
- The pump handler's `JoinHandle` tracks the data plane (a test that
|
||||||
|
simulates early-return would hang/EOF-instantly — the R-02 shape;
|
||||||
|
assert the correct shape survives).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] All 6 suites present; ≥20 integration tests total
|
||||||
|
- [ ] `cargo test` green; `cargo test --features local` green
|
||||||
|
- [ ] Repeat-run stable (3× clean — the POCs' flakiness bar)
|
||||||
|
- [ ] Clippy/fmt clean; wasm32 check passes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/wire.md + consumer.md + producer.md (the normative
|
||||||
|
behaviors under test)
|
||||||
|
- POC test suites: `/workspace/alktunnels-udp-poc/tests/tunnel_poc.rs`
|
||||||
|
(10), `/workspace/alktunnels-reverse-poc/tests/tunnel_poc.rs` (16)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/local-socket-halves
|
||||||
|
name: "local feature — real socket halves functions (TCP dial, UDP connect, unix)"
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/producer-open-op, tunnels/wire-codec]
|
||||||
|
scope: moderate
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
tags: [local, backend, sockets, feature-gate]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Add the `local` feature module (`src/local/`) per overview.md §Feature
|
||||||
|
Gates + ADR-004: the real-socket halves functions the assembly layer
|
||||||
|
injects as `DialFn`/`AcceptFn` closures. The POCs' substrate adapters
|
||||||
|
are the reference (they ran all of this); the task is packaging them
|
||||||
|
into the feature-gated module with the crate's conventions.
|
||||||
|
|
||||||
|
### Surface
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// src/local/mod.rs (feature = "local"; non-wasm by design)
|
||||||
|
pub async fn dial_tcp(target: &str) -> Result<TargetHandle, TunnelEstablishError>;
|
||||||
|
// TcpStream::connect (5s timeout per the POCs) → into_split → boxed halves
|
||||||
|
|
||||||
|
pub async fn connect_udp(target: &str) -> Result<TargetHandle, TunnelEstablishError>;
|
||||||
|
// UdpSocket::bind(("0.0.0.0", 0)) → connect(target) → the FRAMED adapter
|
||||||
|
// (UdpHalf + codec — ADR-003; truncation fail-loud per OQ-TN-13)
|
||||||
|
|
||||||
|
pub async fn dial_unix(path: &str) -> Result<TargetHandle, TunnelEstablishError>;
|
||||||
|
// UnixStream::connect → into_split (OQ-TN-14: ships with local v1 — cheap,
|
||||||
|
// same halves shape as TCP)
|
||||||
|
|
||||||
|
pub struct TcpListenerHalves { listener: tokio::net::TcpListener }
|
||||||
|
impl TcpListenerHalves {
|
||||||
|
pub async fn bind(addr: &str) -> Result<Self, ...>;
|
||||||
|
pub fn accept_fn(&self, queue: &AcceptQueue) -> impl Future; // the accept loop feeding the queue
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UdpAssociateHalves { sock: Arc<UdpSocket> } // the POC's associate shape
|
||||||
|
```
|
||||||
|
|
||||||
|
- **UDP adapter (`UdpHalf`)**: port from the forward POC
|
||||||
|
(`params.rs`'s `UdpHalfRead`/`UdpHalfWrite`) with the truncation fix:
|
||||||
|
`poll_recv` into a too-small caller buffer must fail loud
|
||||||
|
(OQ-TN-13's resolved posture — ADR-003). The codec wraps at this
|
||||||
|
boundary; the pump never sees UDP specifics.
|
||||||
|
- **Error mapping** to `TunnelEstablishError` variants (the POC's
|
||||||
|
`Into<EstablishmentError>` path).
|
||||||
|
- `TargetHandle` re-used from producer.rs (the halves type lives in
|
||||||
|
producer.rs; local/ depends on it, never vice versa).
|
||||||
|
|
||||||
|
### Feature wiring
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
local = ["tokio/net"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tokio/net` is the only dep addition (no new external crates; the
|
||||||
|
POCs proved `rt+sync+io-util+macros+time+net` covers everything).
|
||||||
|
- The default crate stays wasm-clean: `local`-gated code must not be
|
||||||
|
importable from `producer.rs`/`consumer.rs` (convention 16 — backend
|
||||||
|
modules are never imported from the shared/producer/consumer
|
||||||
|
modules).
|
||||||
|
- Unix is IN v1 (OQ-TN-14's lean-yes posture; same halves shape as
|
||||||
|
TCP); stdio bridging is NOT (different lifecycle — deferred).
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Behind the feature: TCP dial round-trip through the real establisher
|
||||||
|
path, UDP associate + datagram round-trip with the framed adapter,
|
||||||
|
unix dial round-trip, truncation fail-loud (a short recv surfaces as
|
||||||
|
an error), the 1400-byte MTU datagram through bounded buffers (the
|
||||||
|
POC's sizing test).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `cargo test --features local` green; `cargo test` (default)
|
||||||
|
unaffected
|
||||||
|
- [ ] `cargo test --all-features` green (AGENTS.md convention 14)
|
||||||
|
- [ ] wasm32 check on the DEFAULT crate passes; `local` is non-wasm by
|
||||||
|
design (documented)
|
||||||
|
- [ ] Truncation fails loud (the OQ-TN-13 test)
|
||||||
|
- [ ] No socket type appears outside `src/local/`
|
||||||
|
- [ ] Clippy/fmt clean
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/overview.md §Feature Gates, §Dependencies
|
||||||
|
- docs/architecture/decisions/004-no-backend-trait.md (the injection
|
||||||
|
point), 003 (the framed adapter)
|
||||||
|
- POC reference: `/workspace/alktunnels-udp-poc/src/producer.rs`
|
||||||
|
(`UdpHalf`, the dial paths), `params.rs` (the UDP adapter)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/params
|
||||||
|
name: TunnelParams + open-op spec (schema, scope gate, constants)
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/crate-init]
|
||||||
|
scope: narrow
|
||||||
|
risk: low
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
tags: [wire, params, open-op]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `src/params.rs` per ADR-001 + wire.md §The Open Op: the
|
||||||
|
`TunnelParams` wire type, the `Substrate` discriminator, and the open-op
|
||||||
|
`OperationSpec` builder. This is the wire-stable surface (one-way door) —
|
||||||
|
exact conformance to the ADR is the point.
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct TunnelParams {
|
||||||
|
pub resource: String,
|
||||||
|
pub substrate: Substrate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Substrate { Tcp, Udp, Unix }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `deny_unknown_fields` is the loud-rejection posture (ADR-001: unknown
|
||||||
|
fields fail schema validation, never silently ignored).
|
||||||
|
- `Substrate::Unix` is included in the type (the wire enum per ADR-001)
|
||||||
|
but NOT in the open-op schema's enum list v1 (`["tcp", "udp"]`) until
|
||||||
|
OQ-TN-14 resolves — an older producer rejecting a newer substrate is
|
||||||
|
the SSH "unknown channel type" posture; the schema list is the
|
||||||
|
authoritative gate. Document this asymmetry in the type docs (type
|
||||||
|
extensible, schema conservative).
|
||||||
|
|
||||||
|
### The open-op spec (in producer.rs or params.rs — put it where
|
||||||
|
`tunnels/producer-open-op` can consume; the spec builder lives in
|
||||||
|
params.rs per the module map, producer.rs re-exports)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const OP_TUNNEL_OPEN: &str = "channels/tunnel/sub";
|
||||||
|
pub const TUNNEL_ALPN: &str = "alk/tunnel";
|
||||||
|
pub const TUNNEL_OPEN_SCOPE: &str = "tunnel:open";
|
||||||
|
|
||||||
|
pub fn tunnel_open_spec() -> OperationSpec
|
||||||
|
```
|
||||||
|
|
||||||
|
- `OperationType::Sub`; `Visibility::External`; channel-open marker
|
||||||
|
`ChannelOpenSpec::new(TUNNEL_ALPN)`.
|
||||||
|
- `input_schema`: `{resource: string (required), substrate: string enum
|
||||||
|
["tcp","udp"] (required)}` — JSON Schema Draft shape as the POCs used.
|
||||||
|
- `output_schema`: `{channel_id: integer > 0}`.
|
||||||
|
- `AccessControl.required_scopes: [TUNNEL_OPEN_SCOPE]` (ADR-006).
|
||||||
|
- `description`: a one-line human hint (round-trips through discovery;
|
||||||
|
the spec's SHOULD).
|
||||||
|
|
||||||
|
### Error surface
|
||||||
|
|
||||||
|
`src/error.rs`: `TunnelError` (thiserror) covering the codec + session
|
||||||
|
errors (filled incrementally by later tasks); plus the re-export of
|
||||||
|
`alkcall::channels::client::ChannelOpenError` and an
|
||||||
|
`establishment_reason(&CallError) -> Option<&str>` helper
|
||||||
|
(ADR-049 §4 surface; the POC consumer's shape).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `TunnelParams` round-trips serde; unknown fields rejected
|
||||||
|
- [ ] `tunnel_open_spec()` matches wire.md §The Open Op exactly (op id,
|
||||||
|
type, ALPN marker, schemas, ACL, description)
|
||||||
|
- [ ] Schema validation test: valid params pass; missing fields,
|
||||||
|
unknown fields, unknown substrate values all fail
|
||||||
|
- [ ] `establishment_reason` maps `channel:open_failed` details.reason
|
||||||
|
(POC-pinned shapes: `unknown_resource`, `dial_failed`,
|
||||||
|
`resource_shortage`)
|
||||||
|
- [ ] Unit tests cover the serde + schema shapes
|
||||||
|
- [ ] wasm32 check passes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/decisions/001-open-params-layout.md
|
||||||
|
- docs/architecture/wire.md §The Open Op
|
||||||
|
- docs/architecture/decisions/006-access-control-posture.md
|
||||||
|
- POC reference: `/workspace/alktunnels-udp-poc/src/params.rs` +
|
||||||
|
`producer.rs::tunnel_open_spec` (the shape, with `deny_unknown_fields`
|
||||||
|
added)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/producer-listen
|
||||||
|
name: Listen establisher — the producer-side listener variant
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/producer-open-op]
|
||||||
|
scope: narrow
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
tags: [producer, establisher, listen, reverse]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement the listen establisher shape per producer.md §The Establisher
|
||||||
|
(shape 2): a producer whose resource is a LISTENER (SSH `-R` far-side
|
||||||
|
listener; a hub exposing its own port to a third party). Same open op,
|
||||||
|
same params, same typed errors — the establisher pops the next accepted
|
||||||
|
connection from an assembly-owned listener queue instead of dialing.
|
||||||
|
No new wire surface (ADR-004).
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub type AcceptFn = Arc<
|
||||||
|
dyn Fn() -> BoxFuture<'static, Result<TargetHandle, TunnelEstablishError>>
|
||||||
|
+ Send + Sync,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub fn listen_establisher(registry: ResourceRegistry, accept: AcceptFn) -> OpenEstablisher
|
||||||
|
```
|
||||||
|
|
||||||
|
- The assembly layer owns the listener + its accept loop and injects an
|
||||||
|
`AcceptFn` that pops one accepted connection (a queue front). The
|
||||||
|
protocol crate never binds (OQ-TN-04).
|
||||||
|
- Error mapping (producer.md's table): empty queue during
|
||||||
|
establishment → `resource_shortage`; closed listener →
|
||||||
|
`dial_failed`; registry miss → `unknown_resource` (same as dial).
|
||||||
|
- The pump handler is UNCHANGED (the same `make_tunnel_pump_handler`) —
|
||||||
|
the listen variant is plan-flow with a different halves source.
|
||||||
|
|
||||||
|
### Assembly-side listener helper (the `local`-gated half lands in
|
||||||
|
`tunnels/local-socket-halves`; here, define the protocol-side queue
|
||||||
|
contract)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct AcceptQueue { /* Mutex<VecDeque<TargetHandle>> + notify */ }
|
||||||
|
impl AcceptQueue {
|
||||||
|
pub fn push(&self, handle: TargetHandle); // the accept loop feeds
|
||||||
|
pub async fn pop(&self) -> Option<TargetHandle>; // the establisher pops (bounded wait)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `pop` during establishment: bounded (the establisher's own deadline
|
||||||
|
applies — ADR-049 §2); `None` → `resource_shortage`.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
Extend the integration suite with a listen-producer topology: an
|
||||||
|
in-process listener queue fed by a test accept loop; a consumer opens
|
||||||
|
toward it; the accepted handle is the plan payload; the two-pump
|
||||||
|
round-trip completes. Cover: pop-during-establishment ordering (the
|
||||||
|
always-before-take guarantee the wrapper's await order gives), empty
|
||||||
|
queue → `resource_shortage`, closed listener → `dial_failed`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] `listen_establisher` + `AcceptFn` + the queue contract compile
|
||||||
|
and wire through `register_tunnel_openable`'s shape (a listen
|
||||||
|
variant of the registration or a dial/accept enum — pick the
|
||||||
|
honest shape, document it)
|
||||||
|
- [ ] The pump handler is untouched (same function registered for
|
||||||
|
listen producers)
|
||||||
|
- [ ] Integration tests: the listen flow end-to-end + the two typed
|
||||||
|
error paths
|
||||||
|
- [ ] Clippy/fmt clean; wasm32 check passes
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/producer.md §The Establisher (shape 2)
|
||||||
|
- docs/architecture/decisions/004-no-backend-trait.md (the listen
|
||||||
|
variant rationale)
|
||||||
|
- Reverse POC (the hub's accept loop stands in for the assembly
|
||||||
|
listener — `docs/research/reverse-poc-summary.md` §The -R template)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/producer-open-op
|
||||||
|
name: Producer half — establisher (dial shape) + pump handler + register_tunnel_openable
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/params, tunnels/wire-codec]
|
||||||
|
scope: broad
|
||||||
|
risk: high
|
||||||
|
impact: phase
|
||||||
|
level: implementation
|
||||||
|
tags: [producer, establisher, open-op, pump]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `src/producer.rs` per producer.md: the dial-shape establisher,
|
||||||
|
the substrate-agnostic pump handler, and the registration surface. This
|
||||||
|
is the crate's core — the open op that both POCs validated (the forward
|
||||||
|
POC's producer on the accept side; the reverse POC's worker on the
|
||||||
|
connect side). Port from the REVERSE POC first
|
||||||
|
(`/workspace/alktunnels-reverse-poc/src/producer.rs`) — it is the 0.6/0.7
|
||||||
|
idiomatic shape (plan flow, no handoff) and its 16 tests are the
|
||||||
|
reference suite.
|
||||||
|
|
||||||
|
### The establisher (dial shape — the listen shape is a separate task)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub type DialFn = Arc<
|
||||||
|
dyn Fn(Substrate, &str) -> BoxFuture<'static, Result<TargetHandle, TunnelEstablishError>>
|
||||||
|
+ Send + Sync,
|
||||||
|
>;
|
||||||
|
// TargetHandle: boxed halves (read: Box<dyn AsyncRead + Send + Sync + Unpin>, write: ...),
|
||||||
|
// or the framed UDP adapter (the establisher wraps BEFORE returning the plan —
|
||||||
|
// ADR-003's placement; the pump stays substrate-agnostic)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `tunnel_establisher(registry: ResourceRegistry, dial: DialFn,
|
||||||
|
identity_witness: Option<...>) -> OpenEstablisher` — the assembly layer
|
||||||
|
injects the substrate dial function (ADR-004); the establisher:
|
||||||
|
1. Parses params (`TunnelParams` — schema-validated upstream, semantic
|
||||||
|
parse here).
|
||||||
|
2. Registry lookup `(resource, substrate)` → backing address —
|
||||||
|
`unknown_resource` on miss (typed reason mapping per ADR-001's
|
||||||
|
table).
|
||||||
|
3. `dial(substrate, backing)` → the handle (the closure owns real
|
||||||
|
socket code — behind `local` or assembly-constructed).
|
||||||
|
4. UDP: wrap in the framed adapter (ADR-003) — `UdpHalf` + codec,
|
||||||
|
truncation fail-loud (OQ-TN-13).
|
||||||
|
5. Return `Ok(Establishment::new(Arc::new(handle)))` (R-01 plan flow).
|
||||||
|
- `ResourceRegistry`: the assembly-owned
|
||||||
|
`HashMap<(String, Substrate), String>` (POC shape; OQ-TN-11's
|
||||||
|
collision domain) with `register`/`lookup` async methods.
|
||||||
|
|
||||||
|
### The pump handler
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn make_tunnel_pump_handler() -> OpenHandler
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Downcast the plan to `TargetHandle` (`Arc::downcast` — error path:
|
||||||
|
log + return; the birth-teardown telemetry catches it).
|
||||||
|
2. `conn.accept_bi()` (yield-once).
|
||||||
|
3. `pump_bidi(bidi, t_read, t_write)` — **awaited inline** (R-02: the
|
||||||
|
returned `JoinHandle` tracks the data-plane lifetime; early return =
|
||||||
|
teardown-at-birth). The handler is substrate-agnostic by
|
||||||
|
construction — it must not know what the halves came from
|
||||||
|
(ADR-004).
|
||||||
|
|
||||||
|
### Registration
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn register_tunnel_openable(
|
||||||
|
core: &ChannelCore,
|
||||||
|
registry: &ResourceRegistry,
|
||||||
|
on_registry: &Arc<OperationRegistry>, // the session's dispatch registry (ADR-047 §4 fork)
|
||||||
|
auth: AuthContext,
|
||||||
|
dial: DialFn,
|
||||||
|
) -> Result<(), String>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Wires `tunnel_open_spec()` + the establisher + the pump via
|
||||||
|
`register_openable_with_establisher` (timeout `None` = the 10s
|
||||||
|
default).
|
||||||
|
- Post-hoc registration is supported (W2): the dispatcher reads through
|
||||||
|
the shared `Arc` per dispatch.
|
||||||
|
|
||||||
|
### Integration tests (the POC suite, ported)
|
||||||
|
|
||||||
|
Port the reverse POC's test topology (`harness.rs`: worker =
|
||||||
|
`from_connection_with_serving` + `ChannelOperations::register_on` +
|
||||||
|
post-hoc openable; hub = adapter + capturing install hook) into
|
||||||
|
`tests/` with the `dial` closure standing in for real sockets
|
||||||
|
(in-process duplex halves — no `local` feature needed). Cover:
|
||||||
|
establishment success + typed errors (unknown_resource / dial_failed /
|
||||||
|
FORBIDDEN / timeout-adjacent), the plan flow under same-resource
|
||||||
|
concurrency, per-call opener identity (CF-006 witness),
|
||||||
|
late-registration visibility, pump round-trip through `pump_bidi`.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Establisher: typed reason mapping exact (ADR-001's table)
|
||||||
|
- [ ] Pump handler: `pump_bidi` inline; no spawn-and-forget; no
|
||||||
|
substrate types leak into the handler
|
||||||
|
- [ ] `register_tunnel_openable` wires spec + establisher + pump +
|
||||||
|
dial injection
|
||||||
|
- [ ] Integration tests: forward AND reverse topology (the harness
|
||||||
|
shapes from both POCs), ≥8 tests covering the above
|
||||||
|
- [ ] Clippy/fmt clean; wasm32 check passes (dial closures are
|
||||||
|
runtime-injected — the crate compiles without `local`)
|
||||||
|
- [ ] `cargo test` green
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/producer.md (the normative shapes)
|
||||||
|
- docs/architecture/decisions/001/003/004 (params, codec placement, no
|
||||||
|
trait)
|
||||||
|
- POC reference: `/workspace/alktunnels-reverse-poc/src/producer.rs` +
|
||||||
|
`harness.rs` + `tests/tunnel_poc.rs` (the 0.7.0-idiomatic shapes)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/review-core-crates
|
||||||
|
name: Mid-phase review — producer + consumer halves before the local feature
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/consumer-session]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: phase
|
||||||
|
level: review
|
||||||
|
tags: [review, injection-point]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Review injection point (SDD §Review Injection — "high-risk tasks: review
|
||||||
|
before proceeding"): `tunnels/producer-open-op` and
|
||||||
|
`tunnels/consumer-session` are the two `risk: high, impact: phase` tasks
|
||||||
|
and the crate's wire/API surface. Review them BEFORE
|
||||||
|
`tunnels/local-socket-halves` and `tunnels/end-to-end-suite` build
|
||||||
|
against the shapes — a wire or API mistake found here costs one task's
|
||||||
|
rework; found later it costs three.
|
||||||
|
|
||||||
|
### Checklist (focused — the full gate is `tunnels/review-impl`)
|
||||||
|
|
||||||
|
1. Wire conformance of what landed (params/codec/open-op vs wire.md +
|
||||||
|
ADR-001/003) — the one-way-door check.
|
||||||
|
2. The pump handler shape: `pump_bidi` inline, JoinHandle tracks the
|
||||||
|
data plane (R-02) — the POC's hang-bug class must be structurally
|
||||||
|
absent.
|
||||||
|
3. Teardown matrix of `TunnelSession` — close/join/Drop soundness, no
|
||||||
|
leaks (the W3 class).
|
||||||
|
4. No substrate types outside `src/local/`-to-be; no hand-rolled
|
||||||
|
two-pump loops; no side-channel handoff.
|
||||||
|
5. Integration tests green + repeat-run stable (3×).
|
||||||
|
|
||||||
|
Deliverable: findings inline here (Notes/Summary); criticals block the
|
||||||
|
downstream tasks (Safe Exit); majors create remediation notes for
|
||||||
|
`tunnels/review-impl` to re-check.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] The 5 checklist items each with a verdict
|
||||||
|
- [ ] Criticals (if any) resolved before proceeding; majors logged
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/ (the spec set under review)
|
||||||
|
- docs/sdd_process.md §Review Injection
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/review-impl
|
||||||
|
name: Review alktunnels v1 implementation for spec conformance (pre-release gate)
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/end-to-end-suite, tunnels/review-core-crates]
|
||||||
|
scope: moderate
|
||||||
|
risk: low
|
||||||
|
impact: project
|
||||||
|
level: review
|
||||||
|
tags: [review, phase-gate]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Review the v1 implementation against the architecture spec before any
|
||||||
|
publish consideration. This is the phase-gate review (the SDD process's
|
||||||
|
review-injection point at the end of the critical path): the wire
|
||||||
|
surface (ADR-001/002/003) is a one-way door, and the API surface
|
||||||
|
(ADR-005/006) becomes ABI-stable once a consumer exists.
|
||||||
|
|
||||||
|
### Review checklist
|
||||||
|
|
||||||
|
1. **Wire conformance** (wire.md + ADR-001/002/003):
|
||||||
|
- params shape exact (`{resource, substrate}`, `deny_unknown_fields`,
|
||||||
|
schema enum without `unix` until OQ-TN-14)
|
||||||
|
- op id `channels/tunnel/sub`, ALPN marker `alk/tunnel`, scope
|
||||||
|
`tunnel:open`
|
||||||
|
- codec: `[len: u16 BE]`, `len=0` = legal empty datagram, `Oversize`
|
||||||
|
at frame time, no sentinel in the codec
|
||||||
|
- typed errors: the five establishment reasons + `FORBIDDEN` +
|
||||||
|
`channel:too_many_channels` surfaces
|
||||||
|
2. **Producer conformance** (producer.md):
|
||||||
|
- establisher = the awaited bounded phase; plan flow (R-01) — no
|
||||||
|
side-channel handoff anywhere (grep for `Mutex<HashMap>` +
|
||||||
|
poll-loop take patterns — the POC's dead shape)
|
||||||
|
- pump handler: `pump_bidi` inline, no substrate types, JoinHandle
|
||||||
|
tracks the data plane (R-02)
|
||||||
|
- registration: post-hoc supported (W2); `ChannelOperations::
|
||||||
|
register_on` guidance correct
|
||||||
|
- listen variant: same op, no new wire surface, typed error mapping
|
||||||
|
3. **Consumer conformance** (consumer.md + ADR-005):
|
||||||
|
- `TunnelSession`: open/adopt/stream_halves/take_halves/
|
||||||
|
send_datagram/recv_datagram/pump_against/close/join/Drop
|
||||||
|
- teardown matrix sound (no leaks; pump-less join `(0, 0, reaped)`)
|
||||||
|
- no `Clone`
|
||||||
|
4. **Conventions sweep** (AGENTS.md):
|
||||||
|
- no comments in code (doc comments fine); no unwrap/expect outside
|
||||||
|
tests; thiserror everywhere; poisoned-lock `unwrap_or_else(into_inner)`
|
||||||
|
- substrate types confined to `src/local/` (grep-verify: no
|
||||||
|
`tokio::net` outside the feature gate + tests)
|
||||||
|
- wasm-clean default crate (the structural guard passed at every
|
||||||
|
step, but re-verify)
|
||||||
|
- `pump_bidi` consumed, never hand-rolled (grep for hand-rolled
|
||||||
|
two-pump loops)
|
||||||
|
5. **Docs ↔ implementation sync**: lib.rs doc comments match
|
||||||
|
overview.md's module map; public API = lib.rs re-exports (convention
|
||||||
|
16); ADR statuses updated from Draft → Accepted where the
|
||||||
|
implementation confirms them (or findings filed back to the OQ
|
||||||
|
tracker).
|
||||||
|
|
||||||
|
### Deliverables
|
||||||
|
|
||||||
|
Findings as a review doc (`docs/reviews/001-implementation-review.md`,
|
||||||
|
the alkhttp/alkcall house pattern) with severity levels + concrete
|
||||||
|
remediation tasks. Criticals block the phase; majors get remediation
|
||||||
|
tasks; minors get a follow-up batch.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] Review doc filed with severity-graded findings
|
||||||
|
- [ ] Remediation tasks created for anything above trivial
|
||||||
|
- [ ] The 5 checklist sections each covered with a verdict
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/ (the full spec set)
|
||||||
|
- House pattern: `/workspace/@alkdev/alkcall/docs/reviews/` (the review
|
||||||
|
numbering + severity legend)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
id: tunnels/wire-codec
|
||||||
|
name: Data-plane codec (frame_datagram / DatagramReader) + sentinel layering tests
|
||||||
|
status: pending
|
||||||
|
depends_on: [tunnels/crate-init, tunnels/params]
|
||||||
|
scope: narrow
|
||||||
|
risk: medium
|
||||||
|
impact: component
|
||||||
|
level: implementation
|
||||||
|
tags: [wire, codec, udp]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Implement `src/wire.rs` per ADR-003 + wire.md §The Data Plane + bast.md:
|
||||||
|
the mandatory UDP length-framing codec. This is a direct port of the
|
||||||
|
forward POC's `/workspace/alktunnels-udp-poc/src/wire.rs` (17 tests rode
|
||||||
|
it; the shape is settled) with the POC-to-crate generalizations the ADRs
|
||||||
|
pin.
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub const MAX_DATAGRAM_LEN: usize = u16::MAX as usize; // 65535
|
||||||
|
|
||||||
|
pub struct DatagramCodecError { .. } // thiserror: Oversize(usize), InvalidLength(...)
|
||||||
|
|
||||||
|
pub fn frame_datagram(payload: &[u8]) -> Result<Bytes, DatagramCodecError>
|
||||||
|
// [len: u16 BE][payload]; len=0 is a legal empty datagram; >65535 = Oversize at frame time
|
||||||
|
|
||||||
|
pub struct DatagramReader { /* incremental decoder state */ }
|
||||||
|
impl DatagramReader {
|
||||||
|
pub fn new() -> Self
|
||||||
|
pub fn feed(&mut self, chunk: &[u8]) -> Result<Vec<Bytes>, DatagramCodecError>
|
||||||
|
// zero or more complete datagrams per chunk; buffers partial frames
|
||||||
|
// across chunk boundaries
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### The invariants the tests MUST pin (all POC-validated; they are the
|
||||||
|
spec's executable form)
|
||||||
|
|
||||||
|
1. Single datagram round-trip (frame → feed → exact bytes out).
|
||||||
|
2. Empty datagram (`len=0`): survives as a real datagram — NEVER
|
||||||
|
confused with EOF (the F-2 invariant; the codec layer never emits a
|
||||||
|
zero-length read).
|
||||||
|
3. Split across chunks (feed a frame in awkward 7-byte chunks; exact
|
||||||
|
reassembly).
|
||||||
|
4. Two datagrams batched in one chunk (feed once, two out, in order).
|
||||||
|
5. Partial header at a chunk boundary (1-byte `len` prefix split).
|
||||||
|
6. Mid-datagram state observable (incremental decode correctness).
|
||||||
|
7. Oversize rejected at frame time (never a wire overflow).
|
||||||
|
8. Truncation fail-loud (OQ-TN-13): the adapter-level receive that
|
||||||
|
would truncate surfaces as an error — test at the codec boundary by
|
||||||
|
asserting `frame_datagram` + reader round-trip with a buffer
|
||||||
|
smaller than a full datagram is not silently accepted (the concrete
|
||||||
|
API shape lands with `tunnels/local-socket-halves`; here, pin the
|
||||||
|
codec-side invariant: a truncated stream yields an error, not a
|
||||||
|
partial datagram).
|
||||||
|
|
||||||
|
No `stream_type` byte, no 5-byte header — a tunnel has one data stream
|
||||||
|
per direction (ADR-003). No EOF sentinel in the codec — EOF is the
|
||||||
|
channels-level `length=0` chunk, a different layer (the two coexist;
|
||||||
|
the POC proved no collision).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] All 8 test families above pass (the POC's 7 + the truncation pin)
|
||||||
|
- [ ] `frame_datagram` returns `Bytes` (zero-copy handoff to the mux)
|
||||||
|
- [ ] `DatagramReader` state is incremental across arbitrary chunk
|
||||||
|
boundaries
|
||||||
|
- [ ] `cargo clippy --all-targets -- -D warnings`, `cargo fmt --check`
|
||||||
|
clean
|
||||||
|
- [ ] wasm32 check passes (the codec is pure byte work — it must be
|
||||||
|
wasm-clean)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- docs/architecture/decisions/003-codec-and-udp-framing.md
|
||||||
|
- docs/architecture/bast.md (the binary contract)
|
||||||
|
- docs/architecture/wire.md §The Data Plane (normative byte diagrams)
|
||||||
|
- POC reference: `/workspace/alktunnels-udp-poc/src/wire.rs` (port with
|
||||||
|
the crate's doc-comment style)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
> Agent fills during implementation.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
> Agent fills this on completion.
|
||||||
Reference in New Issue
Block a user