Skip to content

Wireframe handshake: install API, 5s timeout, bootstrap integration#234

Merged
leynos merged 14 commits intomainfrom
terragon/implement-wireframe-handshake-ve85sy
Dec 2, 2025
Merged

Wireframe handshake: install API, 5s timeout, bootstrap integration#234
leynos merged 14 commits intomainfrom
terragon/implement-wireframe-handshake-ve85sy

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Nov 25, 2025

Summary

  • Adds a dedicated Wireframe handshake module to implement Hotline handshake semantics with a five-second idle timeout.
  • Introduces a new install API to attach success/failure hooks and enforce timeout, exposed as handshake::install(server, timeout) and wired via .accept_backoff(backoff).
  • Wires the handshake installation into the Wireframe bootstrap so idle sockets are dropped after the handshake timeout.
  • Public API exposure: src/wireframe/mod.rs now exports the handshake module.
  • Includes unit and behavioural tests, updated documentation, and vendored Wireframe updates to reflect the new handshake integration.

Changes

  • New module: src/wireframe/handshake.rs
    • install(server, timeout) -> configures Hotline preamble handling by:
      • on_preamble_decode_success: write the 8-byte Hotline OK reply
      • on_preamble_decode_failure: map DecodeError to Hotline error codes
      • preamble_timeout(timeout): enforce the handshake idle timeout
    • success_handler() -> writes HANDSHAKE_OK reply
    • failure_handler() -> writes appropriate error codes based on DecodeError
    • error_code_for_decode() -> maps decode errors to HANDSHAKE_ERR_INVALID, HANDSHAKE_ERR_UNSUPPORTED_VERSION, HANDSHAKE_ERR_TIMEOUT
    • Tests (unit and integration) validating success, invalid protocol, unsupported version, and idle-timeout behavior
  • Wireframe bootstrap integration: src/server/wireframe.rs
    • Replace inline Hotline preamble wiring with a handshake install call:
      • handshake::install(server, protocol::HANDSHAKE_TIMEOUT).accept_backoff(backoff)
    • Keeps Hotline preamble in place and wires timeout through HANDSHAKE_TIMEOUT
  • Public API exposure: src/wireframe/mod.rs
    • Added pub mod handshake; exposes handshake module to users
  • Tests
    • Added tests/features/wireframe_handshake_hooks.feature with scenarios for success, invalid protocol, unsupported version, and idle timeout
    • Implemented comprehensive tests in handshake.rs to verify success, error mapping, and timeout behavior
    • Added test helpers to spin up a temporary wireframe server and validate replies
  • Documentation
    • docs/design.md: notes that the Wireframe bootstrap mirrors legacy handshake semantics via preamble callbacks and a 5-second HANDSHAKE_TIMEOUT
    • docs/users-guide.md: updated to reflect that the Wireframe listener decodes Hotline 12-byte handshake preamble, sends 8-byte replies, and drops idle sockets after the handshake timeout
    • docs/roadmap.md: updated acceptance criteria to include five-second handshake timeout handling as part of the handshake wiring
  • Tests/features/wireframe_handshake_hooks.feature: new, covering OK, invalid protocol, unsupported version, and idle timeout
  • Vendor updates: included vendor wireframe updates to reflect the new handshake integration (in sync with the new API and tests)

Why

  • Aligns Wireframe with Hotline handshake semantics by emitting the correct 8-byte reply on success and appropriate Hotline error codes on failure, while enforcing a five-second handshake idle timeout.
  • Ensures idle sockets are dropped promptly if no handshake is completed, improving resource usage and observability.
  • Provides a reusable, testable handshake integration point that can be adjusted for shorter timeouts in tests without changing production defaults.

Test plan

  • Run unit tests for the handshake module:
    • Validate SUCCESS path writes HANDSHAKE_OK
    • Validate INVALID and UNSUPPORTED_VERSION error paths map to correct codes
    • Validate TIMEOUT path is emitted on idle handshake
  • Run integration tests:
    • Start a Wireframe server and perform handshake scenarios against the new hooks
    • Verify the 8-byte reply structure and error codes
  • Behavioural tests:
    • Use tests/features/wireframe_handshake_hooks.feature to validate end-to-end handshake behaviour

How to test locally

  • Build and test: cargo test --workspace
  • To exercise timeout scenarios quickly in tests, rely on HANDSHAKE_TIMEOUT or override with a shorter duration in tests as needed
  • Validate documentation updates by building docs if desired

Notes

  • This change includes vendored wireframe updates and accompanying tests; ensure vendor directory is in sync when reviewing locally.
  • The public API surface now includes the handshake module, enabling usage like: WireframeServer::new(...).with_preamble::<HotlinePreamble>() // plus handshake install for timeout handling

Task link

Summary by Sourcery

Integrate a reusable Wireframe handshake module for Hotline that wires preamble hooks, reply codes, and timeouts into the server bootstrap while updating docs, tests, and dependencies to reflect the new behaviour.

New Features:

  • Add a public Wireframe handshake module that exposes an install API to attach Hotline-specific handshake behaviour with configurable idle timeouts.

Enhancements:

  • Wire the Hotline handshake installation into the Wireframe server bootstrap so accepted connections automatically enforce the protocol handshake semantics and timeout.
  • Bump the wireframe crate dependency and document the updated bootstrap and protocol behaviour.
  • Add development Cargo profile tuned for modest optimisation and debuggability.

Documentation:

  • Extend the design and user guides with details on Wireframe preamble hooks, Hotline handshake reply codes, and the five-second idle timeout semantics.
  • Add a dedicated Wireframe library user guide describing the library’s components, configuration, and runtime behaviour in depth.

Tests:

  • Add unit, integration, and BDD feature tests to verify Hotline handshake success, invalid protocol, unsupported version, and idle-timeout behaviour via the new handshake hooks.

📎 Task: https://www.terragonlabs.com/task/0423192a-d49c-4450-ab18-2085cdc18a71

…eout

- Introduce handshake hooks module implementing Hotline handshake semantics.
- Register preamble callbacks that send 8-byte Hotline reply on success or error.
- Enforce a 5-second idle timeout for handshake phase on wireframe server.
- Wire handshake hooks into WireframeBootstrap server setup pipeline.
- Add tests and BDD scenarios validating handshake success, error, and timeout replies.
- Update relevant docs and roadmap to reflect handshake handling improvements.

This change improves the wireframe transport by integrating Hotline protocol handshake
semantics directly, ensuring protocol compliance and improved error handling at the
connection establishment phase.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced Hotline handshake handling with improved error responses for invalid protocols, unsupported versions, and connection timeouts.
    • Added comprehensive Wireframe library documentation with quick-start guides and code examples.
  • Documentation

    • Updated design documentation reflecting new handshake architecture and completion of connection hook registration.
  • Chores

    • Upgraded Wireframe dependency to stable v0.1.0 release.
    • Optimised development build configuration for faster compilation.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Install a Wireframe handshake layer that decodes 12‑byte Hotline preambles, maps decode outcomes to 8‑byte Hotline reply codes (0–3), enforces a configurable idle handshake timeout (default 5s), bump the wireframe dependency to v0.1.0, and add documentation, tests, and build/config updates.

Changes

