Files
alkcall/docs/architecture/decisions/014-irpc-never-integrated-hand-rolled-framing.md
T
glm-5.3-flash 7b8a3e7558 docs: spec-level sync with the single-publish fix + port-debt repair
The spec docs had not caught up with the stub-sweep fix commits
(4eae18f..3317ea0), and the ported docs still carried alknet
numbering that now collides with alkcall's own ADR range.

Spec sync with the landed code:
- call-protocol.md: register_imported/register_imported_all return
  Result and refuse marked specs; marked-spec registration refusal
  contract spelled out (ADR-047 am. 4 §4.8, ADR-022 am. 2026-10-13)
- client-and-adapters.md: from_call flow gains the Pub sink-forwarding
  branch and the marked-op refusal (resolves OQ-37 — no from_call
  relay wrapper; sanctioned compositions are
  ChannelRelay::register_relay_openable / ChannelClient)
- ADR-051: "channel/control is not implemented" stale sentence
  replaced with the ADR-052 per-leg control contract
- OQ-37 resolved; OQ-22 split (primitive resolved; broker placement
  out of scope) with new OQ-42 for the downstream hub broker
  (deferred(scope), concretely blocked); OQ-24 resolved by the shipped
  add/strip surface; OQ-41 → deferred(scope) with blocking condition
- README: OQ bullets renumbered to tracker IDs; alknet-collision note
  rewritten (range 001..053, alknet prefix convention); new principle
  11 (implement-or-stop-advertising); AGENTS.md ADR range 001..053

Port-debt repair:
- 149 broken decision links across 17 docs — ported with alknet
  numbers/paths, most now remapped to alkcall files by slug; the
  rest link the alknet repo per the README convention
- stale OQ numbering rewritten to tracker IDs in call-protocol.md,
  call-README.md, operation-registry.md, client-and-adapters.md,
  ADR-025, channels-overview/README/wire, channel-client
- unambiguous "alknet ADR-NNN" labels where an alknet number collides
  with an alkcall ADR (052 TTY vs channel/control, 033 storage vs
  rust-canonical, 025/026/027+020 vault vs PeerEntry/auth ADRs)

Verified: cargo test (697 + guard), fmt, clippy --all-targets -D
warnings; full markdown link scan clean.
2026-10-13 09:34:37 +00:00

164 lines
8.4 KiB
Markdown

