Skip to content

CodecError taxonomy, EOF handling, recovery policies, hooks, metrics#417

Merged
leynos merged 12 commits intomainfrom
terragon/introduce-codecerror-taxonomy-3abr1i
Jan 8, 2026
Merged

CodecError taxonomy, EOF handling, recovery policies, hooks, metrics#417
leynos merged 12 commits intomainfrom
terragon/introduce-codecerror-taxonomy-3abr1i

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Jan 6, 2026

Summary

  • Adds a structured CodecError taxonomy (FramingError, ProtocolError, IoError, EofError) with recovery policies, EOF handling, and observability. Introduces supporting infrastructure (RecoveryPolicyHook, CodecErrorContext, RecoveryConfig), extends error surfaces, updates EOF handling, and wires in metrics and protocol hooks. Includes tests and documentation updates, with backward-compatible surfacing of codec errors in existing error paths.

Changes

  • New modules
    • src/codec/error.rs: Introduces the CodecError taxonomy and helpers:
      • FramingError (e.g., OversizedFrame, InvalidLengthEncoding, IncompleteHeader, ChecksumMismatch, EmptyFrame)
      • ProtocolError (e.g., MissingHeader, UnsupportedVersion, UnknownMessageType, SequenceViolation, InvalidStateTransition)
      • EofError (CleanClose, MidFrame, MidHeader)
      • CodecError ( Framing | Protocol | Io | Eof )
      • default_recovery_policy, is_clean_close, should_disconnect, error_type
      • From for io::Error
    • src/codec/recovery.rs: Adds recovery infrastructure
      • RecoveryPolicy (Drop, Quarantine, Disconnect)
      • CodecErrorContext
      • RecoveryPolicyHook trait
      • DefaultRecoveryPolicy and RecoveryConfig
  • Core codec integration
    • src/codec.rs:
      • pub mod error;
      • pub mod recovery;
      • pub use error::{CodecError, EofError, FramingError, ProtocolError};
      • pub use recovery::{CodecErrorContext, DefaultRecoveryPolicy, RecoveryConfig, RecoveryPolicy, RecoveryPolicyHook};
    • Updated LengthDelimitedDecoder in same file to produce structured EOF errors:
      • decode_eof now returns CodecError::Eof with CleanClose, MidHeader, or MidFrame
      • Added build_eof_error helper and conversion to io::Error
    • LengthDelimitedEncoder: on oversize, return CodecError::Framing(FramingError::OversizedFrame …) and convert to io::Error
  • Wireframe error surface and compatibility
    • src/response.rs: Extend WireframeError to include Codec(CodecError)
    • Implement From for WireframeError and is_clean_close helper
    • Update Display and source error mappings to include codec errors
  • App error surface
    • src/app/error.rs: Add Codec(CodecError) variant and From
    • Documentation and error propagation adjusted accordingly
  • Protocol hooks and lifecycle
    • src/hooks.rs: Introduce on_eof hook in ProtocolHooks
    • ProtocolHooks::from_protocol now wires an on_eof callback to protocol implementation
  • Metrics and observability
    • src/metrics.rs: Add CODEC_ERRORS counter name and inc_codec_error(error_type, recovery_policy)
    • Wirecodec error events into metrics when feature is enabled
  • Public API re-exports
    • src/lib.rs: Re-export CodecError, CodecErrorContext, DefaultRecoveryPolicy, EofError, FramingError, LengthDelimitedFrameCodec, MAX_FRAME_LENGTH, MIN_FRAME_LENGTH, ProtocolError, RecoveryConfig, RecoveryPolicy, RecoveryPolicyHook
  • Tests
    • tests/codec_error.rs: New integration tests for taxonomy and recovery policy behavior
    • tests/features/codec_error.feature: New Cucumber feature for codec errors
    • tests/steps/codec_error_steps.rs and tests/worlds/codec_error.rs: New BDD scaffolding
    • tests/cucumber.rs: Register new CodecErrorWorld in runner
    • tests/features/codec_error.feature: New Cucumber feature for codec errors
    • tests/steps/codec_error_steps.rs and tests/worlds/codec_error.rs: New BDD scaffolding
  • Documentation
    • docs/users-guide.md: Add section on codec error handling, taxonomy, recovery hooks, EOF handling, and metrics
    • docs/adr-004-pluggable-protocol-codecs.md: Document CodecError taxonomy and recovery decisions
    • docs/roadmap.md: Mark 9.1.2 items as done

Why this is needed

  • Enables structured, policy-driven handling of framing, protocol, and IO errors
  • Allows fine-grained recovery strategies (Drop, Quarantine, Disconnect) per error type
  • Improves observability with dedicated metrics for codec errors and error context
  • Supports EOF scenarios with clear surface (mid-frame vs clean close) and protocol hooks
  • Provides a foundation for behavioural tests (unit, integration, cucumber) around codec errors

Testing plan

  • Unit tests for CodecError taxonomy:
    • default_recovery_policy, is_clean_close, should_disconnect, error_type, From for io::Error
  • Encoder/decoder tests for EOF handling and error propagation
  • Integration tests in tests/codec_error.rs to verify taxonomy and recovery policy application
  • Cucumber behavioural tests in tests/features/codec_error.feature with corresponding world and steps
  • Validation gates include fmt, lint, markdownlint, and tests as part of CI

Backwards compatibility and migrations

  • New types and variants are additive; existing error paths remain intact
  • WireframeError now surfaces codec errors via a Codec variant; From and conversions implemented for seamless integration
  • No changes required to existing call-sites unless they opt into codec error handling paths

How to test locally

  • Build and run tests with metrics enabled to exercise inc_codec_error
  • Run unit tests: cargo test
  • Run integration tests: cargo test --test codec_error
  • Run cucumber tests if you have the test harness: cargo test --features cucumber

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragon-labs.com/task/58f26ea3-eb37-43d9-8805-f2bb4680cc01

Summary by Sourcery

Introduce a structured codec error taxonomy with recovery policies, EOF handling, and observability, and surface these errors consistently through public APIs.

New Features:

  • Add CodecError taxonomy (framing, protocol, I/O, EOF) with default recovery policies and context metadata for error handling.
  • Expose recovery policy configuration and hooks to customize how codec errors are handled per connection.
  • Provide protocol-level EOF hooks to allow protocols to react to clean and premature connection closures.
  • Add metrics support for codec errors, tagged by error type and recovery policy, and re-export codec-related types in the public API.

Enhancements:

  • Update length-delimited encoder/decoder to emit structured codec and EOF errors instead of generic I/O errors.
  • Extend WireframeError types in app and response layers to include codec errors and helpers such as clean-close detection.
  • Document codec error handling, recovery policies, EOF hooks, and the finalized design in the users guide, ADR, and execution plan, and mark the roadmap item as complete.

Documentation:

  • Document codec error handling, taxonomy, recovery hooks, EOF behaviour, and metrics in the user guide and ADR, plus a detailed execution plan for the roadmap item.

Tests:

  • Add integration, unit, and Cucumber tests to validate the codec error taxonomy, recovery policies, EOF handling, and error propagation through WireframeError surfaces.

📎 Task: https://www.terragon-labs.com/task/58f26ea3-eb37-43d9-8805-f2bb4680cc01

📎 Task: https://www.terragon-labs.com/task/58f26ea3-eb37-43d9-8805-f2bb4680cc01

📎 Task: https://www.terragonlabs.com/task/58f26ea3-eb37-43d9-8805-f2bb4680cc01

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 6, 2026

Walkthrough

Introduce a CodecError taxonomy (Framing, Protocol, IO, EOF), add a pluggable RecoveryPolicy (Drop, Quarantine, Disconnect) with CodecErrorContext and hooks, surface codec errors through wireframe/app error types, add WireframeProtocol::on_eof and CODEC_ERRORS metric, and add tests and docs.

Changes

Cohort / File(s) Summary
Documentation Updates
docs/adr-004-pluggable-protocol-codecs.md, docs/execplans/9-1-2-introdu-a-codec-error-taxonomy.md, docs/roadmap.md, docs/users-guide.md
Add resolved decisions, detailed user guide for codec error handling, recovery policy examples, on_eof guidance and metrics notes; mark roadmap items complete.
Codec Error Taxonomy
src/codec/error.rs, src/codec.rs
Add FramingError, ProtocolError, EofError, top-level CodecError; implement classification helpers, IO conversion (From<CodecError> for io::Error), default recovery policy, and re-export error types and frame-length constants.
Recovery Framework
src/codec/recovery.rs
Add RecoveryPolicy enum, CodecErrorContext, RecoveryPolicyHook trait, DefaultRecoveryPolicy, and RecoveryConfig with builder-style API and defaults.
Length-Delimited Codec
src/codec.rs, src/codec/tests.rs
Expose LENGTH_HEADER_SIZE, MIN_FRAME_LENGTH, MAX_FRAME_LENGTH; add max_frame_length to encoder; refine decode_eof to emit structured EofError (header vs payload) and adjust tests for EOF and oversize handling.
Wireframe / App Error Surface
src/response.rs, src/app/error.rs
Add Codec(crate::codec::CodecError) variants to WireframeError and SendError; add from_codec and is_clean_close helpers; adjust derives and Display/Error impls to surface codec errors.
Protocol Hooks
src/hooks.rs
Add WireframeProtocol::on_eof(&EofError, &[u8], &mut ConnectionContext) with default no-op; introduce OnEofHook alias and store/invoke ProtocolHooks.on_eof to delegate EOF handling to protocol implementations.
Observability / Metrics
src/metrics.rs, src/lib.rs
Add CODEC_ERRORS metric name and inc_codec_error(error_type, recovery_policy) function (no-op when metrics disabled); re-export codec error and recovery types and CODEC_ERRORS from crate root.
Integration & Unit Tests
tests/codec_error.rs, src/codec/error_tests.rs, src/codec/tests.rs
Add integration and unit tests covering CodecError variants, recovery defaults, IO conversions, decode_eof scenarios, and display/type expectations.
Behavioural Tests (Cucumber)
tests/features/codec_error.feature, tests/steps/codec_error_steps.rs, tests/steps/mod.rs
Add feature scenarios for clean EOF, mid-frame EOF, oversized frames, and recovery defaults; implement step handlers and register them.
Test Worlds / Helpers
tests/world.rs, tests/worlds/mod.rs, tests/worlds/codec_error/mod.rs, tests/worlds/codec_error/decoder_ops.rs, tests/cucumber.rs
Add CodecErrorWorld, enums and helpers to simulate framing/EoF variants, decoder ops for sending partial/complete frames, EOF classification, oversized-frame simulation and verification; register world in cucumber runner.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Decoder as LengthDelimitedDecoder
  participant Protocol as WireframeProtocol
  participant Recovery as RecoveryPolicyHook
  participant Manager as ConnectionManager
  participant Metrics as Metrics

  Client->>Decoder: send bytes / close
  Decoder-->>Decoder: parse header and payload
  alt complete frame parsed
    Decoder->>Protocol: deliver frame
  else EOF or codec error detected
    Decoder->>Protocol: on_eof(error, partial_data, ctx)
    Decoder->>Recovery: recovery_policy(&CodecError, &CodecErrorContext)
    Recovery-->>Manager: return policy (Drop/Quarantine/Disconnect)
    Manager->>Metrics: inc_codec_error(error_type, policy)
    Manager-->>Client: apply policy (drop/quarantine/disconnect)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🧭 Frames, faults and tidy rules unite,
A taxonomy born for day and night,
Hooks call the protocol when EOF draws near,
Policies decide: drop, quarantine, or clear,
Metrics hum as tests keep watchful sight.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title accurately summarises the main change: introduction of a CodecError taxonomy with EOF handling, recovery policies, and metrics infrastructure.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing new modules, core codec integration, error surfaces, hooks, metrics, tests, and documentation updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch terragon/introduce-codecerror-taxonomy-3abr1i

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 6, 2026

Reviewer's Guide

Introduce a structured CodecError taxonomy with associated recovery policies, wire it through the codec, app, and response error surfaces, add EOF-specific handling and protocol hooks, expose metrics and public re-exports, and cover the behavior with integration, BDD, and documentation updates.

Sequence diagram for EOF handling and on_eof protocol hook

sequenceDiagram
    actor Peer
    participant TcpStream
    participant LengthDelimitedDecoder
    participant InnerLengthCodec as LengthDelimitedCodec
    participant ConnectionActor
    participant ProtocolHooks
    participant WireframeProtocol

    Peer->>TcpStream: Close TCP connection
    TcpStream->>LengthDelimitedDecoder: decode_eof(src)

    alt buffer_is_empty
        LengthDelimitedDecoder-->>TcpStream: Ok(None)
        TcpStream-->>ConnectionActor: No_frame_clean_close
        ConnectionActor->>ProtocolHooks: on_eof(CleanClose, [], ctx)
        ProtocolHooks->>WireframeProtocol: on_eof(CleanClose, [], ctx)
    else buffer_not_empty
        LengthDelimitedDecoder->>InnerLengthCodec: decode_eof(src)
        alt inner_returns_frame
            InnerLengthCodec-->>LengthDelimitedDecoder: Ok(Some(frame))
            LengthDelimitedDecoder-->>TcpStream: Ok(Some(Bytes))
        else inner_returns_none_or_err
            InnerLengthCodec-->>LengthDelimitedDecoder: Ok(None) or Err
            LengthDelimitedDecoder->>LengthDelimitedDecoder: build_eof_error(src)
            LengthDelimitedDecoder-->>TcpStream: Err(io::Error from CodecError::Eof)
            TcpStream-->>ConnectionActor: Decode_error(io::Error)
            ConnectionActor->>ProtocolHooks: on_eof(EofError(MidHeader or MidFrame), partial_data, ctx)
            ProtocolHooks->>WireframeProtocol: on_eof(EofError(...), partial_data, ctx)
        end
    end
Loading

Class diagram for codec error taxonomy and recovery infrastructure

classDiagram
    class FramingError {
        <<enumeration>>
        +OversizedFrame(size: usize, max: usize)
        +InvalidLengthEncoding
        +IncompleteHeader(have: usize, need: usize)
        +ChecksumMismatch(expected: u32, actual: u32)
        +EmptyFrame
    }

    class ProtocolError {
        <<enumeration>>
        +MissingHeader(field: String)
        +UnsupportedVersion(version: u32)
        +UnknownMessageType(type_id: u32)
        +SequenceViolation(expected: u64, actual: u64)
        +InvalidStateTransition(from: String, to: String)
    }

    class EofError {
        <<enumeration>>
        +CleanClose
        +MidFrame(bytes_received: usize, expected: usize)
        +MidHeader(bytes_received: usize, header_size: usize)
    }

    class IoError {
    }

    class CodecError {
        +Framing(err: FramingError)
        +Protocol(err: ProtocolError)
        +Io(err: IoError)
        +Eof(err: EofError)
        +default_recovery_policy() RecoveryPolicy
        +is_clean_close() bool
        +should_disconnect() bool
        +error_type() str
    }

    class RecoveryPolicy {
        <<enumeration>>
        +Drop
        +Quarantine
        +Disconnect
    }

    class CodecErrorContext {
        +connection_id: Option~u64~
        +peer_address: Option~SocketAddr~
        +correlation_id: Option~u64~
        +codec_state: Option~String~
        +new() CodecErrorContext
        +with_connection_id(id: u64) CodecErrorContext
        +with_peer_address(addr: SocketAddr) CodecErrorContext
        +with_correlation_id(id: u64) CodecErrorContext
        +with_codec_state(state: String) CodecErrorContext
    }

    class RecoveryPolicyHook {
        <<interface>>
        +recovery_policy(error: CodecError, ctx: CodecErrorContext) RecoveryPolicy
        +quarantine_duration(error: CodecError, ctx: CodecErrorContext) Duration
        +on_error(error: CodecError, ctx: CodecErrorContext, policy: RecoveryPolicy) void
    }

    class DefaultRecoveryPolicy {
        +recovery_policy(error: CodecError, ctx: CodecErrorContext) RecoveryPolicy
        +quarantine_duration(error: CodecError, ctx: CodecErrorContext) Duration
        +on_error(error: CodecError, ctx: CodecErrorContext, policy: RecoveryPolicy) void
    }

    class RecoveryConfig {
        +max_consecutive_drops: u32
        +quarantine_duration: Duration
        +log_dropped_frames: bool
        +max_consecutive_drops(count: u32) RecoveryConfig
        +quarantine_duration(duration: Duration) RecoveryConfig
        +log_dropped_frames(enabled: bool) RecoveryConfig
    }

    class AppWireframeError {
        <<enum_like>>
        +DuplicateRoute(id: u32)
        +Codec(err: CodecError)
    }

    class ResponseWireframeError {
        <<enum_like>>
        +Io(err: IoError)
        +Protocol(err: E)
        +Codec(err: CodecError)
        +from_io(e: IoError) ResponseWireframeError
        +from_codec(e: CodecError) ResponseWireframeError
        +is_clean_close() bool
    }

    CodecError *-- FramingError
    CodecError *-- ProtocolError
    CodecError *-- EofError
    CodecError *-- RecoveryPolicy

    RecoveryPolicyHook <|.. DefaultRecoveryPolicy
    RecoveryPolicyHook ..> CodecError
    RecoveryPolicyHook ..> CodecErrorContext
    RecoveryPolicyHook ..> RecoveryPolicy

    RecoveryConfig ..> RecoveryPolicy

    AppWireframeError o-- CodecError
    ResponseWireframeError o-- CodecError

    CodecErrorContext ..> SocketAddr
    CodecError ..> IoError
Loading

File-Level Changes

Change Details Files
Add a structured codec error taxonomy and recovery infrastructure, including default recovery policies and conversions.
  • Introduce FramingError, ProtocolError, EofError, and CodecError enums with Display, recovery helpers, and error categorisation utilities.
  • Implement default_recovery_policy, is_clean_close, should_disconnect, and error_type helpers on CodecError.
  • Provide From for io::Error for compatibility with tokio codecs.
  • Add RecoveryPolicy, CodecErrorContext, RecoveryPolicyHook, DefaultRecoveryPolicy, and RecoveryConfig with builder-style configuration and tests.
src/codec/error.rs
src/codec/recovery.rs
tests/codec_error.rs
Integrate CodecError into the core codec implementation, including EOF handling and frame size validation.
  • Expose codec::error and codec::recovery submodules and re-export key types from src/codec.rs.
  • Clamp MIN_FRAME_LENGTH and MAX_FRAME_LENGTH as public constants and document header size.
  • Teach LengthDelimitedDecoder::decode_eof to distinguish clean close vs mid-header/mid-frame EOF using build_eof_error and LENGTH_HEADER_SIZE.
  • Update LengthDelimitedEncoder to return structured OversizedFrame framing errors, converted to io::ErrorKind::InvalidData, and adjust tests accordingly.
src/codec.rs
Surface codec errors through application and response error types with helpers for clean close detection.
  • Extend app::error::WireframeError with a Codec(CodecError) variant, implement From, and custom PartialEq/Eq.
  • Extend app::error::SendError with a Codec(CodecError) variant and From.
  • Extend response::WireframeError with a Codec(CodecError) variant, add from_codec and is_clean_close helpers, and update Display/source impls.
src/app/error.rs
src/response.rs
tests/codec_error.rs
Add protocol-level EOF hooks and wire them into ProtocolHooks to expose EOF conditions to protocols.
  • Extend WireframeProtocol with an on_eof(&EofError, &[u8], &mut ConnectionContext) hook and document usage.
  • Add an OnEofHook type alias and on_eof field to ProtocolHooks, with a runner method calling the hook when present.
  • Update ProtocolHooks::from_protocol to register the on_eof callback from a WireframeProtocol implementation.
src/hooks.rs
Add codec error metrics and re-export them through the public API.
  • Introduce CODEC_ERRORS metric name and inc_codec_error(error_type, recovery_policy) helper, gated on the metrics feature, with a no-op fallback.
  • Re-export CODEC_ERRORS and codec error types, recovery types, and frame length constants from lib.rs for external consumers.
src/metrics.rs
src/lib.rs
Add BDD/world scaffolding and integration tests for the codec error taxonomy and recovery behavior.
  • Add tests/codec_error.rs to exercise taxonomy semantics, recovery policies, EOF handling in the length-delimited codec, and WireframeError integration.
  • Introduce CodecErrorWorld plus supporting enums and methods to model error types/variants and expected recovery policies.
  • Add Cucumber steps and feature file for codec error scenarios (clean EOF, mid-frame EOF, oversized frame, default recovery policies) and register CodecErrorWorld in the cucumber runner and steps module.
tests/codec_error.rs
tests/worlds/codec_error.rs
tests/steps/codec_error_steps.rs
tests/features/codec_error.feature
tests/world.rs
tests/worlds/mod.rs
tests/steps/mod.rs
tests/cucumber.rs
Document the codec error taxonomy, recovery behavior, EOF hooks, and roadmap/ADR updates.
  • Extend the user guide with a codec error handling section covering error categories, recovery policies, custom RecoveryPolicyHook usage, EOF hooks, and metrics labels.
  • Update ADR-004 to move the CodecError taxonomy and recovery policy decision into a resolved decision with implementation notes.
  • Add an execution plan document for roadmap item 9.1.2 describing the intended design, steps, and validation for introducing CodecError and recovery policies, and mark roadmap item 9.1.2 and its sub-tasks as completed.
docs/users-guide.md
docs/adr-004-pluggable-protocol-codecs.md
docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
docs/roadmap.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 6, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

src/codec.rs