Cohort / File(s) Summary
Build & Tooling
\.cargo/config.toml`, `.markdownlint-cli2.jsonc`, `Cargo.toml``
Add [profile.dev] build options (opt-level=1, debug=1, debug-assertions, overflow-checks, incremental, panic="unwind", strip="none"); expand markdownlint ignores to include vendor/wireframe/**; bump wireframe dependency from 0.1.0-alpha1 to 0.1.0.
Documentation
docs/design.md, docs/roadmap.md, docs/users-guide.md, docs/wireframe-users-guide.md
Document handshake/preamble callback semantics, Hotline reply mapping and 5s handshake idle timeout; mark handshake hooks migration complete; add comprehensive Wireframe user guide with examples and API descriptions.
Handshake Module
src/wireframe/handshake.rs, src/wireframe/mod.rs
Add handshake module and pub fn install<F, S>(...) to register preamble success/failure handlers, map DecodeError/HandshakeError variants to Hotline reply codes, enforce idle timeout and include unit/integration/behaviour tests.
Server Wiring
src/server/wireframe.rs
Replace .with_preamble::<HotlinePreamble>() with handshake::install(...).accept_backoff(backoff) using protocol handshake timeout.
Preamble Error Mapping
src/wireframe/preamble.rs
Map HandshakeError::InvalidProtocol and HandshakeError::UnsupportedVersion(ver) to explicit DecodeError::OtherString tokens using new handshake tokens; remove generic fallback and centralise test helpers.
Protocol Constants
src/protocol.rs
Add public constants: HANDSHAKE_INVALID_PROTOCOL_TOKEN and HANDSHAKE_UNSUPPORTED_VERSION_TOKEN.
Test Helpers & BDD Tests
src/wireframe/test_helpers.rs, tests/features/wireframe_handshake_hooks.feature
Add test helpers preamble_bytes and async recv_reply; add feature scenarios validating reply codes for success (0), invalid protocol (1), unsupported version (2) and handshake timeout (3).

Sequence Diagram

sequenceDiagram
    autonumber
    actor Client
    participant TCP as TCP Connection
    participant Server as Wireframe Server
    participant Handshake as Handshake Layer
    participant Decoder as Protocol Decoder

    Client->>TCP: TCP connect
    TCP->>Server: accept connection
    Server->>Handshake: installed wrapper handles preamble & timeout
    activate Handshake

    alt Client sends valid preamble
        Client->>Server: send 12‑byte Hotline preamble
        Server->>Decoder: decode preamble
        Decoder-->>Server: success
        Server->>Handshake: on_preamble_decode_success
        Handshake-->>Client: send 8‑byte reply (code 0)
        Handshake->>Server: proceed to route
    else Client sends invalid protocol
        Client->>Server: send preamble (bad protocol)
        Server->>Decoder: decode preamble
        Decoder-->>Server: InvalidProtocol
        Server->>Handshake: on_preamble_decode_failure
        Handshake-->>Client: send 8‑byte reply (code 1)
        Handshake->>Server: close connection
    else Client sends unsupported version
        Client->>Server: send preamble (unsupported version)
        Server->>Decoder: decode preamble
        Decoder-->>Server: UnsupportedVersion
        Server->>Handshake: on_preamble_decode_failure
        Handshake-->>Client: send 8‑byte reply (code 2)
        Handshake->>Server: close connection
    else Client idle (no preamble)
        Note over Handshake: idle timer (configurable, default 5s)
        Handshake-->>Client: send 8‑byte reply (code 3) on timeout
        Handshake->>Server: close connection
    end
    deactivate Handshake
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25–35 minutes

  • Inspect src/wireframe/handshake.rs for correct DecodeError→reply mapping, reply framing, timeout cancellation and error handling.
  • Verify src/server/wireframe.rs integrates handshake::install without changing accept/backoff semantics.
  • Confirm src/wireframe/preamble.rs uses the new protocol tokens consistently.
  • Run or review BDD feature tests and src/wireframe/test_helpers.rs for correct framing and IO behaviour.
  • Spot‑check docs in docs/wireframe-users-guide.md against actual API names/signatures.

Possibly related issues

Possibly related PRs

Poem

🤝 Hotline preambles hum tonight,
Eight bytes answer wrong or right,
Five seconds wait, then doors are shut,
Wireframe sings its handshake strut.

Pre-merge checks and finishing touches

✅ 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 directly and precisely summarises the PR's main changes: adding a Wireframe handshake module with an install API, a 5-second timeout, and bootstrap integration.
Description check ✅ Passed The description comprehensively documents the PR's scope, changes, rationale, and testing strategy—all directly related to the changeset across build configuration, core handshake module, bootstrap integration, documentation, and vendor updates.
✨ 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/implement-wireframe-handshake-ve85sy

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

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.

We failed to fetch the diff for pull request #234

You can try again by commenting this pull request with @sourcery-ai review, or contact us for help.

codescene-delta-analysis[bot]

This comment was marked as outdated.

…ure imports

- Replace custom inline error string matching with a dedicated helper function error_code_from_str for cleaner code.
- Update imports from futures to futures_util to unify and simplify future-related imports.
- Change return types to use BoxFuture alias for readability and consistency.
- Minor test imports updated accordingly.

These changes improve code clarity without altering existing functionality.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos changed the title Implement Hotline handshake with 5s timeout in Wireframe Wireframe handshake module with 5s timeout and bootstrap integration Nov 25, 2025
codescene-delta-analysis[bot]

This comment was marked as outdated.

- Upgrade wireframe from 0.1.0-alpha1 to 0.1.0, removing vendored fork
- Use upstream preamble hooks to send 8-byte Hotline reply codes
- Enforce five-second handshake timeout, dropping idle sockets
- Update .cargo/config.toml for development profile optimizations
- Add derive_more crate dependencies and update Cargo.lock accordingly
- Update design, roadmap, and user-guide docs to reflect handshake integration
- Refactor handshake tests for synchronous server startup and cleaner fixtures

This enables the Wireframe listener to implement the 12-byte Hotline handshake preamble with proper reply handling and timeout support, matching documented behavior and improving integration test coverage.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

…tion

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review November 30, 2025 04:15
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Nov 30, 2025

Reviewer's Guide

Implements a reusable Wireframe handshake module that wires Hotline handshake semantics (including a 5s idle timeout) into the Wireframe server via preamble hooks, integrates it into the server bootstrap, exposes it as a public API, updates docs and roadmap, adds unit/BBD tests and feature scenarios, and bumps wireframe to v0.1.0 alongside minor dev-profile tuning.

Sequence diagram for Hotline handshake with Wireframe preamble hooks and timeout

sequenceDiagram
    actor Client
    participant TcpStream
    participant WireframeServer
    participant HotlinePreamble
    participant HandshakeModule

    Client->>WireframeServer: connect()
    WireframeServer->>TcpStream: accept()
    activate WireframeServer

    WireframeServer->>WireframeServer: read_preamble_with_timeout(timeout)
    note over WireframeServer: WireframeServer configured with<br/>HotlinePreamble and HandshakeModule::install

    alt Handshake bytes received before timeout
        WireframeServer->>HotlinePreamble: decode(bytes)
        alt Decode success
            WireframeServer->>HandshakeModule: on_preamble_decode_success(preamble, stream)
            HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_OK)
            TcpStream-->>Client: 8 byte OK reply
            WireframeServer->>WireframeServer: rewind_leftover_bytes()
            WireframeServer->>WireframeServer: handle_connection()
            Client->>WireframeServer: normal protocol traffic
        else Decode failure (invalid protocol or version)
            WireframeServer->>HandshakeModule: on_preamble_decode_failure(error, stream)
            HandshakeModule->>HandshakeModule: error_code_for_decode(error)
            alt Invalid protocol id
                HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_INVALID)
            else Unsupported version
                HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
            end
            TcpStream-->>Client: 8 byte error reply
            WireframeServer->>TcpStream: close()
        end
    else Handshake idle timeout
        WireframeServer->>WireframeServer: timeout reached
        WireframeServer->>HandshakeModule: on_preamble_decode_failure(DecodeError_TimedOut, stream)
        HandshakeModule->>HandshakeModule: error_code_for_decode(timeout_error)
        HandshakeModule->>TcpStream: write_handshake_reply(HANDSHAKE_ERR_TIMEOUT)
        TcpStream-->>Client: 8 byte timeout reply
        WireframeServer->>TcpStream: close()
    end

    deactivate WireframeServer
Loading

Class diagram for Wireframe handshake module and server bootstrap integration

classDiagram
    class WireframeServer {
        +with_preamble_HotlinePreamble() WireframeServer
        +on_preamble_decode_success(handler) WireframeServer
        +on_preamble_decode_failure(handler) WireframeServer
        +preamble_timeout(timeout_ms) WireframeServer
        +accept_backoff(backoff) WireframeServer
        +bind(addr) WireframeServer
        +run_with_shutdown(shutdown_future) Result
    }

    class WireframeApp {
        +default() WireframeApp
    }

    class HotlinePreamble {
        +decode(bytes) Result
    }

    class ServerState {
        <<trait>>
    }

    class HandshakeModule {
        +install(server, timeout) WireframeServer
        +success_handler() SuccessHandlerFn
        +failure_handler() FailureHandlerFn
        +error_code_for_decode(err) u32~Option~
        +error_code_from_str(text) u32~Option~
    }

    class ProtocolConstants {
        <<module>>
        +HANDSHAKE_OK u32
        +HANDSHAKE_ERR_INVALID u32
        +HANDSHAKE_ERR_UNSUPPORTED_VERSION u32
        +HANDSHAKE_ERR_TIMEOUT u32
        +HANDSHAKE_TIMEOUT Duration
        +write_handshake_reply(stream, code) io_Result
    }

    class TcpStream {
        +read()
        +write()
        +shutdown()
    }

    class DecodeError {
        <<enum>>
        OtherString
        Other
        Io
    }

    HandshakeModule ..> WireframeServer : configures
    HandshakeModule ..> ProtocolConstants : uses
    HandshakeModule ..> TcpStream : writes_replies
    HandshakeModule ..> DecodeError : maps_errors
    WireframeServer --> HotlinePreamble : uses_for_preamble
    WireframeServer --> WireframeApp : builds_per_connection
    WireframeServer ..> ServerState : generic_over

    class WireframeBootstrap {
        +run(bind_addr, backoff) Result
    }

    WireframeBootstrap ..> WireframeServer : constructs
    WireframeBootstrap ..> HotlinePreamble : with_preamble
    WireframeBootstrap ..> HandshakeModule : calls_install
    WireframeBootstrap ..> ProtocolConstants : HANDSHAKE_TIMEOUT

    %% Rust generics shown as comments on associations
    %% WireframeServer<F, HotlinePreamble, S> where F: Fn() -> WireframeApp, S: ServerState
Loading

File-Level Changes

Change Details Files
Introduce reusable Hotline/Wireframe handshake module with success/failure hooks and timeout-aware error mapping.
  • Add src/wireframe/handshake.rs with install(server, timeout) that configures on_preamble_decode_success, on_preamble_decode_failure, and preamble_timeout on WireframeServer
  • Implement success_handler that writes HANDSHAKE_OK via write_handshake_reply
  • Implement failure_handler that maps DecodeError to Hotline error codes (invalid protocol, unsupported version, timeout) and conditionally writes replies
  • Implement error_code_for_decode and error_code_from_str helpers to inspect DecodeError and assign HANDSHAKE_ERR_INVALID, HANDSHAKE_ERR_UNSUPPORTED_VERSION, or HANDSHAKE_ERR_TIMEOUT
  • Add unit tests exercising OK, invalid protocol, unsupported version, and idle-timeout behavior using a real WireframeServer with HotlinePreamble
src/wireframe/handshake.rs
Add BDD-style handshake behaviour coverage and feature definitions.
  • Create HandshakeWorld test harness that spins up a runtime, starts a short-timeout server via install, and drives TcpStream-level interactions
  • Define rstest_bdd steps for starting the server, sending specific handshake variants, and asserting reply codes
  • Map these steps to Gherkin scenarios covering success, invalid protocol, unsupported version, and idle timeout
src/wireframe/handshake.rs
tests/features/wireframe_handshake_hooks.feature
Wire handshake installation into Wireframe bootstrap and expose the module in the public API.
  • Replace prior inline preamble wiring in the server bootstrap with handshake::install(server, protocol::HANDSHAKE_TIMEOUT).accept_backoff(backoff) while leaving HotlinePreamble configuration intact
  • Export the new handshake module from src/wireframe/mod.rs as pub mod handshake; so callers can opt into the hooks
src/server/wireframe.rs
src/wireframe/mod.rs
Document the new handshake semantics and completion of roadmap task.
  • Extend docs/design.md to describe using upstream preamble hooks (success/failure/timeout) to mirror Hotline handshake semantics and removal of the vendored fork
  • Update docs/users-guide.md to document 12-byte preamble decoding, 8-byte reply codes (0–3), and the five-second handshake timeout drop behavior
  • Mark the roadmap handshake hooks task as completed, with details of the implementation and test coverage
docs/design.md
docs/users-guide.md
docs/roadmap.md
Update Wireframe dependency and add dedicated Wireframe library guide and minor tooling tweaks.
  • Bump wireframe crate from 0.1.0-alpha1 to 0.1.0 in Cargo.toml (with corresponding lockfile changes) to use the stable preamble hooks API
  • Add docs/wireframe-users-guide.md as a comprehensive guide to the wireframe crate internals, usage patterns, fragmentation, protocol hooks, and server runtime
  • Tune .cargo/config.toml dev profile (opt-level, debug info, assertions, overflow checks, panic, strip) and adjust markdown lint configuration if needed
Cargo.toml
Cargo.lock
docs/wireframe-users-guide.md
.cargo/config.toml
.markdownlint-cli2.jsonc

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

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 there - I've reviewed your changes - here's some feedback:

  • The error_code_for_decode logic relies on matching DecodeError string contents (e.g. "invalid protocol id", "unsupported version"), which is brittle against message wording changes; consider surfacing structured errors from HotlinePreamble or wrapping decode failures so you can match on a stable enum/marker instead of parsing strings.
  • The preamble_bytes helper is duplicated in both tests and bdd modules of handshake.rs; extracting it into a shared test utility (or reusing one of them) would reduce duplication and keep the handshake encoding logic in a single place.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `error_code_for_decode` logic relies on matching `DecodeError` string contents (e.g. `"invalid protocol id"`, `"unsupported version"`), which is brittle against message wording changes; consider surfacing structured errors from `HotlinePreamble` or wrapping decode failures so you can match on a stable enum/marker instead of parsing strings.
- The `preamble_bytes` helper is duplicated in both `tests` and `bdd` modules of `handshake.rs`; extracting it into a shared test utility (or reusing one of them) would reduce duplication and keep the handshake encoding logic in a single place.

## Individual Comments

### Comment 1
<location> `src/wireframe/handshake.rs:80-85` </location>
<code_context>
+    }
+}
+
+fn error_code_from_str(text: &str) -> Option<u32> {
+    if text.contains("invalid protocol id") {
+        Some(HANDSHAKE_ERR_INVALID)
+    } else if text.contains("unsupported version") {
+        Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
+    } else {
+        None
+    }
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Mapping decode errors via substring matching on error messages is brittle.

This couples protocol behavior to human-readable strings and will silently break if those messages change upstream. If we control the preamble decoder, it would be more robust to expose structured error variants or numeric codes and map those to Hotline error codes instead of matching on substrings.

Suggested implementation:

```rust
    match err {
        DecodeError::InvalidProtocolId => Some(HANDSHAKE_ERR_INVALID),
        DecodeError::UnsupportedVersion => Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION),
        DecodeError::Io { inner, .. } if inner.kind() == io::ErrorKind::TimedOut => {
            Some(HANDSHAKE_ERR_TIMEOUT)
        }
        _ => None,
    }
}

fn success_handler()

```

```rust
fn success_handler()
-> impl for<'a> Fn(&'a HotlinePreamble, &'a mut TcpStream) -> BoxFuture<'a, io::Result<()>> + Send + Sync
{

```

To fully implement this approach and remove the brittle string matching, you will also need to:

1. **Extend `DecodeError`** (likely in the preamble/decoder module, e.g. `src/wireframe/preamble.rs` or wherever `DecodeError` is defined) to include structured variants:
   ```rust
   pub enum DecodeError {
       // existing variants...
       InvalidProtocolId,
       UnsupportedVersion,
       // ...
   }
   ```

2. **Update the preamble decoding logic** to emit these variants instead of `OtherString`/`Other` with textual messages. For example, wherever you currently do something like:
   ```rust
   return Err(DecodeError::OtherString("invalid protocol id".to_string()));
   ```
   change it to:
   ```rust
   return Err(DecodeError::InvalidProtocolId);
   ```
   and similarly for the unsupported version case:
   ```rust
   return Err(DecodeError::UnsupportedVersion);
   ```

3. **Adjust any other code paths** that currently rely on those specific error strings (if any) to work with the new structured variants instead.

After these changes, handshake error mapping will be based on stable, structured error variants rather than brittle substring comparisons.
</issue_to_address>

### Comment 2
<location> `docs/wireframe-users-guide.md:37-38` </location>
<code_context>
+}
+```
+
+The snippet below wires the builder into a Tokio runtime, decodes inbound
+payloads, and emits a serialised response. It showcases the typical `main`
+function for a microservice that listens on localhost and responds to a `Ping`
+message with a `Pong` payload.[^2][^10][^15]
</code_context>

<issue_to_address>
**nitpick (typo):** Use a consistent spelling of “serialized” within the guide.

This occurrence uses the British spelling “serialised” while later sections use “serialized.” Please update this to “serialized” for consistency with the rest of the guide.

```suggestion
The snippet below wires the builder into a Tokio runtime, decodes inbound
payloads, and emits a serialized response. It showcases the typical `main`
```
</issue_to_address>

### Comment 3
<location> `src/wireframe/handshake.rs:106` </location>
<code_context>
+    use super::*;
+    use crate::protocol::{HANDSHAKE_LEN, HANDSHAKE_TIMEOUT, PROTOCOL_ID, REPLY_LEN, VERSION};
+
+    fn preamble_bytes(
+        protocol: [u8; 4],
+        sub_protocol: [u8; 4],
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting shared test helpers (like the handshake preamble and client/reply logic) into a common module and simplifying the BDD world to avoid custom runtimes and interior mutability.

You can keep all current functionality while reducing complexity and duplication in the test modules.

### 1. Reuse shared helpers instead of duplicating them

`preamble_bytes` is implemented twice; you can define it once in a small shared `#[cfg(test)]` helper module and use it from both `tests` and `bdd`:

```rust
#[cfg(test)]
mod test_support {
    use crate::protocol::HANDSHAKE_LEN;

    pub fn preamble_bytes(
        protocol: [u8; 4],
        sub_protocol: [u8; 4],
        version: u16,
        sub_version: u16,
    ) -> [u8; HANDSHAKE_LEN] {
        let mut buf = [0u8; HANDSHAKE_LEN];
        buf[0..4].copy_from_slice(&protocol);
        buf[4..8].copy_from_slice(&sub_protocol);
        buf[8..10].copy_from_slice(&version.to_be_bytes());
        buf[10..12].copy_from_slice(&sub_version.to_be_bytes());
        buf
    }
}
```

Then:

```rust
mod tests {
    use super::test_support::preamble_bytes;
    // ...
}

mod bdd {
    use super::test_support::preamble_bytes;
    // ...
}
```

You can do the same with `recv_reply`/client logic by exposing a small helper that the BDD world calls instead of re‑implementing `read_exact` + `timeout`.

### 2. Drop the custom `Runtime` and most `RefCell`s in `HandshakeWorld`

You can still keep the BDD style and feature files, but simplify the world by running under Tokio’s test runtime and storing values directly instead of via `RefCell<Option<…>>`.

For example:

```rust
struct HandshakeWorld {
    addr: SocketAddr,
    shutdown: oneshot::Sender<()>,
    reply: Option<Result<[u8; REPLY_LEN], String>>,
}

impl HandshakeWorld {
    async fn start_server() -> Self {
        let (addr, shutdown) = super::tests::start_server(Duration::from_millis(100));
        Self { addr, shutdown, reply: None }
    }

    async fn connect_and_maybe_send(&mut self, bytes: Option<Vec<u8>>) {
        let mut stream = TcpStream::connect(self.addr).await.expect("connect");
        if let Some(data) = bytes {
            stream.write_all(&data).await.expect("write handshake");
        }
        let mut buf = [0u8; REPLY_LEN];
        let res = timeout(Duration::from_secs(1), stream.read_exact(&mut buf))
            .await
            .map(|r| { r.expect("read reply"); buf })
            .map_err(|e| e.to_string());
        self.reply = Some(res);
    }
}
```

Then adapt your BDD steps to be async and run under `#[tokio::test]`/async `scenario` support (rstest_bdd supports async steps), avoiding `Runtime::block_on` entirely:

```rust
#[fixture]
#[tokio::main(flavor = "current_thread")]
async fn world() -> HandshakeWorld {
    HandshakeWorld::start_server().await
}

#[when("I send a valid Hotline handshake")]
async fn when_valid(world: &mut HandshakeWorld) {
    let bytes = preamble_bytes(*PROTOCOL_ID, *b"CHAT", VERSION, 0);
    world.connect_and_maybe_send(Some(bytes.to_vec())).await;
}
```

This keeps the BDD feature coverage but removes the extra runtime management and interior mutability, and consolidates the handshake helpers into a single place.
</issue_to_address>

### Comment 4
<location> `src/wireframe/handshake.rs:106` </location>
<code_context>
+    use super::*;
+    use crate::protocol::{HANDSHAKE_LEN, HANDSHAKE_TIMEOUT, PROTOCOL_ID, REPLY_LEN, VERSION};
+
+    fn preamble_bytes(
+        protocol: [u8; 4],
+        sub_protocol: [u8; 4],
</code_context>

<issue_to_address>
**issue (review_instructions):** Deduplicate the `preamble_bytes` helper by sharing a single implementation between the `tests` and `bdd` modules to keep the test code DRY.

You already define an identical `preamble_bytes` helper in the unit-test module above. Extract a single shared version (for example, into the parent module or a common `tests` helper submodule) and reuse it from both `tests` and `bdd` to avoid duplication and keep the test code DRY while remaining readable.

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

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

**Instructions:**
Keep code DRY, but readable. Use refactoring approaches best suited for the language in question.

</details>
</issue_to_address>

### Comment 5
<location> `src/wireframe/handshake.rs:298` </location>
<code_context>
+
+    // `rstest` reports this function as a fixture; rustc flags the required block
+    // as `unused_braces`, so suppress the false positive locally.
+    #[allow(unused_braces)]
+    #[fixture]
+    fn world() -> HandshakeWorld { HandshakeWorld::new() }
</code_context>

<issue_to_address>
**issue (review_instructions):** `#[allow]` is forbidden by the lint policy; this suppression should be removed or rewritten as a narrowly scoped `#[expect(...)]` with a reason.

The instructions explicitly forbid `#[allow]`. If you genuinely need to suppress this lint, please switch to a narrowly scoped `#[expect(unused_braces, reason = "FIXME: <link/explanation>")]` on the minimal item necessary, and include a FIXME and link if a future fix is expected.

If the lint is a false positive or not harmful, consider restructuring the code (e.g. by removing the braces or tweaking the fixture signature) so that no suppression is needed.

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

**Path patterns:** `**/*.rs`

**Instructions:**
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.

</details>
</issue_to_address>

### Comment 6
<location> `src/wireframe/handshake.rs:125` </location>
<code_context>
+            .workers(1)
+            .with_preamble::<HotlinePreamble>();
+        let server = install(server, timeout);
+        let server = server.bind("127.0.0.1:0".parse().unwrap()).expect("bind");
+        let addr = server.local_addr().expect("addr");
+        let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
</code_context>

<issue_to_address>
**issue (review_instructions):** `unwrap()` is used here; the guidelines require preferring `.expect()` instead.

To follow the guideline, please replace `.unwrap()` with `.expect("parse socket addr")` (or similar context string):

```rust
let server = server
    .bind("127.0.0.1:0".parse().expect("parse socket addr"))
    .expect("bind");
