- Rewrote em dash prose across the whole tree outside docs/reviews/ (the historical reviews stay verbatim, matching the alktunnels and alktty precedent): src doc comments and inline comments (587), docs/architecture (319), docs/research + sdd_process (484), tests (361), tasks (899), fuzz (28), README/CHANGELOG/Cargo.toml (77), AGENTS.md (85), and the .opencode/agents prompts (109) - Fenced content follows the precedent: comments, ASCII-diagram label separators, and template placeholders rewritten; wire data, JSON/BAST documents, byte diagrams, and transcripts survive verbatim; assert, expect, panic, error-display, and format message literals rewritten as prose; no code, fixture, or test data change - src/rfc1928/LICENSE: the two dashes in alksocks' own provenance header rewritten; the MIT legal text untouched - AGENTS.md adds Project Convention 18: no em dashes in prose; varied restructure, no spaced-hyphen substitutes, fenced prose rule, en dash numeric ranges exempt, docs/reviews/ exempt from the greps - tasks/*.md frontmatter: the name values rewritten by the sweep are now quoted (a raw colon inside an unquoted YAML scalar parsed as null); taskgraph validate reports 37 tasks, no issues Verification: cargo test; cargo clippy --all-targets -D warnings; cargo fmt --check; cargo doc --no-deps; fuzz corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml); wasm check and clippy for wasm32-unknown-unknown with -D warnings; em dash grep over the repo returns matches only in docs/reviews/ and the bast.md BAST JSON data fence
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, i.e. 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, by 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 {}`, with 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, per 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, which 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, since 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, since 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. |