# ADR-014: irpc Was Never Integrated — Hand-Rolled EventEnvelope Framing
## Status
Accepted
## Context
ADR-013 accepted "irpc as the call protocol foundation" based on the
previous architecture's use of irpc. When the call protocol was implemented,
it turned out that **no `.rs` file in the workspace ever imported irpc**.
The workspace `Cargo.toml` declared `irpc = "0.16"` / `irpc-derive = "0.16"`
as workspace dependencies, and `crates/alknet-call/Cargo.toml` declared
`irpc = { workspace = true }`, but the import was never written. The wire
protocol (`crates/alknet-call/src/protocol/wire.rs`) is hand-rolled
length-prefixed JSON — 4-byte big-endian length prefix + UTF-8 JSON body —
not an irpc service.
This was discovered when building an external app against the crates: the
`irpc 0.16` workspace dep was a version-gap blocker for `alknet-blobs` (which
pulls `irpc 0.17` transitively via `iroh-blobs 0.103`). A grep for any `irpc`
import in the workspace found zero hits — the dep was dead weight carried
over from the previous architecture without verification.
The framing, operation registry, dispatch, and subscription patterns that
ADR-013 attributed to irpc are all hand-rolled in alknet-call:
- **Framing**: `FrameFramedReader` / `FrameFramedWriter` in `wire.rs` —
length-prefixed JSON, hand-written against `tokio::io::AsyncRead`/
`AsyncWrite`. Not an irpc service.
- **Operation registry**: `OperationSpec`, `Handler`, `OperationRegistry`,
`AccessControl` — hand-rolled in alknet-call, not irpc's `Service` trait.
- **Event types**: `call.requested`, `call.responded`, `call.completed`,
`call.aborted`, `call.error` — the alknet call protocol's own event
vocabulary, not irpc's.
- **Subscription/streaming**: `StreamingHandler` / `invoke_streaming()`
(ADR-021) — hand-rolled, not irpc's streaming patterns.
The `EventEnvelope { type, id, payload }` shape was derived from the
`@alkdev/pubsub` TypeScript `EventEnvelope` (`/workspace/@alkdev/pubsub/src/
types.ts`), not from irpc. ADR-013's claim that "the wire format is irpc's
EventEnvelope framing" was wrong — irpc was never imported, and the envelope
shape has a different origin (the pubsub prior art, ADR-033). The framing
coincidentally resembles irpc's (both are length-prefixed JSON), which is
how the misattribution went unnoticed.
### What ADR-013 got right
Despite the irpc misattribution, ADR-013's *architectural* decisions are
correct and stand unchanged:
- The call protocol uses length-prefixed JSON `EventEnvelope` framing
(hand-rolled, not irpc-supplied).
- The wire format is cross-language and consumable from TypeScript, Python,
any language (JSON is inherently cross-language — ADR-013's "mitigated:
it's length-prefixed JSON" note was the load-bearing point, not the irpc
attribution).
- Operations use JSON Schema discovery. The `OperationSpec` shape is
hand-rolled, JSON-Schema-compatible — the same property ADR-013 attributed
to irpc, achieved without irpc.
### Why a new ADR rather than an amendment
ADR-013's Decision and Consequences are built on the premise "alknet-call
uses irpc as its foundation — irpc IS the call protocol's core." That
premise is false. Amending ADR-013 to say "actually it's hand-rolled" would
leave an ADR whose Context, Decision, and Consequences sections all argue
for a choice that was never made. The correct record is: ADR-013 is
superseded; the call protocol uses hand-rolled framing (this ADR-014); the
architectural properties ADR-013 sought (proven framing, cross-language
JSON, streaming) are preserved, but the mechanism is hand-rolled, not
irpc-sourced.
### The dead dep removal
The `irpc` / `irpc-derive` workspace deps and the `alknet-call` consumer dep
were removed in commit `668d777` (2026-07-09). `irpc` may be re-added as
`0.17` when `alknet-blobs` lands (it pulls `irpc 0.17` transitively via
`iroh-blobs 0.103`), but that would be a *transitive* dependency of
`alknet-blobs`, not a direct dependency of `alknet-call` — alknet-call does
not import irpc and has no plans to. See
[`docs/research/transport-generalization/findings.md`](/workspace/@alkdev/alknet/docs/research/transport-generalization/findings.md)
§3.1 for the removal trace.
## Decision
1. **ADR-013 is superseded.** The call protocol does not use irpc. irpc was
never imported by any `.rs` file in the workspace. The dead `irpc` /
`irpc-derive` workspace and crate deps are removed.
2. **The call protocol uses hand-rolled `EventEnvelope` framing.** The wire
format is length-prefixed JSON (4-byte big-endian length + UTF-8 JSON
body), implemented in `crates/alknet-call/src/protocol/wire.rs`. The
`EventEnvelope { type, id, payload }` shape was derived from the
`@alkdev/pubsub` TypeScript prior art (ADR-033), not from irpc. The
framing, operation registry, dispatch, and streaming patterns are all
hand-rolled in alknet-call.
3. **The architectural properties ADR-013 sought are preserved by the
hand-rolled implementation:**
- Proven framing — length-prefixed JSON is a well-understood,
battle-tested pattern; the hand-rolled implementation is tested (207
lib + 2 integration tests passing).
- Cross-language — JSON is inherently consumable from any language;
NAPI, WASM, and browser clients speak the same wire format.
- Streaming — `StreamingHandler` / `invoke_streaming()` (ADR-021) provide
the subscription/streaming patterns ADR-013 attributed to irpc,
hand-rolled.
4. **irpc is not a planned dependency for alknet-call.** If `alknet-blobs`
pulls irpc transitively, it will be a transitive dependency of that
crate, not a direct dependency of alknet-call. alknet-call's framing,
registry, and dispatch are hand-rolled and will remain so. The "mitigated:
irpc is lightweight and we can fork if needed" caveat in ADR-013 is moot
— there is nothing to fork because nothing was integrated.
5. **The vault's irpc drop (ADR-025) stands.** ADR-025 dropped irpc from
alknet-vault. With this ADR, irpc is also confirmed absent from
alknet-call. The vault and call decisions are now consistent: neither
crate uses irpc. The only difference is that ADR-025 *removed* a real
(but unused-for-its-primary-path) irpc dependency from the vault, while
this ADR records that alknet-call's irpc dependency was never integrated
at all — it was a Cargo.toml entry with no corresponding import.
## Consequences
**Positive:**
- The spec matches the code. ADR-013's irpc claims were a spec/code
divergence that surfaced only when the `irpc 0.16` version gap blocked
`alknet-blobs`. This ADR closes the divergence.
- `alknet-blobs` is unblocked — the dead `irpc 0.16` workspace dep is
gone; `iroh-blobs 0.103` (which pulls `irpc 0.17` transitively) no longer
conflicts with a workspace-pinned older irpc.
- The call protocol's framing, registry, and dispatch are documented
accurately as hand-rolled — readers of the spec aren't sent looking for
an irpc integration that doesn't exist.
- The cross-language story is unchanged: JSON wire format, JSON Schema
discovery. The mechanism changed (hand-rolled vs irpc), but the property
ADR-013 sought is preserved.
**Negative:**
- The call protocol does not inherit irpc's testing or production pedigree
for its framing. Mitigation: length-prefixed JSON is a trivial,
well-understood pattern; the hand-rolled implementation is tested; and
the framing is small enough to audit completely (~30 lines in `wire.rs`).
- ADR-013's claim that "the call protocol inherits irpc's streaming and
subscription patterns" was wrong — those patterns are hand-rolled
(ADR-021). The streaming implementation is younger and less battle-tested
than irpc's would have been, but it is also simpler and fully owned.
## References
- ADR-013: irpc as call protocol foundation (superseded by this ADR)
- ADR-025: Vault local-only dispatch (dropped irpc from the vault; this ADR
records irpc was never integrated into alknet-call either)
- ADR-033: Rust as canonical implementation (the `@alkdev/pubsub` prior art
that the `EventEnvelope` shape was actually derived from)
- ADR-021: Streaming handler for subscriptions (the hand-rolled streaming
dispatch path)
- Call protocol wire format: `crates/alknet-call/src/protocol/wire.rs`
- Transport generalization findings:
[`docs/research/transport-generalization/findings.md`](/workspace/@alkdev/alknet/docs/research/transport-generalization/findings.md)
§3.1 (dead `irpc` dep removal)
- Removal commit: `668d777` (2026-07-09)