```

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

**Path patterns:** `**/*.rs`

**Instructions:**
Prefer `.expect()` over `.unwrap()`

</details>
</issue_to_address>

### Comment 7
<location> `src/wireframe/handshake.rs:154` </location>
<code_context>
+
+        let reply = recv_reply(&mut stream).await;
+        assert_eq!(&reply[0..4], PROTOCOL_ID);
+        assert_eq!(
+            u32::from_be_bytes(reply[4..8].try_into().unwrap()),
+            HANDSHAKE_OK
</code_context>

<issue_to_address>
**issue (review_instructions):** This assertion relies on a `.unwrap()` inside `try_into()`, which should be changed to `.expect()` per the guideline.

A few lines below, `u32::from_be_bytes(reply[4..8].try_into().unwrap())` uses `unwrap()`. Please switch that to `expect("convert reply slice to array")` (or similar) to comply with the "prefer `.expect()`" rule:

```rust
u32::from_be_bytes(reply[4..8].try_into().expect("convert reply slice to array"))
```

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

**Path patterns:** `**/*.rs`

**Instructions:**
Prefer `.expect()` over `.unwrap()`

</details>
</issue_to_address>

### Comment 8
<location> `src/wireframe/handshake.rs:154` </location>
<code_context>
+
+        let reply = recv_reply(&mut stream).await;
+        assert_eq!(&reply[0..4], PROTOCOL_ID);
+        assert_eq!(
+            u32::from_be_bytes(reply[4..8].try_into().unwrap()),
+            HANDSHAKE_OK
</code_context>

<issue_to_address>
**issue (review_instructions):** The second handshake test also uses `.unwrap()` inside `try_into()` and should be updated to `.expect()`.

Similarly in this test, the expression `u32::from_be_bytes(reply[4..8].try_into().unwrap())` should use `.expect("convert reply slice to array")` (or similar) instead of `unwrap()` to match the project guidelines.

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

**Path patterns:** `**/*.rs`

**Instructions:**
Prefer `.expect()` over `.unwrap()`

</details>
</issue_to_address>

### Comment 9
<location> `src/wireframe/handshake.rs:283` </location>
<code_context>
+            };
+            reply
+                .as_ref()
+                .map(|buf| u32::from_be_bytes(buf[4..8].try_into().unwrap()))
+                .map_err(ToString::to_string)
+        }
</code_context>

<issue_to_address>
**issue (review_instructions):** `unwrap()` is used when converting the reply slice, and should be replaced with `.expect()` with a descriptive message.

Here as well, please replace `try_into().unwrap()` with `try_into().expect("convert reply slice to array")` (or a clearer context-specific message) to adhere to the "prefer `.expect()`" convention.

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

**Path patterns:** `**/*.rs`

**Instructions:**
Prefer `.expect()` over `.unwrap()`

</details>
</issue_to_address>

### Comment 10
<location> `src/wireframe/handshake.rs:80` </location>
<code_context>
+    }
+}
+
+fn error_code_from_str(text: &str) -> Option<u32> {
+    if text.contains("invalid protocol id") {
+        Some(HANDSHAKE_ERR_INVALID)
</code_context>

<issue_to_address>
**suggestion (review_instructions):** `error_code_from_str` uses an if/else-if/else chain (>2 branches); per the guideline this logic should be factored into predicate helpers instead of a multi-branch conditional.

Inside `error_code_from_str` there is an `if ... else if ... else` chain that has three branches. The instructions ask that conditionals with more than two branches be moved into predicate functions.

One way to align with this would be to introduce small predicates, e.g.:

```rust
fn is_invalid_protocol(text: &str) -> bool { text.contains("invalid protocol id") }
fn is_unsupported_version(text: &str) -> bool { text.contains("unsupported version") }