Comment on lines +299 to +309

    fn decode_eof_with_empty_buffer_returns_clean_close() {
        let codec = LengthDelimitedFrameCodec::new(128);
        let mut decoder = codec.decoder();
        let mut buf = BytesMut::new();

        let err = decoder
            .decode_eof(&mut buf)
            .expect_err("expected clean close error");
        assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
        assert!(err.to_string().contains("connection closed"));
    }

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: tests.decode_eof_with_empty_buffer_returns_clean_close,tests.decode_eof_with_partial_header_returns_mid_header

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 6, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix:

tests/codec_error.rs

Comment on lines +170 to +180

fn decoder_eof_empty_buffer_returns_clean_close() {
    let codec = LengthDelimitedFrameCodec::new(128);
    let mut decoder = codec.decoder();
    let mut buf = BytesMut::new();

    let err = decoder
        .decode_eof(&mut buf)
        .expect_err("expected clean close");
    assert_eq!(err.kind(), io::ErrorKind::UnexpectedEof);
    assert!(err.to_string().contains("connection closed"));
}

❌ New issue: Code Duplication
The module contains 3 functions with similar structure: decoder_eof_empty_buffer_returns_clean_close,decoder_eof_partial_header_returns_mid_header,decoder_eof_partial_payload_returns_mid_frame

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos leynos force-pushed the terragon/introduce-codecerror-taxonomy-3abr1i branch from 6bc921e to fd0a4a2 Compare January 7, 2026 01:53
@leynos leynos marked this pull request as ready for review January 7, 2026 01:59
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In build_eof_error you call src.get(..LENGTH_HEADER_SIZE) and then get_u32 on the result, but BytesMut::get returns a &[u8] (not a Buf), so this either won’t compile or won’t do what you expect; consider wrapping the slice in a Cursor or using Bytes::copy_from_slice and then Bytes::get_u32 to parse the length prefix safely.
  • There is quite a bit of duplicated EOF/oversized-frame behaviour testing between src/codec.rs tests and tests/codec_error.rs (e.g. assert_decode_eof_error and related cases); you may want to consolidate these helpers/tests into one place to avoid divergence over time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `build_eof_error` you call `src.get(..LENGTH_HEADER_SIZE)` and then `get_u32` on the result, but `BytesMut::get` returns a `&[u8]` (not a `Buf`), so this either won’t compile or won’t do what you expect; consider wrapping the slice in a `Cursor` or using `Bytes::copy_from_slice` and then `Bytes::get_u32` to parse the length prefix safely.
- There is quite a bit of duplicated EOF/oversized-frame behaviour testing between `src/codec.rs` tests and `tests/codec_error.rs` (e.g. `assert_decode_eof_error` and related cases); you may want to consolidate these helpers/tests into one place to avoid divergence over time.

## Individual Comments

### Comment 1
<location> `src/codec.rs:160-169` </location>
<code_context>
+fn build_eof_error(src: &BytesMut) -> io::Error {
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `Buf::get_u32` on a `&[u8]` slice will not compile; parse the length prefix without relying on `Buf` for slices.

`src.get(..LENGTH_HEADER_SIZE)` returns an `Option<&[u8]>`, so `let Some(mut header)` makes `header` a `&[u8]` that does not implement `Buf`; `header.get_u32()` will not compile. Instead, parse the length manually:

```rust
overflowed
let header = match src.get(..LENGTH_HEADER_SIZE) {
    Some(h) => h,
    None => {
        return CodecError::Eof(EofError::MidHeader {
            bytes_received,
            header_size: LENGTH_HEADER_SIZE,
        }).into();
    }
};
let expected = u32::from_be_bytes(header.try_into().expect("slice is 4 bytes")) as usize;
```

Or, if you prefer `Buf`, wrap the slice in a `Cursor` before calling `get_u32()`.
</issue_to_address>

### Comment 2
<location> `src/app/error.rs:29-38` </location>
<code_context>
+    Codec(#[from] CodecError),
 }

+impl PartialEq for WireframeError {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::DuplicateRoute(a), Self::DuplicateRoute(b)) => a == b,
+            // CodecError cannot be compared for equality (contains io::Error)
+            // All other combinations are not equal
+            _ => false,
+        }
+    }
+}
+
+impl Eq for WireframeError {}
+
 /// Errors produced when sending a handler response over a stream.
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The manual `PartialEq`/`Eq` impl only compares `DuplicateRoute`, making `Codec` variants never equal even when identical.

Because the manual impl now treats all non-`DuplicateRoute` variants as always-unequal (including identical `Codec` errors), any existing `assert_eq!` or equality-based logic on `WireframeError` will behave differently and potentially unexpectedly.

You could either:
- Remove `PartialEq`/`Eq` from `WireframeError` and rely on pattern matching, or
- Restrict equality to a test-only helper (e.g., feature-gated) if it’s primarily used in tests.

If you keep this impl, it’d be good to audit current equality usage to ensure this behavior is intentional.

Suggested implementation:

```rust
    #[error("codec error: {0}")]
    Codec(#[from] CodecError),
}

/// Errors produced when sending a handler response over a stream.
#[derive(Debug, Error)]
#[non_exhaustive]

```

1. Search for any usages of `WireframeError` in equality contexts (e.g., `assert_eq!(..., WireframeError::...)` or `if err1 == err2`).
2. Replace these with explicit pattern matching, for example:
   - `matches!(err, WireframeError::DuplicateRoute(id) if id == expected_id)`
   - or `matches!(err, WireframeError::Codec(_))` when only the variant matters.
3. If tests rely heavily on equality, consider adding a test-only helper under `#[cfg(test)]`, such as:
   ```rust
   #[cfg(test)]
   impl WireframeError {
       pub fn same_variant(&self, other: &Self) -> bool {
           use WireframeError::*;
           match (self, other) {
               (DuplicateRoute(a), DuplicateRoute(b)) => a == b,
               (Codec(_), Codec(_)) => true,
               _ => false,
           }
       }
   }
   ```
   and then use `assert!(err1.same_variant(&err2));` in tests instead of `assert_eq!`.
</issue_to_address>

### Comment 3
<location> `src/codec/error.rs:1` </location>
<code_context>
+//! Error types for the codec layer.
+//!
+//! This module provides a structured error taxonomy that distinguishes between
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural (Cucumber) tests that exercise the new codec error taxonomy and recovery behaviour end-to-end; the newly added feature files and step/world modules are currently empty.

Implement behavioural scenarios in `tests/features/codec_error.feature` and corresponding step/world code in `tests/steps/codec_error_steps.rs`, `tests/worlds/codec_error.rs`, and `tests/codec_error.rs` so they actually drive a Wireframe connection through framing, protocol, I/O, and EOF error cases.

You already have solid unit coverage in `src/codec/error.rs`, `src/codec/recovery.rs`, and `src/codec.rs::tests`, but the behavioural side is missing: the new files under `tests/` and `tests/features/` are empty, so no end-to-end assertions verify that recovery policies, hooks, and metrics are applied correctly in a running app.

