- TunnelParams {resource, substrate} with deny_unknown_fields (ADR-001
loud-rejection posture); Substrate enum ships unix in v1 (OQ-TN-14)
- tunnel_open_spec(): channels/tunnel/sub, Sub-typed, alk/tunnel
channel-open marker, input schema enum [tcp,udp,unix], channel_id
output schema (minimum 1, required), tunnel:open ACL (ADR-006),
discovery description
- establishment_reason delegates to alkcall 0.7.0's typed
ChannelOpenError::establishment_reason; error re-exported through
params
- 6 unit tests: serde round-trip, unknown/missing-field rejection,
unknown-substrate rejection, spec conformance to wire.md
Verified: cargo test, clippy --all-targets -D warnings (native +
wasm32), fmt --check, wasm32 check — all clean
126 lines
4.9 KiB
Markdown
126 lines
4.9 KiB
Markdown
---
|
||
id: tunnels/params
|
||
name: TunnelParams + open-op spec (schema, scope gate, constants)
|
||
status: completed
|
||
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 AND in the open-op schema's
|
||
enum list v1 (`["tcp", "udp", "unix"]` — OQ-TN-14 resolved: unix has
|
||
an implementation path via the `local` feature). The schema list is
|
||
the authoritative gate for newer-substrate negotiation: an older
|
||
producer rejecting a newer substrate value is the SSH "unknown
|
||
channel type" posture (loud, not silent).
|
||
|
||
### 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","unix"] (required)}` — JSON Schema Draft shape as the
|
||
POCs used (unix in the list per OQ-TN-14's resolution).
|
||
- `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
|
||
|
||
- [x] `TunnelParams` round-trips serde; unknown fields rejected
|
||
- [x] `tunnel_open_spec()` matches wire.md §The Open Op exactly (op id,
|
||
type, ALPN marker, schemas, ACL, description)
|
||
- [x] Schema validation test: valid params pass; missing fields,
|
||
unknown fields, unknown substrate values all fail
|
||
- [x] `establishment_reason` maps `channel:open_failed` details.reason
|
||
(POC-pinned shapes: `unknown_resource`, `dial_failed`,
|
||
`resource_shortage`)
|
||
- [x] Unit tests cover the serde + schema shapes
|
||
- [x] 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
|
||
|
||
- `TunnelParams` + `Substrate` per the task's exact type shapes
|
||
(`deny_unknown_fields`, `rename_all = "lowercase"`, `Unix` in the
|
||
enum). `Eq` added to `TunnelParams` (test equality).
|
||
- `tunnel_open_spec()` lives in params.rs per the module map;
|
||
producer.rs re-exports. Output schema pins `channel_id` with
|
||
`minimum: 1` + `required` (wire.md: integer > 0) — the POC's
|
||
`minimum: 0` / no-required was loose.
|
||
- `establishment_reason` delegates to alkcall 0.7.0's
|
||
`ChannelOpenError::establishment_reason` method (upstream has it);
|
||
`ChannelOpenError` re-exported from params.rs (pub use, since lib.rs
|
||
re-exports through it).
|
||
- Schema-validation tests use the `TunnelParams` deserializer as the
|
||
enum validator (same deny-unknown/enum posture as the registry's
|
||
JSON Schema check; no jsonschema dep added for v1 — the
|
||
input_schema string is asserted structurally in
|
||
`open_spec_matches_wire_md`).
|
||
- 6 unit tests: round-trip ×3 substrates, unknown-field rejection,
|
||
missing-field rejection, unknown-substrate rejection, spec-shape
|
||
conformance, schema enum validation.
|
||
|
||
## Summary
|
||
|
||
`src/params.rs` complete: the wire-stable `TunnelParams`/`Substrate`
|
||
types (ADR-001 exact shapes), `tunnel_open_spec()` (op id, Sub type,
|
||
External visibility, input/output schemas with the 3-value substrate
|
||
enum, `tunnel:open` ACL, channel-open marker, description),
|
||
constants, and the `establishment_reason` helper. Verified: `cargo
|
||
test` (6 passed), clippy --all-targets -D warnings (native + wasm32),
|
||
fmt --check, wasm32 check — all clean. |