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

8.4 KiB

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 §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 §3.1 (dead irpc dep removal)
  • Removal commit: 668d777 (2026-07-09)