Files
alknet/tasks/integration/phase2/raw-framing-interface-implementation.md
glm-5.1 aafee72f4c Decompose Phase 2 (Core Bridge) into 8 dependency-ordered tasks
Phase 2 completes the interface-to-protocol bridge and adds core types
that external crates depend on. The 8 tasks are organized into 5
generations with clear dependencies:

- Gen 1: StreamInterface/MessageInterface trait split (must go first)
- Gen 2: SshSession bridge, RawFraming impl, CredentialProvider (parallel)
- Gen 3: API keys in DynamicConfig (depends on CredentialProvider)
- Gen 4: ListenerConfig HTTP/DNS stubs + axum scaffold
- Gen 5: Review gate before Phase 3

Key design decisions:
- 2.4a/2.4b split: SecretStoreCredentialProvider deferred to Phase 3
- API keys (2.6) must land before axum scaffold (2.7)
- ListenerConfig (2.5) must land before axum scaffold (2.7)
- Gen 2 tasks are parallelizable (separate modules)
2026-06-09 09:33:22 +00:00

5.1 KiB

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
raw-framing-interface-implementation Implement RawFramingInterface accept/recv/send with first-frame auth pending
stream-interface-message-interface-split
narrow low component implementation

Description

Implement RawFramingInterface and RawFramingSession to handle length-prefixed EventEnvelope frames over a byte stream, with first-frame authentication. Currently RawFramingInterface::accept() returns an error and RawFramingSession stubs exist.

Per the integration plan section 2.2 and interface.md:

RawFramingInterface: Reads 4-byte length-prefixed JSON EventEnvelope frames from a transport stream (TCP, TLS, iroh, etc.). No SSH wrapping — the raw framing interface carries call protocol events directly.

First-frame auth: The first InterfaceEvent on a RawFramingSession carries an auth token in the InterfaceEvent.identity field or a dedicated auth event type. After IdentityProvider::resolve_from_token() verifies the token and produces an Identity, the session is authenticated. Subsequent frames are call protocol EventEnvelope data. If auth fails, the session is terminated immediately.

Current state of the code:

  • RawFramingInterface accepts any TransportStream but returns an error
  • RawFramingSession is an empty struct with stub recv() (returns None) and send() (returns error)
  • call::frame::{encode, decode, decode_with_remainder} already implement the wire format
  • IdentityProvider::resolve_from_token() exists but is not yet wired to AuthToken verification (that's coming in the API keys task)

Implementation approach:

  1. RawFramingInterface::accept() takes a TransportStream, wraps it in a BufReader for buffered reading, stores it in RawFramingSession. The RawFramingSession is created in an "unauthenticated" state.
  2. RawFramingSession::recv() reads frames from the stream:
    • If unauthenticated: read the first frame, extract the auth token, call IdentityProvider::resolve_from_token(). On success, transition to "authenticated" with the resolved Identity. On failure, return an error (session terminated).
    • If authenticated: read EventEnvelope frames, wrap in InterfaceEvent::with_identity(envelope, identity).
  3. RawFramingSession::send() writes EventEnvelope frames to the stream using call::frame::encode.

The RawFramingSession needs:

  • A BufReader<Box<dyn TransportStream>> for reading framed data
  • A Box<dyn TransportStream> (or WriteHalf) for writing framed data
  • An Option<Identity> tracking auth state
  • A reference to IdentityProvider for token resolution
  • A buffer for partial frame reads (decode_with_remainder pattern)

Acceptance Criteria

  • RawFramingInterface::accept() takes a TransportStream and StreamInterfaceConfig::RawFraming config, creates a RawFramingSession wrapping the stream
  • RawFramingSession holds a buffered reader and writer over the transport stream, an auth state (Option<Identity>), and a reference to IdentityProvider
  • RawFramingSession::recv() reads length-prefixed EventEnvelope frames from the stream using call::frame::decode_with_remainder
  • First-frame auth: the first recv() call resolves the auth token via IdentityProvider::resolve_from_token() and stores the resulting Identity
  • Subsequent recv() calls produce InterfaceEvent::with_identity(envelope, identity) using the authenticated identity
  • Auth failure terminates the session: recv() returns an error result on bad tokens
  • RawFramingSession::send() writes EventEnvelope frames to the stream using call::frame::encode
  • Unit test: RawFramingInterface::accept() succeeds with a valid stream
  • Unit test: RawFramingSession round-trips an EventEnvelope through send() and recv() (after mock auth)
  • Unit test: First-frame auth with a valid token transitions to authenticated state
  • Unit test: First-frame auth with an invalid token returns an error
  • Integration test: RawFramingSession over a tokio::io::duplex stream (simulated TCP) sends and receives multiple frames

References

  • docs/research/integration-plan.md — Phase 2.2
  • docs/architecture/interface.md — RawFramingInterface, first-frame auth model
  • crates/alknet-core/src/interface/raw_framing.rs — Current stubs
  • crates/alknet-core/src/call/frame.rs — Frame encode/decode
  • crates/alknet-core/src/auth/identity.rs — IdentityProvider, resolve_from_token

Notes

The frame format is already implemented and tested in call::frame. This task is primarily about wiring the frame reader/writer to the InterfaceSession trait and adding first-frame auth logic.

Consider using tokio::io::BufReader for buffered reading and tokio::io::BufWriter for buffered writing. The decode_with_remainder function handles partial reads by returning how many bytes were consumed — the session needs to maintain a read buffer for reassembly.

The RawFramingInterface config should include an Arc<dyn IdentityProvider> for first-frame auth. This follows the same pattern as SshInterfaceConfig.

Summary

To be filled on completion