fn error_code_from_str(text: &str) -> Option<u32> {
    if is_invalid_protocol(text) {
        Some(HANDSHAKE_ERR_INVALID)
    } else if is_unsupported_version(text) {
        Some(HANDSHAKE_ERR_UNSUPPORTED_VERSION)
    } else {
        None
    }
}
```

or equivalently structure the checks so that no single conditional has more than two branches.

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

**Path patterns:** `**/*.rs`

**Instructions:**
Move conditionals with >2 branches into a predicate function.

</details>
</issue_to_address>

### Comment 11
<location> `docs/wireframe-users-guide.md:38` </location>
<code_context>
+```
+
+The snippet below wires the builder into a Tokio runtime, decodes inbound
+payloads, and emits a serialised response. It showcases the typical `main`
+function for a microservice that listens on localhost and responds to a `Ping`
+message with a `Pong` payload.[^2][^10][^15]
</code_context>

<issue_to_address>
**suggestion (review_instructions):** "serialised" should use the -ize form under en-GB-oxendic conventions ("serialized").

En-GB-oxendic style prefers -ize/-yse endings, so this should read "emits a serialized response" rather than "serialised".

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

**Path patterns:** `**/*.md`

**Instructions:**
Use en-GB-oxendic (-ize / -yse / -our) spelling and grammar.

</details>
</issue_to_address>

### Comment 12
<location> `docs/wireframe-users-guide.md:102` </location>
<code_context>
+```
+
+Route identifiers must be unique; the builder returns
+`WireframeError::DuplicateRoute` when you try to register a handler twice,
+keeping the dispatch table unambiguous.[^2][^5] New applications default to the
+bundled bincode serializer, a 1024-byte frame buffer, and a 100 ms read
</code_context>

<issue_to_address>
**suggestion (review_instructions):** This sentence uses the second-person pronoun "you", which the style guide forbids.

Consider rephrasing to avoid second person, for example: "`WireframeError::DuplicateRoute` when a handler is registered for the same route twice," or similar.

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

**Path patterns:** `**/*.md`

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

</details>
</issue_to_address>

### Comment 13
<location> `docs/wireframe-users-guide.md:106` </location>
<code_context>
+keeping the dispatch table unambiguous.[^2][^5] New applications default to the
+bundled bincode serializer, a 1024-byte frame buffer, and a 100 ms read
+timeout. Clamp these limits with `buffer_capacity` and `read_timeout_ms`, or
+swap the serializer with `with_serializer` when you need a different encoding
+strategy.[^3][^4]
+
</code_context>

<issue_to_address>
**suggestion (review_instructions):** This line contains "you", which is not permitted by the pronoun guideline.

A neutral alternative might be: "swap the serializer with `with_serializer` when a different encoding strategy is required".

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

**Path patterns:** `**/*.md`

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

</details>
</issue_to_address>

### Comment 14
<location> `docs/wireframe-users-guide.md:113` </location>
<code_context>
+`WireframeServer``handle_connection(stream)` builds (or reuses) the middleware
+chain, wraps the transport in a length-delimited codec, enforces per-frame read
+timeouts, and writes responses. Serialization helpers `send_response` and
+`send_response_framed` return typed `SendError` variants when encoding or I/O
+fails, and the connection closes after ten consecutive deserialization
+errors.[^6][^7]
</code_context>

<issue_to_address>
**suggestion (review_instructions):** The acronym "I/O" is used here without being expanded on first use.

To comply with the acronym rule, consider expanding on first mention, for example: "input/output (I/O)".

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

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 15
<location> `docs/wireframe-users-guide.md:359` </location>
<code_context>
+
+## Running servers
+
+`WireframeServer::new` clones the application factory per worker, defaults the
+worker count to the host CPU total (never below one), supports a readiness
+signal, and normalizes accept-loop backoff settings through
</code_context>

<issue_to_address>
**suggestion (review_instructions):** "CPU" later in this sentence is not expanded on first use.

Consider expanding the acronym on first occurrence, for example: "host central processing unit (CPU) total".

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

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 16
<location> `docs/wireframe-users-guide.md:576` </location>
<code_context>
+
+## Additional utilities
+
+- `read_preamble` decodes up to 1 KiB using bincode, returning the decoded
+  value plus any leftover bytes that must be replayed before normal frame
+  processing.[^37]
</code_context>