Define concrete scenarios (e.g. oversized frame dropped, mid-frame EOF quarantine/disconnect, custom `RecoveryPolicyHook` override) and wire them into the existing Cucumber harness to satisfy the requirement for behavioural tests for this new feature.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural and unit tests are required.

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/codec.rs Outdated
Comment thread src/app/error.rs Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd0a4a27ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/codec.rs Outdated
Comment thread src/codec.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🤖 Fix all issues with AI agents
In @docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md:
- Around line 21-40: The progress checklist is out of date: update the remaining
unchecked items to checked since the PR implements the CodecError taxonomy and
related features; specifically mark the entries for creating src/codec/error.rs
(CodecError), src/codec/recovery.rs (recovery policy types/traits), extending
WireframeError (in src/response.rs and src/app/error.rs) with a Codec variant,
updating LengthDelimitedDecoder to emit structured EOF errors, integrating
CodecErrorContext population, adding recovery policy evaluation in
src/app/connection.rs, adding structured logging and metrics for codec errors,
adding the on_eof hook to ProtocolHooks, and all unit/integration/Cucumber tests
as completed, then save the document so the checklist reflects the implemented
work.
- Around line 130-132: The three EOF sub-variant lines for CleanClose, MidFrame,
and MidHeader use inconsistent dash/parenthesis formatting; update each list
item to use a consistent explanatory separator (either an em-dash or a colon)
and consistent wording style so all three read uniformly (e.g., "CleanClose —
EOF at frame boundary (normal)", "MidFrame — EOF during payload read
(premature)", "MidHeader — EOF during header read (premature)"), keeping the
variant names exactly as CleanClose, MidFrame, and MidHeader.
- Line 142: Replace British "-isation" spelling with Oxford "-ization" in this
document: find any instance of "customisation" (and other "-isation" words) near
the RecoveryPolicyHook description and change to "customization" (and analogous
words to use the -ize suffix) so the text conforms to the en-GB-oxendict
guideline; update the sentence containing "RecoveryPolicyHook" accordingly to
use the corrected spelling.

In @src/codec.rs:
- Around line 143-156: The current decode_eof implementation drops the original
error from self.inner.decode_eof(src); instead, preserve that inner error for
diagnostics by mapping Err(e) to a new EOF error that wraps or chains e as its
source (e.g., construct the error from build_eof_error(src) but attach e as the
source/inner error or return a composed error type), so update the match arm
handling Err(_) to capture Err(inner_err) and return
Err(eof_err.with_source(inner_err) or otherwise chain inner_err into the
returned error while keeping use of build_eof_error(src) for context.
- Around line 159-189: The match arm in build_eof_error contains an unreachable
defensive else for src.get(..LENGTH_HEADER_SIZE) because bytes_received >=
LENGTH_HEADER_SIZE guarantees the slice exists; remove that else block and
simplify the code to directly obtain the header slice (e.g., bind header from
src.get(..LENGTH_HEADER_SIZE) without the Some/else) and parse the u32 length,
keeping the subsequent CodecError::Eof(EofError::MidFrame { ... }) logic intact.

In @src/codec/error.rs:
- Around line 312-441: The file is over the 400-line limit because the internal
test module #[cfg(test)] mod tests { ... } is included inline; extract that
entire tests module into a separate test file (e.g., create error_tests.rs with
the same module contents and either place it in the same directory as a sibling
or include it via #[path = "error_tests.rs"] mod tests;) and remove the inline
#[cfg(test)] mod tests block from the current file so the production code file
drops below 400 lines; ensure all referenced symbols (CodecError, FramingError,
ProtocolError, EofError, RecoveryPolicy, should_disconnect, is_clean_close) are
in scope in the new test file (use super::* or appropriate use statements) so
tests compile unchanged.

In @src/metrics.rs:
- Around line 141-149: The inc_codec_error function allocates unnecessarily by
calling .to_string() on its &str params; update the counter! invocation in
inc_codec_error to pass the &str labels directly (e.g., "error_type" =>
error_type, "recovery_policy" => recovery_policy) so CODEC_ERRORS uses &str
labels without heap allocations, matching the other metric functions.

In @src/response.rs:
- Around line 240-257: WireframeError::is_clean_close currently duplicates the
pattern match for CodecError; change it to delegate to codec's implementation by
matching only the WireframeError::Codec variant and returning the inner
crate::codec::CodecError::is_clean_close() result (return false for all other
variants). Update the is_clean_close method to: if let WireframeError::Codec(e)
= self { e.is_clean_close() } else { false }, keeping the from_codec constructor
unchanged.

In @tests/steps/codec_error_steps.rs:
- Around line 47-48: The literal 100 in when_client_closes_abrupt is a magic
number—define a named constant like const PARTIAL_BYTES: usize = 100 (near the
top of the test module) and replace the call world.record_mid_frame_eof(100)
with world.record_mid_frame_eof(PARTIAL_BYTES) so the intent is explicit;
reference the when_client_closes_abrupt function and the record_mid_frame_eof
method when making the change.
- Around line 67-72: The then_server_oversized step is redundantly mutating
state already set in when_client_sends_oversized; remove the calls to
world.set_error_type("framing") and world.set_framing_variant("oversized") from
then_server_oversized and leave only the verification call
(world.verify_recovery_policy("drop")) so that the step only asserts behavior
rather than re-establishing state set by when_client_sends_oversized; reference
functions: then_server_oversized, when_client_sends_oversized and methods
set_error_type, set_framing_variant, verify_recovery_policy on CodecErrorWorld.
- Around line 74-78: The code in then_recovery_policy unnecessarily converts the
incoming String to a Box<str>; change it to use as_str() and pass a &str to
verify_recovery_policy instead: in function then_recovery_policy replace the
into_boxed_str() usage with either let policy = policy.as_str(); or call
world.verify_recovery_policy(policy.as_str()) directly so the parameter to
CodecErrorWorld::verify_recovery_policy is a &str.
- Around line 17-32: The given_error_type_variant helper silently ignores
unknown error_type values and uses verbose into_boxed_str(); change the String
conversions to use as_str() and validate the match exhaustively: after setting
world.set_error_type(error_type.as_str()) and handling "framing" and "eof" via
world.set_framing_variant(variant.as_str()) /
world.set_eof_variant(variant.as_str()), return an Err(TestResult) with a clear
message including the unrecognized error_type in the match _ branch instead of
doing nothing so typos in feature files surface as test failures.

In @tests/worlds/codec_error.rs:
- Around line 42-50: The doc comment for CodecErrorWorld is placed after the
#[derive(..., World)] attribute which breaks rustdoc/attribute ordering; move
the triple-slash comment so it precedes the derive attribute: place the /// Test
world for codec error taxonomy scenarios. line immediately above the
#[derive(Debug, Default, World)] that applies to the struct named
CodecErrorWorld so the documentation is attached correctly.
- Around line 175-180: record_mid_frame_eof currently sets EofError::MidFrame
with expected: 0 which loses diagnostic info; change record_mid_frame_eof(&mut
self, bytes: usize) to accept an expected: usize parameter (e.g.,
record_mid_frame_eof(&mut self, bytes: usize, expected: usize)) and set
detected_eof = Some(EofError::MidFrame { bytes_received: bytes, expected });
update all call sites in tests to pass the correct expected value (or 0 where
unknown) so tests can inspect the real expected byte count; alternatively if you
prefer to keep the current signature, rename the method to
record_mid_frame_eof_with_unknown_expected to make the limitation explicit (but
prefer adding the expected parameter).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87c6425 and fd0a4a2.

📒 Files selected for processing (20)
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/roadmap.md
  • docs/users-guide.md
  • src/app/error.rs
  • src/codec.rs
  • src/codec/error.rs
  • src/codec/recovery.rs
  • src/hooks.rs
  • src/lib.rs
  • src/metrics.rs
  • src/response.rs
  • tests/codec_error.rs
  • tests/cucumber.rs
  • tests/features/codec_error.feature
  • tests/steps/codec_error_steps.rs
  • tests/steps/mod.rs
  • tests/world.rs
  • tests/worlds/codec_error.rs
  • tests/worlds/mod.rs
🧰 Additional context used
📓 Path-based instructions (6)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • tests/steps/mod.rs
  • src/metrics.rs
  • tests/steps/codec_error_steps.rs
  • src/codec/error.rs
  • tests/codec_error.rs
  • tests/worlds/mod.rs
  • src/response.rs
  • tests/worlds/codec_error.rs
  • src/hooks.rs
  • src/lib.rs
  • tests/cucumber.rs
  • tests/world.rs
  • src/codec/recovery.rs
  • src/app/error.rs
  • src/codec.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/steps/mod.rs
  • src/metrics.rs
  • tests/steps/codec_error_steps.rs
  • src/codec/error.rs
  • tests/codec_error.rs
  • tests/worlds/mod.rs
  • src/response.rs
  • tests/worlds/codec_error.rs
  • src/hooks.rs
  • src/lib.rs
  • tests/cucumber.rs
  • tests/world.rs
  • src/codec/recovery.rs
  • src/app/error.rs
  • src/codec.rs
docs/**/*.md

📄 CodeRabbit inference engine (docs/contents.md)

Follow the documentation style guide conventions when writing project documentation

Write official documentation with separate guides for 'Duplex Messaging & Pushes', 'Streaming Responses', and 'Message Fragmentation', each including runnable examples and explaining relevant concepts and APIs

docs/**/*.md: Use markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state. Ensure documentation remains accurate and current.
Documentation must use en-GB-oxendict spelling and grammar ('-ize' / '-yse' / '-our'), with the exception of the filename LICENSE.
Reference the documentation style guide provided at docs/documentation-style-guide.md for documentation writing standards.

Documentation describing multi-packet streaming MUST reflect the tuple-based API and explain helper constructors that prepare the channel pair

Replace or retire documentation that refers to Cucumber, including guides and examples in docs/

docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: suffix -ize in words like 'realize' and 'organization', suffix -lyse in words like 'analyse' and 'paralyse', suffix -our in words like 'colour' and 'behaviour', suffix -re in words like 'calibre' and 'centre', double 'l' in words like 'cancelled' and 'counsellor', maintain the 'e' in words like 'likeable' and 'liveable', suffix -ogue in words like 'analogue' and 'catalogue'
The word 'outwith' is acceptable in documentation
Keep US spelling when used in an API, for example 'color'
Use the Oxford comma: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns, for example: 'Lille Industries are expanding'
Write hea...

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

Every doctest should validate the public API of a crate from the perspective of an external user, treating each documentation test as a separate temporary crate that imports the library as an external dependency

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Markdown tables and headings must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes in Markdown.

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
docs/adr-[0-9][0-9][0-9]-*.md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

docs/adr-[0-9][0-9][0-9]-*.md: ADR naming convention: Name ADR files using the pattern 'adr-NNN-short-description.md', where NNN is a zero-padded sequence number (e.g. 'adr-001-async-fixtures-and-tests.md'). Place ADRs in the 'docs/' directory
ADR required sections: Include Status (one of 'Proposed', 'Accepted', 'Superseded', or 'Deprecated'; for 'Accepted' status include the date and a brief summary of what was decided), Date (format: YYYY-MM-DD), and Context and Problem Statement sections in order
ADR conditional sections: Include Decision Drivers, Requirements, Options Considered, Decision Outcome / Proposed Direction, Goals and Non-Goals, Migration Plan, Known Risks and Limitations, Outstanding Decisions, and Architectural Rationale sections as appropriate to the decision's complexity
ADR formatting: Use second-level headings (##) for major sections and third-level headings (###) for subsections (e.g. phases, option names)
ADR formatting: Use tables to compare options when multiple dimensions are relevant and include a caption below the table (e.g. 'Table 1: Trade-offs between X and Y.')
ADR formatting: Include code snippets with language identifiers when illustrating technical approaches. Use 'no_run' for illustrative Rust code that should not be executed
ADR formatting: Add screen reader descriptions before complex diagrams or code blocks
ADR formatting: Reference external sources using inline links or footnotes

Files:

  • docs/adr-004-pluggable-protocol-codecs.md
docs/**/roadmap*.md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

docs/**/roadmap*.md: Roadmap task writing: Define outcomes, not intentions. Phrase tasks in terms of the capability delivered (e.g. 'Implement role-based access control for API endpoints'), not aspirations
Roadmap task writing: Quantify completion criteria with measurable finish lines (e.g. '90% test coverage for new modules', 'response times under 200ms')
Roadmap task writing: Break into atomic increments. Ensure tasks can be completed in weeks, not quarters. Large goals should be decomposed into clear, deliverable units
Roadmap task writing: Tie to dependencies and sequencing. Document prerequisites so tasks can be scheduled realistically
Roadmap task writing: Bound scope explicitly. Note both in-scope and out-of-scope elements (e.g. 'Build analytics dashboard (excluding churn prediction)')
Roadmap formatting: Use dotted numbering for phases (1, 2, 3, …), steps (1.1, 1.2, 1.3, …), and headline tasks (1.1.1, 1.1.2, 1.1.3, …)
Roadmap formatting: Precede task and sub-task items with a GitHub-Flavoured Markdown (GFM) checkbox ([]) to track completion status
Roadmap formatting: Note non-linear dependencies explicitly using dotted notation (e.g. 'Requires 2.3.1')
Roadmap formatting: Include explicit success criteria only where not immediately obvious from the task description
Roadmap formatting: Where applicable, cite the relevant design document section for each task (e.g. 'See design-doc.md §3.2')

Files:

  • docs/roadmap.md
🧠 Learnings (2)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • tests/steps/mod.rs
  • src/metrics.rs
  • tests/steps/codec_error_steps.rs
  • src/codec/error.rs
  • tests/codec_error.rs
  • tests/worlds/mod.rs
  • src/response.rs
  • tests/worlds/codec_error.rs
  • src/hooks.rs
  • src/lib.rs
  • tests/cucumber.rs
  • tests/world.rs
  • src/codec/recovery.rs
  • src/app/error.rs
  • src/codec.rs
📚 Learning: 2026-01-06T18:51:34.645Z
Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/roadmap.md:0-0
Timestamp: 2026-01-06T18:51:34.645Z
Learning: Applies to docs/src/**/*.rs : Introduce a `CodecError` taxonomy separating framing, protocol, and IO failures

Applied to files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • src/codec/error.rs
🧬 Code graph analysis (7)
src/metrics.rs (3)
src/codec/error.rs (1)
  • error_type (288-295)
tests/codec_error.rs (1)
  • recovery_policy (147-149)
src/codec/recovery.rs (1)
  • recovery_policy (208-211)
tests/steps/codec_error_steps.rs (1)
src/codec/error.rs (1)
  • error_type (288-295)
src/response.rs (1)
src/codec/error.rs (1)
  • is_clean_close (254-254)
tests/worlds/codec_error.rs (3)
src/codec.rs (1)
  • default (120-124)
src/codec/recovery.rs (1)
  • default (277-283)
src/codec/error.rs (1)
  • error_type (288-295)
src/codec/recovery.rs (5)
src/codec.rs (3)
  • default (120-124)
  • new (102-106)
  • correlation_id (84-84)
src/hooks.rs (1)
  • default (141-150)
src/extractor.rs (1)
  • state (82-90)
tests/codec_error.rs (4)
  • recovery_policy (147-149)
  • recovery_policy_default_is_drop (122-124)
  • context_with_peer_address (109-115)
  • recovery_config_builder (127-138)
tests/multi_packet.rs (1)
  • count (71-76)
src/app/error.rs (2)
src/codec/error.rs (1)
  • from (299-309)
src/response.rs (3)
  • from (110-110)
  • from (114-114)
  • from (233-233)
src/codec.rs (2)
tests/codec_error.rs (1)
  • assert_decode_eof_error (181-202)
src/codec/error.rs (1)
  • from (299-309)
🪛 LanguageTool
docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md

[typographical] ~94-~94: To join two clauses or introduce examples, consider using an em dash.
Context: ... Codec implementation: - src/codec.rs - FrameCodec trait and `LengthDelimitedF...

(DASH_RULE)


[typographical] ~95-~95: To join two clauses or introduce examples, consider using an em dash.
Context: ...tedFrameCodec-src/codec/examples.rs- Example codec implementations -src/app...

(DASH_RULE)


[typographical] ~96-~96: To join two clauses or introduce examples, consider using an em dash.
Context: ...mentations - src/app/combined_codec.rs - Adapter for Tokio Framed Error handli...

(DASH_RULE)


[typographical] ~100-~100: To join two clauses or introduce examples, consider using an em dash.
Context: ...ndling paths: - src/app/connection.rs - Connection processing and error handling...

(DASH_RULE)


[typographical] ~101-~101: To join two clauses or introduce examples, consider using an em dash.
Context: ...r handling - src/app/frame_handling.rs - Frame deserialization and response forwa...

(DASH_RULE)


[typographical] ~102-~102: To join two clauses or introduce examples, consider using an em dash.
Context: ...and response forwarding - src/hooks.rs - Protocol hooks including error callbacks...

(DASH_RULE)


[typographical] ~106-~106: To join two clauses or introduce examples, consider using an em dash.
Context: ...nfrastructure: - tests/frame_codec.rs - Codec integration tests - `tests/feature...

(DASH_RULE)


[typographical] ~107-~107: To join two clauses or introduce examples, consider using an em dash.
Context: ...ec integration tests - tests/features/ - Cucumber feature files - tests/steps/ ...

(DASH_RULE)


[typographical] ~108-~108: To join two clauses or introduce examples, consider using an em dash.
Context: ...les - tests/steps/ and tests/worlds/ - Step definitions and world types Docume...

(DASH_RULE)


[typographical] ~112-~112: To join two clauses or introduce examples, consider using an em dash.
Context: ...entation to update: - docs/roadmap.md - Mark 9.1.2 done - docs/users-guide.md ...

(DASH_RULE)


[typographical] ~113-~113: To join two clauses or introduce examples, consider using an em dash.
Context: ... Mark 9.1.2 done - docs/users-guide.md - Public interface documentation - `docs/a...

(DASH_RULE)


[typographical] ~114-~114: To join two clauses or introduce examples, consider using an em dash.
Context: ...cs/adr-004-pluggable-protocol-codecs.md` - Design decisions ## Plan of Work ### P...

(DASH_RULE)


[typographical] ~123-~123: To join two clauses or introduce examples, consider using an em dash.
Context: ...r classification: 1. Framing errors - Wire-level frame boundary issues (e.g., ...

(DASH_RULE)


[typographical] ~125-~125: To join two clauses or introduce examples, consider using an em dash.
Context: ...hecksum mismatch) 2. Protocol errors - Semantic violations after frame extracti...

(DASH_RULE)


[typographical] ~128-~128: To join two clauses or introduce examples, consider using an em dash.
Context: ... sequence violation) 3. IO errors - Transport layer failures (wrapped `io::E...

(DASH_RULE)


[typographical] ~129-~129: To join two clauses or introduce examples, consider using an em dash.
Context: ... (wrapped io::Error) 4. EOF errors - End-of-stream handling with three sub-va...

(DASH_RULE)


[typographical] ~131-~131: To join two clauses or introduce examples, consider using an em dash.
Context: ... frame boundary (normal) - MidFrame - EOF during payload read (premature) -...

(DASH_RULE)


[typographical] ~132-~132: To join two clauses or introduce examples, consider using an em dash.
Context: ...ayload read (premature) - MidHeader - EOF during header read (premature) Each...

(DASH_RULE)


[typographical] ~136-~136: To join two clauses or introduce examples, consider using an em dash.
Context: ...cy()method returning one of: -Drop` - Discard the malformed frame and continue...

(DASH_RULE)


[typographical] ~137-~137: To join two clauses or introduce examples, consider using an em dash.
Context: ...inue (recoverable errors) - Quarantine - Pause the connection temporarily (rate l...

(DASH_RULE)


[typographical] ~138-~138: To join two clauses or introduce examples, consider using an em dash.
Context: ...mporarily (rate limiting) - Disconnect - Terminate the connection (fatal errors) ...

(DASH_RULE)


[style] ~142-~142: Would you like to use the Oxford spelling “customization”? The spelling ‘customisation’ is also correct.
Context: ... RecoveryPolicyHook trait that allows customisation of error handling behaviour: - `Recove...

(OXFORD_SPELLING_Z_NOT_S)


[typographical] ~171-~171: Two consecutive dots
Context: ...e)for EOF at frame boundary 3. ReturnCodecError::Eof(EofError::MidFrame{..})for EOF during payload 4. ReturnCo...

(DOUBLE_PUNCTUATION)


[typographical] ~172-~172: Two consecutive dots
Context: ...{..})for EOF during payload 4. ReturnCodecError::Eof(EofError::MidHeader{..})for EOF during header 5. Maintaini...

(DOUBLE_PUNCTUATION)


[uncategorized] ~232-~232: Loose punctuation mark.
Context: ...documentation: 1. docs/users-guide.md: - Add section on codec error handlin...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~238-~238: Loose punctuation mark.
Context: ...cs/adr-004-pluggable-protocol-codecs.md: - Document the CodecError` taxonomy...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~243-~243: Loose punctuation mark.
Context: ...OF handling design 3. docs/roadmap.md: - Mark all 9.1.2 sub-items as done ...

(UNLIKELY_OPENING_PUNCTUATION)


[duplication] ~255-~255: Possible typo: you repeated a word.
Context: ...CodecError::default_recovery_policy() implementation - From<CodecError> for io::Error implementation 2. Create src/codec/recovery.rs with...

(ENGLISH_WORD_REPEAT_RULE)


[style] ~260-~260: Using four (or more) nouns in a row may decrease readability.
Context: ...ryPolicyenum -CodecErrorContextstruct -RecoveryPolicyHooktrait -DefaultRecoveryPolicyimplementation -RecoveryConfigbuilder config 3. Updatesrc/codec.rs`: - ...

(FOUR_NN)

docs/users-guide.md

[typographical] ~175-~175: To join two clauses or introduce examples, consider using an em dash.
Context: ... Error categories: - FramingError - Wire-level frame boundary issues (oversi...

(DASH_RULE)


[typographical] ~177-~177: To join two clauses or introduce examples, consider using an em dash.
Context: ...atches, empty frames). - ProtocolError - Semantic violations after frame extracti...

(DASH_RULE)


[typographical] ~179-~179: To join two clauses or introduce examples, consider using an em dash.
Context: ... types, invalid versions). - io::Error - Transport layer failures (connection res...

(DASH_RULE)


[typographical] ~180-~180: To join two clauses or introduce examples, consider using an em dash.
Context: ...nnection resets, timeouts). - EofError - End-of-stream conditions with clean/mid-...

(DASH_RULE)

🔍 Remote MCP Deepwiki

Summary of Relevant Context for PR #417 Review

Based on my search of the leynos/wireframe repository, here is the key context that applies to this CodecError taxonomy PR:

Existing Error Handling Architecture

The framework currently uses a comprehensive WireframeError<E> enum that distinguishes between unrecoverable transport-level I/O errors (Io) and protocol-specific logical errors (Protocol). The new CodecError taxonomy in PR #417 adds a third category specifically for codec-layer errors—introducing a more granular error classification below the protocol level. This aligns with the existing architecture's layered approach: TransportFraming (Codec)ProtocolApplication.

Connection Context and Observability Integration

The ConnectionContext struct and peer_addr tracking are already established patterns in the codebase. The PR's CodecErrorContext (with fields for connection_id, peer_address, correlation_id, codec_state) follows the same design pattern and fits naturally into the existing connection lifecycle hooks. The ConnectionInfo extractor already provides peer address information to handlers, so the PR's use of peer_address in error context is consistent with current practices.

Metrics and Observability Implementation

The framework exposes metrics via the metrics crate with Prometheus integration, including counters like wireframe_errors_total (with kind labels) and wireframe_frames_processed_total (with direction labels). The PR's new CODEC_ERRORS metric (wireframe_codec_errors_total) with error_type and recovery_policy labels follows this established pattern of labeled counters and integrates cleanly with the existing conditional compilation pattern (#[cfg(feature = "metrics")]).

API Stability and Backward Compatibility

The project enforces strict API stability through comprehensive documentation, caret-locked dependencies, and phased development. The PR is backward-compatible—all additions are additive with new public exports and optional new code paths. The From<CodecError> for io::Error conversion ensures existing code accepting io::Error continues to work without modification.

LengthDelimitedDecoder EOF Semantics

The existing LengthPrefixedProcessor returns Ok(None) when insufficient data is available to form a complete frame. The PR's new LengthDelimitedDecoder::decode_eof() method and structured EofError types (CleanClose, MidFrame, MidHeader) provide semantic clarity around what constitutes a clean EOF versus a protocol violation—distinguishing between "no more data at a frame boundary" (CleanClose → Ok(None)) and "incomplete frame received" (MidFrame/MidHeader → error). Per the commit history, this behavior was refined to return Ok(None) for clean closes rather than an error.

Recovery Policies and Protocol Hooks

The PR introduces the RecoveryPolicyHook trait with recovery_policy(), quarantine_duration(), and on_error() methods, plus the on_eof hook in WireframeProtocol. These are new extension points that parallel the existing pattern of protocol lifecycle hooks (e.g., handle_error on WireframeProtocol), allowing applications to customize behavior at error boundaries.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Sourcery review
🔇 Additional comments (46)
docs/roadmap.md (1)

352-361: Roadmap status update accurately reflects delivered work.

The completion markers for 9.1.2 align with the CodecError taxonomy, recovery policies, error surface extensions, EOF handling, and test coverage introduced in this PR.

src/metrics.rs (2)

36-44: LGTM: Metric constant follows established conventions.

The CODEC_ERRORS constant is appropriately named and documented with clear Prometheus output examples demonstrating the label dimensions.


151-152: LGTM: No-op implementation follows established pattern.

The disabled-feature version correctly ignores parameters and matches the pattern of other metric no-ops in this file.

tests/steps/mod.rs (1)

8-8: LGTM: Module declaration follows established pattern.

The codec_error_steps module is wired consistently with the other step definitions in this aggregation module.

tests/world.rs (1)

10-10: LGTM: World re-export follows established pattern.

CodecErrorWorld is appropriately positioned alphabetically and follows the same re-export pattern as other test worlds.

tests/worlds/mod.rs (1)

32-32: LGTM: Module declaration follows established pattern.

The codec_error module is appropriately positioned alphabetically and follows the same public module pattern as the other test world declarations.

docs/users-guide.md (1)

168-270: LGTM: comprehensive codec error handling documentation.

The new section clearly documents the CodecError taxonomy, recovery policies, context metadata, protocol hooks, and metrics integration. Code examples are properly tagged, the table is well-formatted, and the documentation aligns with the implementation described in the PR.

Note: The static analysis hints about em dashes are false positives—the dashes in lines 175-180 are list markers and inline separators, not prose dashes requiring typographical correction.

tests/cucumber.rs (1)

4-4: LGTM: CodecErrorWorld integration follows established patterns.

The additions correctly extend the cucumber test suite to include codec error scenarios. Import order, documentation, and runner sequence all follow the existing conventions.

Also applies to: 15-15, 31-31, 44-44, 67-67

docs/adr-004-pluggable-protocol-codecs.md (1)

256-277: LGTM: resolved decision properly documented.

The "Resolved Decisions" section clearly captures the CodecError taxonomy design outcome, including error classification, recovery policies, backward compatibility strategy, protocol hooks, and observability integration. The documentation aligns with the implementation and provides appropriate file references.

tests/features/codec_error.feature (1)

1-25: LGTM: clear BDD scenarios for codec error behaviour.

The feature file defines four well-structured scenarios covering clean EOF, premature EOF, framing errors, and recovery policy defaults. The Gherkin syntax is correct and the scenarios align with the codec error taxonomy introduced in the PR.

tests/codec_error.rs (1)

1-327: LGTM: comprehensive integration tests for codec error taxonomy.

The test suite thoroughly validates the CodecError taxonomy, recovery policies, error context, custom hooks, EOF handling, and integration with WireframeError and app-level error types. The helper function assert_decode_eof_error appropriately consolidates EOF testing logic, and all tests follow safe patterns without direct indexing or inappropriate .expect() usage.

tests/steps/codec_error_steps.rs (1)

1-4: LGTM!

Module-level doc comment and imports are appropriate.

src/response.rs (2)

200-229: LGTM!

The new Codec variant is well-documented, and the doc comment accurately describes the error categorisation.


260-278: LGTM!

Display and Error implementations correctly handle the new Codec variant.

src/lib.rs (2)

17-31: LGTM!

Re-exports are comprehensive and correctly expose the new codec error taxonomy and recovery infrastructure.


89-89: LGTM!

CODEC_ERRORS metric correctly re-exported alongside existing metrics.

src/codec/error.rs (7)

1-30: LGTM!

Excellent module-level documentation explaining the error taxonomy and recovery policies.


32-72: LGTM!

FramingError variants are well-documented with meaningful field names and appropriate #[error] messages.


74-118: LGTM!

ProtocolError variants cover the key semantic violations with clear documentation.


120-155: LGTM!

EofError variants correctly distinguish clean closure from premature disconnection scenarios.


157-192: LGTM!

Top-level CodecError enum with #[from] conversions and comprehensive doctests.


194-232: LGTM!

Recovery policy defaults are well-documented and the match logic is clear. The table in the doc comment accurately reflects the implementation.


298-310: Verify that UnexpectedEof is semantically correct for CleanClose.

CleanClose represents a graceful closure at a frame boundary, yet it maps to io::ErrorKind::UnexpectedEof. This seems contradictory—callers might interpret UnexpectedEof as an error condition when it is actually normal termination. Verify this is intentional, or use a different ErrorKind (e.g., ConnectionAborted or BrokenPipe) to distinguish clean closure from premature EOF.

src/hooks.rs (5)

9-12: LGTM!

Import correctly added for EofError.


65-102: LGTM!

The on_eof hook is well-documented with a comprehensive example demonstrating all three EOF variants.


121-122: Export OnEofHook for external consumers.

Other hook type aliases (BeforeSendHook, OnConnectionSetupHook, etc.) are private (type), but downstream users implementing custom protocol hooks may benefit from a public type alias for OnEofHook. If external use is intended, add pub visibility; otherwise, document the intent to keep it private.


136-137: LGTM!

Field, default, and dispatcher method for on_eof follow the established pattern used by other hooks.

Also applies to: 148-148, 186-191


223-236: LGTM!

Hook wiring in from_protocol correctly clones the protocol Arc and delegates to protocol_eof.on_eof.

src/app/error.rs (3)

7-27: LGTM!

The Codec variant is correctly added with comprehensive documentation explaining the error categorisation.


29-40: LGTM!

Manual PartialEq and Eq implementations are correctly implemented. The comment on lines 33–34 clearly explains why CodecError cannot be compared for equality.


42-55: LGTM!

SendError correctly extended with the Codec variant and #[from] conversion.

tests/worlds/codec_error.rs (3)

52-102: LGTM!

The setter methods correctly parse string inputs, return structured errors on unknown variants, and trigger build_error to keep internal state consistent. Error messages are clear and follow the expected pattern.


104-143: LGTM!

build_error cleanly maps the configured type/variant combinations to concrete CodecError instances. The use of hardcoded test values (e.g., size: 2000, max: 1024) is appropriate for a test world.


145-206: LGTM!

Verification methods correctly propagate errors via TestResult and produce informative failure messages. The pattern-matching on Option<EofError> is idiomatic.

src/codec.rs (5)

6-19: LGTM!

The module documentation clearly explains the error taxonomy and recovery policy semantics, linking to the relevant submodules.


26-48: LGTM!

Module declarations, re-exports, and public constants are well-documented. The clamping bounds (MIN_FRAME_LENGTH and MAX_FRAME_LENGTH) are sensible defaults.


200-210: LGTM!

The encoder now emits a structured CodecError::Framing(FramingError::OversizedFrame { ... }) which converts cleanly to io::Error via the From impl. This aligns with the new taxonomy.


297-322: LGTM!

The assert_decode_eof_error helper centralises EOF error assertions, reducing duplication across tests. The closure-based message validator is flexible.


324-375: LGTM!

EOF tests cover the key scenarios: clean close returning Ok(None), partial header, partial payload, and complete frame. Assertions are clear and use the new helper where appropriate.

src/codec/recovery.rs (7)

1-36: LGTM!

The module documentation is thorough, includes a working code example, and explains the three recovery policies clearly. The doc example uses the correct import paths.


42-91: LGTM!

RecoveryPolicy is well-documented with "When to Use" guidance for each variant. The #[default] attribute on Drop is appropriate as it represents the least disruptive recovery action.


93-162: LGTM!

CodecErrorContext provides a clean builder API with #[must_use] annotations. The fields cover the essential diagnostic context (connection ID, peer address, correlation ID, codec state).


203-226: LGTM!

The RecoveryPolicyHook trait provides sensible defaults: delegating to CodecError::default_recovery_policy(), a 30-second quarantine duration, and a no-op on_error hook. The design allows incremental customisation.


228-235: LGTM!

DefaultRecoveryPolicy is a zero-configuration implementation that inherits all trait defaults. The empty impl block is idiomatic Rust.


237-307: LGTM!

RecoveryConfig uses the builder pattern consistently with #[must_use] on all setters. The defaults (10 consecutive drops, 30-second quarantine, logging enabled) align with the trait defaults in RecoveryPolicyHook.


309-389: LGTM!

Tests cover the key behaviours: default policy is Drop, builder methods set fields correctly, and DefaultRecoveryPolicy delegates to CodecError::default_recovery_policy(). The use of .expect() in tests follows project guidelines.

Comment thread docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md Outdated
Comment thread docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md Outdated
Comment thread docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
Comment thread src/codec.rs
Comment thread src/codec.rs Outdated
Comment thread tests/steps/codec_error_steps.rs Outdated
Comment thread tests/steps/codec_error_steps.rs
Comment thread tests/steps/codec_error_steps.rs
Comment thread tests/worlds/codec_error.rs Outdated
Comment thread tests/worlds/codec_error.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd0a4a2 and 5a4706d.

📒 Files selected for processing (5)
  • src/app/error.rs
  • src/codec.rs
  • tests/codec_error.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • tests/codec_error.rs
  • src/codec.rs
  • src/app/error.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/codec_error.rs
  • src/codec.rs
  • src/app/error.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error.rs
🧠 Learnings (1)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • tests/codec_error.rs
  • src/codec.rs
  • src/app/error.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error.rs
🧬 Code graph analysis (3)
src/codec.rs (2)
src/codec/recovery.rs (1)
  • new (133-133)
src/codec/error.rs (1)
  • from (299-309)
src/app/error.rs (1)
src/codec/error.rs (1)
  • from (299-309)
tests/steps/codec_error_steps.rs (1)
src/codec/error.rs (1)
  • error_type (288-295)
🔍 Remote MCP Deepwiki

Summary of additional repository facts relevant to PR review (from repo docs/wiki):

  • Repo architecture confirms layered error placement: Transport → Framing (codec) → Protocol → Application; codec-layer taxonomy aligns with existing design pattern for layered errors and protocol hooks.

  • Public API and re-exports: crate already exposes many codec/metrics/public types; PR's re-exports (CodecError, RecoveryPolicy, CODEC_ERRORS, MAX_FRAME_LENGTH/MIN_FRAME_LENGTH, CodecErrorContext, RecoveryConfig, RecoveryPolicyHook, etc.) fit existing lib export pattern.

  • Frame/codec behavior & EOF semantics:

    • Length-prefixed framing is primary frame processor; existing decode() returns Ok(None) for incomplete frames. PR's LengthDelimitedDecoder::decode_eof() and EofError variants (CleanClose, MidFrame, MidHeader) directly affect that decoder path and must preserve expected Ok(None) for clean closes as per prior behavior.
  • Protocol hooks and connection flow:

    • WireframeProtocol already supplies lifecycle hooks (on_connection_setup, before_send, on_command_end, handle_error). PR adds on_eof hook; wiring pattern (ProtocolHooks::from_protocol) in repo shows how to integrate and where to verify hook registration.
  • Observability & metrics conventions:

    • Repo uses metrics crate with conditional compilation feature "metrics" and Prometheus exporter; new CODEC_ERRORS counter name wireframe_codec_errors_total and inc_codec_error(error_type, recovery_policy) match existing labeled-counter patterns (e.g., wireframe_frames_processed_total). Ensure labels and feature-flagged no-op variant follow existing pattern.
  • Testing infrastructure & patterns:

    • Project uses wireframe_testing helpers, rstest fixtures, and Cucumber BDD world files. PR adds unit/integration tests and a cucumber world (CodecErrorWorld) and feature file; ensure new tests use existing helpers (drive_with_*, logger fixtures) and follow rstest conventions. Duplicate EOF tests noted in PR should be consolidated per project testing patterns (fixtures/helpers preferred).
  • Recovery policy defaults & behavior integration points:

    • Repo already applies recovery/behavior via protocol hooks and connection actor; new RecoveryPolicyHook, DefaultRecoveryPolicy, RecoveryConfig should be checked for where connection processing evaluates policies (connection actor error handling paths / WireframeProtocol::handle_error).

Files/areas to review closely (concrete locations suggested by repo docs):

  • codec modules and public re-exports: src/codec.rs, src/codec/error.rs, src/codec/recovery.rs, src/lib.rs. Confirm API stability and docs.
  • decoder/encoder EOF and oversized-frame changes: LengthDelimitedDecoder/LengthDelimitedEncoder in src/codec.rs — ensure decode_eof behavior preserves clean-close semantics and conversion to io::Error is safe.
  • Wireframe error surfaces and conversions: src/response.rs, src/app/error.rs — verify From conversions and Display/Error::source behavior follow existing WireframeError patterns.
  • Protocol hooks wiring: src/hooks.rs — validate on_eof hook registration in ProtocolHooks::from_protocol and usage in ConnectionActor/error paths.
  • Metrics integration: src/metrics.rs and src/lib.rs exports — ensure conditional compilation and metric naming/labels are consistent with existing metrics conventions.
  • Tests and BDD: tests/{codec_error.rs, features/codec_error.feature, worlds/codec_error.rs, steps/codec_error_steps.rs, cucumber.rs} — ensure they use wireframe_testing helpers/fixtures and avoid duplication (extract helper per repo testing style).

Sources:

  • Repository wiki/docs and file index (used for above facts and integration patterns): Deepwiki read of leynos/wireframe documentation and API pages.
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test
🔇 Additional comments (12)
src/app/error.rs (2)

7-27: LGTM – codec error integration is well-structured.

The Codec variant with #[from] enables seamless error propagation from the codec layer. Documentation clearly explains the error categories and their origin. The removal of PartialEq/Eq derives addresses the previous review concern about inconsistent equality semantics.


39-42: Consistent pattern applied to SendError.

The Codec variant mirrors the pattern used in WireframeError, maintaining API consistency across the error surfaces.

src/codec.rs (4)

1-36: Well-documented module with clear error taxonomy.

The module-level documentation explains recovery policies concisely. Public re-exports make the error types and recovery infrastructure accessible to downstream consumers.


159-167: Good use of #[expect] for wire endianness.

The lint expectation is appropriately scoped and clearly explains why big-endian is intentional for wire protocol compatibility.


215-224: Structured error emission for oversized frames.

The encoder now emits CodecError::Framing(OversizedFrame) instead of a plain io::Error, enabling recovery policy decisions upstream.


312-390: Test helper consolidates EOF assertions effectively.

The assert_decode_eof_error helper extracts the common test pattern, improving maintainability and making it straightforward to add new EOF test cases. Test coverage includes clean close, partial header, partial payload, and complete frame scenarios.

tests/codec_error.rs (2)

1-14: Comprehensive integration test coverage for the error taxonomy.

The tests exercise recovery policy behaviour, error conversions, context builders, and end-to-end integration with WireframeError and SendError. Organisation with section comments aids readability.


138-157: Good demonstration of custom recovery hook override.

The StrictRecovery example clearly shows how consumers can implement RecoveryPolicyHook to override default policies.

tests/steps/codec_error_steps.rs (1)

1-8: Clear module documentation for cucumber steps.

The module comment explains the purpose and approach of these behavioural tests.

tests/worlds/codec_error.rs (3)

73-190: Well-structured test world with clear error handling.

The set_* methods return TestResult and reject unknown values with descriptive error messages. The build_error method correctly materialises all error type/variant combinations.


192-322: Real decoder operations provide end-to-end validation.

The E2E methods (send_complete_frame, decode_eof_clean_close, encode_oversized_frame, etc.) exercise actual codec paths rather than mocking, ensuring the error taxonomy integrates correctly with the decoder implementation.


324-369: Verification methods provide clear test assertions.

The verify_* methods produce descriptive error messages when assertions fail, aiding test debugging.

@leynos leynos changed the title Introduce CodecError taxonomy and recovery policies Introduce CodecError taxonomy, recovery policies, and metrics Jan 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In @src/codec.rs:
- Around line 200-209: The slice-to-array conversion in the header parsing uses
an unreachable else branch; since bytes_received >= LENGTH_HEADER_SIZE is
already guaranteed, remove the conditional let Ok(...) = ... else { ... } block
and perform the conversion without the unreachable branch (e.g., directly unwrap
the result of <[u8; LENGTH_HEADER_SIZE]>::try_from(header_slice>), keeping
references to header_slice, LENGTH_HEADER_SIZE, and the existing error variant
CodecError::Eof(EofError::MidHeader) only removed in this location.
- Around line 155-167: The match arm handling Err(_) after calling
self.inner.decode_eof(src) discards the original error; preserve its context by
capturing the error (e) and return an EOF error that wraps or chains the
original error instead of throwing it away. Update either the match arm to pass
the captured error into build_eof_error (e.g., change build_eof_error to accept
an Option<io::Error> or &io::Error) or construct a new io::Error that includes
build_eof_error(src) as the message and sets the original error as the source so
diagnostics retain the inner error from self.inner.decode_eof.

In @src/codec/error_tests.rs:
- Around line 73-85: Extend the test
`codec_error_converts_to_io_error_with_correct_kind` to also assert that
`CodecError::Protocol(...)` converts to an `io::Error` with kind
`io::ErrorKind::InvalidData`, and that `CodecError::Io(inner_io_error)` is a
passthrough (i.e., convert a known `io::Error` via `CodecError::Io` to
`io::Error` and assert the resulting `.kind()` matches the original
`inner_io_error.kind()`); use the existing test patterns (creating the variant,
converting with `.into()`, and `assert_eq!(io_err.kind(), ...)`) and reference
the `CodecError::Protocol` and `CodecError::Io` variants when adding these two
assertions.

In @src/metrics.rs:
- Around line 141-152: The two feature-gated implementations of inc_codec_error
use mismatched parameter types; change the non-metrics (no-op) signature to
match the metrics-enabled one by accepting &'static str for both parameters
(error_type and recovery_policy) so the function signature is identical across
cfg branches; update the function definition for inc_codec_error in the
#[cfg(not(feature = "metrics"))] block to use &'static str for both parameters
to unify the API.

In @tests/worlds/codec_error.rs:
- Around line 196-210: setup_default_codec duplicates the reset logic in
setup_codec_with_max_length; change setup_default_codec to delegate to
setup_codec_with_max_length(1024) (i.e., call
self.setup_codec_with_max_length(1024)) and remove the duplicated assignments,
or alternately extract a private helper like reset_codec(&mut self) that sets
max_frame_length, buffer, decoder_error, and clean_close_detected and call it
from both setup_default_codec and setup_codec_with_max_length; update references
to the functions setup_default_codec and setup_codec_with_max_length
accordingly.
- Around line 80-89: The set_error_type method sets self.error_type but doesn’t
call build_error(), causing current_error to be stale; after assigning
self.error_type in set_error_type, call self.build_error() (like
set_framing_variant and set_eof_variant do) so current_error is rebuilt
immediately—update the pub fn set_error_type(&mut self, error_type: &str) ->
TestResult to invoke self.build_error() before returning Ok(()).
- Around line 57-71: The struct CodecErrorWorld has inconsistent documentation:
add doc comments for the fields error_type, framing_variant, eof_variant,
current_error, and detected_eof to match the existing style used for
max_frame_length, buffer, decoder_error, and clean_close_detected; update the
CodecErrorWorld declaration so each field (error_type, framing_variant,
eof_variant, current_error, detected_eof) has a brief /// comment describing its
purpose and type to maintain consistency across the struct.
- Around line 264-281: The classify_eof_error method currently infers header vs
frame EOF by string-matching the error text and sets EofError::MidFrame {
expected: 0 }, which is fragile and discards the true expected size; update
classify_eof_error (and the EofError usage) by adding a clear comment above the
function noting this fragility and the dependency on upstream error text, and
either (preferred) compute and set a meaningful expected value for
EofError::MidFrame using available context (e.g., parse the partial header from
self.buffer or read any stored expected frame length), or (if that data isn't
available today) change the MidFrame expected field to an Option and set it to
None so callers know the value is unknown instead of 0; ensure references to
detected_eof and EofError::MidHeader/EofError::MidFrame are updated accordingly.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a4706d and e9a4326.

📒 Files selected for processing (8)
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • src/codec.rs
  • src/codec/error.rs
  • src/codec/error_tests.rs
  • src/metrics.rs
  • src/response.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error.rs
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/metrics.rs
  • src/codec/error.rs
  • src/response.rs
  • src/codec/error_tests.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs
  • tests/steps/codec_error_steps.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/metrics.rs
  • src/codec/error.rs
  • src/response.rs
  • src/codec/error_tests.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs
  • tests/steps/codec_error_steps.rs
docs/**/*.md

📄 CodeRabbit inference engine (docs/contents.md)

Follow the documentation style guide conventions when writing project documentation

Write official documentation with separate guides for 'Duplex Messaging & Pushes', 'Streaming Responses', and 'Message Fragmentation', each including runnable examples and explaining relevant concepts and APIs

docs/**/*.md: Use markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state. Ensure documentation remains accurate and current.
Documentation must use en-GB-oxendict spelling and grammar ('-ize' / '-yse' / '-our'), with the exception of the filename LICENSE.
Reference the documentation style guide provided at docs/documentation-style-guide.md for documentation writing standards.

Documentation describing multi-packet streaming MUST reflect the tuple-based API and explain helper constructors that prepare the channel pair

Replace or retire documentation that refers to Cucumber, including guides and examples in docs/

docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: suffix -ize in words like 'realize' and 'organization', suffix -lyse in words like 'analyse' and 'paralyse', suffix -our in words like 'colour' and 'behaviour', suffix -re in words like 'calibre' and 'centre', double 'l' in words like 'cancelled' and 'counsellor', maintain the 'e' in words like 'likeable' and 'liveable', suffix -ogue in words like 'analogue' and 'catalogue'
The word 'outwith' is acceptable in documentation
Keep US spelling when used in an API, for example 'color'
Use the Oxford comma: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns, for example: 'Lille Industries are expanding'
Write hea...

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

Every doctest should validate the public API of a crate from the perspective of an external user, treating each documentation test as a separate temporary crate that imports the library as an external dependency

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Markdown tables and headings must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes in Markdown.

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
🧠 Learnings (2)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • src/metrics.rs
  • src/codec/error.rs
  • src/response.rs
  • src/codec/error_tests.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs
  • tests/steps/codec_error_steps.rs
📚 Learning: 2026-01-06T18:51:34.645Z
Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/roadmap.md:0-0
Timestamp: 2026-01-06T18:51:34.645Z
Learning: Applies to docs/src/**/*.rs : Introduce a `CodecError` taxonomy separating framing, protocol, and IO failures

Applied to files:

  • src/codec/error.rs
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
🧬 Code graph analysis (5)
src/metrics.rs (3)
src/codec/error.rs (1)
  • error_type (288-295)
tests/codec_error.rs (1)
  • recovery_policy (141-143)
src/codec/recovery.rs (1)
  • recovery_policy (208-211)
src/response.rs (1)
src/codec/error.rs (1)
  • is_clean_close (254-254)
src/codec/error_tests.rs (2)
src/codec/error.rs (4)
  • default_recovery_policy (224-232)
  • should_disconnect (278-280)
  • is_clean_close (254-254)
  • error_type (288-295)
src/response.rs (1)
  • is_clean_close (251-253)
tests/worlds/codec_error.rs (4)
src/frame/format.rs (1)
  • bytes (54-54)
src/codec.rs (4)
  • default (120-124)
  • new (102-106)
  • decoder (70-70)
  • decoder (244-248)
src/codec/recovery.rs (2)
  • default (277-283)
  • new (133-133)
src/codec/error.rs (2)
  • error_type (288-295)
  • from (299-309)
tests/steps/codec_error_steps.rs (1)
src/codec/error.rs (1)
  • error_type (288-295)
🪛 GitHub Actions: CI
src/codec.rs

[error] 142-146: Formatting check failed: cargo fmt --check detected formatting issues in src/codec.rs (diff reported around the expect attribute). Run 'cargo fmt --all' or 'cargo fmt --all -- --check' to fix.

🪛 LanguageTool
docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md

[typographical] ~94-~94: To join two clauses or introduce examples, consider using an em dash.
Context: ... Codec implementation: - src/codec.rs - FrameCodec trait and `LengthDelimitedF...

(DASH_RULE)


[typographical] ~95-~95: To join two clauses or introduce examples, consider using an em dash.
Context: ...tedFrameCodec-src/codec/examples.rs- Example codec implementations -src/app...

(DASH_RULE)


[typographical] ~96-~96: To join two clauses or introduce examples, consider using an em dash.
Context: ...mentations - src/app/combined_codec.rs - Adapter for Tokio Framed Error handli...

(DASH_RULE)


[typographical] ~100-~100: To join two clauses or introduce examples, consider using an em dash.
Context: ...ndling paths: - src/app/connection.rs - Connection processing and error handling...

(DASH_RULE)


[typographical] ~101-~101: To join two clauses or introduce examples, consider using an em dash.
Context: ...r handling - src/app/frame_handling.rs - Frame deserialization and response forwa...

(DASH_RULE)


[typographical] ~102-~102: To join two clauses or introduce examples, consider using an em dash.
Context: ...and response forwarding - src/hooks.rs - Protocol hooks including error callbacks...

(DASH_RULE)


[typographical] ~106-~106: To join two clauses or introduce examples, consider using an em dash.
Context: ...nfrastructure: - tests/frame_codec.rs - Codec integration tests - `tests/feature...

(DASH_RULE)


[typographical] ~107-~107: To join two clauses or introduce examples, consider using an em dash.
Context: ...ec integration tests - tests/features/ - Cucumber feature files - tests/steps/ ...

(DASH_RULE)


[typographical] ~108-~108: To join two clauses or introduce examples, consider using an em dash.
Context: ...les - tests/steps/ and tests/worlds/ - Step definitions and world types Docume...

(DASH_RULE)


[typographical] ~112-~112: To join two clauses or introduce examples, consider using an em dash.
Context: ...entation to update: - docs/roadmap.md - Mark 9.1.2 done - docs/users-guide.md ...

(DASH_RULE)


[typographical] ~113-~113: To join two clauses or introduce examples, consider using an em dash.
Context: ... Mark 9.1.2 done - docs/users-guide.md - Public interface documentation - `docs/a...

(DASH_RULE)


[typographical] ~114-~114: To join two clauses or introduce examples, consider using an em dash.
Context: ...cs/adr-004-pluggable-protocol-codecs.md` - Design decisions ## Plan of Work ### P...

(DASH_RULE)


[typographical] ~123-~123: To join two clauses or introduce examples, consider using an em dash.
Context: ...r classification: 1. Framing errors - Wire-level frame boundary issues (e.g., ...

(DASH_RULE)


[typographical] ~125-~125: To join two clauses or introduce examples, consider using an em dash.
Context: ...hecksum mismatch) 2. Protocol errors - Semantic violations after frame extracti...

(DASH_RULE)


[typographical] ~128-~128: To join two clauses or introduce examples, consider using an em dash.
Context: ... sequence violation) 3. IO errors - Transport layer failures (wrapped `io::E...

(DASH_RULE)


[typographical] ~129-~129: To join two clauses or introduce examples, consider using an em dash.
Context: ... (wrapped io::Error) 4. EOF errors - End-of-stream handling with three sub-va...

(DASH_RULE)


[typographical] ~136-~136: To join two clauses or introduce examples, consider using an em dash.
Context: ...cy()method returning one of: -Drop` - Discard the malformed frame and continue...

(DASH_RULE)


[typographical] ~137-~137: To join two clauses or introduce examples, consider using an em dash.
Context: ...inue (recoverable errors) - Quarantine - Pause the connection temporarily (rate l...

(DASH_RULE)


[typographical] ~138-~138: To join two clauses or introduce examples, consider using an em dash.
Context: ...mporarily (rate limiting) - Disconnect - Terminate the connection (fatal errors) ...

(DASH_RULE)


[typographical] ~171-~171: Two consecutive dots
Context: ...e)for EOF at frame boundary 3. ReturnCodecError::Eof(EofError::MidFrame{..})for EOF during payload 4. ReturnCo...

(DOUBLE_PUNCTUATION)


[typographical] ~172-~172: Two consecutive dots
Context: ...{..})for EOF during payload 4. ReturnCodecError::Eof(EofError::MidHeader{..})for EOF during header 5. Maintaini...

(DOUBLE_PUNCTUATION)


[uncategorized] ~232-~232: Loose punctuation mark.
Context: ...documentation: 1. docs/users-guide.md: - Add section on codec error handlin...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~238-~238: Loose punctuation mark.
Context: ...cs/adr-004-pluggable-protocol-codecs.md: - Document the CodecError` taxonomy...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~243-~243: Loose punctuation mark.
Context: ...OF handling design 3. docs/roadmap.md: - Mark all 9.1.2 sub-items as done ...

(UNLIKELY_OPENING_PUNCTUATION)


[duplication] ~255-~255: Possible typo: you repeated a word.
Context: ...CodecError::default_recovery_policy() implementation - From<CodecError> for io::Error implementation 2. Create src/codec/recovery.rs with...

(ENGLISH_WORD_REPEAT_RULE)


[style] ~260-~260: Using four (or more) nouns in a row may decrease readability.
Context: ...ryPolicyenum -CodecErrorContextstruct -RecoveryPolicyHooktrait -DefaultRecoveryPolicyimplementation -RecoveryConfigbuilder config 3. Updatesrc/codec.rs`: - ...

(FOUR_NN)

🔍 Remote MCP Deepwiki

Summary of additional repository facts relevant to this PR (concise, review-focused):

  • Layered architecture & expected placement: repo uses Transport → Framing (codec) → Protocol → Application layering; codec-level errors and protocol hooks belong in the codec/module and WireframeProtocol hook surfaces (so adding CodecError, EofError, and on_eof is consistent).

  • Length-prefixed framing expectations: the project uses a LengthPrefixedProcessor that returns Ok(None) for incomplete frames; decode_eof must preserve prior semantics for clean close vs incomplete-frame detection (i.e., produce clean-close Ok(None) behavior where appropriate) — review LengthDelimitedDecoder changes carefully for backward compatibility.

  • Protocol hook wiring pattern: ProtocolHooks::from_protocol constructs boxed hooks (existing pattern used for before_send/on_command_end/etc.); adding on_eof must follow same registration/wiring pattern and be invoked by connection actor where EOF is detected.

  • Observability & metrics conventions: metrics are feature-gated via the "metrics" feature and use labeled Prometheus-style counters exported through metrics crate; new CODEC_ERRORS counter and inc_codec_error must match existing naming/label patterns and no-op when feature disabled.

  • Connection actor semantics & EOF handling location: ConnectionActor owns per-connection read/write loops and invokes protocol hooks; EOF detection/recovery/policy evaluation should be integrated where the connection read/decode_eof path runs (ensure recovery policies are consulted before terminating or quarantine actions).

  • Testing infra / preferred patterns: project favors rstest fixtures, wireframe_testing helpers (drive_with_bincode, drive_with_frame), and cucumber BDD worlds. Duplicate EOF tests should be consolidated into helper(s) per existing test utility patterns (wireframe_testing provides helpers and logger fixtures).

  • Session/registry & quarantine implications: RecoveryPolicy::Quarantine semantics (if used) will likely interact with SessionRegistry/PushHandle lifecycles — verify quarantine does not accidentally leak PushHandles/weak refs or prevent expected cleanup.

Files/areas to inspect closely in review (concrete targets):

  • src/codec/{error.rs,recovery.rs} and src/codec.rs — public types, From for io::Error, MIN/MAX_FRAME_LENGTH re-export behavior
  • LengthDelimitedDecoder/Encoder implementations in src/codec.rs — decode_eof behavior and encoder oversized-frame handling
  • src/hooks.rs and ProtocolHooks::from_protocol — ensure on_eof hook is constructed/registered like other hooks
  • Connection read/actor paths where EOF/errors are handled (ConnectionActor) — ensure recovery policy evaluation and on_eof invocation are placed correctly
  • src/metrics.rs and src/lib.rs exports — feature gating and label names for CODEC_ERRORS and inc_codec_error consistency
  • Tests and BDD additions (tests/, wireframe_testing helpers) — consolidate duplicated EOF tests using existing test helpers and follow rstest/drive_with_ patterns

Sources used: Deepwiki documentation pages for the leynos/wireframe repository: Overview, Core Architecture, Frame processing, Protocol hooks, Observability/metrics, ConnectionActor, and Testing pages.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (28)
src/metrics.rs (1)

36-44: LGTM!

The CODEC_ERRORS constant follows established naming conventions and includes clear documentation with example Prometheus output.

src/codec/error_tests.rs (1)

1-7: LGTM!

Well-structured test module with comprehensive coverage of recovery policies, error conversions, categorisation, and Display implementations.

src/response.rs (2)

224-253: LGTM!

The Codec variant integration is clean: from_codec mirrors the existing from_io pattern, is_clean_close correctly delegates to CodecError::is_clean_close(), and documentation references the detailed type.


256-277: LGTM!

The Display and Error trait implementations for the Codec variant are consistent with the existing Io and Protocol patterns.

src/codec/error.rs (6)

1-31: LGTM!

Excellent module-level documentation explaining the error taxonomy, categories, and recovery policies with clear structure.


32-72: LGTM!

The FramingError enum provides comprehensive wire-level error coverage with clear, actionable messages and appropriate structured fields.


74-118: LGTM!

The ProtocolError enum covers semantic violations comprehensively. The use of String fields for MissingHeader and InvalidStateTransition provides flexibility for protocol-specific contexts.


120-155: LGTM!

The EofError enum cleanly distinguishes EOF scenarios with appropriate byte count fields for diagnostics. The Copy derive is efficient for these small variants.


194-296: LGTM!

The CodecError methods are well-implemented: default_recovery_policy logic matches the documented table, is_clean_close is concise, and error_type returns consistent static strings for metrics.


298-310: LGTM!

The From<CodecError> for io::Error implementation correctly maps error categories to appropriate ErrorKind values while preserving diagnostic context.

src/codec.rs (3)

324-402: LGTM!

The assert_decode_eof_error helper consolidates test logic effectively. Test coverage for empty buffer (clean close), partial header, partial payload, and complete frame scenarios is comprehensive.


227-234: LGTM!

The encoder correctly emits CodecError::Framing(FramingError::OversizedFrame) with structured context for oversized payloads.


143-148: The lint suppression is valid and follows guidelines. No formatting issues exist.

The code at lines 143–148 correctly uses a narrowly scoped #[expect(lint, reason = "...")] (not #[allow]) as required. The reason provided clearly explains the semantic distinction: Ok(None) and Err(_) represent different failure paths within the decoder's EOF handling, even though both arms return Err(build_eof_error(src)). This justifies suppressing the match_same_arms warning.

docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md (3)

29-39: Update the progress checklist.

Several items remain unchecked but may be partially or fully implemented based on the PR changes:

  • Line 29: CodecErrorContext population - verify status
  • Line 30: Recovery policy evaluation in src/app/connection.rs - verify status
  • Line 31: Structured logging - verify status
  • Line 32: on_eof hook to ProtocolHooks - verify status
  • Lines 37-39: Documentation updates - verify status

Mark completed items as done to keep this living document accurate.


142-143: LGTM!

The spelling "customization" at line 143 follows en-GB-oxendict conventions correctly.


1-68: LGTM!

The execution plan is well-structured with clear purpose, progress tracking, decision log, and concrete steps. The living document format supports iterative development.

tests/steps/codec_error_steps.rs (3)

22-40: LGTM!

The given_error_type_variant step correctly handles unknown error types with an explicit error at line 37, and the #[expect] attribute properly documents the cucumber macro requirement.


46-75: LGTM!

The when steps clearly delegate to world methods with inline comments explaining the intent. The max + 1 at line 74 correctly ensures the frame exceeds the configured limit.


81-101: LGTM!

The then steps cleanly delegate verification to world methods. The string handling at line 100 correctly passes &policy without unnecessary allocations.

tests/worlds/codec_error.rs (9)

1-24: LGTM!

Module documentation is present and imports are appropriately scoped.


25-53: LGTM!

Enum definitions are clean with appropriate derives and defaults.


125-164: LGTM!

Error construction logic is comprehensive and covers all variant combinations.


166-190: LGTM!

Policy verification with clear error messages for test diagnostics.


212-231: LGTM!

Frame sending helpers are clean with appropriate error handling.


240-262: LGTM!

EOF clean close detection is well-structured with appropriate state updates.


283-301: LGTM!

Partial data EOF detection correctly expects and captures the error.


303-322: LGTM!

Oversized frame encoding test helper is straightforward.


324-369: LGTM!

Verification helpers provide clear diagnostics on assertion failures.

Comment thread src/codec.rs
Comment thread src/codec.rs Outdated
Comment thread src/metrics.rs Outdated
Comment thread tests/worlds/codec_error.rs Outdated
Comment thread tests/worlds/codec_error.rs Outdated
Comment thread tests/worlds/codec_error.rs Outdated
Comment thread tests/worlds/codec_error.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/codec.rs (1)

1-403: Decompose the file to meet the 400-line limit.

The file contains 403 lines, exceeding the 400-line limit specified in the coding guidelines. Move the test module (lines 264-403) to a separate file such as src/codec/tests.rs and conditionally compile it with #[cfg(test)].

♻️ Proposed decomposition

In src/codec.rs, replace the tests module:

-#[cfg(test)]
-mod tests {
-    // ... all test code ...
-}
+#[cfg(test)]
+mod tests;

Create src/codec/tests.rs:

use super::*;
use bytes::{Bytes, BytesMut};
use std::io;

#[test]
fn length_delimited_codec_clamps_max_frame_length() {
    // ... move all tests here ...
}
// ... rest of tests ...

As per coding guidelines, files must not exceed 400 lines; large modules must be decomposed.

🤖 Fix all issues with AI agents
In @src/codec.rs:
- Around line 196-209: The match arm using let Ok(header) = <[u8;
LENGTH_HEADER_SIZE]>::try_from(header_slice) else { ... } is dead code because
bytes_received >= LENGTH_HEADER_SIZE guarantees the slice is exactly
LENGTH_HEADER_SIZE; remove the unreachable else branch and replace that fallible
pattern with an infallible conversion (e.g., call try_from(...) and immediately
unwrap/expect) while keeping the variable name header and the same header_slice
source, then proceed with the existing u32_from_be_bytes call and the existing
CodecError::Eof(...) handling elsewhere.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9a4326 and a2109fd.

📒 Files selected for processing (1)
  • src/codec.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/codec.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/codec.rs
🧠 Learnings (1)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • src/codec.rs
🧬 Code graph analysis (1)
src/codec.rs (2)
src/codec/recovery.rs (1)
  • new (133-133)
src/codec/error.rs (1)
  • from (299-309)
🔍 Remote MCP Deepwiki

Summary of additional repository facts relevant to reviewing this PR (concise, review-focused):

  • Overall architecture: actor-per-connection model with ConnectionActor owning read/write loops, framed by FrameProcessor (LengthPrefixedProcessor). Codec/Eof changes must preserve ConnectionActor semantics and framing expectations (Ok(None) for incomplete frames).

  • Frame/decoder expectations: LengthPrefixedProcessor.decode() returns Ok(None) when a frame is incomplete; decode_eof must distinguish clean-close vs mid-frame per existing framing semantics — verify LengthDelimitedDecoder changes maintain backward compatibility for complete-frame clean closes vs structured EOF errors for partial data.

  • Connection lifecycle and hooks: ConnectionActor wires ProtocolHooks and calls protocol lifecycle hooks (on_connection_setup, before_send, on_command_end). New on_eof must be registered in ProtocolHooks::from_protocol and invoked where EOF is detected in the connection read/decode path (connection actor). Ensure hook wiring matches existing hook registration patterns.

  • Observability/metrics conventions: metrics are feature-gated; counters and gauge naming follow existing patterns (prometheus-style labels). CODEC_ERRORS and inc_codec_error must follow existing conditional compilation/no-op pattern when metrics feature disabled and match naming/label conventions used elsewhere (wireframe_metrics exports).

  • Session/quarantine interactions: RecoveryPolicy::Quarantine may interact with SessionRegistry / PushHandle lifecycles; verify quarantine handling doesn't leak PushHandles or prevent connection cleanup via Weak/Arc patterns used by SessionRegistry.

  • Testing infra and duplication guidance: repository uses wireframe_testing helpers (drive_with_bincode, drive_with_frame) and rstest fixtures; preferred approach is extracting shared assertions into helpers. The PR comment about consolidating duplicate decode_eof tests aligns with existing testing patterns — implement a helper that uses wireframe_testing utilities and preserves test names/#[test]/rstest attributes.

Files/areas to inspect closely in review (concrete targets):

  • src/codec/{error.rs,recovery.rs} and src/codec.rs — public types, From for io::Error, MIN/MAX_FRAME_LENGTH re-exports.
  • LengthDelimitedDecoder/Encoder changes — ensure decode_eof preserves Ok(None)/clean-close semantics and encoder oversized-frame behavior.
  • src/hooks.rs / ProtocolHooks::from_protocol — confirm on_eof hook constructed/registered analogously to other hooks.
  • Connection read/actor code (ConnectionActor) — ensure on_eof invocation and recovery policy evaluation occur at the correct point and do not break shutdown/fairness behavior.
  • src/metrics.rs and src/lib.rs exports — confirm feature-gating, label names, and export consistency with existing metrics.
  • Tests and Cucumber additions — replace duplicated EOF tests by central helper using wireframe_testing functions to follow established test utilities and fixtures; keep test discoverability and names.

Sources used: Deepwiki repository documentation pages and code overview.

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test
🔇 Additional comments (6)
src/codec.rs (6)

1-19: Clear documentation of the new error taxonomy.

The module-level documentation appropriately describes the structured error handling and recovery policies introduced in this PR. The cross-references to the error and recovery modules help users navigate the new API surface.


26-36: Public API re-exports follow Rust conventions.

The new error and recovery module exports provide a clean, discoverable API surface for the structured error taxonomy.


38-48: Frame length constants are well-documented.

The public constants provide clear bounds for frame sizes with appropriate documentation explaining their purpose.


143-168: EOF handling correctly distinguishes clean closes from partial data.

The decode_eof implementation properly handles:

  • Clean closes (empty buffer) returning Ok(None)
  • Complete frames at EOF
  • Partial data synthesised into structured EOF errors
  • Preservation of InvalidData errors (oversized frames) for correct recovery policy application

The #[expect] for clippy::match_same_arms is appropriately justified.


171-179: Endianness helper is correctly implemented.

The inline helper with explicit endianness handling and appropriately scoped lint suppression is clear and correct.


227-234: Oversized frame detection correctly uses structured errors.

The encoder properly emits CodecError::Framing(FramingError::OversizedFrame) for frames exceeding the limit, which will be converted to io::Error with InvalidData kind per the From implementation in src/codec/error.rs.

Comment thread src/codec.rs Outdated
Comment thread src/codec.rs Outdated
@leynos leynos changed the title Introduce CodecError taxonomy, recovery policies, and metrics Introduce CodecError taxonomy, EOF handling, recovery policies, metrics Jan 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/codec/tests.rs:
- Around line 64-121: Remove the assert_decode_eof_error helper and the two
tests decode_eof_with_partial_header_returns_mid_header and
decode_eof_with_partial_payload_returns_mid_frame, add use rstest::rstest; to
the test module, and replace them with a single #[rstest] parameterized test
function decode_eof_with_incomplete_data_returns_error that takes a buffer case
and an expected_substr case; inside the test create a
LengthDelimitedFrameCodec::new(128), call codec.decoder(), initialize a BytesMut
from the buffer, call decoder.decode_eof(&mut buf).expect_err(...), assert
err.kind() == io::ErrorKind::UnexpectedEof and assert
err.to_string().contains(expected_substr) (use "header" for the partial-header
case and "4" for the partial-payload case).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2109fd and 2dd10af.

📒 Files selected for processing (2)
  • src/codec.rs
  • src/codec/tests.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/codec/tests.rs
  • src/codec.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/codec/tests.rs
  • src/codec.rs
🧠 Learnings (1)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • src/codec/tests.rs
  • src/codec.rs
🧬 Code graph analysis (2)
src/codec/tests.rs (3)
src/frame/format.rs (1)
  • bytes (54-54)
src/codec.rs (10)
  • new (102-106)
  • max_frame_length (87-87)
  • max_frame_length (110-110)
  • max_frame_length (261-261)
  • encoder (73-73)
  • encoder (250-255)
  • decoder (70-70)
  • decoder (244-248)
  • frame_payload (76-76)
  • frame_payload (257-257)
src/extractor.rs (1)
  • as_ref (198-198)
src/codec.rs (6)
src/frame/format.rs (1)
  • bytes (54-54)
tests/wireframe_protocol.rs (1)
  • decode_eof (91-95)
tests/worlds/codec_stateful.rs (1)
  • decode_eof (109-111)
src/app/combined_codec.rs (1)
  • decode_eof (31-33)
src/fragment/index.rs (1)
  • try_from (48-48)
tests/worlds/correlation.rs (1)
  • expected (45-45)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test

Comment thread src/codec/tests.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue has been resolved in commit e9a4326.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/codec.rs:
- Around line 182-217: The build_eof_error function uses direct slicing
(&src[..LENGTH_HEADER_SIZE]) with an #[expect] suppression; change this to use
the safe accessor src.get(..LENGTH_HEADER_SIZE) and handle the None case by
returning the same CodecError::Eof(EofError::MidHeader { bytes_received,
header_size: LENGTH_HEADER_SIZE }) . If get(...) returns Some(slice) then
convert to an array with try_from(slice) as before and proceed to compute
expected via u32_from_be_bytes(header) and return the MidFrame error; remove the
#[expect] and any indexing-based assumptions so there is no potential panic.

In @src/codec/tests.rs:
- Around line 1-6: Add a module-level doc comment (starting with //! ) at the
very top of this test module describing its purpose and what it tests (e.g.,
that it contains unit/integration tests for the codec functions using
Bytes/BytesMut and rstest); place this comment above the existing use statements
and ensure it briefly explains the module's intent and any test utilities or
conventions used so it satisfies the coding guideline for module documentation.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2dd10af and c7eb72a.

📒 Files selected for processing (5)
  • src/codec.rs
  • src/codec/error_tests.rs
  • src/codec/tests.rs
  • src/metrics.rs
  • tests/worlds/codec_error.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • src/codec/error_tests.rs
  • src/codec/tests.rs
  • src/metrics.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • src/codec/error_tests.rs
  • src/codec/tests.rs
  • src/metrics.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs
🧠 Learnings (1)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • src/codec/error_tests.rs
  • src/codec/tests.rs
  • src/metrics.rs
  • src/codec.rs
  • tests/worlds/codec_error.rs
🧬 Code graph analysis (5)
src/codec/error_tests.rs (2)
src/codec/error.rs (4)
  • default_recovery_policy (224-232)
  • should_disconnect (278-280)
  • is_clean_close (254-254)
  • error_type (288-295)
src/response.rs (1)
  • is_clean_close (251-253)
src/codec/tests.rs (1)
src/codec.rs (10)
  • new (102-106)
  • max_frame_length (87-87)
  • max_frame_length (110-110)
  • max_frame_length (262-262)
  • encoder (73-73)
  • encoder (251-256)
  • decoder (70-70)
  • decoder (245-249)
  • frame_payload (76-76)
  • frame_payload (258-258)
src/metrics.rs (3)
src/codec/error.rs (1)
  • error_type (288-295)
src/codec/recovery.rs (1)
  • recovery_policy (208-211)
tests/codec_error.rs (1)
  • recovery_policy (141-143)
src/codec.rs (6)
src/frame/format.rs (1)
  • bytes (54-54)
tests/wireframe_protocol.rs (1)
  • decode_eof (91-95)
tests/worlds/codec_stateful.rs (1)
  • decode_eof (109-111)
src/app/combined_codec.rs (1)
  • decode_eof (31-33)
src/fragment/index.rs (1)
  • try_from (48-48)
tests/worlds/correlation.rs (1)
  • expected (45-45)
tests/worlds/codec_error.rs (3)
src/codec.rs (6)
  • default (120-124)
  • new (102-106)
  • encoder (73-73)
  • encoder (251-256)
  • decoder (70-70)
  • decoder (245-249)
src/codec/recovery.rs (2)
  • default (277-283)
  • new (133-133)
src/codec/error.rs (2)
  • error_type (288-295)
  • from (299-309)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test
🔇 Additional comments (7)
src/metrics.rs (1)

36-152: LGTM!

The metrics integration for codec errors is well-implemented. The function signature aligns with the error taxonomy (error_type() returns &'static str), documentation includes clear Prometheus examples, and the feature-gating pattern is consistent with existing metrics functions.

src/codec.rs (3)

1-52: LGTM!

The module documentation clearly explains the error taxonomy and recovery policies. Public exports are well-organised, and the frame length constants are properly documented with clamping behaviour explained.


143-169: LGTM!

The decode_eof implementation correctly differentiates clean close, complete frames, and EOF error conditions. The logic preserves InvalidData framing errors (like oversized frames) to maintain correct recovery policies, whilst synthesising structured EOF errors for incomplete data. The #[expect] for match_same_arms is well-justified.


219-266: LGTM!

The encoder correctly emits structured CodecError::Framing for oversized frames, replacing the previous generic io::Error approach. The test module organisation maintains the file within the 400-line limit.

src/codec/error_tests.rs (1)

1-144: LGTM!

The unit tests comprehensively cover error taxonomy, recovery policies, io::Error conversions, and Display implementations. Each test is focused on a distinct aspect of the error types with clear assertions. The test structure is well-organised and maintainable.

src/codec/tests.rs (1)

79-123: LGTM!

The parameterised EOF error tests using #[rstest] eliminate code duplication whilst maintaining clear test case documentation. The test cases cover partial header and partial payload scenarios with precise assertions on error kind and message content. The complete frame test provides good complementary coverage.

tests/worlds/codec_error.rs (1)

1-390: LGTM!

The test world provides comprehensive BDD scaffolding for codec error scenarios with well-structured public APIs and end-to-end decoder operations. The classify_eof_error method's fragility is appropriately documented (lines 275-284), noting the string-matching approach is acceptable for test code where error messages are controlled. The implementation correctly balances pragmatism with clear documentation of trade-offs.

Comment thread src/codec/tests.rs
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/worlds/mod.rs (1)

1-7: Update the module doc comment to include codec_error.

The module-level documentation lists several world types but omits the newly added codec_error world. Add it to the list for completeness.

📝 Proposed fix
 //! Cucumber test world implementations and shared helpers.
 //!
 //! Provides world types for behaviour-driven tests covering fragmentation,
 //! correlation, panic recovery, stream termination, multi-packet channels,
-//! stateful codecs, request parts, and message assembler parsing. Shared
+//! stateful codecs, request parts, message assembler parsing, and codec error
+//! taxonomy. Shared
 //! utilities like `build_small_queues` keep individual worlds focused on their
 //! respective scenarios.
🤖 Fix all issues with AI agents
In @src/codec.rs:
- Around line 182-209: Add a doc comment to build_eof_error that explains the
EOF variants: when fewer than LENGTH_HEADER_SIZE bytes have been read it returns
CodecError::Eof(EofError::MidHeader { .. }) because the length prefix is
incomplete, and when at least LENGTH_HEADER_SIZE bytes exist but the payload is
truncated it returns CodecError::Eof(EofError::MidFrame { bytes_received:
src.len() - LENGTH_HEADER_SIZE, expected }) indicating a complete header but
incomplete frame; reference LENGTH_HEADER_SIZE, MidHeader, MidFrame, and
build_eof_error in the comment.

In @tests/steps/codec_error_steps.rs:
- Around line 58-62: Change the when step function when_client_sends_partial to
return TestResult rather than unit to match other steps; call
world.send_partial_frame_header_only() as before and return Ok(()) at the end so
the signature and return type are consistent and future failures can be
propagated. Ensure you update the function signature for
when_client_sends_partial and its usage of
CodecErrorWorld::send_partial_frame_header_only to reflect the TestResult
return.

In @tests/worlds/codec_error/decoder_ops.rs:
- Around line 87-99: In extract_expected_length(), replace the hard-coded array
type <[u8; 4]> with the constant-sized array using LENGTH_HEADER_SIZE (i.e., use
<[u8; LENGTH_HEADER_SIZE]>::try_from(slice).ok()) so the array length matches
the slice range and remains consistent; keep the rest of the map_or logic
unchanged to convert the big-endian bytes to a usize via u32::from_be_bytes.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afe8c93 and cfa5ed4.

📒 Files selected for processing (5)
  • src/codec.rs
  • tests/steps/codec_error_steps.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/mod.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • tests/steps/codec_error_steps.rs
  • tests/worlds/mod.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • src/codec.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/steps/codec_error_steps.rs
  • tests/worlds/mod.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • src/codec.rs
🧠 Learnings (2)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • tests/steps/codec_error_steps.rs
  • tests/worlds/mod.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • src/codec.rs
📚 Learning: 2026-01-08T08:16:31.989Z
Learnt from: leynos
Repo: leynos/wireframe PR: 411
File: src/client/tests/lifecycle.rs:0-0
Timestamp: 2026-01-08T08:16:31.989Z
Learning: In Rust, apply #[expect(lint, reason = "...")] only to code that actually triggers the specified lint. If the code does not trigger the lint, Clippy will fail with 'unfulfilled lint expectation' errors, which is intentional to prevent stale suppressions. Do not add #[expect] attributes proactively or to code that doesn't trigger the lint, even if similar code elsewhere requires suppression. This guideline should be applied broadly to Rust source files across the repository.

Applied to files:

  • tests/steps/codec_error_steps.rs
  • tests/worlds/mod.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • src/codec.rs
🧬 Code graph analysis (4)
tests/steps/codec_error_steps.rs (1)
src/codec/error.rs (1)
  • error_type (288-295)
tests/worlds/codec_error/mod.rs (2)
src/codec.rs (2)
  • default (120-124)
  • build_eof_error (182-209)
src/codec/error.rs (1)
  • error_type (288-295)
tests/worlds/codec_error/decoder_ops.rs (1)
src/codec.rs (5)
  • new (102-106)
  • encoder (73-73)
  • encoder (243-248)
  • decoder (70-70)
  • decoder (237-241)
src/codec.rs (4)
src/frame/format.rs (1)
  • bytes (54-54)
tests/worlds/codec_error/mod.rs (1)
  • build_eof_error (152-164)
tests/worlds/correlation.rs (1)
  • expected (45-45)
src/fragment/index.rs (1)
  • try_from (48-48)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (25)
tests/worlds/mod.rs (1)

32-33: LGTM!

The module declaration correctly follows the established pattern for path-based modules in this file.

tests/steps/codec_error_steps.rs (4)

1-8: LGTM!

Clear module documentation and appropriate imports for cucumber step definitions.


14-20: LGTM!

Setup steps are appropriately simple and delegate to the world methods.


22-40: LGTM!

The lint expectation for needless_pass_by_value is correctly scoped and justified. The match arm handling for unknown error types provides clear error messages.


46-56: LGTM!

The remaining steps correctly delegate to world methods and return TestResult where appropriate. The lint expectations are properly scoped with clear reasons.

Also applies to: 64-75, 81-101

tests/worlds/codec_error/mod.rs (6)

1-14: LGTM!

Clear module documentation with appropriate imports. The #[cfg(not(loom))] attribute correctly excludes this test world from loom testing.


16-44: LGTM!

The enums are well-structured with sensible defaults. The variants align with the FramingError and EofError types from the codec module.


46-67: LGTM!

The world struct appropriately uses pub(crate) visibility for fields accessed by the sibling decoder_ops module, whilst keeping error_type, framing_variant, and eof_variant private to this module.


69-104: LGTM!

The setter methods follow a consistent pattern: parse the string, update state, rebuild the error. Error messages are clear and actionable.


106-164: LGTM!

The builder methods correctly construct error variants with representative test data. The helper methods are appropriately private.


166-191: LGTM!

The verification method correctly maps string policy names to RecoveryPolicy variants and provides clear diagnostic messages on mismatch.

tests/worlds/codec_error/decoder_ops.rs (7)

1-13: LGTM!

Clear module documentation and appropriate imports for decoder operations.


15-33: LGTM!

The setup methods correctly initialise state and delegate to reset_codec_state. The default max frame length of 1024 matches the codec's default (per src/codec.rs:119-123).


35-54: LGTM!

Frame encoding and partial frame simulation are straightforward. The magic bytes [0x00, 0x00, 0x00, 0x64] correctly encode 100 in big-endian.


56-85: LGTM!

The clean-close detection logic correctly drains complete frames before calling decode_eof, and distinguishes between the three possible outcomes with clear error messages.


101-131: Acknowledge the fragility and approve.

The doc comment at lines 103-114 explicitly calls out that string-matching is fragile and explains why it's acceptable for test code. This is good defensive documentation.


133-172: LGTM!

Both decode_eof_with_partial_data and encode_oversized_frame correctly capture errors and return appropriate test results. The error classification is handled upstream.


174-219: LGTM!

The verification methods provide clear diagnostic messages and correctly distinguish between clean closes, incomplete EOFs, and oversized frame errors. The check for InvalidData error kind aligns with the OversizedFrameio::Error conversion in the codec.

src/codec.rs (7)

1-19: Excellent module documentation.

The expanded documentation clearly explains the codec layer's error taxonomy and recovery policies, providing a helpful overview for API consumers.


26-36: LGTM: Public API surface appropriately expanded.

The module declarations and re-exports provide a clean, ergonomic API for error handling and recovery customisation.


38-48: LGTM: Frame-length and header constants properly exposed.

The public constants are well-documented and provide necessary constraints for codec users. The 16 MiB maximum and 64-byte minimum are reasonable defaults.

Also applies to: 127-128


143-170: Robust EOF handling with proper error preservation.

The enhanced decode_eof correctly distinguishes clean closes from incomplete data and preserves framing errors (e.g., oversized frames) to maintain accurate recovery policies. The diagnostic logging at line 162 aids troubleshooting without impacting error semantics.


172-179: LGTM: Correct endianness handling with justified lint expectation.

The #[expect] attribute is appropriately scoped and includes a clear reason explaining that wire endianness is an explicit protocol requirement. The big-endian parsing aligns with the length-delimited codec specification.


220-227: LGTM: Proper oversized frame validation.

The encoder correctly validates frame size before encoding and returns a structured CodecError::Framing with context (size, max), enabling appropriate recovery policy selection.


258-258: Incorrect file reference: test duplication is in src/codec/error_tests.rs and tests/codec_error.rs, not in src/codec.rs at line 258.

Line 258 simply declares mod tests;, which is correct. The actual duplicated EOF tests exist elsewhere:

  • src/codec/error_tests.rs contains clean_eof_is_detectable() and mid_frame_eof_recommends_disconnect()
  • tests/codec_error.rs contains equivalent tests: eof_clean_close_is_detectable() and eof_mid_frame_is_not_clean()

Extract common test logic into a helper function accepting buffer data, expected error kind, and message validator. Test both files directly to remediate the duplication.

Likely an incorrect or invalid review comment.

Comment thread src/codec.rs
Comment thread tests/steps/codec_error_steps.rs
Comment thread tests/worlds/codec_error/decoder_ops.rs
@leynos leynos changed the title Introduce CodecError taxonomy, EOF handling, recovery policies, metrics CodecError taxonomy, EOF handling, recovery policies, metrics Jan 8, 2026
leynos and others added 11 commits January 8, 2026 19:18
Introduce a structured CodecError taxonomy to classify codec-layer errors into framing, protocol, I/O, and EOF categories. Define default recovery policies (Drop, Quarantine, Disconnect) per error type and implement a RecoveryPolicyHook trait for customization. Extend WireframeError to include Codec variants and update the LengthDelimitedDecoder to produce structured EOF errors distinguishing clean and premature connection closures.

Add comprehensive unit, integration, and behavioural tests covering error classification, recovery policies, and error propagation. Enhance documentation in users-guide, roadmap, and ADR to describe the error taxonomy, recovery policies, and EOF handling. Add metrics support for tracking codec errors by type and recovery policy.

This commit improves error handling robustness, observability, and protocol extensibility in the codec layer.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
…sertions

Introduced a helper function to centralize and simplify testing of EOF error behavior in the length-delimited decoder. This helper replaces duplicated test code that asserts error kind and message content for various EOF edge cases (empty buffer, partial header, partial payload). Tests now call this helper with parameters describing the expected error conditions, improving maintainability and readability.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Previously, decode_eof returned an error on clean close when the buffer was empty. The fix changes it to return Ok(None) for a clean connection close, improving correctness and aligning with expected behavior.

Updated tests to verify the new correct behavior.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
- Fix `build_eof_error` to parse length prefix without relying on `Buf`
  trait - use `u32::from_be_bytes` with explicit clippy allow directives
- Remove `PartialEq`/`Eq` implementations from `WireframeError` as the
  `Codec` variant made equality comparisons meaningless
- Consolidate duplicate EOF and oversized-frame tests: removed redundant
  integration tests from `tests/codec_error.rs` that duplicated unit
  tests already present in `src/codec.rs`
- Implement end-to-end behavioural tests for codec error taxonomy:
  replace simulated step implementations with real decoder operations
  that exercise the actual codec error paths

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Address review comments:
- Preserve framing errors (InvalidData) at EOF instead of converting all
  errors to EOF errors; only convert incomplete data errors (Other kind)
- Update execplan progress checklist with completed items
- Use Oxford spelling "customization" instead of "customisation"
- Use colons in EOF sub-variant list for consistent formatting
- Extract tests from error.rs to error_tests.rs to stay under 400 lines
- Remove unnecessary .to_string() allocations in inc_codec_error metrics
- Delegate WireframeError::is_clean_close to CodecError::is_clean_close
- Handle unknown error types explicitly in cucumber steps
- Simplify String handling with as_str() instead of into_boxed_str()
- Move doc comment before derive attributes in CodecErrorWorld
- Add #[expect] attributes with reason for clippy suppressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The multi-line string in the `#[expect]` attribute was reformatted to avoid trailing spaces and improve readability without changing semantics.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
Extract tests from `src/codec.rs` into a new `src/codec/tests.rs` file to improve code organization and maintainability without changing test functionality.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
- Replace individual EOF error tests with parameterized tests using rstest.
- Add detailed tests verifying correct io::ErrorKind conversion for CodecError variants.
- Enhance CodecErrorWorld test helper with new fields and methods to manage error states.
- Minor defensive comment and code improvements in codec.rs for EOF error handling.
- Improve test code comments explaining EOF error classification fragility.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
This refactors the length-delimited codec's EOF error construction by:
- Using safe buffer access to parse length header with `get()` and `try_from` instead of unchecked slicing.
- Introducing a dedicated `parse_length_header` function for clarity.
- Logging inner decoder errors on EOF before converting to structured EOF errors.

Additionally, the EOF error classification in codec error tests now extracts the expected payload length from the buffer's length header instead of defaulting to zero, improving test robustness.

These changes enhance safety, diagnostics, and test accuracy without altering external behavior.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
- Remove legacy codec_error test world implementation.
- Introduce new modular codec_error test world with explicit error and EOF variants.
- Add realistic end-to-end decoder operations for validating error handling and recovery policies.
- Update test steps to reference new codec_error world implementation.
- Make LENGTH_HEADER_SIZE constant public for test usage.

This change improves the coverage and accuracy of codec error scenario tests by using actual decoding and encoding operations, facilitating robust validation of framing, protocol, IO, and EOF error handling with proper recovery policy checks.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
- Expanded `build_eof_error` doc to clarify EOF error cases (MidHeader vs MidFrame).
- Corrected documentation in `extract_expected_length` test function about length header.
- Updated module-level docs to mention codec error taxonomy world.

These changes improve code clarity and documentation accuracy without behavior changes.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos force-pushed the terragon/introduce-codecerror-taxonomy-3abr1i branch from e5ea54e to 2e71a3d Compare January 8, 2026 19:20
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 8, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jan 8, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In @docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md:
- Line 36: The document still references Cucumber tests (e.g., the checklist
item "- [x] (2026-01-07) Add Cucumber behavioural tests for codec error
scenarios" and occurrences at lines 36, 218-226, 307-313, 441, 444); remove or
replace each Cucumber mention with the approved alternative testing approach
(e.g., "integration tests using Jest/Playwright" or "end-to-end tests using
<replacement framework>") and update any checklist items and test descriptions
to reflect the new test names, runners, and file paths so the ExecPlan
accurately describes the replacement testing strategy.

In @src/codec/error_tests.rs:
- Line 131: Replace the char literal in the assertion with a string literal to
match the rest of the file: change the assertion that calls
display.contains('5') to use display.contains("5") so all contains() checks
(e.g., the one using "10") use string literals consistently; update only the
assertion referencing display to use "5".
- Around line 99-111: Replace the four repetitive assertions in the test
function error_type_returns_correct_category with an rstest parameterized test:
add #[rstest] to the test, import rstest::rstest, and declare test parameters
(e.g. error: CodecError, expected: &str) with multiple #[case(...)] entries for
CodecError::Framing(FramingError::EmptyFrame) -> "framing",
CodecError::Protocol(ProtocolError::UnknownMessageType { type_id: 1 }) ->
"protocol", CodecError::Io(io::Error::other("test")) -> "io", and
CodecError::Eof(EofError::CleanClose) -> "eof", then assert that
error.error_type() == expected inside the test body.
- Around line 8-71: Consolidate the repetitive tests by adding rstest
(dev-dependency) and replacing the repeated assertions with a parameterized test
that calls CodecError::default_recovery_policy() and
CodecError::should_disconnect(); create one #[rstest] fn (e.g.
recovery_policy_cases) taking parameters (err, expected_policy: RecoveryPolicy,
expected_disconnect: bool) and supply tuples for OversizedFrame, EmptyFrame,
InvalidLengthEncoding, Protocol::UnknownMessageType,
Io(io::Error::other("test")), Eof::MidFrame and Eof::MidHeader;
assert_eq!(err.default_recovery_policy(), expected_policy) and
assert_eq!(err.should_disconnect(), expected_disconnect). Leave the clean EOF
case as its own small test asserting err.is_clean_close() and
should_disconnect() for Eof::CleanClose. Ensure you import rstest::rstest and
reference the exact symbols CodecError,
FramingError::OversizedFrame/EmptyFrame/InvalidLengthEncoding,
ProtocolError::UnknownMessageType, EofError::CleanClose/MidFrame/MidHeader,
RecoveryPolicy, default_recovery_policy, should_disconnect, and is_clean_close.

In @src/metrics.rs:
- Around line 122-152: The RecoveryPolicy enum lacks a conversion to the string
values expected by inc_codec_error; add an impl for RecoveryPolicy with a pub
const fn as_str(self) -> &'static str that returns "drop", "quarantine", or
"disconnect" for the Drop, Quarantine, and Disconnect variants respectively, and
update callers to pass recovery_policy.as_str() to inc_codec_error so metrics
use the canonical strings.

In @tests/worlds/codec_error/decoder_ops.rs:
- Around line 101-131: Add a FIXME comment above the classify_eof_error method
noting the fragility of string-matching on std::io::Error messages and link to
the tracking issue (e.g., FIXME: track replacing message-based classification
with downcasting to CodecError – issue #1234). Reference the relevant symbols in
the comment so future readers know what to change: classify_eof_error, EofError,
LENGTH_HEADER_SIZE, and extract_expected_length; mention that when an underlying
error type is exposed, the logic should switch from msg.contains("header") to
downcasting and using concrete error variants.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5ea54e and 2e71a3d.

📒 Files selected for processing (23)
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
  • docs/roadmap.md
  • docs/users-guide.md
  • src/app/error.rs
  • src/codec.rs
  • src/codec/error.rs
  • src/codec/error_tests.rs
  • src/codec/recovery.rs
  • src/codec/tests.rs
  • src/hooks.rs
  • src/lib.rs
  • src/metrics.rs
  • src/response.rs
  • tests/codec_error.rs
  • tests/cucumber.rs
  • tests/features/codec_error.feature
  • tests/steps/codec_error_steps.rs
  • tests/steps/mod.rs
  • tests/world.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • tests/worlds/mod.rs
🧰 Additional context used
📓 Path-based instructions (6)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt (cargo fmt --workspace -- --check) before committing to validate Rust formatting across the entire workspace without modifying files.
Run make lint (cargo clippy --workspace --all-targets --all-features -- -D warnings) before committing to lint every target with all features enabled and deny all Clippy warnings.
Run make test (cargo test --workspace) before committing to run the full workspace test suite.
Clippy warnings MUST be disallowed. Fix any warnings emitted during tests in the code itself rather than silencing them.
Use make fmt (cargo fmt --workspace) to apply formatting fixes reported by the formatter check.
Where a Rust function is too long, extract meaningfully named helper functions adhering to separation of concerns and CQRS.
Where a Rust function has too many parameters, group related parameters in meaningfully named structs.
Where a Rust function is returning a large error, consider using Arc to reduce the amount of data returned.
Write unit and behavioural tests for new Rust functionality. Run both before and after making any change.
Every Rust module must begin with a module level (//!) comment explaining the module's purpose and utility.
Document public Rust APIs using Rustdoc comments (///) so documentation can be generated with cargo doc.
Prefer immutable data and avoid unnecessary mut bindings in Rust code.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly with a 'SAFETY' comment.
Place function attributes after doc comments in Rust code.
Do not use return in single-line Rust functions.
Use predicate functions for Rust conditional criteria with more than two branches.
Lints must not be silenced in Rust except as a last resort. Lint rule suppressions must be tightly scoped and include a clear reason.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where...

Files:

  • tests/codec_error.rs
  • tests/world.rs
  • src/response.rs
  • src/metrics.rs
  • src/hooks.rs
  • src/codec/tests.rs
  • src/codec/error.rs
  • tests/worlds/mod.rs
  • src/app/error.rs
  • tests/steps/mod.rs
  • src/codec/error_tests.rs
  • tests/steps/codec_error_steps.rs
  • src/codec.rs
  • tests/cucumber.rs
  • src/lib.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • src/codec/recovery.rs

⚙️ CodeRabbit configuration file

**/*.rs: * Seek to keep the cognitive complexity of functions no more than 9.

  • Adhere to single responsibility and CQRS
  • Place function attributes after doc comments.
  • Do not use return in single-line functions.
  • Move conditionals with >2 branches into a predicate function.
  • Avoid unsafe unless absolutely necessary.
  • Every module must begin with a //! doc comment that explains the module's purpose and utility.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lints must not be silenced except as a last resort.
    • #[allow] is forbidden.
    • Only narrowly scoped #[expect(lint, reason = "...")] is allowed.
    • No lint groups, no blanket or file-wide suppression.
    • Include FIXME: with link if a fix is expected.
  • Where code is only used by specific features, it must be conditionally compiled or a conditional expectation for unused_code applied.
  • Use rstest fixtures for shared setup and to avoid repetition between tests.
  • Replace duplicated tests with #[rstest(...)] parameterised cases.
  • Prefer mockall for mocks/stubs.
  • Prefer .expect() over .unwrap() in tests.
  • .expect() and .unwrap() are forbidden outside of tests. Errors must be propagated.
  • Ensure that any API or behavioural changes are reflected in the documentation in docs/
  • Ensure that any completed roadmap steps are recorded in the appropriate roadmap in docs/
  • Files must not exceed 400 lines in length
    • Large modules must be decomposed
    • Long match statements or dispatch tables should be decomposed by domain and collocated with targets
    • Large blocks of inline data (e.g., test fixtures, constants or templates) must be moved to external files and inlined at compile-time or loaded at run-time.
  • Environment access (env::set_var and env::remove_var) are always unsafe in Rust 2024 and MUST be marked as such
    • For testing of functionality depending upon environment variables, dependency injection and...

Files:

  • tests/codec_error.rs
  • tests/world.rs
  • src/response.rs
  • src/metrics.rs
  • src/hooks.rs
  • src/codec/tests.rs
  • src/codec/error.rs
  • tests/worlds/mod.rs
  • src/app/error.rs
  • tests/steps/mod.rs
  • src/codec/error_tests.rs
  • tests/steps/codec_error_steps.rs
  • src/codec.rs
  • tests/cucumber.rs
  • src/lib.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • src/codec/recovery.rs
docs/**/*.md

📄 CodeRabbit inference engine (docs/contents.md)

Follow the documentation style guide conventions when writing project documentation

Write official documentation with separate guides for 'Duplex Messaging & Pushes', 'Streaming Responses', and 'Message Fragmentation', each including runnable examples and explaining relevant concepts and APIs

docs/**/*.md: Use markdown files within the docs/ directory as a knowledge base and source of truth for project requirements, dependency choices, and architectural decisions.
When new decisions are made, requirements change, libraries are added/removed, or architectural patterns evolve, proactively update the relevant file(s) in the docs/ directory to reflect the latest state. Ensure documentation remains accurate and current.
Documentation must use en-GB-oxendict spelling and grammar ('-ize' / '-yse' / '-our'), with the exception of the filename LICENSE.
Reference the documentation style guide provided at docs/documentation-style-guide.md for documentation writing standards.

Documentation describing multi-packet streaming MUST reflect the tuple-based API and explain helper constructors that prepare the channel pair

Replace or retire documentation that refers to Cucumber, including guides and examples in docs/

docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: suffix -ize in words like 'realize' and 'organization', suffix -lyse in words like 'analyse' and 'paralyse', suffix -our in words like 'colour' and 'behaviour', suffix -re in words like 'calibre' and 'centre', double 'l' in words like 'cancelled' and 'counsellor', maintain the 'e' in words like 'likeable' and 'liveable', suffix -ogue in words like 'analogue' and 'catalogue'
The word 'outwith' is acceptable in documentation
Keep US spelling when used in an API, for example 'color'
Use the Oxford comma: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns, for example: 'Lille Industries are expanding'
Write hea...

Files:

  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
docs/**/*.{rs,md}

📄 CodeRabbit inference engine (docs/rust-doctest-dry-guide.md)

Every doctest should validate the public API of a crate from the perspective of an external user, treating each documentation test as a separate temporary crate that imports the library as an external dependency

Files:

  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

**/*.md: Validate Markdown files using make markdownlint.
Run make fmt after any documentation changes to format all Markdown files and fix table markup.
Validate Mermaid diagrams in Markdown files by running make nixie.
Markdown paragraphs and bullet points must be wrapped at 80 columns.
Code blocks in Markdown must be wrapped at 120 columns.
Markdown tables and headings must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes in Markdown.

Files:

  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/users-guide.md
  • docs/adr-004-pluggable-protocol-codecs.md
  • docs/roadmap.md
  • docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md
docs/adr-[0-9][0-9][0-9]-*.md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

docs/adr-[0-9][0-9][0-9]-*.md: ADR naming convention: Name ADR files using the pattern 'adr-NNN-short-description.md', where NNN is a zero-padded sequence number (e.g. 'adr-001-async-fixtures-and-tests.md'). Place ADRs in the 'docs/' directory
ADR required sections: Include Status (one of 'Proposed', 'Accepted', 'Superseded', or 'Deprecated'; for 'Accepted' status include the date and a brief summary of what was decided), Date (format: YYYY-MM-DD), and Context and Problem Statement sections in order
ADR conditional sections: Include Decision Drivers, Requirements, Options Considered, Decision Outcome / Proposed Direction, Goals and Non-Goals, Migration Plan, Known Risks and Limitations, Outstanding Decisions, and Architectural Rationale sections as appropriate to the decision's complexity
ADR formatting: Use second-level headings (##) for major sections and third-level headings (###) for subsections (e.g. phases, option names)
ADR formatting: Use tables to compare options when multiple dimensions are relevant and include a caption below the table (e.g. 'Table 1: Trade-offs between X and Y.')
ADR formatting: Include code snippets with language identifiers when illustrating technical approaches. Use 'no_run' for illustrative Rust code that should not be executed
ADR formatting: Add screen reader descriptions before complex diagrams or code blocks
ADR formatting: Reference external sources using inline links or footnotes

Files:

  • docs/adr-004-pluggable-protocol-codecs.md
docs/**/roadmap*.md

📄 CodeRabbit inference engine (docs/documentation-style-guide.md)

docs/**/roadmap*.md: Roadmap task writing: Define outcomes, not intentions. Phrase tasks in terms of the capability delivered (e.g. 'Implement role-based access control for API endpoints'), not aspirations
Roadmap task writing: Quantify completion criteria with measurable finish lines (e.g. '90% test coverage for new modules', 'response times under 200ms')
Roadmap task writing: Break into atomic increments. Ensure tasks can be completed in weeks, not quarters. Large goals should be decomposed into clear, deliverable units
Roadmap task writing: Tie to dependencies and sequencing. Document prerequisites so tasks can be scheduled realistically
Roadmap task writing: Bound scope explicitly. Note both in-scope and out-of-scope elements (e.g. 'Build analytics dashboard (excluding churn prediction)')
Roadmap formatting: Use dotted numbering for phases (1, 2, 3, …), steps (1.1, 1.2, 1.3, …), and headline tasks (1.1.1, 1.1.2, 1.1.3, …)
Roadmap formatting: Precede task and sub-task items with a GitHub-Flavoured Markdown (GFM) checkbox ([]) to track completion status
Roadmap formatting: Note non-linear dependencies explicitly using dotted notation (e.g. 'Requires 2.3.1')
Roadmap formatting: Include explicit success criteria only where not immediately obvious from the task description
Roadmap formatting: Where applicable, cite the relevant design document section for each task (e.g. 'See design-doc.md §3.2')

Files:

  • docs/roadmap.md
🧠 Learnings (5)
📚 Learning: 2026-01-03T17:44:28.792Z
Learnt from: leynos
Repo: leynos/wireframe PR: 412
File: tests/streaming_request.rs:85-107
Timestamp: 2026-01-03T17:44:28.792Z
Learning: In Rust code (e.g., the wireframe project), avoid direct indexing like vec[0] because it can panic. Prefer safe accessors that return Option, such as vec.get(0), vec.first(), or patterns that handle Option safely (e.g., match or if let Some). This should be applied across Rust files, including tests (e.g., tests/streaming_request.rs), regardless of prior length checks, to ensure safety and avoid panics from out-of-bounds access.

Applied to files:

  • tests/codec_error.rs
  • tests/world.rs
  • src/response.rs
  • src/metrics.rs
  • src/hooks.rs
  • src/codec/tests.rs
  • src/codec/error.rs
  • tests/worlds/mod.rs
  • src/app/error.rs
  • tests/steps/mod.rs
  • src/codec/error_tests.rs
  • tests/steps/codec_error_steps.rs
  • src/codec.rs
  • tests/cucumber.rs
  • src/lib.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • src/codec/recovery.rs
📚 Learning: 2026-01-08T08:16:31.989Z
Learnt from: leynos
Repo: leynos/wireframe PR: 411
File: src/client/tests/lifecycle.rs:0-0
Timestamp: 2026-01-08T08:16:31.989Z
Learning: In Rust, apply #[expect(lint, reason = "...")] only to code that actually triggers the specified lint. If the code does not trigger the lint, Clippy will fail with 'unfulfilled lint expectation' errors, which is intentional to prevent stale suppressions. Do not add #[expect] attributes proactively or to code that doesn't trigger the lint, even if similar code elsewhere requires suppression. This guideline should be applied broadly to Rust source files across the repository.

Applied to files:

  • tests/codec_error.rs
  • tests/world.rs
  • src/response.rs
  • src/metrics.rs
  • src/hooks.rs
  • src/codec/tests.rs
  • src/codec/error.rs
  • tests/worlds/mod.rs
  • src/app/error.rs
  • tests/steps/mod.rs
  • src/codec/error_tests.rs
  • tests/steps/codec_error_steps.rs
  • src/codec.rs
  • tests/cucumber.rs
  • src/lib.rs
  • tests/worlds/codec_error/decoder_ops.rs
  • tests/worlds/codec_error/mod.rs
  • src/codec/recovery.rs
📚 Learning: 2026-01-05T20:11:49.999Z
Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/adr-004-pluggable-protocol-codecs.md:0-0
Timestamp: 2026-01-05T20:11:49.999Z
Learning: Outstanding decisions pending: codec error taxonomy and recovery policy (drop frame vs. close connection), realignment of fragmentation with the FragmentAdapter design, and unification of codec handling between app router and Connection actor

Applied to files:

  • docs/adr-004-pluggable-protocol-codecs.md
📚 Learning: 2026-01-05T20:11:49.999Z
Learnt from: CR
Repo: leynos/wireframe PR: 0
File: docs/adr-004-pluggable-protocol-codecs.md:0-0
Timestamp: 2026-01-05T20:11:49.999Z
Learning: Applies to docs/**/*_test.rs,**/tests/**/*.rs : Add codec-specific tests that exercise encoder/decoder round-trips, payload extraction, correlation identifiers, and maximum frame length enforcement

Applied to files:

  • src/codec/tests.rs
  • src/codec.rs
📚 Learning: 2026-01-08T18:03:59.395Z
Learnt from: leynos
Repo: leynos/wireframe PR: 417
File: tests/steps/codec_error_steps.rs:58-62
Timestamp: 2026-01-08T18:03:59.395Z
Learning: In the wireframe repo, test step functions that always succeed should not return TestResult, because clippy::unnecessary_wraps will fire and cannot be suppressed in tests. Do not use #[allow(...)] or #[expect(clippy::unnecessary_wraps)] to silence it. Refactor such steps to return () or a type that does not trigger the lint, or move these steps to non-test code or a helper that isn’t linted the same way. This guidance applies to all files under tests/steps that contain similar patterns.

Applied to files:

  • tests/steps/mod.rs
  • tests/steps/codec_error_steps.rs
🧬 Code graph analysis (11)
tests/codec_error.rs (5)
src/codec/error.rs (4)
  • default_recovery_policy (224-232)
  • should_disconnect (278-280)
  • error_type (288-295)
  • is_clean_close (254-254)
src/response.rs (2)
  • is_clean_close (251-253)
  • from_codec (243-243)
src/codec.rs (3)
  • new (102-106)
  • correlation_id (84-84)
  • default (120-124)
src/codec/recovery.rs (10)
  • new (133-133)
  • context_with_peer_address (333-337)
  • recovery_policy_default_is_drop (316-318)
  • recovery_config_builder (370-379)
  • default (277-283)
  • max_consecutive_drops (289-292)
  • quarantine_duration (217-219)
  • quarantine_duration (296-299)
  • log_dropped_frames (303-306)
  • recovery_policy (208-211)
src/hooks.rs (1)
  • default (141-150)
src/response.rs (1)
src/codec/error.rs (1)
  • is_clean_close (254-254)
src/metrics.rs (3)
src/codec/error.rs (1)
  • error_type (288-295)
tests/codec_error.rs (1)
  • recovery_policy (141-143)
src/codec/recovery.rs (1)
  • recovery_policy (208-211)
src/codec/error.rs (3)
src/response.rs (4)
  • from (110-110)
  • from (114-114)
  • from (233-233)
  • is_clean_close (251-253)
src/codec.rs (1)
  • new (102-106)
src/codec/recovery.rs (1)
  • new (133-133)
src/app/error.rs (2)
src/codec/error.rs (1)
  • from (299-309)
src/response.rs (3)
  • from (110-110)
  • from (114-114)
  • from (233-233)
src/codec/error_tests.rs (1)
src/codec/error.rs (4)
  • default_recovery_policy (224-232)
  • should_disconnect (278-280)
  • is_clean_close (254-254)
  • error_type (288-295)
tests/steps/codec_error_steps.rs (1)
src/codec/error.rs (1)
  • error_type (288-295)
src/codec.rs (4)
src/frame/format.rs (1)
  • bytes (54-54)
tests/worlds/codec_error/mod.rs (1)
  • build_eof_error (152-164)
tests/worlds/correlation.rs (1)
  • expected (45-45)
src/fragment/index.rs (1)
  • try_from (48-48)
tests/worlds/codec_error/decoder_ops.rs (3)
src/frame/format.rs (1)
  • bytes (54-54)
src/codec.rs (5)
  • new (102-106)
  • encoder (73-73)
  • encoder (250-255)
  • decoder (70-70)
  • decoder (244-248)
src/fragment/index.rs (1)
  • try_from (48-48)
tests/worlds/codec_error/mod.rs (2)
src/codec.rs (2)
  • default (120-124)
  • build_eof_error (189-216)
src/codec/error.rs (1)
  • error_type (288-295)
src/codec/recovery.rs (1)
tests/codec_error.rs (4)
  • recovery_policy (141-143)
  • recovery_policy_default_is_drop (116-118)
  • context_with_peer_address (103-109)
  • recovery_config_builder (121-132)
🪛 LanguageTool
docs/users-guide.md

[typographical] ~175-~175: To join two clauses or introduce examples, consider using an em dash.
Context: ... Error categories: - FramingError - Wire-level frame boundary issues (oversi...

(DASH_RULE)


[typographical] ~177-~177: To join two clauses or introduce examples, consider using an em dash.
Context: ...atches, empty frames). - ProtocolError - Semantic violations after frame extracti...

(DASH_RULE)


[typographical] ~179-~179: To join two clauses or introduce examples, consider using an em dash.
Context: ... types, invalid versions). - io::Error - Transport layer failures (connection res...

(DASH_RULE)


[typographical] ~180-~180: To join two clauses or introduce examples, consider using an em dash.
Context: ...nnection resets, timeouts). - EofError - End-of-stream conditions with clean/mid-...

(DASH_RULE)

docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md

[typographical] ~94-~94: To join two clauses or introduce examples, consider using an em dash.
Context: ... Codec implementation: - src/codec.rs - FrameCodec trait and `LengthDelimitedF...

(DASH_RULE)


[typographical] ~95-~95: To join two clauses or introduce examples, consider using an em dash.
Context: ...tedFrameCodec-src/codec/examples.rs- Example codec implementations -src/app...

(DASH_RULE)


[typographical] ~96-~96: To join two clauses or introduce examples, consider using an em dash.
Context: ...mentations - src/app/combined_codec.rs - Adapter for Tokio Framed Error handli...

(DASH_RULE)


[typographical] ~100-~100: To join two clauses or introduce examples, consider using an em dash.
Context: ...ndling paths: - src/app/connection.rs - Connection processing and error handling...

(DASH_RULE)


[typographical] ~101-~101: To join two clauses or introduce examples, consider using an em dash.
Context: ...r handling - src/app/frame_handling.rs - Frame deserialization and response forwa...

(DASH_RULE)


[typographical] ~102-~102: To join two clauses or introduce examples, consider using an em dash.
Context: ...and response forwarding - src/hooks.rs - Protocol hooks including error callbacks...

(DASH_RULE)


[typographical] ~106-~106: To join two clauses or introduce examples, consider using an em dash.
Context: ...nfrastructure: - tests/frame_codec.rs - Codec integration tests - `tests/feature...

(DASH_RULE)


[typographical] ~107-~107: To join two clauses or introduce examples, consider using an em dash.
Context: ...ec integration tests - tests/features/ - Cucumber feature files - tests/steps/ ...

(DASH_RULE)


[typographical] ~108-~108: To join two clauses or introduce examples, consider using an em dash.
Context: ...les - tests/steps/ and tests/worlds/ - Step definitions and world types Docume...

(DASH_RULE)


[typographical] ~112-~112: To join two clauses or introduce examples, consider using an em dash.
Context: ...entation to update: - docs/roadmap.md - Mark 9.1.2 done - docs/users-guide.md ...

(DASH_RULE)


[typographical] ~113-~113: To join two clauses or introduce examples, consider using an em dash.
Context: ... Mark 9.1.2 done - docs/users-guide.md - Public interface documentation - `docs/a...

(DASH_RULE)


[typographical] ~114-~114: To join two clauses or introduce examples, consider using an em dash.
Context: ...cs/adr-004-pluggable-protocol-codecs.md` - Design decisions ## Plan of Work ### P...

(DASH_RULE)


[typographical] ~123-~123: To join two clauses or introduce examples, consider using an em dash.
Context: ...r classification: 1. Framing errors - Wire-level frame boundary issues (e.g., ...

(DASH_RULE)


[typographical] ~125-~125: To join two clauses or introduce examples, consider using an em dash.
Context: ...hecksum mismatch) 2. Protocol errors - Semantic violations after frame extracti...

(DASH_RULE)


[typographical] ~128-~128: To join two clauses or introduce examples, consider using an em dash.
Context: ... sequence violation) 3. IO errors - Transport layer failures (wrapped `io::E...

(DASH_RULE)


[typographical] ~129-~129: To join two clauses or introduce examples, consider using an em dash.
Context: ... (wrapped io::Error) 4. EOF errors - End-of-stream handling with three sub-va...

(DASH_RULE)


[typographical] ~136-~136: To join two clauses or introduce examples, consider using an em dash.
Context: ...cy()method returning one of: -Drop` - Discard the malformed frame and continue...

(DASH_RULE)


[typographical] ~137-~137: To join two clauses or introduce examples, consider using an em dash.
Context: ...inue (recoverable errors) - Quarantine - Pause the connection temporarily (rate l...

(DASH_RULE)


[typographical] ~138-~138: To join two clauses or introduce examples, consider using an em dash.
Context: ...mporarily (rate limiting) - Disconnect - Terminate the connection (fatal errors) ...

(DASH_RULE)


[typographical] ~171-~171: Two consecutive dots
Context: ...e)for EOF at frame boundary 3. ReturnCodecError::Eof(EofError::MidFrame{..})for EOF during payload 4. ReturnCo...

(DOUBLE_PUNCTUATION)


[typographical] ~172-~172: Two consecutive dots
Context: ...{..})for EOF during payload 4. ReturnCodecError::Eof(EofError::MidHeader{..})for EOF during header 5. Maintaini...

(DOUBLE_PUNCTUATION)


[uncategorized] ~232-~232: Loose punctuation mark.
Context: ...documentation: 1. docs/users-guide.md: - Add section on codec error handlin...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~238-~238: Loose punctuation mark.
Context: ...cs/adr-004-pluggable-protocol-codecs.md: - Document the CodecError` taxonomy...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~243-~243: Loose punctuation mark.
Context: ...OF handling design 3. docs/roadmap.md: - Mark all 9.1.2 sub-items as done ...

(UNLIKELY_OPENING_PUNCTUATION)


[duplication] ~255-~255: Possible typo: you repeated a word.
Context: ...CodecError::default_recovery_policy() implementation - From<CodecError> for io::Error implementation 2. Create src/codec/recovery.rs with...

(ENGLISH_WORD_REPEAT_RULE)


[style] ~260-~260: Using four (or more) nouns in a row may decrease readability.
Context: ...ryPolicyenum -CodecErrorContextstruct -RecoveryPolicyHooktrait -DefaultRecoveryPolicyimplementation -RecoveryConfigbuilder config 3. Updatesrc/codec.rs`: - ...

(FOUR_NN)

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (78)
tests/steps/mod.rs (1)

9-9: LGTM!

The module declaration cleanly extends the test step definitions to include codec error scenarios, following the existing pattern.

tests/features/codec_error.feature (1)

1-25: Well-structured BDD scenarios.

The feature file provides clear, comprehensive coverage of the codec error taxonomy and recovery policies. The scenarios are well-organised and follow proper Gherkin syntax.

src/codec/tests.rs (8)

1-11: LGTM!

Proper module-level documentation and clean imports.


13-17: LGTM!

Proper boundary test for maximum frame length clamping with safe arithmetic.


19-42: LGTM!

Thorough round-trip test validating encode/decode cycle with proper error handling.


44-58: LGTM!

Proper validation of oversized payload rejection with appropriate error kind checking.


60-68: LGTM!

Valid test for payload reuse optimisation using pointer comparison to verify zero-copy behaviour.


70-82: LGTM!

Proper handling of the clean close case where an empty buffer at EOF returns Ok(None).


84-108: Excellent use of parameterised tests.

The rstest parameterisation eliminates code duplication whilst maintaining clear test intent. Each case is well-documented with buffer contents, expected error kind, and message validation. This aligns perfectly with the coding guidelines' recommendation to use parameterised tests.

Based on coding guidelines: "Replace duplicated tests with #[rstest(...)] parameterised cases."


110-128: LGTM!

Proper success path test for EOF handling when a complete frame is present in the buffer.

tests/world.rs (1)

11-11: LGTM!

Clean addition of the codec error test world to the public exports, following the established pattern.

tests/worlds/mod.rs (2)

1-7: LGTM!

Documentation properly updated to reflect the addition of codec error taxonomy coverage.


33-34: LGTM!

Proper module declaration with path attribute, following the established pattern in the file.

docs/adr-004-pluggable-protocol-codecs.md (1)

256-276: Well-documented resolution of the outstanding decision.

The new "Resolved Decisions" section clearly captures the CodecError taxonomy, recovery policies, and observability hooks. This aligns with the learning that codec error taxonomy and recovery policy were pending decisions. The cross-references to src/codec/error.rs and src/codec/recovery.rs provide useful navigation for implementers.

docs/users-guide.md (1)

168-270: Comprehensive codec error handling documentation.

The new section thoroughly documents the error taxonomy, recovery policies, and integration points. The code examples are clear and demonstrate practical usage patterns. The table format for recovery policies aids quick reference.

tests/codec_error.rs (7)

1-14: Module documentation and imports are correct.

The module-level doc comment explains the file's purpose. Imports cover the full public API surface needed for testing the CodecError taxonomy.


20-84: Thorough taxonomy coverage.

The tests exercise all CodecError variants and verify their recovery policies, disconnect behaviour, and error type categorization. The assertions align with the implementation in src/codec/error.rs.


90-109: Context builder tests are adequate.

The tests verify the builder pattern for CodecErrorContext, including all fields and peer address handling.


115-132: Recovery policy and config tests are correct.

The tests verify the default policy and builder pattern for RecoveryConfig, aligning with the implementation.


138-157: Custom hook override test demonstrates the extension point.

The StrictRecovery implementation and test show how custom recovery policies override defaults, which is a key use case for the hook system.


159-190: Clear scope delineation and WireframeError integration tests.

The NOTE comment clarifies that decoder EOF tests reside in src/codec.rs, avoiding confusion about test duplication. The WireframeError integration tests verify the from_codec constructor and display formatting.


196-216: App error integration tests are appropriate.

The tests verify From<CodecError> conversions for both WireframeError and SendError in the app error surface, ensuring consistent error propagation.

docs/roadmap.md (1)

352-361: Roadmap accurately reflects completed work.

The 9.1.2 tasks are correctly marked complete. The descriptions align with the implemented CodecError taxonomy, recovery policies, EOF handling, and test coverage delivered in this PR.

src/metrics.rs (1)

36-44: Metric constant follows established conventions.

The CODEC_ERRORS constant uses the wireframe_ prefix and _total suffix consistent with other counters in this module. The documentation clearly describes the label structure.

tests/cucumber.rs (1)

1-72: Cucumber runner integration is complete and consistent.

All touchpoints are updated: module doc comment, import, and runner call. The CodecErrorWorld follows the established pattern for test world integration.

src/response.rs (3)

198-230: WireframeError Codec variant is well-integrated.

The new Codec variant follows the established pattern for Io and Protocol. The documentation explains the error categorisation clearly.


240-254: Helper methods follow established patterns.

The from_codec constructor mirrors from_io, and is_clean_close correctly delegates to CodecError::is_clean_close() as shown in the relevant code snippet from src/codec/error.rs.


256-277: Display and Error implementations are complete.

The Display formatting uses "codec error:" prefix consistent with the other variants' style. The source() implementation correctly exposes the inner CodecError.

src/app/error.rs (2)

7-27: Codec error integration correctly implemented.

The addition of Codec(#[from] CodecError) variant properly extends WireframeError to propagate codec-layer failures. The removal of PartialEq and Eq derives is necessary because CodecError contains io::Error, which doesn't implement these traits. The expanded documentation appropriately describes error categorisation by origin (framing, protocol, I/O, EOF).


39-41: SendError codec integration mirrors WireframeError pattern.

The Codec(#[from] CodecError) variant enables codec errors to propagate through send operations, maintaining consistency with the WireframeError integration.

tests/steps/codec_error_steps.rs (3)

1-40: Given steps correctly handle success and failure cases.

The distinction between infallible setup steps (returning ()) and fallible configuration steps (returning TestResult) aligns with the repository's lint policy. The #[expect(clippy::needless_pass_by_value)] attribute on lines 23-26 is correctly scoped and reasoned, as Cucumber step macros require owned String for {word} captures.


42-75: When steps appropriately distinguish setup from operations that can fail.

The when_client_sends_partial step (lines 58-62) correctly returns () as it only sets up state without error paths, whilst other When steps that invoke codec operations return TestResult to propagate potential failures.


77-101: Then steps correctly return TestResult for verification operations.

All verification steps appropriately return TestResult to propagate assertion failures. The #[expect(clippy::needless_pass_by_value)] attribute on lines 95-98 is correctly scoped with a clear reason, matching the pattern established in the Given steps.

src/codec/error.rs (6)

1-25: Comprehensive module-level documentation.

The module documentation clearly describes the error taxonomy (framing, protocol, I/O, EOF) and recovery policies (Drop, Quarantine, Disconnect), providing context for the structured error handling introduced by this PR.


32-72: FramingError variants comprehensively cover wire-level failures.

The enum design appropriately distinguishes between oversized frames (size enforcement), corrupt length encoding (framing state corruption), incomplete headers (need more data), checksum failures (data integrity), and empty frames (protocol constraint). Field names are clear, and error messages are descriptive. The use of {:#x} for checksum formatting (line 61) improves diagnostic readability.


74-118: ProtocolError variants appropriately capture semantic violations.

The enum design correctly distinguishes between header issues (missing fields), version mismatches, unknown message types, sequence violations, and state machine errors. The use of String for field and state names provides clear diagnostic context, whilst numeric types (u32 for version/type_id, u64 for sequences) are appropriately chosen.


120-155: EofError variants effectively distinguish clean from premature closure.

The enum design correctly separates clean EOF (at frame boundary, no data lost) from premature closure scenarios (mid-frame, mid-header). The inclusion of Copy in the derives (line 124) is appropriate, as all fields are usize with no heap allocations. Error messages provide clear diagnostic context about bytes received versus expected.


194-232: Recovery policy logic appropriately distinguishes recoverable from unrecoverable errors.

The default_recovery_policy method correctly categorises errors:

  • Drop policy (lines 227-228): OversizedFrame, EmptyFrame, and all Protocol errors are recoverable—discard the malformed frame and continue.
  • Disconnect policy (line 230): Other Framing errors (corrupt framing state), Io errors (transport failure), and all Eof errors (connection lost) require termination.

This logic aligns with the principle that framing state corruption and transport failures cannot be safely recovered, whilst oversized or malformed individual frames can be dropped without affecting subsequent frames.


298-310: CodecError to io::Error conversion preserves error semantics.

The From implementation correctly maps codec errors to io::ErrorKind:

  • InvalidData (lines 302-303): Framing and protocol errors represent data validation failures.
  • UnexpectedEof (lines 304-307): EOF errors represent premature connection closure during reads.
  • Special case (lines 304-306): CleanClose receives a simplified message whilst preserving its distinct semantic meaning (graceful shutdown).

Error sources are preserved in all cases, maintaining diagnostic context.

src/hooks.rs (3)

66-102: on_eof hook extends WireframeProtocol with backward compatibility.

The new on_eof method correctly provides a default no-op implementation, allowing existing protocol implementations to continue working without modification. The signature appropriately borrows &EofError and &[u8] (no ownership transfer needed) whilst providing mutable access to ConnectionContext. The comprehensive documentation includes a practical example demonstrating EOF variant handling.


121-149: OnEofHook type alias and field follow established hook patterns.

The OnEofHook type alias (lines 121-122) correctly uses FnMut (allowing hook state mutation), with Send + 'static bounds for cross-thread usage. The on_eof field addition to ProtocolHooks (lines 136-137) and its None initialization in Default (line 148) match the patterns established by existing hooks (before_send, on_command_end, etc.).


186-237: on_eof implementation and protocol wiring correctly follow established patterns.

The on_eof method (lines 186-191) appropriately invokes the registered hook when present, matching the conditional pattern used by other hook methods. The from_protocol wiring (lines 223-228, 236) correctly clones the Arc, creates a boxed closure that delegates to the protocol's on_eof method, and includes the hook in the returned ProtocolHooks. This implementation mirrors the patterns established by before_send, on_command_end, and other hooks.

src/lib.rs (2)

89-89: CODEC_ERRORS metric export is correctly defined.

The metric is properly declared in the metrics module with the naming pattern wireframe_codec_errors_total, enabling codec-layer failure observability consistent with the existing instrumentation.


17-31: Well-structured consolidation of codec exports.

Unify the codec module surface with this expanded pub use codec:: block. The re-exports correctly include the error taxonomy (CodecError, FramingError, ProtocolError, EofError), recovery infrastructure (RecoveryPolicy, RecoveryConfig, DefaultRecoveryPolicy, RecoveryPolicyHook, CodecErrorContext), codec types (FrameCodec, LengthDelimitedFrameCodec), and frame length constants (MAX_FRAME_LENGTH, MIN_FRAME_LENGTH). All items are publicly accessible from the codec module and properly aggregated for consumer convenience.

src/codec.rs (9)

6-19: LGTM!

The module-level documentation extension clearly explains the error taxonomy and recovery policies. The en-GB spelling ("customising") is correct and the doc references are properly linked.


26-36: LGTM!

Public module declarations and re-exports are well-organised. The re-exported types provide a clean public API surface for consumers.


38-48: LGTM!

Constants are appropriately documented with Rustdoc comments explaining their purpose and usage constraints.


127-128: LGTM!

The LENGTH_HEADER_SIZE constant is correctly defined and documented.


143-170: LGTM!

The decode_eof implementation correctly handles the three EOF scenarios:

  1. Clean close when buffer is empty.
  2. Preserves InvalidData framing errors (e.g., oversized frames).
  3. Converts other errors to structured EOF errors with diagnostic logging.

The tracing diagnostic at line 162-165 aids debugging without leaking sensitive data.


172-179: LGTM!

The #[expect] attribute is correctly scoped with an appropriate reason. The cast from u32 to usize is safe on all supported platforms.


189-216: LGTM!

The build_eof_error function uses safe accessors (.get()) and correctly distinguishes between mid-header and mid-frame EOF based on whether a complete length header is present. The saturating_sub prevents underflow.


227-236: LGTM!

The encoder now returns structured CodecError::Framing(FramingError::OversizedFrame) instead of a generic io::Error, enabling proper recovery policy selection.


264-265: LGTM!

Tests extracted to a separate module keeps the main file under the 400-line limit and improves organisation.

docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md (4)

1-68: LGTM!

The ExecPlan structure is well-organised with clear purpose, progress tracking, and decision log. The three-tier error taxonomy plus EOF distinction is well-reasoned.


116-139: LGTM!

The plan of work clearly defines the error taxonomy with appropriate recovery policy mappings for each category.


246-325: LGTM!

Concrete steps are actionable and well-sequenced. The validation gate commands are comprehensive.


348-364: LGTM!

Acceptance criteria are observable and measurable, covering error taxonomy, recovery policies, logging, metrics, and test gates.

tests/worlds/codec_error/mod.rs (6)

1-6: LGTM!

Module-level documentation explains the purpose. The #![cfg(not(loom))] attribute correctly excludes this module from loom testing.


16-44: LGTM!

The enum definitions are clean with appropriate #[derive] attributes. Using #[default] on specific variants makes intent explicit.


46-67: LGTM!

The CodecErrorWorld struct appropriately uses pub(crate) visibility for fields accessed by sibling modules (like decoder_ops) while keeping internal state private.


69-104: LGTM!

The setter methods follow the pattern of matching string input to enum variants and rebuilding the error. Error messages are clear and include the invalid input.


122-164: LGTM!

The error builder methods are well-structured and use representative test values. The build_eof_error method matches the pattern in the relevant code snippet from src/tests/worlds/codec_error/mod.rs.


166-191: LGTM!

The verify_recovery_policy method correctly uses ok_or for the Option check and provides clear error messages for policy mismatches.

tests/worlds/codec_error/decoder_ops.rs (8)

1-4: LGTM!

Module-level documentation clearly describes the purpose of the module.


15-33: LGTM!

The reset and setup methods provide clean state management for test scenarios.


35-54: LGTM!

Frame sending helpers correctly use the codec's encoder. The partial frame helper writes an explicit big-endian length prefix with a clear comment explaining the expected payload size.


56-85: LGTM!

The decode_eof_clean_close method correctly drains complete frames before checking EOF behaviour. The match arms handle all possible outcomes appropriately.


87-99: LGTM!

The #[expect] attribute is correctly scoped with appropriate reason. The safe accessor chain (.get(), .and_then(), .map_or()) follows the learnings about avoiding direct indexing.


133-151: LGTM!

The decode_eof_with_partial_data method correctly captures the error and classifies it. Error paths return appropriate test failures.


153-172: LGTM!

The oversized frame test helper correctly expects an error and captures it for verification.


174-219: LGTM!

Verification helpers provide clear assertions with descriptive error messages. The verify_oversized_error correctly checks for InvalidData kind which is the mapped error kind for OversizedFrame.

src/codec/recovery.rs (7)

1-36: LGTM!

Comprehensive module documentation with clear examples demonstrating custom recovery hook implementation. The en-GB spelling ("customising") is correct.


42-91: LGTM!

The RecoveryPolicy enum is well-documented with clear guidance on when to use each variant. The #[default] attribute on Drop aligns with the recoverable-by-default principle.


93-162: LGTM!

The CodecErrorContext struct provides a clean builder pattern with #[must_use] on all builder methods. Public fields allow direct access for structured logging while builders enable fluent construction.


164-226: LGTM!

The RecoveryPolicyHook trait provides sensible defaults while allowing full customisation. The Send + Sync bounds ensure thread safety for async contexts. The example demonstrates a realistic use case.


228-235: LGTM!

The DefaultRecoveryPolicy struct correctly implements the trait with an empty impl block, inheriting all default behaviour.


237-307: LGTM!

The RecoveryConfig struct provides sensible defaults (10 consecutive drops, 30s quarantine, logging enabled) and a fluent builder API. Documentation clearly explains each field's purpose and default value.


309-389: LGTM!

Tests cover:

  • Default policy value
  • Context builder methods
  • Peer address setting
  • Policy delegation from DefaultRecoveryPolicy
  • Quarantine duration default
  • RecoveryConfig builder and defaults

The tests use .expect() appropriately for test code per guidelines.

Comment thread docs/execplans/9-1-2-introduce-a-codec-error-taxonomy.md Outdated
Comment thread src/codec/error_tests.rs Outdated
Comment thread src/codec/error_tests.rs Outdated
Comment thread src/codec/error_tests.rs Outdated
Comment thread src/codec/error_tests.rs
Comment thread src/metrics.rs
Comment thread tests/worlds/codec_error/decoder_ops.rs
- Converted multiple individual tests in `src/codec/error_tests.rs` to parameterized rstest tests,
  improving test readability and maintenance.
- Added parameterized tests for recovery policy defaults, io::Error conversions, and error type category strings.
- Updated clean EOF testing functions to clarify behaviours.
- No behaviour changes; all tests pass successfully.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title CodecError taxonomy, EOF handling, recovery policies, metrics CodecError taxonomy, EOF handling, recovery policies, hooks, metrics Jan 8, 2026
@coderabbitai coderabbitai Bot dismissed their stale review January 8, 2026 20:39

Addressed in latest commit

@leynos leynos merged commit 746bbd0 into main Jan 8, 2026
6 checks passed
@leynos leynos deleted the terragon/introduce-codecerror-taxonomy-3abr1i branch January 8, 2026 20:43
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Jan 8, 2026

@coderabbitai Have the following now been resolved?

Please address the comments from this code review:

## Overall Comments
- In `build_eof_error` you call `src.get(..LENGTH_HEADER_SIZE)` and then `get_u32` on the result, but `BytesMut::get` returns a `&[u8]` (not a `Buf`), so this either won’t compile or won’t do what you expect; consider wrapping the slice in a `Cursor` or using `Bytes::copy_from_slice` and then `Bytes::get_u32` to parse the length prefix safely.
- There is quite a bit of duplicated EOF/oversized-frame behaviour testing between `src/codec.rs` tests and `tests/codec_error.rs` (e.g. `assert_decode_eof_error` and related cases); you may want to consolidate these helpers/tests into one place to avoid divergence over time.

## Individual Comments

### Comment 1
<location> `src/codec.rs:160-169` </location>
<code_context>
+fn build_eof_error(src: &BytesMut) -> io::Error {
</code_context>

<issue_to_address>
**issue (bug_risk):** Using `Buf::get_u32` on a `&[u8]` slice will not compile; parse the length prefix without relying on `Buf` for slices.

`src.get(..LENGTH_HEADER_SIZE)` returns an `Option<&[u8]>`, so `let Some(mut header)` makes `header` a `&[u8]` that does not implement `Buf`; `header.get_u32()` will not compile. Instead, parse the length manually:

```rust
overflowed
let header = match src.get(..LENGTH_HEADER_SIZE) {
    Some(h) => h,
    None => {
        return CodecError::Eof(EofError::MidHeader {
            bytes_received,
            header_size: LENGTH_HEADER_SIZE,
        }).into();
    }
};
let expected = u32::from_be_bytes(header.try_into().expect("slice is 4 bytes")) as usize;
```

Or, if you prefer `Buf`, wrap the slice in a `Cursor` before calling `get_u32()`.
</issue_to_address>

### Comment 2
<location> `src/app/error.rs:29-38` </location>
<code_context>
+    Codec(#[from] CodecError),
 }

+impl PartialEq for WireframeError {
+    fn eq(&self, other: &Self) -> bool {
+        match (self, other) {
+            (Self::DuplicateRoute(a), Self::DuplicateRoute(b)) => a == b,
+            // CodecError cannot be compared for equality (contains io::Error)
+            // All other combinations are not equal
+            _ => false,
+        }
+    }
+}
+
+impl Eq for WireframeError {}
+
 /// Errors produced when sending a handler response over a stream.
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The manual `PartialEq`/`Eq` impl only compares `DuplicateRoute`, making `Codec` variants never equal even when identical.

Because the manual impl now treats all non-`DuplicateRoute` variants as always-unequal (including identical `Codec` errors), any existing `assert_eq!` or equality-based logic on `WireframeError` will behave differently and potentially unexpectedly.

You could either:
- Remove `PartialEq`/`Eq` from `WireframeError` and rely on pattern matching, or
- Restrict equality to a test-only helper (e.g., feature-gated) if it’s primarily used in tests.

If you keep this impl, it’d be good to audit current equality usage to ensure this behavior is intentional.

Suggested implementation:

```rust
    #[error("codec error: {0}")]
    Codec(#[from] CodecError),
}

/// Errors produced when sending a handler response over a stream.
#[derive(Debug, Error)]
#[non_exhaustive]

```

1. Search for any usages of `WireframeError` in equality contexts (e.g., `assert_eq!(..., WireframeError::...)` or `if err1 == err2`).
2. Replace these with explicit pattern matching, for example:
   - `matches!(err, WireframeError::DuplicateRoute(id) if id == expected_id)`
   - or `matches!(err, WireframeError::Codec(_))` when only the variant matters.
3. If tests rely heavily on equality, consider adding a test-only helper under `#[cfg(test)]`, such as:
   ```rust
   #[cfg(test)]
   impl WireframeError {
       pub fn same_variant(&self, other: &Self) -> bool {
           use WireframeError::*;
           match (self, other) {
               (DuplicateRoute(a), DuplicateRoute(b)) => a == b,
               (Codec(_), Codec(_)) => true,
               _ => false,
           }
       }
   }
   ```
   and then use `assert!(err1.same_variant(&err2));` in tests instead of `assert_eq!`.
</issue_to_address>

### Comment 3
<location> `src/codec/error.rs:1` </location>
<code_context>
+//! Error types for the codec layer.
+//!
+//! This module provides a structured error taxonomy that distinguishes between
</code_context>

<issue_to_address>
**issue (review_instructions):** Add behavioural (Cucumber) tests that exercise the new codec error taxonomy and recovery behaviour end-to-end; the newly added feature files and step/world modules are currently empty.

Implement behavioural scenarios in `tests/features/codec_error.feature` and corresponding step/world code in `tests/steps/codec_error_steps.rs`, `tests/worlds/codec_error.rs`, and `tests/codec_error.rs` so they actually drive a Wireframe connection through framing, protocol, I/O, and EOF error cases.

You already have solid unit coverage in `src/codec/error.rs`, `src/codec/recovery.rs`, and `src/codec.rs::tests`, but the behavioural side is missing: the new files under `tests/` and `tests/features/` are empty, so no end-to-end assertions verify that recovery policies, hooks, and metrics are applied correctly in a running app.

Define concrete scenarios (e.g. oversized frame dropped, mid-frame EOF quarantine/disconnect, custom `RecoveryPolicyHook` override) and wire them into the existing Cucumber harness to satisfy the requirement for behavioural tests for this new feature.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural and unit tests are required.

</details>
</issue_to_address>

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the build_eof_error function in src/codec.rs
echo "=== build_eof_error function in src/codec.rs ==="
rg -n -A 30 "fn build_eof_error" src/codec.rs

echo -e "\n=== Test helpers in src/codec.rs ==="
rg -n -A 5 "fn assert_decode_eof_error" src/codec.rs

echo -e "\n=== Test helpers in tests/codec_error.rs ==="
rg -n -A 5 "fn assert_decode_eof_error" tests/codec_error.rs

echo -e "\n=== PartialEq implementation in src/app/error.rs ==="
rg -n -A 15 "impl PartialEq for WireframeError" src/app/error.rs

Length of output: 1257


🏁 Script executed:

#!/bin/bash
# Check the behavioral test files
echo "=== Cucumber feature file ==="
cat tests/features/codec_error.feature

echo -e "\n=== Step definitions file ==="
cat tests/steps/codec_error_steps.rs

echo -e "\n=== World module structure ==="
fd -t f . tests/worlds/codec_error/

Length of output: 5083


🏁 Script executed:

#!/bin/bash
# Check if there are any decode_eof test functions that might be duplicates
echo "=== EOF test functions in src/codec.rs ==="
rg -n "fn decode_eof" src/codec.rs | head -20

echo -e "\n=== EOF test functions in tests/codec_error.rs ==="
rg -n "fn decoder_eof" tests/codec_error.rs | head -20

Length of output: 192


🏁 Script executed:

#!/bin/bash
# Check the tests module file referenced in src/codec.rs
echo "=== Checking if tests are in separate file ==="
cat src/codec/tests.rs 2>/dev/null || echo "File does not exist"

echo -e "\n=== Check tests module in tests/codec_error.rs ==="
rg -n "#\[test\]" tests/codec_error.rs | head -20

echo -e "\n=== Check WireframeError derives in src/app/error.rs ==="
rg -n -B 2 "enum WireframeError" src/app/error.rs

Length of output: 4575


🏁 Script executed:

#!/bin/bash
# Check if the world module files are populated
echo "=== Check CodecErrorWorld implementation ==="
wc -l tests/worlds/codec_error/mod.rs tests/worlds/codec_error/decoder_ops.rs

echo -e "\n=== Sample from decoder_ops.rs ==="
head -50 tests/worlds/codec_error/decoder_ops.rs

Length of output: 1853


🏁 Script executed:

#!/bin/bash
# Search for any PartialEq or Eq mentions in app/error.rs
echo "=== All derives on WireframeError ==="
rg -n "^#\[derive" src/app/error.rs
rg -n "^pub enum WireframeError" src/app/error.rs

echo -e "\n=== Check for manual PartialEq/Eq impls ==="
rg -n "impl.*Eq.*WireframeError" src/app/error.rs

Length of output: 165


@leynos: Yes, all the issues from the code review have been resolved! Here's the status of each:

✅ Comment 1 - build_eof_error bug (RESOLVED)

The function now uses safe array conversion instead of attempting get_u32() on a &[u8]:

let expected = src
    .get(..LENGTH_HEADER_SIZE)
    .and_then(|slice| <[u8; LENGTH_HEADER_SIZE]>::try_from(slice).ok())
    .map(parse_length_header);

This properly converts the slice to a fixed-size array before parsing, exactly as recommended.

✅ Overall Comment - Test Duplication (RESOLVED)

The test duplication between src/codec.rs and tests/codec_error.rs has been eliminated. The EOF tests in src/codec/tests.rs now use parameterised testing with rstest:

#[rstest]
#[case::partial_header(&[0x00, 0x10], io::ErrorKind::UnexpectedEof, "header")]
#[case::partial_payload(&[0x00, 0x00, 0x00, 0x10, 0x01, 0x02, 0x03, 0x04], io::ErrorKind::UnexpectedEof, "16")]
fn decode_eof_error_cases(
    #[case] initial_buffer: &[u8],
    #[case] expected_kind: io::ErrorKind,
    #[case] expected_substring: &str,
) { /* ... */ }

This consolidates the three duplicate test functions into a single parameterised test, which is an even better solution than the helper function approach.

✅ Comment 2 - PartialEq/Eq Implementation (RESOLVED)

The manual PartialEq and Eq implementations have been removed from WireframeError. It now only derives Debug and Error:

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum WireframeError {

This avoids the problematic equality semantics for codec errors containing io::Error.

✅ Comment 3 - Behavioural Tests (RESOLVED)

The behavioural test infrastructure is fully implemented:

  • Feature file: 4 complete scenarios covering clean EOF, mid-frame EOF, oversized frames, and recovery policies
  • Step definitions: 101 lines implementing all Given/When/Then steps
  • World modules: 413 lines (191 + 222) providing complete test infrastructure with real codec operations

The tests exercise the codec error taxonomy end-to-end with realistic decoder operations, verifying recovery policies, EOF handling, and frame size constraints.


All review comments have been successfully addressed! 🎉

(ง •_•)ง

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant