Design session outcome for the relay unit. The hub/spoke family (hub
or spoke may relay; the hub re-exposes spoke services by ACL without
binding ports) needs the call-half support too, so the unit's scope
grew beyond the as-pinned sketch and is now three sub-units.
- ADR-051 (new): the relay is the wrapper composition — translate hop
= the ADR-049 establisher shape, byte-forward hop = pump_bidi per
ADR-050; the relay holds Arc<CallConnection> + ChannelManager per
producer leg (not a ChannelClient — take_call_connection's detach
is wrong for a hub with three CallConnection claimants); the reason
mapping preserves the spoke's code+message (timeout is the one
non-1:1 case, mapping to dial_failed); the registration seam is
two-phase (discover/stash → per-connection fork-register — the
ADR-047 §4 fork mechanism makes a one-call import impossible); the
ADR-042 relay map dissolves (implicit per-channel mapping) and
channel/close needs no translation surface (EOF cascade propagates
with correct ledger accounting on both legs); Pub-typed marked
specs are a loud assembly error; establishment bounds compound per
hop (noted, no fix); the ACL layering note is pinned (the spoke's
AccessControl sees only the hub identity; forwarded_for is never
checked).
- ADR-042 amended: the §Scope note is revised (implementation is an
alkcall export; hub crates compose it) and the two mechanism
supersessions are recorded — the contract and auth-model rationale
unchanged.
- ADR-047 amendment 3's forward reference and the README ADR index
updated (001..051).
- Review 008 remediation plan: Unit 3 split into 3a (ChannelRelay
component + gates), 3b (hub-leg install template — the call-half
support), 3c (gate-2 e2e incl. the mid-establishment disconnect
window); sequencing note updated; adoption note records the
session's decisions.
Verified: cargo doc --no-deps (markdown-only change).
- architecture README: index rows for ADR-049 (establishment phase) and
ADR-050 (pump_bidi); stale ADR-001..045 range labels corrected
- channel-operations.md: design-decision table + references list the
two new ADRs
- CHANGELOG: missing link references for 0.4.0/0.4.1/0.5.0/0.6.0
- AGENTS.md: ADR range 001..050
- Remove alktype from Cargo.toml (its only usage was a thin wrapper
over jsonschema::options().build())
- Replace alktype::validation::build_validator with direct jsonschema
in dispatch.rs
- Add docs/architecture/chunk-header.bast.json — the chunk header's
BAST (Binary Abstract Syntax Tree) machine-readable wire spec
- Embed as channels::wire::CHUNK_HEADER_BAST via include_str! so
downstream Rust crates can consume it without a file lookup
- Add test asserting the embedded BAST doc is valid JSON and matches
the wire format
- Update AGENTS.md §10 and implementation-specialist.md: BAST docs
are the contract; trivial/hot-path formats stay hand-rolled,
complex formats use the alktype engine or codegen
Verification:
- cargo test: 543 passed, 0 failed
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
- cargo doc --no-deps: clean
- BAST doc compiles + round-trips against alktype v0.2.0 engine
- docs/architecture/README.md: add Roles and Composition section with
the four roles (producer, consumer, hub, spoke), dependency layering
diagram, protocol crate pattern, and the two-path adapter model
- docs/architecture/channels-overview.md: fix stale alknet ADR numbers
(071/072/073/074/075/076/077/078/079/080/081 -> 034-044), update
relationship section for post-extraction world, update crate
dependencies to reflect single-crate alkcall
- src/lib.rs: add Downstream composition section with role table and
protocol crate pattern, linking to the architecture README
Verification: cargo test (483 passed), cargo clippy (clean),
cargo fmt (clean), cargo doc (no warnings)
Conventions satisfied, `cargo doc` clean, no actively-wrong comments,
producer/consumer naming consistent in the call/registry docs.
P-10 — `pump_sink` matched the string literals "call.published",
"call.completed", "call.aborted" (dispatch.rs) instead of the
EVENT_PUBLISHED / EVENT_COMPLETED / EVENT_ABORTED constants the rest
of the file imports. Replaced with the constants — pure refactor
hazard, no behavior change.
C-20 remainder — the wrong "SAFETY:" comment at from_call.rs:271
marked no `unsafe` block and was factually wrong (described a `'static`
return that isn't what `derive_alpn_from_op_name` does — it returns
`Option<String>`; the leak happens in `leak_alpn`). Reworded to a
plain note about the `'static` lifetime requirement. Also reworded
the abort-cancels claims in the `pump_sink` and `pump_stream` doc
comments (dispatch.rs): both claimed `call.aborted` "cancels the task
and drops the handler future" — the handler is actually `join!`-ed to
completion and not yet cancelled (the abort-cancels-Pub mechanism is
review 001 Unit 9). Trimmed step-numbered narration comments in
adapter.rs / client.rs that restated what the code does, keeping the
ordering-constraint and REQ-CH comments. The big reassembly.rs
deliberation landed with Unit 4; this finishes the remainder.
C-09 — fixed the 2 remaining `cargo doc` warnings (was 4; the
register_openable links were fixed in Unit 3):
- `unresolved link to default_policy` (operations.rs:50) — the
[`default_policy`] intra-doc link resolves to
super::policy::default_policy; used the full path.
- `env is both a module and a macro` (channels/mod.rs:30) — the
[`env`] link collided with the std `env!` macro; qualified as
[`self::env`].
`cargo doc --no-deps` now emits 0 warnings.
C-22 remainder — removed the filler `PhantomData` test at client.rs
(`let _ = std::marker::PhantomData::<ChannelClient>;` — asserts
nothing). The env.rs tautology was already removed in Unit 3.
C-24 — replaced "client→server streaming" with "producer→consumer
streaming" in call-protocol.md, operation-registry.md, README.md, and
open-questions.md (4 occurrences). Per AGENTS.md §8 the convention is
producer/consumer, not server/client. The remaining "client→server"
references in channels ADRs 034/037 are in stream_type table contexts
that Unit 10 (C-26) will handle as part of the spec-doc renumbering.
P-11 — amended ADR-046 §3's SinkHandler type so the stream item type
matches §6. §3 declared `Pin<Box<dyn Stream<Item = Value> + Send>>`;
§6 declared `Pin<Box<dyn Stream<Item = Result<Value, CallError>> +
Send>>`. The code uses §6's shape uniformly (registration.rs:32-40,
aliased as PublishStream). §3's text and the Door-type section are
amended to match §6; the Door-type section already marked the concrete
stream item type a two-way-door detail, so this is a text correction,
not a design change. Added an amendment note dated 2026-08-13.
Verification:
- cargo test --lib → 449 passed, 0 failed (was 450; -1 removed filler test)
- cargo clippy --all-targets -- -D warnings → clean
- cargo fmt --check → clean
- cargo doc --no-deps → 0 warnings (was 2)
The call protocol had Subscription (server→client streaming) but lacked
the directional complement: client→server streaming, where the initiator
produces a stream and the responder's handler consumes it. This gap was
inherited from the @alkdev/pubsub EventEnvelope prior art, which has
subscribe but no wire-level publish.
ADR-046 adds the Pub primitive:
- OperationType::Pub (client→server streaming)
- OperationType::Subscription renamed to Sub (wire: "subscription" → "sub")
- SinkHandler type + HandlerKind::Sink variant
- PublishStream type alias (Stream<Item = Result<Value, CallError>>)
- OperationRegistry::invoke_sink() dispatch path
- call.published wire event (sixth event type, additive)
- OperationSpec.publish_schema (Option<Value>, validates per-chunk input)
- DispatchResult::Sink + SinkDispatch (handler future + chunk channel)
- Dispatcher::pump_sink (feeds call.published chunks from wire to handler)
- CallConnection::publish() / publish_with_payload() client methods
- from_call sink forwarding handler (make_sink_forwarding_handler)
- make_sink_handler() helper
Fan-out/broker (one producer, N consumers, topic matching) is deferred to
the channels session — the call protocol is point-to-point; the broker is
a routing concern that sits above it. The Pub primitive is the
load-bearing piece the broker will compose on.
- 23 new tests (366 total, up from 343)
- clippy clean, fmt clean
Verification:
cargo test — 366 passed
cargo clippy --all-targets -- -D warnings — clean
cargo fmt --check — clean