- Rewrote the remaining em dashes outside docs/reviews/ (preserved as historical record, matching the alktty precedent): tests/ (313), fuzz/ (34 plus one shell comment), tasks/ (671 plus 12 kept in fences), docs/research + sdd_process + plans (525 plus 25 in fences), docs/architecture/overview.md fence labels (13), README, CHANGELOG, AGENTS.md, fuzz/README.md (176), and the .opencode/agents prompts (110) - Rewrote fenced content too: ASCII tree and module-map label separators, Rust snippet comments, and template placeholders; wire data, JSON/BAST examples, and transcripts survive verbatim - Rewrote 6 assert/expect message literals in tests/ and 2 format strings in fuzz/shared/src; no test data changed - Added Project Convention 18: no em dashes in prose (doc comments, markdown including docs/ and tasks/, agent prompts, commit messages); fence content rule, en-dash clause rule, and the no-spaced-hyphen prohibition follow alktty's convention 15 Follows6b20924(src doc comments) andcd0afe0(docs/architecture). Verification: cargo test (141 pass); cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/
11 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tunnels/direct-op | Direct op, params, spec, establisher, consumer open_direct (ADR-007) | completed | broad | medium | component | implementation |
|
Description
Implement the direct op (channels/tunnel/direct, ADR-007) end to
end: the params type + spec builder (src/params.rs), the establisher
- registration (
src/producer.rs), and the consumer constructor (src/consumer.rs). The op rides the samealk/tunnelALPN (ADR-002 Amendment 1's flavor-form op ids; the upstream discovery derivation landed in alkcall 0.8.0, ADR-047 amendment 3). This is the sshdirect-tcpip/ SOCKS5 CONNECT egress capability with structural ACL separation fromtunnel:open.
params.rs: types + spec
pub const OP_TUNNEL_DIRECT: &str = "channels/tunnel/direct";
pub const TUNNEL_DIRECT_SCOPE: &str = "tunnel:direct";
/// The substrate-shaped address object (ADR-007 §2): keyed by the
/// sibling `substrate` field of the params. Also the `peer` value
/// shape of the forwarded op (ADR-008 §3); define once here.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SubstrateAddr {
HostPort { host: String, port: u16 },
Path { path: String },
}
/// The direct-op input params (ADR-007 §2). Wire-stable.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TunnelDirectParams {
pub substrate: Substrate,
pub target: SubstrateAddr,
}
pub fn tunnel_direct_spec() -> OperationSpec
- Spec per wire.md §The Direct Op:
Sub-typed,Visibility::External,ChannelOpenSpec::new(TUNNEL_ALPN), scope["tunnel:direct"](never implied bytunnel:open, ADR-006 Amendment 1), output schema{channel_id: integer > 0}. input_schemapins both fields required and the target sub-schema per-substrate, as aoneOfon the siblingsubstratevalue (tcp/udp→{host, port},unix→{path}), so malformed targets fail the registry's schema validation (the typedINVALID_INPUTclass) before the establisher runs. The establisher's serde parse is the second gate (defensive; see the error-mapping note).deny_unknown_fieldson both params types (the loud-rejection posture, ADR-001's precedent).- The
Substrateenum is reused as the discriminator (no new substrate values (ADR-001's additive rule).
producer.rs: the establisher + registration
pub fn direct_establisher(dial: DialFn) -> OpenEstablisher
pub fn register_tunnel_direct_openable(
core: &ChannelCore,
on_registry: &Arc<OperationRegistry>,
auth: AuthContext,
dial: DialFn, // the SAME DialFn the base op uses
identity_witness: Option<IdentityWitness>,
) -> Result<(), String>
- DialFn reuse is the design pin: the establisher parses
TunnelDirectParams, renders the target to the same backing-string form the registry backings use ("host:port"for tcp/udp, the path for unix), and calls the injectedDialFn(substrate, rendered). No second dial type: a producer that registereddial_tcpfor the base op registers the direct op with zero new substrate code (ADR-007'slocalinteraction note). The UDP framed-adapter wrap (ADR-003) happens inside the dial closure (thelocalfeature'sconnect_udpalready does this), so the establisher stays substrate-agnostic. - No registry lookup:
unknown_resourcecan never fire (ADR-007 §2). Error mapping (ADR-007 §4's table minus the registry row): the serde-parse failure of a schema-valid-shaped target mapsHandlerError(the schema gate should have caught it; the parse is the backstop); dial failures pass through theDialFn's own typed errors unchanged (dial_failed/resource_shortage/handler_error). - The per-call
authcarries the opener identity (CF-006); the witness seam records it, same as the base op (the direct op is the arbitrary-egress capability; identity visibility here matters more, not less). - Registration uses the SAME pump handler (
make_tunnel_pump_handler(the direct op's plan payload is aTargetHandleexactly like the base op's, and plan-flow is unchanged).
consumer.rs: open_direct
pub async fn open_direct(
client: &ChannelClient,
target: SubstrateAddr,
substrate: Substrate,
) -> Result<Self, TunnelOpenError>
- A separate constructor, not a params enum (ADR-007 §5: two scopes
are two capabilities; the caller states which one it holds).
Serializes
{substrate, target},open_channel(OP_TUNNEL_DIRECT, …, TUNNEL_ALPN),from_halves, with an identical session type, data planes, and teardown API asopen. - Uses plain
open_channel(no reply fields expected on the direct op, because dial establishers never bind; wire.md §The Direct Op). - Documented error surface: the base open errors minus
unknown_resource; malformed targets are the schema/invalid_input-class failure (registry-side); an old producer rejects the op loudly (NOT_FOUND, the SSH posture).
Tests
Unit (params.rs): TunnelDirectParams round-trip per substrate;
untagged SubstrateAddr disambiguation ({host, port} vs {path});
deny_unknown_fields rejection; spec-shape conformance to wire.md §The
Direct Op (op id, scope, oneOf schema structure; structural
assertions, no jsonschema dep here, per tunnels/params's precedent).
Integration (extend the duplex harness from tunnels/producer-open-op):
open_directe2e round-trip throughpump_bidi(tcp duplex target).open_directudp e2e (the framed adapter wraps at the dial closure, so the datagram boundary is preserved).- Malformed target (unix without
path) → schema-class failure, no phantom session. - Producer without the direct op registered →
NOT_FOUND-class. - Identity without
tunnel:direct(but withtunnel:open) → FORBIDDEN (the scopes do not imply each other). - Direct establisher sees the per-call opener identity (witness).
Acceptance Criteria
TunnelDirectParams/SubstrateAddrround-trip; malformed targets rejected (serde + schema shape pinned)tunnel_direct_spec()matches wire.md §The Direct Op exactlydirect_establisher: no registry lookup, target rendering into theDialFnbacking string, typed errors per ADR-007 §4register_tunnel_direct_openablereuses the base pump handler + dial injection; witness seam presentTunnelSession::open_direct(separate ctor; identical session/teardown semantics)- Integration tests: e2e round-trips (tcp + udp), malformed-target rejection, scope separation (FORBIDDEN), NOT_FOUND posture, per-call identity witness
- Clippy/fmt clean; wasm32 check passes (no substrate code added)
cargo testgreen
References
- docs/architecture/decisions/007-direct-open-op.md (the normative op)
- docs/architecture/producer.md §The Direct Op; consumer.md §Direct path; wire.md §The Direct Op
- docs/architecture/decisions/006-access-control-posture.md Amendment 1
- tasks/tunnels/params.md + tasks/tunnels/producer-open-op.md (the shapes this extends; the harness to extend)
Notes
- The
input_schemapins the per-substrate target sub-schema as twoif/thenbranches underallOf, keyed on the siblingsubstratevalue (tcp/udp →{host, port}withadditionalProperties: false; unix →{path}, same posture); JSON Schemaif/thencomposes through alkcall's jsonschema 0.46 compile, and the integration tests prove the gate fires (INVALID_INPUTbefore the establisher). Structural assertions pin the branch shape pertunnels/params's no-jsonschema-dep precedent. - Untagged
SubstrateAddris deliberately lenient about extra target fields (untagged variants carry nodeny_unknown_fields): a cross-shape target parses as whichever variant fits. The schema is the shape-vs-substrate gate; the establisher's serde parse is the backstop only (unknown substrate value →handler_error,direct_establishermapsTunnelEstablishError::HandlerError). This asymmetry is documented in both the type docs and the test. TunnelDirectParams::render_target()centralizes the backing-string form ("host:port"/ path); the establisher injects the SAMEDialFnwith the rendered string; the render is pinned by test to the registry-backing form.TunnelSession::open_directuses plainopen_channel(no reply fields, since dial establishers never bind);SubstrateAddrre-exports as the future forwarded-oppeershape (ADR-008 §3, defined once).- Harness:
RegistrationMode::Direct,wire_direct/wire_direct_with_identity(the raw-call topology),wire_direct_forward/wire_direct_forward_with_identity(the session-level forward topology; identity parameterized so the session-level FORBIDDEN probe runs through the real client API),direct_identity/both_scopes_identity.
Summary
Implemented the direct op end to end per ADR-007:
- params.rs:
OP_TUNNEL_DIRECT/TUNNEL_DIRECT_SCOPEconstants;SubstrateAddr(untagged{host, port}|{path}, the ADR-008 §3peershape too);TunnelDirectParams(deny_unknown_fields,render_target());tunnel_direct_spec()(Sub-typed, External,ChannelOpenSpec::new(TUNNEL_ALPN), scope["tunnel:direct"], output{channel_id: integer > 0}, per-substrateif/thentarget sub-schema). 7 unit tests: round- trips per substrate, untagged disambiguation, loud unknown-field rejection, spec-shape conformance, schema structure. - producer.rs:
direct_establisher/direct_establisher_with_witness(parse → render → injectedDialFn; no registry lookup; typed-error mapping per ADR-007 §4 minus the registry row; CF-006 witness seam) +register_tunnel_direct_openable(direct spec + establisher + the SAMEmake_tunnel_pump_handler, plan-flow unchanged). - consumer.rs:
TunnelSession::open_direct(client, target, substrate): separate constructor, serializes{substrate, target}, plainopen_channel(OP_TUNNEL_DIRECT, …, TUNNEL_ALPN),from_halves, with identical session/data-planes/teardown asopen. - lib.rs: the new surface re-exported.
Tests: tests/producer_direct_op.rs has 15 integration tests over the
duplex harness: tcp + udp e2e round-trips through pump_bidi
(datagram boundary preserved via the framed dial closure), malformed
targets (unix without path, tcp without port, missing target)
→ INVALID_INPUT with no phantom channel/session, dial-failure
pass-through (dial_failed), NOT_FOUND on a producer without the op,
scope separation both ways (tunnel:open ↛ tunnel:direct and
tunnel:direct ↛ tunnel:open, all FORBIDDEN (raw-call AND
session-level), CF-006 witness on the direct establisher (raw + session
paths), the establisher parse backstop (handler_error), and the
target-rendering pin (the dial closure observes the exact
backing-string form). Harness: Direct registration mode +
wire_direct* topologies + direct_identity/both_scopes_identity.
Verified: cargo test green (93 native; 94 with --all-features),
clippy -D warnings clean (native + wasm32), fmt clean, wasm32
check passes (default crate stays wasm-clean; no substrate code was
added), cargo doc --no-deps clean. CHANGELOG updated (Unreleased →
Added).