- 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
6.9 KiB
id, name, status, depends_on, scope, risk, impact, level
| id | name | status | depends_on | scope | risk | impact | level | |
|---|---|---|---|---|---|---|---|---|
| implementation/core-params-errors | Core: params schema + scope constants + typed error surface | completed |
|
narrow | low | component | 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:
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 onSOCKS5_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 serdedeny_unknown_fields, empty) for the consumer's open call.
- Constants:
src/error.rs: the typed open-error surface (alkcall ADR-049 §4):SocksOpenError: the consumer-side open-failure vocabulary mapping thechannel:open_failedreasons (dial_failed/unknown_resource/resource_shortage/handler_error/timeout) plus the registry'sFORBIDDEN/INVALID_INPUTclasses, shaped on alktunnels'TunnelOpenError+establishment_reasonhelper.- The session-level error type for the consumer sessions
(
SocksSessionError-shaped: open errors vs in-streamSocksErrorvs 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).
- 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 testgreen; 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 structSocks5Params {}(serdedeny_unknown_fields, +Serializeso tests round-trip it;as_json()names the canonical wire form),socks5_open_spec()(Sub/External/channel-open marker onalk/socks5; input schema{type: object, additionalProperties: false}; reply schemachannel_idinteger minimum 1 required;required_scopes =["socks5:open"]), and theestablishment_reasonhelper re-export (the alktunnels posture, which delegates toChannelOpenError::establishment_reason`).src/error.rs:SocksOpenError(singleOpenvariant wrappingChannelOpenError, the same shape as alktunnels'TunnelOpenError; theFromimpl covers thechannel:open_failed/FORBIDDEN/INVALID_INPUTclasses), the typedSocksEstablishmentReasonvocabulary (ADR-049 §3's five codes,parse/as_str; unknown codes areNone, never silently aliased, since the vocabulary is one-way),SocksOpenError::reason()as the typed view, andSocksSessionError(open errors vs in-streamRfc(SocksError)vsIo; the RFC reply-code refusals ride the session type per consumer.md §Error surface).SocksError/SocksServerError/ReplyErrorre-exported fromrfc1928(SocksServerErrorlives inrfc1928::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 sendsjson!({}));SocksEstablishmentReasonis 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::SocksOpenErrorfromparamsare 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 idchannels/socks5/sub, ALPNalk/socks5, scopessocks5:open/socks5:bind, trivial empty-object input schema with loudadditionalProperties: false, reply{channel_id: integer, minimum 1}required).Socks5Params {}withdeny_unknown_fields: empty object valid, unknown fields rejected.error:SocksOpenErrormapping the ADR-049 §4 reasons (dial_failed/unknown_resource/resource_shortage/handler_error/timeout) through the typedSocksEstablishmentReason(unknown codes never aliased); the registry'sFORBIDDEN/INVALID_INPUTclasses correctly non-establishment;SocksSessionErrorshaped 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.