- params: the ADR-001/ADR-006 constants (`channels/socks5/sub`,
`alk/socks5`, `socks5:open`, `socks5:bind`), `socks5_open_spec()`
(Sub/External/channel-open marker; trivial empty-object input schema
with loud `additionalProperties: false`; reply
`{channel_id: integer, minimum 1}` required; `socks5:open` scope),
`Socks5Params {}` (deny_unknown_fields — empty stays valid forever),
the `establishment_reason` helper re-export.
- error: `SocksOpenError` (the ADR-049 §4 typed surface, shaped on
alktunnels' `TunnelOpenError`), the typed
`SocksEstablishmentReason` vocabulary (the five reason codes,
parse/as_str; unknown codes never aliased), `SocksOpenError::reason()`
as the typed view, and `SocksSessionError` (open vs in-stream `Rfc`
vs `Io` — a refused target is an RFC reply code through the session
type, consumer.md §Error surface). `SocksError`/`SocksServerError`/
`ReplyError` re-exported from the forked core.
- lib.rs re-exports the new public surface (convention 15).
- 9 new tests: spec shape, constants, serde round-trip +
unknown-field rejection, the reason mapping table, the
non-establishment classes (`FORBIDDEN`/`INVALID_INPUT`/
`too_many_channels`), future-reason non-aliasing, the three-plane
session surface.
- task `implementation/core-params-errors`: status → completed;
Notes + Summary filled.
Verification: cargo test (104 green), clippy --all-targets -D
warnings clean, fmt --check clean, doc --no-deps zero warnings,
both wasm checks (check + clippy on wasm32-unknown-unknown) clean.
151 lines
6.9 KiB
Markdown
151 lines
6.9 KiB
Markdown
---
|
|
id: implementation/core-params-errors
|
|
name: Core — params schema + scope constants + typed error surface
|
|
status: completed
|
|
depends_on: [implementation/core-crate-deps]
|
|
scope: narrow
|
|
risk: low
|
|
impact: component
|
|
level: implementation
|
|
---
|
|
|
|
## Description
|
|
|
|
Implement `src/params.rs` and `src/error.rs` per wire.md §The Open
|
|
Op, ADR-001, and ADR-006 — the wire-stable surface (the one-way door)
|
|
and the typed open-error vocabulary. Template:
|
|
`/workspace/@alkdev/alktunnels/src/params.rs` (the sibling's spec
|
|
builder + scope-constant posture).
|
|
|
|
Deliverables:
|
|
|
|
1. **`src/params.rs`**:
|
|
- Constants: `OP_SOCKS5_OPEN = "channels/socks5/sub"`,
|
|
`SOCKS5_ALPN = "alk/socks5"`, `SOCKS5_OPEN_SCOPE =
|
|
"socks5:open"`, `SOCKS5_BIND_SCOPE = "socks5:bind"` (ADR-001/
|
|
ADR-006; the scope-string convention `<alpn-family>:
|
|
<capability>`; stable once published).
|
|
- `socks5_open_spec() -> OperationSpec` — `Sub`-typed, external,
|
|
channel-open marker on `SOCKS5_ALPN`, **trivial params**:
|
|
input schema `{type: object}`, `additionalProperties: false`
|
|
(empty params stay valid forever — ADR-001's additive-only
|
|
extension rule); reply schema `{"channel_id": integer,
|
|
minimum 1}`; `required_scopes = ["socks5:open"]`. Follow the
|
|
alktunnels spec-builder shape exactly (the registry validates
|
|
against these schemas).
|
|
- The trivial params struct (`Socks5Params {}` — serde
|
|
`deny_unknown_fields`, empty) for the consumer's open call.
|
|
2. **`src/error.rs`** — the typed open-error surface (alkcall
|
|
ADR-049 §4):
|
|
- `SocksOpenError` — the consumer-side open-failure vocabulary
|
|
mapping the `channel:open_failed` reasons (`dial_failed` /
|
|
`unknown_resource` / `resource_shortage` / `handler_error` /
|
|
`timeout`) plus the registry's `FORBIDDEN`/`INVALID_INPUT`
|
|
classes, shaped on alktunnels' `TunnelOpenError` +
|
|
`establishment_reason` helper.
|
|
- The session-level error type for the consumer sessions
|
|
(`SocksSessionError`-shaped: open errors vs in-stream
|
|
`SocksError` vs I/O) — the exact variant set is the task's
|
|
judgment; keep it faithful to what the sessions actually
|
|
surface (a refused session never yields a phantom channel; a
|
|
refused target is an RFC reply code surfaced through the
|
|
session type — consumer.md §Error surface).
|
|
3. **lib.rs re-exports** for the new public surface (convention 15).
|
|
|
|
Tests: spec-builder shape assertions (op id, ALPN marker, scopes,
|
|
schemas — the trivial-params schema rejects unknown fields), params
|
|
serde round-trip (empty object valid; unknown field rejected), the
|
|
error-reason mapping table.
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] Constants + spec builder per ADR-001/ADR-006 exactly (op id,
|
|
ALPN, both scopes, trivial schemas)
|
|
- [ ] Input schema rejects unknown fields (the loud-rejection
|
|
posture)
|
|
- [ ] Typed open-error surface maps the ADR-049 §4 reasons
|
|
- [ ] lib.rs re-exports the public surface
|
|
- [ ] `cargo test` green; clippy/fmt clean; both wasm checks clean
|
|
|
|
## References
|
|
|
|
- docs/architecture/wire.md §The Open Op (the normative op shape)
|
|
- docs/architecture/decisions/001-alpn-and-open-op.md (trivial
|
|
params; the one-way door)
|
|
- docs/architecture/decisions/006-access-control.md (the two scopes)
|
|
- docs/architecture/decisions/011-inline-vendor.md §4 (the typed
|
|
open-error surface)
|
|
- Template: `/workspace/@alkdev/alktunnels/src/params.rs`
|
|
(`tunnel_open_spec`, the scope constants, `establishment_reason`)
|
|
- POC sketch: `/workspace/alksocks-connect-poc/src/params.rs`
|
|
|
|
## Notes
|
|
|
|
> Filled by the implementation agent (2026-09-23).
|
|
|
|
- `src/params.rs`: the four wire-stable constants (`OP_SOCKS5_OPEN`,
|
|
`SOCKS5_ALPN`, `SOCKS5_OPEN_SCOPE`, `SOCKS5_BIND_SCOPE`), the
|
|
trivial-params struct `Socks5Params {}` (serde
|
|
`deny_unknown_fields`, + `Serialize` so tests round-trip it;
|
|
`as_json()` names the canonical wire form), `socks5_open_spec()`
|
|
(Sub/External/channel-open marker on `alk/socks5`; input schema
|
|
`{type: object, additionalProperties: false}`; reply schema
|
|
`channel_id` integer minimum 1 required; `required_scopes =
|
|
["socks5:open"]`), and the `establishment_reason` helper re-export
|
|
(the alktunnels posture — delegates to
|
|
`ChannelOpenError::establishment_reason`).
|
|
- `src/error.rs`: `SocksOpenError` (single `Open` variant wrapping
|
|
`ChannelOpenError` — the same shape as alktunnels'
|
|
`TunnelOpenError`; the `From` impl covers the
|
|
`channel:open_failed`/`FORBIDDEN`/`INVALID_INPUT` classes), the
|
|
typed `SocksEstablishmentReason` vocabulary (ADR-049 §3's five
|
|
codes, `parse`/`as_str`; unknown codes are `None` — never silently
|
|
aliased, the vocabulary is one-way), `SocksOpenError::reason()` as
|
|
the typed view, and `SocksSessionError` (open errors vs in-stream
|
|
`Rfc(SocksError)` vs `Io`; the RFC reply-code refusals ride the
|
|
session type per consumer.md §Error surface). `SocksError`/
|
|
`SocksServerError`/`ReplyError` re-exported from `rfc1928`
|
|
(`SocksServerError` lives in `rfc1928::server`, not the module
|
|
root).
|
|
- lib.rs re-exports both modules' public surface (convention 15).
|
|
- Deliberate deviations from the task text: the params struct
|
|
derives `Serialize` (test round-trip needs it; harmless on the
|
|
wire — the consumer sends `json!({})`); `SocksEstablishmentReason`
|
|
is a separate named type rather than an inline match so the
|
|
mapping table is testable in both directions; unknown reason codes
|
|
do NOT alias a variant (one-way vocabulary discipline).
|
|
- rustdoc links to `error::SocksOpenError` from `params` are
|
|
fully-qualified (`crate::error::…`) — plain `[SocksOpenError]`
|
|
does not resolve cross-module in this crate's layout.
|
|
|
|
## Summary
|
|
|
|
> Filled on completion (2026-09-23).
|
|
|
|
Implemented the wire-stable open-op surface and the typed
|
|
open-error vocabulary:
|
|
|
|
- `params`: constants + `socks5_open_spec()` per ADR-001/ADR-006
|
|
exactly (op id `channels/socks5/sub`, ALPN `alk/socks5`, scopes
|
|
`socks5:open`/`socks5:bind`, trivial empty-object input schema with
|
|
loud `additionalProperties: false`, reply
|
|
`{channel_id: integer, minimum 1}` required).
|
|
- `Socks5Params {}` with `deny_unknown_fields` — empty object valid,
|
|
unknown fields rejected.
|
|
- `error`: `SocksOpenError` mapping the ADR-049 §4 reasons
|
|
(`dial_failed`/`unknown_resource`/`resource_shortage`/
|
|
`handler_error`/`timeout`) through the typed
|
|
`SocksEstablishmentReason` (unknown codes never aliased); the
|
|
registry's `FORBIDDEN`/`INVALID_INPUT` classes correctly
|
|
non-establishment; `SocksSessionError` shaped open-vs-Rfc-vs-Io
|
|
per consumer.md §Error surface.
|
|
- 9 new tests (spec shape, constants, serde round-trip + unknown-field
|
|
rejection, the reason mapping table, non-establishment classes,
|
|
future-reason non-aliasing, the three-plane session surface).
|
|
- lib.rs re-exports the public surface.
|
|
|
|
Verification: `cargo test` (104 green: 67 lib + 37 rfc1928 + 0
|
|
doc), `cargo clippy --all-targets -- -D warnings` clean,
|
|
`cargo fmt --check` clean, `cargo doc --no-deps` zero warnings, both
|
|
wasm checks (`cargo check` + `cargo clippy` on
|
|
`wasm32-unknown-unknown`) clean. |