<issue_to_address>
**suggestion (review_instructions):** "KiB" is introduced without definition, which conflicts with the acronym rule.

Consider expanding this as "kibibyte (KiB)" on first use, for example: "decodes up to 1 kibibyte (KiB)".

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

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</details>
</issue_to_address>

### Comment 17
<location> `docs/wireframe-users-guide.md:452` </location>
<code_context>
+`Response::MultiPacket` exposes a different surface: callers hand ownership of
+the receiving half of a `tokio::sync::mpsc` channel to the connection actor,
+retain the sender, and push frames whenever back-pressure allows. The
+`Response::with_channel` helper constructs the pair and returns the sender
+alongside a `Response::MultiPacket`, making the ergonomic tuple pattern
+documented in ADR 0001 trivial to adopt. The library awaits channel capacity
</code_context>

<issue_to_address>
**suggestion (review_instructions):** The reference to "ADR 0001" in the following line introduces the ADR acronym without expansion.

On its first occurrence, consider expanding ADR, for example: "Architecture Decision Record (ADR) 0001".

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

**Path patterns:** `**/*.md`

**Instructions:**
Define uncommon acronyms on first use.

</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/wireframe/handshake.rs
Comment thread docs/wireframe-users-guide.md Outdated
Comment thread src/wireframe/handshake.rs Outdated
Comment thread src/wireframe/handshake.rs Outdated
Comment thread src/wireframe/handshake.rs Outdated
Comment thread docs/wireframe-users-guide.md Outdated
Comment thread docs/wireframe-users-guide.md Outdated
Comment thread docs/wireframe-users-guide.md
Comment thread docs/wireframe-users-guide.md Outdated
Comment thread docs/wireframe-users-guide.md
- Replace string matching of handshake errors with constants for clarity.
- Extract test helper functions for handshake preamble and reply handling.
- Update tests to use helper functions and better error code conversions.
- Enhance decode_error_for_handshake to return specific error strings.
- Minor cleanups in handshake and preamble modules to improve maintainability.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Wireframe handshake module with 5s timeout and bootstrap integration Wireframe Hotline handshake: install API and 5s timeout (bootstrap) Nov 30, 2025
codescene-delta-analysis[bot]

This comment was marked as 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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/server/wireframe.rs (1)

1-8: Wire in handshake install and refresh module docs

Register the Hotline handshake as part of bootstrap, as done here, but also update the module-level doc comment so it no longer claims the handshake is “future work” now that handshake::install(server, protocol::HANDSHAKE_TIMEOUT) is in the run path.

Use wording along these lines:

-//! The initial implementation keeps the runtime intentionally small: it
-//! parses configuration, resolves the bind address, and starts an empty
-//! [`WireframeServer`]. Future work will register the Hotline handshake,
-//! serializer, and protocol routes described in the roadmap.
+//! The current implementation keeps the runtime intentionally small: it
+//! parses configuration, resolves the bind address, registers the Hotline
+//! handshake, and starts a [`WireframeServer`]. Future work will attach the
+//! serializer and protocol routes described in the roadmap.

Also applies to: 22-27, 76-92

src/wireframe/preamble.rs (1)

51-63: Align handshake error tags with tests and lint policy

Update this module so the new handshake-specific DecodeError strings and the tests stay in agreement, and avoid using #[allow] for Clippy:

  1. Fix test expectations for the new error tags

decode_error_for_handshake now emits OtherString("handshake:invalid-protocol-id") and OtherString("handshake:unsupported-version"). The Display impl for bincode's DecodeError prints variants in Debug form, so err.to_string() from these variants no longer contains "invalid protocol id" or "unsupported version" with spaces.

Update the unit tests in this file to assert on the canonical tags instead:

@@
-    #[rstest]
-    #[tokio::test]
-    async fn rejects_invalid_protocol() {
@@
-        assert!(
-            err.to_string().contains("invalid protocol id"),
-            "expected detailed message, got {err:?}"
-        );
+        assert!(
+            err.to_string().contains("handshake:invalid-protocol-id"),
+            "expected detailed message tag, got {err:?}"
+        );
@@
-    #[rstest]
-    #[tokio::test]
-    async fn rejects_unsupported_version() {
@@
-        assert!(
-            err.to_string().contains("unsupported version"),
-            "expected detailed message, got {err:?}"
-        );
+        assert!(
+            err.to_string().contains("handshake:unsupported-version"),
+            "expected detailed message tag, got {err:?}"
+        );
  1. Replace #[allow] with #[expect] for Clippy

then_failure still uses #[allow(clippy::needless_pass_by_value)], which breaks the lint policy that forbids #[allow] and requires scoped #[expect(..., reason = \"…\")] instead.

Mirror the pattern used on given_malformed:

-    #[then("decoding fails with \"{message}\"")]
-    #[allow(clippy::needless_pass_by_value)]
-    fn then_failure(world: &HandshakeWorld, message: String) {
+    #[then("decoding fails with \"{message}\"")]
+    #[expect(
+        clippy::needless_pass_by_value,
+        reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \
+                  captures"
+    )]
+    fn then_failure(world: &HandshakeWorld, message: String) {
♻️ Duplicate comments (2)
src/wireframe/handshake.rs (2)

49-74: Deduplicate preamble_bytes with the existing helper

Deduplicate test_support::preamble_bytes with the existing helper in src/wireframe/preamble.rs to keep the handshake layout defined in one place. Expose a single shared helper (for example in a small test module) and reuse it from both preamble and handshake tests rather than maintaining two near‑identical implementations.


232-365: Replace #[allow] attributes and simplify the BDD runtime world

Remove both #[allow(unused_braces)] on the world fixture and #[allow(clippy::needless_pass_by_value)] on when_custom; the lint policy forbids #[allow] and requires either fixing the code or using narrowly scoped #[expect(.., reason = "FIXME: …")].

  • Restructure the world fixture to avoid the unused_braces warning (for example, by writing it as a normal function body without the extra braces), or, if a suppression is genuinely unavoidable, replace #[allow(unused_braces)] with #[expect(unused_braces, reason = "FIXME: link-to-tracking-issue")] on the minimal item.
  • Eliminate needless_pass_by_value by changing tag: String in when_custom to borrow (&str) so the step definition no longer needs a lint suppression.

In a follow‑up, drop the bespoke Runtime plus RefCell state in HandshakeWorld and run the BDD steps on the existing async test runtime to reduce complexity and align with the adjacent async tests.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b97885b and 256ac44.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .cargo/config.toml (1 hunks)
  • .markdownlint-cli2.jsonc (1 hunks)
  • Cargo.toml (1 hunks)
  • docs/design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • docs/users-guide.md (1 hunks)
  • docs/wireframe-users-guide.md (1 hunks)
  • src/server/wireframe.rs (2 hunks)
  • src/wireframe/handshake.rs (1 hunks)
  • src/wireframe/mod.rs (1 hunks)
  • src/wireframe/preamble.rs (1 hunks)
  • tests/features/wireframe_handshake_hooks.feature (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt, make lint, and make test before committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
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.
Handle errors with the Result type instead of panicking in Rust where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
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.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow in Rust error handling.
Use rstest fixtures for shared setup in Rust tests.
Replace duplicated Rust tests with #[rstest(...)] parameterized cases.
Prefer mockall for mocks/stubs in Rust tests.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...

Files:

  • src/wireframe/mod.rs
  • src/wireframe/handshake.rs
  • src/wireframe/preamble.rs
  • src/server/wireframe.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/wireframe/mod.rs
  • src/wireframe/handshake.rs
  • src/wireframe/preamble.rs
  • src/server/wireframe.rs
docs/**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

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.
Documentation must use en-GB-oxendict spelling and grammar ('-ize' / '-yse' / '-our'). (EXCEPTION: the LICENSE filename is left unchanged for community consistency.)

docs/**/*.md: Use British English based on the Oxford English Dictionary (en-GB-oxendict), including: -ize suffixes (realize, organization), -lyse suffixes (analyse, paralyse, catalyse), -our suffixes (colour, behaviour, neighbour), -re suffixes (calibre, centre, fibre), double 'l' (cancelled, counsellor, cruellest), maintain 'e' (likeable, liveable, rateable), -ogue suffixes (analogue, catalogue)
Keep US spelling when used in an API (e.g., 'color')
Use the Oxford comma: 'ships, planes, and hovercraft' where it aids comprehension
Treat company names as collective nouns: 'Lille Industries are expanding'
Write headings in sentence case
Use Markdown headings (#, ##, ###, and so on) in order without skipping levels
Follow markdownlint recommendations for Markdown formatting
Always provide a language identifier for fenced code blocks; use 'plaintext' for non-code text
Use '-' as the first level bullet and renumber lists when items change
Prefer inline links using 'text' or angle brackets around the URL
Ensure blank lines before and after bulleted lists and fenced blocks
Ensure tables have a delimiter line below the header row
Expand any uncommon acronym on first use, for example, Continuous Integration (CI)
Wrap paragraphs at 80 columns
Do not wrap tables in documentation
Use footnotes referenced with '[^label]'
Where it adds clarity, include Mermaid diagrams in documentation
When embedding figures in documentation, use 'alt text' and provide brief alt text describing the content
Add a short description before each Mermaid diagram so screen readers can understand it

All Markdown documents that include a ...

Files:

  • docs/wireframe-users-guide.md
  • docs/users-guide.md
  • docs/roadmap.md
  • docs/design.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.
Tables and headings in Markdown must not be wrapped.
Use dashes (-) for list bullets in Markdown.
Use GitHub-flavoured Markdown footnotes ([^1]) for references and footnotes.

Files:

  • docs/wireframe-users-guide.md
  • docs/users-guide.md
  • docs/roadmap.md
  • docs/design.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/wireframe-users-guide.md
  • docs/users-guide.md
  • docs/roadmap.md
  • docs/design.md
docs/**/*.{md,rs}

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

Wrap code at 120 columns

Files:

  • docs/wireframe-users-guide.md
  • docs/users-guide.md
  • docs/roadmap.md
  • docs/design.md
**/Cargo.toml

📄 CodeRabbit inference engine (AGENTS.md)

**/Cargo.toml: Use explicit version ranges in Cargo.toml and keep dependencies up-to-date.
All crate versions specified in Cargo.toml must use SemVer-compatible caret requirements (e.g., some-crate = "1.2.3").
The use of wildcard (*) or open-ended inequality (>=) version requirements in Cargo.toml is strictly forbidden.
Tilde requirements (~) in Cargo.toml should only be used where a dependency must be locked to patch-level updates for a specific, documented reason.

Files:

  • Cargo.toml
🧬 Code graph analysis (3)
src/wireframe/mod.rs (1)
src/wireframe/preamble.rs (1)
  • handshake (28-28)
src/wireframe/handshake.rs (2)
src/protocol.rs (1)
  • write_handshake_reply (94-102)
src/wireframe/preamble.rs (2)
  • preamble_bytes (67-79)
  • world (196-200)
src/server/wireframe.rs (4)
src/wireframe/preamble.rs (1)
  • handshake (28-28)
tests/handshake.rs (1)
  • handshake (16-52)
tests/payload_reject.rs (1)
  • handshake (21-42)
src/wireframe/handshake.rs (1)
  • install (35-47)
🪛 LanguageTool
docs/wireframe-users-guide.md

[grammar] ~369-~369: There seems to be a noun/verb agreement error. Did you mean “accepts” or “accepted”?
Context: ...he stream to the application. Transient accept failures trigger exponential backoff ca...

(SINGULAR_NOUN_VERB_AGREEMENT)


[uncategorized] ~379-~379: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...bind and accept failures as typed errors so callers can react appropriately.[^21] ...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~493-~493: Possible missing comma found.
Context: ...through the same hooks used for regular frames so existing observability continues to ...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~502-~502: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...ma.[^10][^12] - Emit version N on egress so clients observe a single schema. - Publ...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~570-~570: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...come no-ops when the feature is disabled so instrumentation can stay in place.[^33]...

(COMMA_COMPOUND_SENTENCE_2)

🔍 Remote MCP Ref

Let me try searching for Hotline protocol specifications:The search results didn't directly provide Hotline protocol handshake error codes. Let me search for more specific information about the Wireframe framework and this implementation:The searches aren't returning specific Wireframe library documentation. Let me refocus on understanding the implementation by searching for any related GitHub repositories or specific error codes:Based on my search efforts, I've found that the Hotline protocol handshake details are documented in the Hotline Wiki. Now let me compile a summary of the relevant context for reviewing this PR.

Summary of Relevant Context for PR Review

Based on the available documentation and context provided, here are the key findings relevant to reviewing this PR:

Hotline Protocol Handshake

After establishing a TCP connection, both client and server start a handshake process to confirm that each complies with the other's requirements. The information provided in this initial data exchange identifies protocols and their versions used in the communication. In cases where the capabilities of one of the subjects do not comply with the other's requirements, the connection is dropped. In case of an error, client and server close the connection.

PR Implementation Scope

The PR introduces:

  1. Error Code Mapping: The implementation maps decode errors to 4 handshake reply codes:

    • Code 0: Success (valid Hotline handshake)
    • Code 1: Invalid protocol
    • Code 2: Unsupported version
    • Code 3: Timeout (idle connection after 5 seconds)
  2. Timeout Configuration: A 5-second (HANDSHAKE_TIMEOUT) idle timeout is enforced before routing occurs, with idle sockets dropped after this duration.

  3. Integration Strategy: Uses Wireframe's preamble callback system (on_preamble_decode_success, on_preamble_decode_failure, preamble_timeout) rather than local patches to the vendored wireframe fork.

  4. Test Coverage: Includes 4 behavioral test scenarios covering success, invalid protocol, unsupported version, and idle timeout cases.

Key Changes to Review

  • Cargo.toml: Wireframe dependency upgraded from 0.1.0-alpha1 to 0.1.0 (stable release)
  • New Module: src/wireframe/handshake.rs - Public API install(server, timeout) function
  • Error Mapping: src/wireframe/preamble.rs enhanced to explicitly map HandshakeError variants to specific error codes
  • Documentation: Updated design.md, users-guide.md, roadmap.md with handshake semantics; new comprehensive wireframe-users-guide.md

Validation Considerations

The implementation appears to handle the documented Hotline protocol handshake flow correctly with appropriate error conditions and timeout enforcement. The move to stable wireframe (0.1.0) and removal of the vendored fork dependency improves maintainability.,

⏰ 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 (postgres)
🔇 Additional comments (9)
Cargo.toml (1)

53-53: Upgrade wireframe to stable 0.1.0 and keep vendor in sync

Retain this pinned SemVer and synchronise vendor/wireframe and Cargo.lock with crate wireframe = "0.1.0", then run the full cargo test --workspace sweep to validate the new upstream behaviour end to end.

.cargo/config.toml (1)

1-8: Use dev profile tuning and check-cfg consistently

Keep this dev profile as the default for local work and ensure make check-fmt, make lint, and cargo check are re-run so the new [profile.dev] and [check-cfg] settings are exercised in CI and local workflows.

Also applies to: 12-12

.markdownlint-cli2.jsonc (1)

13-20: Ignore vendored Wireframe docs in markdownlint

Leave vendor/wireframe/** in the ignore list so markdownlint enforces house style only on first-party Markdown while avoiding churn from upstream-vendored content.

docs/roadmap.md (1)

52-59: Keep handshake roadmap status aligned with implementation

Retain this completed task description; it accurately records the move to wireframe v0.1.0 preamble hooks, the five-second handshake timeout, and the associated test coverage, and it keeps the roadmap in sync with the implemented behaviour.

src/wireframe/mod.rs (1)

7-8: Expose handshake adapter module publicly

Keep pub mod handshake; so server bootstrap and tests can install the Hotline handshake layer via crate::wireframe::handshake while preserving the transport/domain separation described in the module docs.

docs/users-guide.md (1)

32-37: Document Hotline handshake semantics and timeout clearly

Retain this description of the 12-byte preamble, reply codes 0–3, and the five-second handshake timeout so the documented Wireframe server behaviour matches the installed handshake layer and associated tests.

tests/features/wireframe_handshake_hooks.feature (1)

1-22: Keep these handshake scenarios as the canonical behavioural spec

Retain these four scenarios; they cover the expected Hotline reply codes (0–3) and align with the BDD step patterns and handshake implementation, giving clear executable documentation for the handshake contract.

src/wireframe/handshake.rs (2)

28-47: Keep install as the single entry-point for Hotline handshake wiring

Leave install as the sole place that attaches the Hotline preamble handlers and timeout. The generic bounds and .on_preamble_decode_*().preamble_timeout(timeout) chain cleanly separate handshake behaviour from server bootstrap and make the five‑second idle cap easy to override in tests.


121-229: Retain these unit tests; they pin the wire protocol contract

Keep the three unit tests (replies_success, replies_handshake_errors, replies_timeout_for_idle_socket) as written. They exercise the public bootstrap (start_server + install) and assert the exact 8‑byte reply layout and error codes for success, invalid protocol, unsupported version, and timeout, which is the right level of detail for guarding future refactors.

Comment thread docs/design.md
Comment thread docs/wireframe-users-guide.md Outdated
…shake

Update handshake error string comparisons to use starts_with instead of exact match, allowing for descriptive suffixes. Also add detailed error descriptions for InvalidProtocol and UnsupportedVersion handshake errors to Improve error handling and diagnostics.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as 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: 5

♻️ Duplicate comments (1)
src/wireframe/handshake.rs (1)

320-324: Replace #[allow(unused_braces)] with #[expect].

#[allow] is forbidden by the coding guidelines. Use a narrowly scoped #[expect] with a reason instead. As per coding guidelines and past review comments.

-    // `rstest` reports this function as a fixture; rustc flags the required block
-    // as `unused_braces`, so suppress the false positive locally.
-    #[allow(unused_braces)]
+    #[expect(
+        unused_braces,
+        reason = "rstest fixture macro requires braces; rustc false positive"
+    )]
     #[fixture]
     fn world() -> HandshakeWorld { HandshakeWorld::new() }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 256ac44 and 8774b70.

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

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt, make lint, and make test before committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
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.
Handle errors with the Result type instead of panicking in Rust where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
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.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow in Rust error handling.
Use rstest fixtures for shared setup in Rust tests.
Replace duplicated Rust tests with #[rstest(...)] parameterized cases.
Prefer mockall for mocks/stubs in Rust tests.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...

Files:

  • src/wireframe/preamble.rs
  • src/wireframe/handshake.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/wireframe/preamble.rs
  • src/wireframe/handshake.rs
🧬 Code graph analysis (1)
src/wireframe/handshake.rs (2)
src/protocol.rs (1)
  • write_handshake_reply (94-102)
src/wireframe/preamble.rs (2)
  • preamble_bytes (67-79)
  • world (196-200)
⏰ 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). (3)
  • GitHub Check: Sourcery review
  • GitHub Check: build-test (postgres)
  • GitHub Check: coverage
🔇 Additional comments (6)
src/wireframe/handshake.rs (6)

1-7: Module documentation is clear and purposeful.

The //! comment explains the module's role, the 5-second timeout, and testability via injectable timeouts. Good.


28-47: Public API is well-documented and idiomatic.

The install function cleanly wires handlers and timeout onto the server builder. The #[must_use] annotation correctly signals that the returned server must be consumed.


82-94: Verify behaviour for unmapped decode errors.

When error_code_for_decode returns None, no reply is sent and Ok(()) is returned. Confirm this is intentional—clients may hang awaiting a reply before the server closes the socket.


143-161: Test server helper is well-designed.

start_server accepts a configurable timeout, enabling fast tests for idle-timeout scenarios. The oneshot shutdown channel ensures clean teardown.


163-229: Unit tests provide good coverage.

The tests exercise success, invalid protocol, unsupported version, and idle-timeout paths. Parameterised cases via #[rstest] reduce duplication. Assertions use .expect() with descriptive messages as per guidelines.


354-365: BDD scenario bindings are correctly wired.

The four scenarios cover the key handshake outcomes: success, invalid protocol, unsupported version, and timeout. The let _ = world; pattern is the expected idiom for rstest_bdd scenario functions.

Comment thread src/wireframe/handshake.rs Outdated
Comment thread src/wireframe/handshake.rs
Comment thread src/wireframe/handshake.rs
Comment thread src/wireframe/handshake.rs Outdated
Comment thread src/wireframe/preamble.rs
Update documentation to explain that idle sockets trigger `preamble_timeout`, which invokes `on_preamble_decode_failure` to emit the 8-byte Hotline timeout reply before closing the connection after the five-second `HANDSHAKE_TIMEOUT`. Also fix minor punctuation and formatting issues in the wireframe users guide to improve readability.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

…tokens

- Introduced HANDSHAKE_INVALID_PROTOCOL_TOKEN and HANDSHAKE_UNSUPPORTED_VERSION_TOKEN constants in protocol.rs to unify error message tokens.
- Improved error message creation by using these constants in preamble.rs and handshake.rs.
- Removed inline handshake test utility functions from handshake.rs.
- Added a new wireframe/test_helpers.rs module to centralize handshake test utilities like constructing preamble buffers and reading handshake replies.
- Updated handshake.rs and tests to use the shared test helpers module for cleaner, more maintainable test code.

This refactoring improves code reuse, consistency in error messages, and test maintainability.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as 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

♻️ Duplicate comments (2)
src/wireframe/handshake.rs (2)

301-303: Replace forbidden #[allow] with #[expect].

The coding guidelines explicitly forbid #[allow] in all circumstances. Use #[expect] with a reason instead.

Apply this diff:

-    // `rstest` reports this function as a fixture; rustc flags the required block
-    // as `unused_braces`, so suppress the false positive locally.
-    #[allow(unused_braces)]
+    #[expect(
+        unused_braces,
+        reason = "rstest fixture macro expansion triggers unused_braces false positive"
+    )]
     #[fixture]
     fn world() -> HandshakeWorld { HandshakeWorld::new() }

As per coding guidelines forbidding #[allow].


314-321: Replace forbidden #[allow] with #[expect].

This #[allow(clippy::needless_pass_by_value)] violates the coding guidelines. Use #[expect] with a reason.

Apply this diff:

-    #[allow(clippy::needless_pass_by_value)]
+    #[expect(
+        clippy::needless_pass_by_value,
+        reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str captures"
+    )]
     #[when("I send a Hotline handshake with protocol \"{tag}\" and version {version}")]
     fn when_custom(world: &HandshakeWorld, tag: String, version: u16) {

As per coding guidelines: "#[allow] is forbidden. Only narrowly scoped #[expect(lint, reason = "...")] is allowed."

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 48b54a7 and 3d52729.

📒 Files selected for processing (4)
  • src/protocol.rs (1 hunks)
  • src/wireframe/handshake.rs (1 hunks)
  • src/wireframe/preamble.rs (2 hunks)
  • src/wireframe/test_helpers.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt, make lint, and make test before committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
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.
Handle errors with the Result type instead of panicking in Rust where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
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.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow in Rust error handling.
Use rstest fixtures for shared setup in Rust tests.
Replace duplicated Rust tests with #[rstest(...)] parameterized cases.
Prefer mockall for mocks/stubs in Rust tests.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...

Files:

  • src/protocol.rs
  • src/wireframe/test_helpers.rs
  • src/wireframe/handshake.rs
  • src/wireframe/preamble.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/protocol.rs
  • src/wireframe/test_helpers.rs
  • src/wireframe/handshake.rs
  • src/wireframe/preamble.rs
🧬 Code graph analysis (3)
src/wireframe/test_helpers.rs (1)
src/wireframe/preamble.rs (1)
  • preamble_bytes (74-86)
src/wireframe/handshake.rs (4)
src/protocol.rs (1)
  • write_handshake_reply (99-107)
src/wireframe/preamble.rs (1)
  • preamble_bytes (74-86)
src/wireframe/test_helpers.rs (2)
  • preamble_bytes (14-26)
  • recv_reply (29-33)
tests/runtime_selection_bdd.rs (1)
  • runtime (26-26)
src/wireframe/preamble.rs (1)
src/protocol.rs (1)
  • parse_handshake (64-79)
🪛 GitHub Actions: CI
src/wireframe/handshake.rs

[error] 19-19: unresolved import super::test_helpers (E0432) during cargo clippy --no-default-features --features postgres -- -D warnings

🪛 GitHub Check: build-test (postgres)
src/wireframe/handshake.rs

[failure] 19-19:
unresolved import super::test_helpers

🪛 GitHub Check: build-test (sqlite)
src/wireframe/handshake.rs

[failure] 19-19:
unresolved import super::test_helpers

🪛 GitHub Check: coverage
src/wireframe/handshake.rs

[failure] 19-19:
unresolved import super::test_helpers

⏰ 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 (13)
src/wireframe/test_helpers.rs (2)

1-11: LGTM: Clean test utilities module.

The module-level documentation clearly explains the purpose, and the #[cfg(test)] gating ensures these utilities are only compiled during testing.


29-33: LGTM: recv_reply is appropriate for test helpers.

The use of .expect() is acceptable here since the entire module is gated with #[cfg(test)].

src/wireframe/preamble.rs (2)

14-21: LGTM: Token imports are correctly scoped.

The explicit error token constants enable structured error mapping without brittle substring matching.


62-71: LGTM: Structured error mapping aligns with handshake protocol.

The decode_error_for_handshake function now uses the shared token constants, ensuring consistent error signalling between the preamble decoder and the handshake reply logic. This addresses the previous review feedback requesting centralised sentinel strings.

Based on learnings from past review comments.

src/protocol.rs (1)

34-37: LGTM: Error token constants are well-defined.

The sentinel constants provide a single source of truth for handshake error signalling, preventing drift between the preamble decoder and reply logic. The documentation clearly explains their purpose.

Based on learnings from past review comments requesting centralised tokens.

src/wireframe/handshake.rs (8)

1-7: LGTM: Module documentation is clear and comprehensive.

The doc comment explains the module's purpose, its integration with the Wireframe runtime, and the testing flexibility around timeout overrides.


30-49: LGTM: install API is clean and well-documented.

The function signature is appropriately generic, the documentation explains the timeout parameter's purpose, and the implementation is straightforward. The use of #[must_use] ensures callers don't accidentally discard the configured server.


51-69: LGTM: Handler implementations follow the Wireframe patterns correctly.

The success and failure handlers correctly return boxed futures with the appropriate signatures. The error code mapping delegates to error_code_for_decode, maintaining separation of concerns.


71-96: LGTM: Error mapping uses structured predicates.

The refactored approach with is_invalid_protocol and is_unsupported_version predicates addresses earlier review feedback about multi-branch conditionals. The use of starts_with on the shared token constants provides robust error detection.

Based on learnings from past review comments requesting predicate extraction.


140-159: LGTM: Success test validates the OK reply correctly.

The test constructs a valid handshake, writes it, receives the reply, and verifies the reply code. The use of .expect() with descriptive messages is appropriate for test code.


161-186: LGTM: Parameterised error tests cover invalid protocol and unsupported version.

The #[rstest] parameterisation elegantly covers both error cases without duplication, and the test logic correctly adjusts the version based on the expected error code.


188-206: LGTM: Timeout test validates idle connection handling.

The test uses a shortened timeout (100ms) to avoid slow tests, connects without sending data, and correctly verifies that the server replies with HANDSHAKE_ERR_TIMEOUT.


229-297: BDD world implementation is functional.

The HandshakeWorld structure with RefCell-based state management and a manual Runtime works correctly, though it adds some cognitive overhead. The implementation properly manages server lifecycle via the Drop trait. As noted in past review comments, this could be simplified with async fixtures once rstest-bdd v0.2.0 is available, but the current approach is acceptable.

Based on learnings from past review comments noting that full async refactoring is pending library support.

Comment thread src/wireframe/handshake.rs Outdated
Comment thread src/wireframe/test_helpers.rs
…t_helpers module

- Removed `preamble_bytes` function from `preamble.rs` test module.
- Exported `test_helpers` module in `wireframe/mod.rs`.
- Adjusted imports in `handshake.rs` and `preamble.rs` tests to use `wireframe::test_helpers::preamble_bytes`.
- Added `#[expect]` attributes to suppress false positives in handshake test fixtures and steps.

This cleans up test helper usage by centralizing common utilities and improves test code hygiene.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Removed an unused `#[expect]` attribute and the unused `test_helpers` import in the handshake module's tests, cleaning up warning suppressions that were no longer needed.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as outdated.

Added #[expect(unused_braces)] to the world fixture in handshake tests to suppress a false positive warning triggered by the rstest fixture macro expansion.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
codescene-delta-analysis[bot]

This comment was marked as 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

Caution

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

⚠️ Outside diff range comments (2)
src/wireframe/preamble.rs (2)

241-243: Replace #[allow] with #[expect].

#[allow] is forbidden per coding guidelines. Use #[expect] with a reason. As per coding guidelines.

-    #[allow(clippy::needless_pass_by_value)]
+    #[expect(
+        clippy::needless_pass_by_value,
+        reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \
+                  captures"
+    )]
     #[then("the sub-protocol is \"{tag}\"")]
     fn then_sub_protocol(world: &HandshakeWorld, tag: String) {

276-278: Replace #[allow] with #[expect].

Same issue as above. #[allow] is forbidden. As per coding guidelines.

-    #[allow(clippy::needless_pass_by_value)]
+    #[expect(
+        clippy::needless_pass_by_value,
+        reason = "rstest-bdd step parameters must be owned; keep String until macro supports &str \
+                  captures"
+    )]
     #[then("decoding fails with \"{message}\"")]
     fn then_failure(world: &HandshakeWorld, message: String) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d52729 and ef2d891.

📒 Files selected for processing (3)
  • src/wireframe/handshake.rs (1 hunks)
  • src/wireframe/mod.rs (1 hunks)
  • src/wireframe/preamble.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Run make check-fmt, make lint, and make test before committing Rust code.
Clippy warnings MUST be disallowed in Rust code.
Fix any warnings emitted during Rust tests in the code itself rather than silencing them.
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.
Handle errors with the Result type instead of panicking in Rust where feasible.
Avoid unsafe code in Rust unless absolutely necessary, and document any usage clearly.
Place function attributes after doc comments in Rust.
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.
Rust lint rule suppressions must be tightly scoped and include a clear reason.
Prefer expect over allow in Rust error handling.
Use rstest fixtures for shared setup in Rust tests.
Replace duplicated Rust tests with #[rstest(...)] parameterized cases.
Prefer mockall for mocks/stubs in Rust tests.
Use concat!() to combine long string literals in Rust rather than escaping newlines with a backslash.
Prefer single-line versions of Rust functions where appropriate (e.g., pub fn new(id: u64) -> Self { Self(id) } instead of a multi-line version).
Use NewTypes to model domain values and eliminate 'i...

Files:

  • src/wireframe/handshake.rs
  • src/wireframe/preamble.rs
  • src/wireframe/mod.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/wireframe/handshake.rs
  • src/wireframe/preamble.rs
  • src/wireframe/mod.rs
🧬 Code graph analysis (3)
src/wireframe/handshake.rs (4)
src/protocol.rs (1)
  • write_handshake_reply (99-107)
src/wireframe/test_helpers.rs (2)
  • preamble_bytes (14-26)
  • recv_reply (29-33)
tests/runtime_selection_bdd.rs (1)
  • runtime (26-26)
src/wireframe/preamble.rs (1)
  • world (194-198)
src/wireframe/preamble.rs (2)
src/protocol.rs (1)
  • parse_handshake (64-79)
src/wireframe/test_helpers.rs (1)
  • preamble_bytes (14-26)
src/wireframe/mod.rs (1)
src/wireframe/preamble.rs (1)
  • handshake (35-35)
🪛 GitHub Actions: CI
src/wireframe/handshake.rs

[error] 300-300: this lint expectation is unfulfilled (during cargo-llvm-cov run).

🪛 GitHub Check: build-test (postgres)
src/wireframe/handshake.rs

[failure] 300-300:
this lint expectation is unfulfilled

🪛 GitHub Check: build-test (sqlite)
src/wireframe/handshake.rs

[failure] 300-300:
this lint expectation is unfulfilled

🪛 GitHub Check: coverage
src/wireframe/handshake.rs

[failure] 300-300:
this lint expectation is unfulfilled

🔍 Remote MCP

The documentation search isn't returning the specific Wireframe or Hotline protocol docs. Let me check if there's a GitHub repository I can access through Deepwiki for the project context:The web search results are about TLS/SSL handshakes, not the specific Hotline protocol or Wireframe library. These appear to be project-internal concepts. Let me search for information about the testing approach and behavior-driven development patterns used:Good! I found relevant information about Cucumber BDD testing framework for Rust. Now let me search for information about tokio async timeouts and preamble decode patterns, which are mentioned in the PR:Excellent! I now have good contextual information about Cucumber BDD testing and Tokio timeouts. Let me compile my findings into a concise summary for the PR reviewer.

Additional Context Summary for PR Review

Based on my research, here is relevant context that would help in reviewing this pull request:

Behavior-Driven Development Testing Approach

The PR includes a new feature file (tests/features/wireframe_handshake_hooks.feature) using the Cucumber BDD framework. Cucumber is a specification for running tests in a BDD (behavior-driven development) style workflow that provides a human-readable syntax for the definition of features, via the language Gherkin. Cucumber is a testing framework for Rust with async support. The feature scenarios in the PR test four distinct handshake outcomes: successful handshakes, invalid protocol errors, unsupported version errors, and idle connection timeouts—each expressed as human-readable Given-When-Then scenarios.

Async Timeout Implementation Patterns

The handshake module implements a 5-second idle timeout. Timeouts in Tokio can be implemented using tokio::time::timeout(), which wraps an async operation and returns an error if it doesn't complete within the specified duration. Timeout wraps a future or stream, setting an upper bound to the amount of time it is allowed to execute. If the future or stream does not complete in time, then it is canceled and an error is returned. The PR's use of preamble_timeout(timeout) to enforce the five-second idle cap follows this pattern—sockets that don't complete a handshake within the timeout window will trigger the on_preamble_decode_failure handler with a timeout error code.

Test Helper Architecture

The new src/wireframe/test_helpers.rs provides utilities for test infrastructure:

  • preamble_bytes() constructs a Hotline preamble buffer for test scenarios
  • recv_reply() is an async function that reads exactly REPLY_LEN bytes from a TcpStream

This pattern aligns with how Cucumber tests in Rust often require shared test utilities to construct and verify protocol-level data.

Dependency and Version Management

The PR upgrades the Wireframe library from version 0.1.0-alpha1 to 0.1.0, migrating from a pre-release to stable. This version bump likely corresponds to the introduction of the preamble callback APIs (on_preamble_decode_success, on_preamble_decode_failure, preamble_timeout) that the new handshake module depends on. The development Cargo profile changes (opt-level=1, debug=1, incremental compilation) optimize the development iteration speed.

Key Review Points:

  • The test helpers and feature scenarios comprehensively cover the four error paths (OK, invalid protocol, unsupported version, timeout)
  • The timeout is enforced at the async level via Tokio, ensuring idle sockets are dropped within the 5-second window
  • The public handshake::install() API provides a clean, composable interface for integrating the handshake layer into the server bootstrap
⏰ 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 (18)
src/wireframe/mod.rs (1)

7-10: LGTM!

The module declarations are correctly structured: handshake is public for server integration, and test_helpers is appropriately gated with #[cfg(test)] to prevent compilation in production builds.

src/wireframe/preamble.rs (4)

14-21: LGTM!

The imports now use the centralised protocol tokens (HANDSHAKE_INVALID_PROTOCOL_TOKEN, HANDSHAKE_UNSUPPORTED_VERSION_TOKEN), ensuring both preamble.rs and handshake.rs reference the same constants. This addresses the earlier review feedback about preventing silent drift.


62-71: LGTM!

The match-based error mapping with stable sentinel tokens is robust and aligns with the handshake module's predicates.


82-85: LGTM!

Test imports correctly reference the shared test_helpers::preamble_bytes, eliminating duplication.


165-168: LGTM!

BDD test imports correctly consolidated to use shared helpers.

src/wireframe/handshake.rs (13)

1-7: LGTM!

The module-level doc comment clearly explains the purpose, the 8-byte reply semantics, and the configurable timeout for test flexibility.


9-28: LGTM!

Imports are well-organised and minimal. The use of BoxFuture from futures_util is appropriate for the callback signatures.


30-49: LGTM!

The install function provides a clean, composable API. The #[must_use] attribute correctly signals that the returned server must not be discarded. Documentation is clear about using HANDSHAKE_TIMEOUT for production.


51-55: LGTM!

The success handler correctly writes HANDSHAKE_OK and ignores the preamble content (validation already passed during decode).


57-69: LGTM!

The failure handler correctly maps decode errors to Hotline error codes. Returning Ok(()) for unmapped errors is appropriate—the connection will be closed by the Wireframe runtime regardless.


71-96: LGTM!

The error mapping uses centralised sentinel tokens and predicate functions, addressing earlier review feedback about brittle string matching. The structured Io variant matching for TimedOut is robust.


120-138: LGTM!

The start_server helper is well-structured, using .expect() with descriptive messages. Spawning the server in a task with shutdown channel is a clean pattern.


140-159: LGTM!

The success test correctly validates the protocol ID and error code in the reply. Uses .expect() with descriptive context.


161-186: LGTM!

Good use of #[rstest] parameterised cases to cover both invalid protocol and unsupported version scenarios without duplication.


188-206: LGTM!

The timeout test uses a short 100ms timeout to keep tests fast whilst still exercising the timeout path. The outer 1-second timeout ensures the test fails fast if the server doesn't respond.


229-297: LGTM!

The HandshakeWorld implementation is functional. The RefCell-based approach and manual Runtime are noted in issue #235 for future simplification once rstest-bdd v0.2.0 provides better async support.


315-326: LGTM!

The #[expect] attribute on when_custom is correctly formed with a reason, complying with coding guidelines.


338-349: LGTM!

The scenario declarations correctly reference the feature file and cover all four handshake outcomes: success, invalid protocol, unsupported version, and timeout.

Comment thread src/wireframe/handshake.rs
- Added allow attribute in handshake.rs to avoid unfulfilled lint expectations during tests.
- Replaced clippy::needless_pass_by_value allow with expect and reason comments in preamble.rs for BDD test functions to clarify owned parameter usage requirements.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Wireframe Hotline handshake: install API and 5s timeout (bootstrap) Wireframe handshake: install API, 5s timeout, bootstrap integration Dec 2, 2025
Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

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

Gates Passed
6 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

@leynos leynos merged commit c59fbf3 into main Dec 2, 2025
8 checks passed
@leynos leynos deleted the terragon/implement-wireframe-handshake-ve85sy branch December 2, 2025 19:55
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