Skip to content

Implement ACP capability masking at protocol proxy seam (2.6.1) - #79

Merged
leynos merged 6 commits into
mainfrom
implement-acp-masking-jabwf2
May 2, 2026
Merged

Implement ACP capability masking at protocol proxy seam (2.6.1)#79
leynos merged 6 commits into
mainfrom
implement-acp-masking-jabwf2

Conversation

@leynos

@leynos leynos commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Intercept and mask terminal/* and fs/* ACP capabilities in the protocol proxy path before forwarding to the container.
  • Mask only the first newline-delimited ACP initialize frame; pass through malformed or non-ACP frames unchanged.
  • Preserve line endings and any trailing protocol data after the masked frame.
  • Adds unit and behavior-driven tests, and updates documentation and roadmaps.

Changes

Core functionality

  • Updated src/engine/connection/exec/protocol.rs to implement ACP masking at the protocol seam:
    • Added constants: ACP_INITIALIZE_METHOD, ACP_CLIENT_CAPABILITIES_FIELD, ACP_FILE_SYSTEM_CAPABILITY, ACP_TERMINAL_CAPABILITY.
    • Introduced forward_initial_protocol_frame_async to read and forward the first frame with masking.
    • Implemented mask_acp_initialize_frame, parse_json_message, remove_masked_acp_capabilities, and serialize_masked_message to perform safe, minimal rewrites.
    • Preserved bounded buffering and the existing stdout/stderr behavior; masking occurs strictly on the first ACP initialize frame.

Tests

  • Added new unit tests and scaffolding:
    • src/engine/connection/exec/protocol_acp_tests.rs: unit tests for masking logic, line-ending preservation, and trailing bytes.
    • tests/features/acp_capability_masking.feature: behavior-driven tests covering masking, malformed frames, and no-op scenarios.
  • The tests exercise: (a) blocked-capability masking, (b) pass-through for malformed/non-ACP frames, and (c) preservation of trailing protocol bytes.

Tests scaffolding

  • Introduced protocol-related ACP tests to verify the narrow seam behavior without impacting the broader protocol proxy.

Documentation & Roadmap

  • docs/podbot-design.md: explicit note on first-frame masking decision and pass-through for malformed frames.
  • docs/users-guide.md: describes ACP masking behavior within protocol mode and its lack of user-visible side effects.
  • docs/podbot-roadmap.md: marks ACP masking interception as completed for Step 2.6.
  • docs/execplans/2-6-1-intercept-acp-initialization.md: new execution plan detailing scope and approach.

Misc

  • No public API changes; ACP masking is contained to protocol.rs.
  • No new production dependencies; relies on existing serde_json/Value utilities.

Why this matters

  • Enforces ACP capability masking boundaries early in the protocol path, ensuring hosted agents never learn terminal or filesystem delegation capabilities, in line with design goals. This keeps container sandbox boundaries intact while preserving protocol semantics for non-ACP traffic.

Validation & Testing

  • Gate checks to run locally: make fmt, make lint, and make test; includes the new ACP masking tests.
  • Behavior verified for startup/steady-state/shutdown paths via unit tests and BDD scenarios.

Potential follow-ups

  • Extend tests to cover edge cases (empty clientCapabilities, unusual frame terminations).
  • Introduce additional regression tests if roadmap gates require broader ACP coverage.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/afef9711-3240-4f69-8b15-8bbb229d1ab0

Summary by Sourcery

Implement ACP capability masking at the protocol stdin proxy seam while documenting and validating the behaviour with unit, BDD, and roadmap updates.

New Features:

  • Mask ACP initialize requests in protocol mode to remove terminal/* and fs/* capabilities before forwarding to the container while preserving non-ACP traffic semantics.

Enhancements:

  • Refine protocol proxy stdin handling to inspect and rewrite only the first newline-delimited ACP frame, maintaining bounded buffering and existing shutdown behaviour.
  • Document ACP initialization masking and buffering behaviour in the design and user guides, including the first-frame-only rewrite and pass-through of malformed or non-ACP frames.
  • Clarify and reflow several existing execution plans and design docs for readability without changing their technical intent.

Documentation:

  • Introduce a new ExecPlan describing ACP initialization interception (Step 2.6.1) and mark the corresponding roadmap task as complete.

Tests:

  • Add focused unit and BDD-style tests to verify ACP capability masking, handling of malformed or non-ACP frames, and preservation of trailing protocol bytes in the protocol stdin proxy.

@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Intercept the first newline‑delimited stdin frame in protocol mode, detect ACP initialize JSON‑RPC, remove params.clientCapabilities.terminal and params.clientCapabilities.fs (drop empty clientCapabilities), preserve original line endings, forward malformed/non‑ACP first frames unchanged, then resume raw‑byte proxying.

Changes

ACP Initialize Masking

Layer / File(s) Summary
Data / Constants
src/engine/connection/exec/acp_helpers.rs
Add constants for max first‑frame bytes and ACP JSON keys (initialize, clientCapabilities, fs, terminal).
Core Helper Logic
src/engine/connection/exec/acp_helpers.rs
Implement mask_acp_initialize_frame, split_frame_line_ending, remove_masked_acp_capabilities, and forward_initial_acp_frame_async with bounded read, newline detection, JSON parse/serialize with fallback-to-original, and preserving line endings.
Session API
src/engine/connection/exec/session.rs
Add rewrite_acp_initialize: bool to ExecSessionOptions and builder with_acp_initialize_rewrite_enabled. Propagate flag into protocol options.
Protocol Wiring
src/engine/connection/exec/protocol.rs
Add rewrite_acp_initialize option to ProtocolSessionOptions; thread flag into stdin forwarding by wrapping spawn_stdin_forwarding_task and passing boolean into forward_host_stdin_to_exec_async.
Unit Tests
src/engine/connection/exec/protocol_acp_tests.rs
Add tests for masking behaviour, malformed frames, line‑ending preservation, removal of empty clientCapabilities, and utilities to run forwarding and assert bytes/shutdown semantics.
Forwarding Tests
src/engine/connection/exec/protocol_acp_forwarding_tests.rs
Add tests for disabled/enabled rewrite and oversized initial frame respecting MAX_FIRST_FRAME_BYTES and shutdown semantics.
BDD Tests
src/engine/connection/exec/protocol_acp_bdd_tests.rs
Add rstest‑bdd fixtures and steps driving forwarding scenarios (masked, malformed, unmodified) and assertions comparing actual vs expected forwarded bytes.
Feature Tests
tests/features/acp_capability_masking.feature
Add Gherkin scenarios covering masked initialize, malformed initialize, and initialize without blocked capabilities.
Docs & Roadmap
docs/execplans/2-6-1-intercept-acp-initialization.md, docs/podbot-design.md, docs/podbot-roadmap.md, docs/users-guide.md, docs/developers-guide.md
Add living ExecPlan and design/developer/user guidance describing first‑frame‑only ACP initialize masking rules, test contract, forwarding invariants and follow‑ups.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ProtocolHandler as Protocol Handler
    participant ACPHelper as ACP Helper
    participant Container

    Client->>ProtocolHandler: stdin stream (newline‑delimited)
    ProtocolHandler->>ACPHelper: capture up‑to‑first‑frame (bounded)
    ACPHelper->>ACPHelper: detect line ending, attempt JSON‑RPC parse
    alt initialize message with clientCapabilities
        ACPHelper->>ACPHelper: remove terminal & fs from clientCapabilities
        ACPHelper->>ProtocolHandler: rewritten frame (preserve line ending)
    else non‑initialize or parse failure
        ACPHelper->>ProtocolHandler: original first‑frame bytes
    end
    ProtocolHandler->>Container: forwarded first frame
    Client->>ProtocolHandler: remaining stdin bytes
    ProtocolHandler->>Container: passthrough raw bytes
Loading

Possibly related issues

Possibly related PRs

Poem

✂️ First line caught, endings kept true,
Remove terminal and fs, let the rest sail through.
Broken bytes pass on, unchanged and free,
Then stream resumes onward, plain as can be. 🎩

🚥 Pre-merge checks | ✅ 13 | ❌ 5

❌ Failed checks (5 warnings)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning PR introduces new internal APIs and architectural changes, but the developers-guide repository layout is significantly outdated and does not document the new acp_helpers.rs module or three ACP-specific test modules. Update the repository layout diagram in docs/developers-guide.md section 3 to include acp_helpers.rs and the three ACP test modules, add field-level doc comments to rewrite_acp_initialize in both structs explaining their purpose.
Testing (Unit And Behavioural) ⚠️ Warning Unit tests validate mask_acp_initialize_frame comprehensively, but behavioural tests exercise test doubles instead of session-level protocol rewriting behaviour and integration. Add integration test creating ExecSessionOptions with rewriting enabled, providing stdin with blocked ACP frame, and verifying masked output at session boundary in both enabled and disabled states.
Testing (Property / Proof) ⚠️ Warning The PR lacks property-based testing for mathematical invariants: bounded buffering, line-ending preservation, and selective capability removal across arbitrary inputs. Introduce proptest for arbitrary byte sequences with random newlines, diverse JSON structures, and all line-ending variants; apply Kani bounded model checker to verify saturating subtraction arithmetic.
Domain Architecture ⚠️ Warning Domain logic for sandbox capability restrictions leaked into protocol adapter; business policy hardcoded as string constants in JSON manipulation code. Extract a domain type representing SandboxCapabilityPolicy that explicitly lists denied capabilities; pass policy as parameter to ACP adapter rather than hardcoding.
Observability ⚠️ Warning ACP masking implementation lacks essential observability: missing container_id context in logs, no metrics for operational visibility, no tracing spans for async task tracking, and no proper error categorisation for production diagnosis. Thread container_id through to acp_helpers with stable contextual field; add #[instrument(skip(...))] for span creation; introduce metrics for masked frames, capacity events, and failures; escalate error handling to distinguish recoverable failures from expected pass-through behaviour.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title directly references the roadmapped execplan step (2.6.1) and accurately describes the core change: implementing ACP capability masking at the protocol proxy seam.
Description check ✅ Passed The description comprehensively documents the PR changes including core functionality, tests, documentation updates, and architectural decisions that align with the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 98.39% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Testing (Overall) ✅ Passed The test suite is substantive and rigorous, exercising masking logic against multiple failure modes with byte-exact assertions resistant to vacuous passes.
User-Facing Documentation ✅ Passed The PR clearly documents the new ACP capability masking functionality in the user's guide with appropriate scope, limitations, and future caveats.
Module-Level Documentation ✅ Passed All new and modified modules include comprehensive module-level docstrings establishing clear relationships between components.
Testing (Compile-Time / Ui) ✅ Passed The PR does not involve compile-time code generation, procedural macros, or type-level contracts that would warrant trybuild testing.
Unit Architecture ✅ Passed Unit architecture preserves clear separation between pure frame transforms, async I/O operations, and side-effects. Fallibility is explicit; dependencies inject through parameters without global state.
Security And Privacy ✅ Passed The PR implements capability masking for terminal and fs keys without introducing secrets, unsafe code, or sensitive data exposure. Logging uses appropriate debug levels, deserialisation is safe, and error paths preserve protocol transparency.
Performance And Resource Use ✅ Passed The ACP initialize-frame masking implementation is bounded by design with a 65 KiB per-session buffer cap that is tested and enforced. Code runs once per session using single-pass JSON parsing with clear loop termination conditions.
Concurrency And State ✅ Passed PR implements sound concurrency model for ACP initialisation rewriting with exclusive task ownership and no shared mutable state between output loop and stdin forwarding task.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch implement-acp-masking-jabwf2

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

codescene-delta-analysis[bot]

This comment was marked as outdated.

@sourcery-ai

sourcery-ai Bot commented Apr 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements ACP capability masking in the protocol stdin proxy by rewriting only the first ACP initialize frame to strip terminal/fs capabilities, adds focused unit and BDD tests, and updates design/docs/roadmap to describe and mark this behaviour as complete while performing minor documentation reflow elsewhere.

Sequence diagram for ACP initialize masking in protocol stdin proxy

sequenceDiagram
    actor IDE
    participant PodbotProtocolProxy as PodbotProtocolProxy
    participant HostStdin as HostStdin
    participant ContainerStdin as ContainerStdin

    IDE->>HostStdin: Write ACP initialize frame and subsequent bytes
    HostStdin->>PodbotProtocolProxy: ExecMode::Protocol stdin stream

    activate PodbotProtocolProxy
    PodbotProtocolProxy->>PodbotProtocolProxy: run_input_loop_async()
    PodbotProtocolProxy->>HostStdin: read_until('\n') via forward_initial_protocol_frame_async
    HostStdin-->>PodbotProtocolProxy: First_frame_bytes

    PodbotProtocolProxy->>PodbotProtocolProxy: mask_acp_initialize_frame(frame)
    PodbotProtocolProxy->>PodbotProtocolProxy: split_frame_line_ending(frame)
    PodbotProtocolProxy->>PodbotProtocolProxy: parse_json_message(payload)
    PodbotProtocolProxy->>PodbotProtocolProxy: remove_masked_acp_capabilities(message)
    PodbotProtocolProxy->>PodbotProtocolProxy: serialize_masked_message(message, line_ending)

    PodbotProtocolProxy->>ContainerStdin: write_all(masked_or_original_first_frame)

    PodbotProtocolProxy->>HostStdin: copy remaining bytes via tokio::io::copy
    HostStdin-->>PodbotProtocolProxy: Remaining_stdin_bytes
    PodbotProtocolProxy->>ContainerStdin: Forward_remaining_bytes

    PodbotProtocolProxy->>ContainerStdin: flush()
    PodbotProtocolProxy->>ContainerStdin: shutdown()
    deactivate PodbotProtocolProxy
Loading

Class diagram for ProtocolProxyIo ACP masking helpers

classDiagram
    class ProtocolProxyIo {
        +new(host_stdin, host_stdout, host_stderr)
        +run_input_loop_async(host_stdin, input)
        +run_output_loop_async(container_id, output, host_stdout, host_stderr)
        -STDIN_SETTLE_TIMEOUT: Duration
        -STDIN_BUFFER_CAPACITY: usize
        -ACP_INITIALIZE_METHOD: &str
        -ACP_CLIENT_CAPABILITIES_FIELD: &str
        -ACP_FILE_SYSTEM_CAPABILITY: &str
        -ACP_TERMINAL_CAPABILITY: &str
    }

    class ACPMaskingHelpers {
        +forward_initial_protocol_frame_async(buffered_stdin, input) io::Result
        +mask_acp_initialize_frame(frame) Vec~u8~
        +split_frame_line_ending(frame) (&[u8], &[u8])
        +parse_json_message(payload) Option~Value~
        +remove_masked_acp_capabilities(message) bool
        +serialize_masked_message(message, line_ending) Option~Vec~u8~~
    }

    ProtocolProxyIo ..> ACPMaskingHelpers: uses
Loading

File-Level Changes

Change Details Files
Intercept and rewrite the first ACP initialize frame at the protocol stdin proxy seam to strip terminal and filesystem capabilities while preserving buffering and protocol semantics.
  • Wrap host stdin in an existing bounded BufReader and introduce forward_initial_protocol_frame_async to read and forward the first newline-delimited frame before resuming the raw copy loop.
  • Implement mask_acp_initialize_frame, split_frame_line_ending, parse_json_message, remove_masked_acp_capabilities, and serialize_masked_message helpers to parse JSON, remove clientCapabilities.terminal/fs, optionally drop now-empty clientCapabilities, and reserialize with original line endings.
  • Ensure malformed or non-ACP frames (non-initialize method or missing params/clientCapabilities) are forwarded unchanged, and keep stdout/stderr behaviour and shutdown ordering identical to previous protocol proxy logic.
src/engine/connection/exec/protocol.rs
Add unit and BDD-style tests to validate ACP masking behaviour, including line-ending preservation and trailing bytes, without affecting broader protocol proxy tests.
  • Introduce protocol_acp_tests.rs with rstest-based unit tests for masking logic (happy/edge/error paths), RecordingInputWriter to capture forwarded bytes, and helper constructors for initialize and follow-up frames.
  • Use rstest-bdd scenarios bound to tests/features/acp_capability_masking.feature to describe and verify masking, malformed-frame pass-through, and no-op masking when blocked capabilities are absent.
  • Ensure tests assert that only the first frame is rewritten, trailing protocol bytes are preserved byte-for-byte, and stdin shutdown is still invoked.
src/engine/connection/exec/protocol_acp_tests.rs
tests/features/acp_capability_masking.feature
Document ACP first-frame masking behaviour and mark the corresponding roadmap step complete, while reflowing several existing execution plans and guides for clarity without changing technical intent.
  • Update podbot-design.md to describe first-frame ACP initialize rewriting, pass-through for malformed/non-ACP frames, and keep bounded-buffering documentation aligned with current constants and behaviour.
  • Extend users-guide.md to explain ACP capability masking in protocol mode and credential validation error messages, and refine descriptions of protocol buffering and testing patterns.
  • Add a new ExecPlan 2-6-1-intercept-acp-initialization.md capturing purpose, risks, implementation stages, and validation for ACP masking, and mark only the Step 2.6 initialization checkbox done in podbot-roadmap.md.
  • Reflow and clarify existing execplans and guides (5-3-1 library boundaries, 3-1-4 GitHub credential errors, 2-5-3 bounded buffering, developers-guide) to improve wording and table formatting without changing behaviour or APIs.
docs/podbot-design.md
docs/users-guide.md
docs/podbot-roadmap.md
docs/execplans/2-6-1-intercept-acp-initialization.md
docs/execplans/5-3-1-stabilize-public-library-boundaries.md
docs/execplans/3-1-4-handle-invalid-or-expired-app-credentials-with-clear-errors.md
docs/execplans/2-5-3-keep-proxy-buffering-bounded.md
docs/developers-guide.md

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

@leynos
leynos marked this pull request as ready for review April 22, 2026 22:18

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/engine/connection/exec/protocol_acp_tests.rs" line_range="65-74" />
<code_context>
+    }
+}
+
+fn initialize_frame(line_ending: &str) -> Vec<u8> {
+    let payload = serde_json::json!({
+        "jsonrpc": "2.0",
+        "id": 0,
+        "method": "initialize",
+        "params": {
+            "protocolVersion": 1,
+            "clientCapabilities": {
+                "fs": {
+                    "readTextFile": true,
+                    "writeTextFile": true
+                },
+                "terminal": true,
+                "_meta": {
+                    "custom": true
+                }
+            },
+            "clientInfo": {
+                "name": "podbot-tests",
+                "version": "1.0.0"
+            }
+        }
+    });
+
+    let mut frame = serde_json::to_vec(&payload).expect("initialize payload should serialize");
+    frame.extend_from_slice(line_ending.as_bytes());
+    frame
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for initialize frames where `clientCapabilities` only contains blocked capabilities so the field gets removed

Currently all tests keep a non-blocked `_meta` entry, so `clientCapabilities` never becomes empty and the branch that removes the entire field is untested. Please add a unit test that builds an `initialize` frame whose `clientCapabilities` only includes `fs` and/or `terminal`, and then assert that after masking: (1) `clientCapabilities` is removed, and (2) other `params` fields such as `clientInfo` and `protocolVersion` are unchanged. This will cover the `client_capabilities.is_empty()` path in `remove_masked_acp_capabilities`.

Suggested implementation:

```rust
use rstest::rstest;
use rstest_bdd::Slot;
use rstest_bdd_macros::{ScenarioState, given, scenario, then, when};
use tokio::io::{AsyncWrite, AsyncWriteExt, DuplexStream};

#[test]
fn remove_masked_acp_capabilities_removes_empty_client_capabilities() {
    // Build an initialize payload whose `clientCapabilities` only contains
    // capabilities that should be fully masked (`fs` and `terminal`).
    let mut payload = serde_json::json!({
        "jsonrpc": "2.0",
        "id": 0,
        "method": "initialize",
        "params": {
            "protocolVersion": 1,
            "clientCapabilities": {
                "fs": {
                    "readTextFile": true,
                    "writeTextFile": true
                },
                "terminal": true
            },
            "clientInfo": {
                "name": "podbot-tests",
                "version": "1.0.0"
            }
        }
    });

    // Exercise the masking logic.
    remove_masked_acp_capabilities(&mut payload);

    // `clientCapabilities` should be completely removed once all entries are masked.
    assert!(
        payload["params"].get("clientCapabilities").is_none(),
        "`clientCapabilities` should be removed when all capabilities are masked"
    );

    // Other `params` fields must remain unchanged.
    assert_eq!(
        payload["params"]["protocolVersion"],
        serde_json::json!(1),
        "`protocolVersion` should remain unchanged after masking"
    );

    assert_eq!(
        payload["params"]["clientInfo"],
        serde_json::json!({
            "name": "podbot-tests",
            "version": "1.0.0"
        }),
        "`clientInfo` should remain unchanged after masking"
    );
}

```

1. Ensure that `remove_masked_acp_capabilities` is in scope for this test. If it lives in the same module as other ACP tests, you may need a `use super::remove_masked_acp_capabilities;` near the top of the file.
2. If the real API operates on frames/bytes instead of a `serde_json::Value`, adapt the test body to:
   * Serialize `payload` to a frame (including the correct line ending),
   * Pass that frame through the masking function under test,
   * Deserialize back to `serde_json::Value` before asserting on `params`.
3. If `remove_masked_acp_capabilities` also strips the `protocolVersion` or modifies `clientInfo` in your implementation, adjust the expectations accordingly so they match the intended behavior described in your comment.
</issue_to_address>

### Comment 2
<location path="src/engine/connection/exec/protocol.rs" line_range="209" />
<code_context>
+        .await
+}
+
+fn mask_acp_initialize_frame(frame: &[u8]) -> Vec<u8> {
+    let (payload, line_ending) = split_frame_line_ending(frame);
+    let Some(mut message) = parse_json_message(payload) else {
</code_context>
<issue_to_address>
**issue (complexity):** Consider inlining the serde parsing/serialization calls into `mask_acp_initialize_frame` and removing the extra helper functions to keep the masking logic in one readable place.

You can simplify `mask_acp_initialize_frame` by inlining the thin serde wrappers and reducing the number of tiny helpers. This keeps the masking behavior identical but makes the control flow easier to follow.

For example:

```rust
fn mask_acp_initialize_frame(frame: &[u8]) -> Vec<u8> {
    let (payload, line_ending) = split_frame_line_ending(frame);

    let mut message: Value = match serde_json::from_slice(payload) {
        Ok(v) => v,
        Err(_) => return frame.to_vec(),
    };

    if !remove_masked_acp_capabilities(&mut message) {
        return frame.to_vec();
    }

    let mut serialized = match serde_json::to_vec(&message) {
        Ok(v) => v,
        Err(_) => return frame.to_vec(),
    };
    serialized.extend_from_slice(line_ending);
    serialized
}
```

With this, you can drop `parse_json_message` and `serialize_masked_message` entirely, and keep `remove_masked_acp_capabilities` as the one domain-specific helper:

```rust
fn remove_masked_acp_capabilities(message: &mut Value) -> bool {
    if message.get("method").and_then(Value::as_str) != Some(ACP_INITIALIZE_METHOD) {
        return false;
    }

    let Some(params) = message.get_mut("params").and_then(Value::as_object_mut) else {
        return false;
    };

    let Some(client_capabilities) = params
        .get_mut(ACP_CLIENT_CAPABILITIES_FIELD)
        .and_then(Value::as_object_mut)
    else {
        return false;
    };

    let removed_terminal = client_capabilities.remove(ACP_TERMINAL_CAPABILITY).is_some();
    let removed_fs = client_capabilities.remove(ACP_FILE_SYSTEM_CAPABILITY).is_some();
    if !removed_terminal && !removed_fs {
        return false;
    }

    if client_capabilities.is_empty() {
        params.remove(ACP_CLIENT_CAPABILITIES_FIELD);
    }

    true
}
```

This keeps all fallback behavior (`return frame.to_vec()` on any parse/serialize failure or no-op case) but removes indirection and makes the overall transformation readable in one place.
</issue_to_address>

### Comment 3
<location path="docs/execplans/2-6-1-intercept-acp-initialization.md" line_range="1" />
<code_context>
+# Step 2.6.1: Intercept ACP initialization
+
+This ExecPlan (execution plan) is a living document. The sections
</code_context>
<issue_to_address>
**issue (review_instructions):** ACP is introduced in this file without being expanded on first use, which violates the acronym-definition requirement.

Please expand ACP on first use in this ExecPlan heading or immediately afterwards, for example: `Intercept Agentic Control Protocol (ACP) initialization`, and then use `ACP` thereafter.

<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/engine/connection/exec/protocol_acp_tests.rs Outdated
Comment thread src/engine/connection/exec/protocol.rs Outdated
Comment thread docs/execplans/2-6-1-intercept-acp-initialization.md Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1f6093609

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/engine/connection/exec/protocol.rs Outdated
@coderabbitai coderabbitai Bot added the Roadmap label Apr 22, 2026
coderabbitai[bot]

This comment was marked as resolved.

@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from b1f6093 to 91f9f27 Compare April 30, 2026 13:24
codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from 91f9f27 to 1d1f746 Compare April 30, 2026 13:27
codescene-delta-analysis[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from 1d1f746 to 3ba5558 Compare April 30, 2026 15:55
codescene-delta-analysis[bot]

This comment was marked as outdated.

Intercept the first newline-delimited Agentic Control Protocol (ACP)
`initialize` frame from protocol stdin and remove host-delegated `terminal`
and `fs` capabilities before the container sees the metadata. Bound the
first-frame inspection buffer so oversized or unterminated input streams
forward promptly into the container instead of waiting indefinitely.

Document the ACP masking trust boundary, add unit and BDD coverage, and mark
roadmap Step 2.6.1 as complete.
@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from 3ba5558 to aa90773 Compare April 30, 2026 16:02
codescene-delta-analysis[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
docs/users-guide.md (1)

66-69: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify the ACP scope while podbot host is still unavailable.

State explicitly that this masking applies only to the current
protocol/library path until podbot host ships; otherwise this paragraph reads
as if the public subcommand already exists.

Triage: [type:docstyle]

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/users-guide.md` around lines 66 - 69, Update the paragraph to explicitly
state that the masking of host-delegated `terminal/*` and `fs/*` capabilities
applies only to the current protocol/library path used by hosted mode and ACP
(i.e., the behavior of the existing `initialize` forwarding to the sandboxed
agent) until the `podbot host` subcommand is implemented; mention `podbot host`
by name as a forthcoming feature and clarify that once `podbot host` ships the
scope/behavior may change. Reference the existing terms `podbot host`, ACP
`initialize`, `terminal/*`, `fs/*`, and "sandboxed agent" so readers can locate
and understand the exact codepath and intended temporary scope.
src/engine/connection/exec/protocol.rs (1)

238-249: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Gate the first-frame interception to ACP sessions only.

This helper still runs on the shared protocol path, so non-ACP transports now
get a newline-delimited pre-read they did not ask for. Any protocol that does
not start with a line break can block here before normal byte copying begins.
Thread the protocol kind through this call site and bypass the rewrite unless
the session is ACP.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol.rs` around lines 238 - 249, The helper
forward_host_stdin_to_exec_async is unconditionally pre-reading the first frame
via forward_initial_protocol_frame_async and must instead only do that for ACP
sessions: add a parameter (e.g., protocol_kind or is_acp: bool) to
forward_host_stdin_to_exec_async, propagate this change to its callers, and
conditionally call forward_initial_protocol_frame_async(&mut buffered_stdin,
&mut input).await? only when the session is ACP; for non-ACP sessions skip the
initial-frame rewrite and go straight to tokio::io::copy, then flush/shutdown as
before. Ensure the function signature and all call sites (places that invoke
forward_host_stdin_to_exec_async) are updated to pass the protocol indicator.
docs/execplans/2-6-1-intercept-acp-initialization.md (1)

257-259: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use Oxford spelling for containerized.

Replace containerised with containerized so the plan follows the
en-GB-oxendict rule.

Triage: [type:spelling] [type:docstyle]

✏️ Proposed edit
-  containerised agent;
+  containerized agent;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/execplans/2-6-1-intercept-acp-initialization.md` around lines 257 - 259,
Change the British spelling "containerised" to American/Oxford "containerized"
in the sentence that reads "ACP `initialize` capability metadata is masked
before reaching the containerised agent"; specifically replace the token
"containerised" with "containerized" so the doc line containing "ACP
`initialize` capability metadata is masked before reaching the containerised
agent" is updated accordingly.
src/engine/connection/exec/protocol_acp_tests.rs (1)

1-459: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this module before it exceeds the repository limit.

Move the BDD state, helper writers, and scenario bindings into a sibling file
so this test module drops back under the 400-line guidance. While you are
there, swap the trailing-bytes regression to a second ACP initialize frame
so it actually exercises the first-frame-only contract.

🧪 Tighten the regression
-    let trailing = session_new_bytes();
+    let trailing = initialize_frame("\n");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol_acp_tests.rs` around lines 1 - 459, This
file exceeds the size guideline; split out the BDD state, helper writer, and
scenario bindings into a sibling module: move AcpMaskingState,
RecordingInputWriter, build_host_stdin/run_forwarding helpers, the rstest
fixture acp_masking_state, and the scenario functions
(acp_initialize_masks_blocked_capabilities,
malformed_acp_initialize_is_forwarded_unchanged,
acp_initialize_without_blocked_capabilities_stays_unchanged) into a new file and
re-export or import them so tests still compile; additionally tighten the
trailing-bytes regression by changing
forwarding_masks_initialize_and_preserves_trailing_bytes and
masked_initialize_with_follow_up to append a second initialize frame (not
session_new_bytes) as the trailing bytes to ensure the test exercises the
first-frame-only behavior (update masked_initialize_with_follow_up
expected/host_stdin construction accordingly and keep mask_acp_initialize_frame
usage unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/execplans/2-6-1-intercept-acp-initialization.md`:
- Around line 257-259: Change the British spelling "containerised" to
American/Oxford "containerized" in the sentence that reads "ACP `initialize`
capability metadata is masked before reaching the containerised agent";
specifically replace the token "containerised" with "containerized" so the doc
line containing "ACP `initialize` capability metadata is masked before reaching
the containerised agent" is updated accordingly.

In `@docs/users-guide.md`:
- Around line 66-69: Update the paragraph to explicitly state that the masking
of host-delegated `terminal/*` and `fs/*` capabilities applies only to the
current protocol/library path used by hosted mode and ACP (i.e., the behavior of
the existing `initialize` forwarding to the sandboxed agent) until the `podbot
host` subcommand is implemented; mention `podbot host` by name as a forthcoming
feature and clarify that once `podbot host` ships the scope/behavior may change.
Reference the existing terms `podbot host`, ACP `initialize`, `terminal/*`,
`fs/*`, and "sandboxed agent" so readers can locate and understand the exact
codepath and intended temporary scope.

In `@src/engine/connection/exec/protocol_acp_tests.rs`:
- Around line 1-459: This file exceeds the size guideline; split out the BDD
state, helper writer, and scenario bindings into a sibling module: move
AcpMaskingState, RecordingInputWriter, build_host_stdin/run_forwarding helpers,
the rstest fixture acp_masking_state, and the scenario functions
(acp_initialize_masks_blocked_capabilities,
malformed_acp_initialize_is_forwarded_unchanged,
acp_initialize_without_blocked_capabilities_stays_unchanged) into a new file and
re-export or import them so tests still compile; additionally tighten the
trailing-bytes regression by changing
forwarding_masks_initialize_and_preserves_trailing_bytes and
masked_initialize_with_follow_up to append a second initialize frame (not
session_new_bytes) as the trailing bytes to ensure the test exercises the
first-frame-only behavior (update masked_initialize_with_follow_up
expected/host_stdin construction accordingly and keep mask_acp_initialize_frame
usage unchanged).

In `@src/engine/connection/exec/protocol.rs`:
- Around line 238-249: The helper forward_host_stdin_to_exec_async is
unconditionally pre-reading the first frame via
forward_initial_protocol_frame_async and must instead only do that for ACP
sessions: add a parameter (e.g., protocol_kind or is_acp: bool) to
forward_host_stdin_to_exec_async, propagate this change to its callers, and
conditionally call forward_initial_protocol_frame_async(&mut buffered_stdin,
&mut input).await? only when the session is ACP; for non-ACP sessions skip the
initial-frame rewrite and go straight to tokio::io::copy, then flush/shutdown as
before. Ensure the function signature and all call sites (places that invoke
forward_host_stdin_to_exec_async) are updated to pass the protocol indicator.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b1fe6f42-f48f-4aef-b3e0-20593d97887a

📥 Commits

Reviewing files that changed from the base of the PR and between 3ba5558 and aa90773.

📒 Files selected for processing (7)
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • docs/podbot-design.md
  • docs/podbot-roadmap.md
  • docs/users-guide.md
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • tests/features/acp_capability_masking.feature

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/engine/connection/exec/protocol_acp_tests.rs (1)

1-484: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Split this test module to stay within the 400-line limit.

The file is 484 lines, exceeding the repository's 400-line budget. Extract the BDD scenario infrastructure (lines 364–483) into a sibling module such as protocol_acp_bdd_tests.rs, keeping the unit tests and forwarding tests in this file.

Proposed split

Create src/engine/connection/exec/protocol_acp_bdd_tests.rs:

//! BDD scenarios for ACP capability masking.

use super::protocol_acp_tests::{
    AcpMaskingState, initialize_frame, initialize_without_blocked_capabilities,
    malformed_initialize_bytes, masked_initialize_with_follow_up, run_forwarding,
    session_new_bytes,
};
// ... move lines 364-483 here

Update protocol_acp_tests.rs to expose necessary items as pub(super) and add:

#[cfg(test)]
#[path = "protocol_acp_bdd_tests.rs"]
mod bdd_tests;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol_acp_tests.rs` around lines 1 - 484, The
test module exceeds the 400-line limit; split the BDD scenario infrastructure
into a sibling file and export the helper symbols so the moved scenarios can
reference them: create src/engine/connection/exec/protocol_acp_bdd_tests.rs and
move the rstest_bdd ScenarioState, given/when/then functions and the three
#[scenario] functions (the BDD wiring that currently starts around
masked_initialize_with_follow_up usage through the three scenario functions),
leaving the unit tests and forwarding tests in the original file; make the
helpers and types used by the moved file pub(super) — e.g. AcpMaskingState,
initialize_frame, initialize_without_blocked_capabilities,
malformed_initialize_bytes, masked_initialize_with_follow_up, run_forwarding,
session_new_bytes, and any fixtures — so the new module can import them via
super::protocol_acp_tests, and add in the original file a test-only include:
#[cfg(test)] #[path = "protocol_acp_bdd_tests.rs"] mod bdd_tests; to wire the
moved scenarios back into the test suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developers-guide.md`:
- Around line 360-367: Replace the British-spelled word "initialisation" with
Oxford spelling "initialization" in the section titled "ACP initialisation
rewrite contract" (update both occurrences in the header and the body where the
phrase appears) so the document uses "initialization" consistently; ensure no
other occurrences of "initialisation" remain in that paragraph and preserve
surrounding punctuation and capitalization.

---

Outside diff comments:
In `@src/engine/connection/exec/protocol_acp_tests.rs`:
- Around line 1-484: The test module exceeds the 400-line limit; split the BDD
scenario infrastructure into a sibling file and export the helper symbols so the
moved scenarios can reference them: create
src/engine/connection/exec/protocol_acp_bdd_tests.rs and move the rstest_bdd
ScenarioState, given/when/then functions and the three #[scenario] functions
(the BDD wiring that currently starts around masked_initialize_with_follow_up
usage through the three scenario functions), leaving the unit tests and
forwarding tests in the original file; make the helpers and types used by the
moved file pub(super) — e.g. AcpMaskingState, initialize_frame,
initialize_without_blocked_capabilities, malformed_initialize_bytes,
masked_initialize_with_follow_up, run_forwarding, session_new_bytes, and any
fixtures — so the new module can import them via super::protocol_acp_tests, and
add in the original file a test-only include: #[cfg(test)] #[path =
"protocol_acp_bdd_tests.rs"] mod bdd_tests; to wire the moved scenarios back
into the test suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 81442c05-1e4f-41a5-ba9f-4321365dde85

📥 Commits

Reviewing files that changed from the base of the PR and between aa90773 and c9ff729.

📒 Files selected for processing (6)
  • docs/developers-guide.md
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • src/engine/connection/exec/acp_helpers.rs
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • src/engine/connection/exec/session.rs

Comment thread docs/developers-guide.md Outdated
@leynos

leynos commented Apr 30, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity.

Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/engine/connection/exec/protocol_acp_tests.rs" line_range="65-74" />
<code_context>
+    }
+}
+
+fn initialize_frame(line_ending: &str) -> Vec<u8> {
+    let payload = serde_json::json!({
+        "jsonrpc": "2.0",
+        "id": 0,
+        "method": "initialize",
+        "params": {
+            "protocolVersion": 1,
+            "clientCapabilities": {
+                "fs": {
+                    "readTextFile": true,
+                    "writeTextFile": true
+                },
+                "terminal": true,
+                "_meta": {
+                    "custom": true
+                }
+            },
+            "clientInfo": {
+                "name": "podbot-tests",
+                "version": "1.0.0"
+            }
+        }
+    });
+
+    let mut frame = serde_json::to_vec(&payload).expect("initialize payload should serialize");
+    frame.extend_from_slice(line_ending.as_bytes());
+    frame
+}
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for initialize frames where `clientCapabilities` only contains blocked capabilities so the field gets removed

Currently all tests keep a non-blocked `_meta` entry, so `clientCapabilities` never becomes empty and the branch that removes the entire field is untested. Please add a unit test that builds an `initialize` frame whose `clientCapabilities` only includes `fs` and/or `terminal`, and then assert that after masking: (1) `clientCapabilities` is removed, and (2) other `params` fields such as `clientInfo` and `protocolVersion` are unchanged. This will cover the `client_capabilities.is_empty()` path in `remove_masked_acp_capabilities`.

Suggested implementation:

```rust
use rstest::rstest;
use rstest_bdd::Slot;
use rstest_bdd_macros::{ScenarioState, given, scenario, then, when};
use tokio::io::{AsyncWrite, AsyncWriteExt, DuplexStream};

#[test]
fn remove_masked_acp_capabilities_removes_empty_client_capabilities() {
    // Build an initialize payload whose `clientCapabilities` only contains
    // capabilities that should be fully masked (`fs` and `terminal`).
    let mut payload = serde_json::json!({
        "jsonrpc": "2.0",
        "id": 0,
        "method": "initialize",
        "params": {
            "protocolVersion": 1,
            "clientCapabilities": {
                "fs": {
                    "readTextFile": true,
                    "writeTextFile": true
                },
                "terminal": true
            },
            "clientInfo": {
                "name": "podbot-tests",
                "version": "1.0.0"
            }
        }
    });

    // Exercise the masking logic.
    remove_masked_acp_capabilities(&mut payload);

    // `clientCapabilities` should be completely removed once all entries are masked.
    assert!(
        payload["params"].get("clientCapabilities").is_none(),
        "`clientCapabilities` should be removed when all capabilities are masked"
    );

    // Other `params` fields must remain unchanged.
    assert_eq!(
        payload["params"]["protocolVersion"],
        serde_json::json!(1),
        "`protocolVersion` should remain unchanged after masking"
    );

    assert_eq!(
        payload["params"]["clientInfo"],
        serde_json::json!({
            "name": "podbot-tests",
            "version": "1.0.0"
        }),
        "`clientInfo` should remain unchanged after masking"
    );
}

```

1. Ensure that `remove_masked_acp_capabilities` is in scope for this test. If it lives in the same module as other ACP tests, you may need a `use super::remove_masked_acp_capabilities;` near the top of the file.
2. If the real API operates on frames/bytes instead of a `serde_json::Value`, adapt the test body to:
   * Serialize `payload` to a frame (including the correct line ending),
   * Pass that frame through the masking function under test,
   * Deserialize back to `serde_json::Value` before asserting on `params`.
3. If `remove_masked_acp_capabilities` also strips the `protocolVersion` or modifies `clientInfo` in your implementation, adjust the expectations accordingly so they match the intended behavior described in your comment.
</issue_to_address>

### Comment 2
<location path="src/engine/connection/exec/protocol.rs" line_range="209" />
<code_context>
+        .await
+}
+
+fn mask_acp_initialize_frame(frame: &[u8]) -> Vec<u8> {
+    let (payload, line_ending) = split_frame_line_ending(frame);
+    let Some(mut message) = parse_json_message(payload) else {
</code_context>
<issue_to_address>
**issue (complexity):** Consider inlining the serde parsing/serialization calls into `mask_acp_initialize_frame` and removing the extra helper functions to keep the masking logic in one readable place.

You can simplify `mask_acp_initialize_frame` by inlining the thin serde wrappers and reducing the number of tiny helpers. This keeps the masking behavior identical but makes the control flow easier to follow.

For example:

```rust
fn mask_acp_initialize_frame(frame: &[u8]) -> Vec<u8> {
    let (payload, line_ending) = split_frame_line_ending(frame);

    let mut message: Value = match serde_json::from_slice(payload) {
        Ok(v) => v,
        Err(_) => return frame.to_vec(),
    };

    if !remove_masked_acp_capabilities(&mut message) {
        return frame.to_vec();
    }

    let mut serialized = match serde_json::to_vec(&message) {
        Ok(v) => v,
        Err(_) => return frame.to_vec(),
    };
    serialized.extend_from_slice(line_ending);
    serialized
}
```

With this, you can drop `parse_json_message` and `serialize_masked_message` entirely, and keep `remove_masked_acp_capabilities` as the one domain-specific helper:

```rust
fn remove_masked_acp_capabilities(message: &mut Value) -> bool {
    if message.get("method").and_then(Value::as_str) != Some(ACP_INITIALIZE_METHOD) {
        return false;
    }

    let Some(params) = message.get_mut("params").and_then(Value::as_object_mut) else {
        return false;
    };

    let Some(client_capabilities) = params
        .get_mut(ACP_CLIENT_CAPABILITIES_FIELD)
        .and_then(Value::as_object_mut)
    else {
        return false;
    };

    let removed_terminal = client_capabilities.remove(ACP_TERMINAL_CAPABILITY).is_some();
    let removed_fs = client_capabilities.remove(ACP_FILE_SYSTEM_CAPABILITY).is_some();
    if !removed_terminal && !removed_fs {
        return false;
    }

    if client_capabilities.is_empty() {
        params.remove(ACP_CLIENT_CAPABILITIES_FIELD);
    }

    true
}
```

This keeps all fallback behavior (`return frame.to_vec()` on any parse/serialize failure or no-op case) but removes indirection and makes the overall transformation readable in one place.
</issue_to_address>

### Comment 3
<location path="docs/execplans/2-6-1-intercept-acp-initialization.md" line_range="1" />
<code_context>
+# Step 2.6.1: Intercept ACP initialization
+
+This ExecPlan (execution plan) is a living document. The sections
</code_context>
<issue_to_address>
**issue (review_instructions):** ACP is introduced in this file without being expanded on first use, which violates the acronym-definition requirement.

Please expand ACP on first use in this ExecPlan heading or immediately afterwards, for example: `Intercept Agentic Control Protocol (ACP) initialization`, and then use `ACP` thereafter.

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

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

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

</details>
</issue_to_address>

@coderabbitai

coderabbitai Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

Let me inspect the current state of the relevant files.


🏁 Script executed:

#!/bin/bash
# Check Comment 1: look for a test covering fully-masked clientCapabilities
echo "=== protocol_acp_tests.rs - searching for empty clientCapabilities test ==="
rg -n "clientCapabilities" src/engine/connection/exec/protocol_acp_tests.rs | head -40

echo ""
echo "=== protocol_acp_tests.rs - searching for 'is_empty' or 'removes_empty' ==="
rg -n "is_empty|removes_empty|only_blocked|all_blocked|no_meta" src/engine/connection/exec/protocol_acp_tests.rs

echo ""
echo "=== Comment 2: check if parse_json_message / serialize_masked_message still exist ==="
rg -rn "parse_json_message\|serialize_masked_message" src/engine/connection/exec/

echo ""
echo "=== Comment 2: check acp_helpers.rs for current mask_acp_initialize_frame implementation ==="
cat src/engine/connection/exec/acp_helpers.rs

echo ""
echo "=== Comment 3: check first lines of execplan doc ==="
head -20 docs/execplans/2-6-1-intercept-acp-initialization.md

Length of output: 6040


@leynos: Yes — all three are resolved.

  • Comment 1 ✅ — initialize_with_only_blocked_capabilities (line 118) and mask_acp_initialize_frame_removes_empty_client_capabilities (line 252) have been added; lines 262–263 assert that clientCapabilities is removed entirely when all entries are masked, covering the is_empty() branch.

  • Comment 2 ✅ — parse_json_message and serialize_masked_message no longer exist. mask_acp_initialize_frame now inlines serde_json::from_slice and serde_json::to_vec directly, with remove_masked_acp_capabilities as the sole domain helper — exactly the simplified form suggested. (This reduces indirection rather than increasing cyclomatic complexity, so no annulment applies.)

  • Comment 3 ✅ — The heading now reads Intercept Agentic Control Protocol (ACP) initialization, expanding the acronym on first use.

@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from c9ff729 to a2eb186 Compare April 30, 2026 20:32
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/engine/connection/exec/session.rs`:
- Around line 33-38: The setter with_acp_initialize_rewrite_enabled on
ExecSessionOptions is test-gated while the downstream protocol_session_options()
and ProtocolSessionOptions::with_acp_initialize_rewrite_enabled are public,
causing inconsistency; fix by choosing one of two coherent approaches: either
remove #[cfg(test)] from the
ExecSessionOptions::with_acp_initialize_rewrite_enabled method (and ensure the
rewrite_acp_initialize field is publically usable) so production code can toggle
the flag, or apply #[cfg(test)] consistently by gating the
rewrite_acp_initialize field, the with_acp_initialize_rewrite_enabled setter,
protocol_session_options() conversion logic, and
ProtocolSessionOptions::with_acp_initialize_rewrite_enabled() behind
#[cfg(test)] so the entire feature is test-only.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e7e15b2b-3c3f-4f7b-b840-55c12f8a2267

📥 Commits

Reviewing files that changed from the base of the PR and between c9ff729 and a2eb186.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • docs/users-guide.md
  • src/engine/connection/exec/acp_helpers.rs
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_bdd_tests.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • src/engine/connection/exec/session.rs

Comment thread src/engine/connection/exec/session.rs Outdated
@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from a2eb186 to 22cb62b Compare April 30, 2026 22:31
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/engine/connection/exec/protocol.rs (1)

179-186: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Enable ACP initialization rewriting in non-test builds.

Fix Line 182. The #[cfg(not(test))] branch hard-codes rewrite_acp_initialize
to false, so production never executes first-frame masking and the ACP seam
contract is not enforced outside tests.

🔧 Proposed fix
     #[cfg(test)]
     let rewrite_acp_initialize = options.rewrite_acp_initialize;
     #[cfg(not(test))]
-    let rewrite_acp_initialize = false;
+    let rewrite_acp_initialize = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol.rs` around lines 179 - 186, The code
currently forces rewrite_acp_initialize to false in non-test builds, preventing
ACP first-frame masking; change the assignment so rewrite_acp_initialize is
always initialized from options.rewrite_acp_initialize (i.e., remove the
#[cfg(not(test))] branch and use let rewrite_acp_initialize =
options.rewrite_acp_initialize), ensuring spawn_stdin_forwarding_task and its
call to forward_host_stdin_to_exec_async receive the real setting in production.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/execplans/2-6-1-intercept-acp-initialization.md`:
- Around line 194-197: Update the documentation references so they point to the
extracted ACP helper and BDD binding modules: change mentions of the old masking
helpers to refer to the ACP masking helpers module and the BDD bindings to refer
to protocol_acp_bdd_tests; ensure the ExecPlan text reflects the shipped module
split (i.e., call out the separate ACP helper module and the
protocol_acp_bdd_tests BDD bindings) and apply the same adjustments to the other
occurrences of these module references in the document.

---

Duplicate comments:
In `@src/engine/connection/exec/protocol.rs`:
- Around line 179-186: The code currently forces rewrite_acp_initialize to false
in non-test builds, preventing ACP first-frame masking; change the assignment so
rewrite_acp_initialize is always initialized from options.rewrite_acp_initialize
(i.e., remove the #[cfg(not(test))] branch and use let rewrite_acp_initialize =
options.rewrite_acp_initialize), ensuring spawn_stdin_forwarding_task and its
call to forward_host_stdin_to_exec_async receive the real setting in production.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f4f0d0d9-1ce5-4d5e-b0ad-dec8bbdad893

📥 Commits

Reviewing files that changed from the base of the PR and between a2eb186 and 22cb62b.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • docs/users-guide.md
  • src/engine/connection/exec/acp_helpers.rs
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_bdd_tests.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • src/engine/connection/exec/session.rs

Comment thread docs/execplans/2-6-1-intercept-acp-initialization.md Outdated
Move ACP initialize masking helpers out of the protocol proxy module so the
proxy loop stays focused and under the file-length budget. Keep ACP first-frame
rewriting behind a protocol-session option so generic protocol sessions retain
raw stdin forwarding semantics unless ACP masking is selected.

Split ACP BDD scenario wiring out of the unit-test module, extend coverage for
the first-frame-only contract, document the current hosted-mode ACP masking
scope, and add the helper documentation and tracing needed by the review gates.
@lodyai
lodyai Bot force-pushed the implement-acp-masking-jabwf2 branch from 5faeb10 to 9a6a1bd Compare May 1, 2026 21:49
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/engine/connection/exec/protocol_acp_tests.rs (1)

1-523: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this test module back under the file-length budget.

Move the frame builders, assertion helpers, or forwarding cases into sibling
test modules. src/engine/connection/exec/protocol_acp_tests.rs is 523 lines
long, so it still breaches the repository’s 400-line limit and will keep
attracting unrelated changes into one test blob.

As per coding guidelines, "Files must not exceed 400 lines in length".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol_acp_tests.rs` around lines 1 - 523, This
test module exceeds the 400-line limit; split it by moving reusable helpers and
builders (RecordingInputWriter, initialize_frame,
initialize_without_blocked_capabilities,
initialize_with_only_blocked_capabilities, malformed_initialize_bytes,
session_new_bytes, parse_frame_payload, client_capabilities, params,
assert_masked_client_capabilities, masked_initialize_with_follow_up) into one or
more sibling test modules (e.g. protocol_acp_helpers.rs) and leave only the
forwarding/tests that exercise behavior (tests using run_forwarding,
run_forwarding_with_rewrite, forward_initial_acp_frame_async,
mask_acp_initialize_frame, MAX_FIRST_FRAME_BYTES, STDIN_BUFFER_CAPACITY,
STDIN_SETTLE_TIMEOUT) in protocol_acp_tests.rs; update module declarations
(use/super::acp_helpers or mod entries such as the existing bdd_tests) so tests
reference the moved symbols and ensure public visibility where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/developers-guide.md`:
- Around line 362-368: Update the paragraph describing the ACP initialization
rewrite to match the implementation in
src/engine/connection/exec/acp_helpers.rs: state that when protocol tests wrap
run_session with RecordingWriter handles the rewrite is only triggered if the
first newline-delimited ACP initialize frame contains blocked entries under
params.clientCapabilities (specifically terminal and/or fs); the rewrite must
remove those capability families from params.clientCapabilities and, if that
removal leaves clientCapabilities empty, remove the entire clientCapabilities
object; preserve original line endings and allow malformed or non-ACP frames to
pass through unchanged.

In `@docs/users-guide.md`:
- Around line 67-73: Update the paragraph describing hosted-mode ACP masking to
explicitly state that Podbot inspects only the first newline-delimited ACP
"initialize" frame and that it rewrites/masks `terminal/*` and `fs/*`
capabilities only for that first frame on the hosted ACP path; also document
that malformed or non-ACP first frames are forwarded unchanged, and that this
behavior is specific to the hosted-mode/ACP path (until `podbot host` is
implemented) rather than applying to every ACP initialize request.

---

Outside diff comments:
In `@src/engine/connection/exec/protocol_acp_tests.rs`:
- Around line 1-523: This test module exceeds the 400-line limit; split it by
moving reusable helpers and builders (RecordingInputWriter, initialize_frame,
initialize_without_blocked_capabilities,
initialize_with_only_blocked_capabilities, malformed_initialize_bytes,
session_new_bytes, parse_frame_payload, client_capabilities, params,
assert_masked_client_capabilities, masked_initialize_with_follow_up) into one or
more sibling test modules (e.g. protocol_acp_helpers.rs) and leave only the
forwarding/tests that exercise behavior (tests using run_forwarding,
run_forwarding_with_rewrite, forward_initial_acp_frame_async,
mask_acp_initialize_frame, MAX_FIRST_FRAME_BYTES, STDIN_BUFFER_CAPACITY,
STDIN_SETTLE_TIMEOUT) in protocol_acp_tests.rs; update module declarations
(use/super::acp_helpers or mod entries such as the existing bdd_tests) so tests
reference the moved symbols and ensure public visibility where needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 639a0370-8734-4166-b340-e1d98642f39a

📥 Commits

Reviewing files that changed from the base of the PR and between 4a806e8 and 5faeb10.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • docs/users-guide.md
  • src/engine/connection/exec/acp_helpers.rs
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_bdd_tests.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • src/engine/connection/exec/session.rs

Comment thread docs/developers-guide.md
Comment thread docs/users-guide.md Outdated
@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

Define the initialize-frame skeleton once and pass only the capability payload
into tests that vary ACP client capabilities. Move forwarding-specific ACP tests
into a child module so the main ACP test file stays below the repository line
budget while preserving the same private test helpers.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
src/engine/connection/exec/protocol_acp_tests.rs (1)

1-523: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this test module below the 400-line budget.

Bring this file back under the repository limit. It currently packs fixtures,
frame builders, forwarding harnesses, unit tests, shared BDD helpers, and the
BDD module include into 523 lines, which makes the ACP coverage harder to
maintain than the surrounding exec test layout.

Move the reusable frame builders and forwarding harness into a sibling helper
module, or move the exported BDD fixtures into the BDD file so this unit-test
module stays focused.

As per coding guidelines, "Files must not exceed 400 lines in length".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/protocol_acp_tests.rs` around lines 1 - 523, This
module exceeds the 400-line limit because it mixes reusable fixtures/harnesses
with unit tests; split out the reusable frame builders and forwarding harness
into a sibling helper module (or move the exported BDD fixtures into
protocol_acp_bdd_tests.rs). Specifically, extract RecordingInputWriter,
initialize_frame, initialize_without_blocked_capabilities,
initialize_with_only_blocked_capabilities, session_new_bytes,
malformed_initialize_bytes, parse_frame_payload,
assert_masked_client_capabilities, client_capabilities, params,
build_host_stdin, run_forwarding, run_forwarding_with_rewrite, and
masked_initialize_with_follow_up into a new helper module (e.g.,
protocol_acp_helpers.rs) and update imports so the remaining tests only contain
test functions; alternatively move masked_initialize_with_follow_up and any
pub(super) fixtures into protocol_acp_bdd_tests.rs and remove their definitions
here to bring this file under 400 lines.
♻️ Duplicate comments (1)
docs/developers-guide.md (1)

362-368: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the narrower ACP rewrite trigger and the empty-object elision.

Mirror the shipped behaviour here. The implementation only rewrites when the
first ACP initialize frame carries blocked terminal and/or fs entries, and
it drops params.clientCapabilities entirely when masking leaves it empty. The
current wording still reads as though any initialize with
params.clientCapabilities is rewritten.

Triage: [type:docstyle]

✏️ Suggested edit
-When protocol tests wrap `run_session` with `RecordingWriter` handles, they
-must preserve the Agentic Control Protocol (ACP) initialization rewrite
-contract: inspect only the first newline-delimited frame, rewrite only ACP
-`initialize` requests that expose `params.clientCapabilities`, and remove the
-`terminal` and `fs` capability families before the frame reaches the container.
-Preserve the original line endings, and let malformed or non-ACP frames pass
-through unchanged.
+When protocol tests wrap `run_session` with `RecordingWriter` handles, they
+must preserve the Agentic Control Protocol (ACP) initialization rewrite
+contract: inspect only the first newline-delimited frame, rewrite only ACP
+`initialize` requests whose `params.clientCapabilities` contain blocked
+`terminal` and/or `fs` entries, remove those capability families before the
+frame reaches the container, and drop `clientCapabilities` entirely when
+masking leaves it empty. Preserve the original line endings, and let malformed
+or non-ACP frames pass through unchanged.

As per coding guidelines, "New or changed internal APIs, architectural
boundaries or abstractions, tooling or build requirements must be clearly
documented in the developer's guide (docs/developers-guide.md)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/developers-guide.md` around lines 362 - 368, The documentation wording
is too broad: update the paragraph describing the ACP initialization rewrite to
match shipped behavior by specifying that RecordingWriter-wrapped run_session
should inspect only the first newline-delimited frame and rewrite only ACP
"initialize" requests whose params.clientCapabilities contain blocked "terminal"
and/or "fs" entries; remove those capability families before the frame reaches
the container and if masking leaves params.clientCapabilities (or
clientCapabilities) empty, drop the entire clientCapabilities object; preserve
original line endings and allow malformed or non-ACP frames to pass through
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/users-guide.md`:
- Around line 66-73: The paragraph in docs/users-guide.md that begins "When
hosted mode uses Agentic Control Protocol (ACP)..." disables MD013 and exceeds
the 80-column wrap rule; remove the HTML suppression and reflow the paragraph to
wrap at or under 80 columns so lines like the ones mentioning ACP, `terminal/*`,
`fs/*`, and `podbot host` are split into shorter lines (for example breaking
after "masks", "initialize", "currently", and "implemented") while preserving
wording and punctuation.

In `@src/engine/connection/exec/protocol_acp_tests.rs`:
- Around line 446-473: The test
forwarding_does_not_wait_indefinitely_for_oversized_initial_frame uses the
production STDIN_SETTLE_TIMEOUT as the tokio::time::timeout deadline; introduce
a stable test-specific timeout (e.g., let test_timeout =
std::time::Duration::from_secs(1)) and use that instead of STDIN_SETTLE_TIMEOUT
when calling tokio::time::timeout for forward_initial_acp_frame_async so the CI
assertion budget is decoupled from production shutdown timing.

---

Outside diff comments:
In `@src/engine/connection/exec/protocol_acp_tests.rs`:
- Around line 1-523: This module exceeds the 400-line limit because it mixes
reusable fixtures/harnesses with unit tests; split out the reusable frame
builders and forwarding harness into a sibling helper module (or move the
exported BDD fixtures into protocol_acp_bdd_tests.rs). Specifically, extract
RecordingInputWriter, initialize_frame, initialize_without_blocked_capabilities,
initialize_with_only_blocked_capabilities, session_new_bytes,
malformed_initialize_bytes, parse_frame_payload,
assert_masked_client_capabilities, client_capabilities, params,
build_host_stdin, run_forwarding, run_forwarding_with_rewrite, and
masked_initialize_with_follow_up into a new helper module (e.g.,
protocol_acp_helpers.rs) and update imports so the remaining tests only contain
test functions; alternatively move masked_initialize_with_follow_up and any
pub(super) fixtures into protocol_acp_bdd_tests.rs and remove their definitions
here to bring this file under 400 lines.

---

Duplicate comments:
In `@docs/developers-guide.md`:
- Around line 362-368: The documentation wording is too broad: update the
paragraph describing the ACP initialization rewrite to match shipped behavior by
specifying that RecordingWriter-wrapped run_session should inspect only the
first newline-delimited frame and rewrite only ACP "initialize" requests whose
params.clientCapabilities contain blocked "terminal" and/or "fs" entries; remove
those capability families before the frame reaches the container and if masking
leaves params.clientCapabilities (or clientCapabilities) empty, drop the entire
clientCapabilities object; preserve original line endings and allow malformed or
non-ACP frames to pass through unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: afbc5542-dc3c-4b0f-a56b-6caca6b38129

📥 Commits

Reviewing files that changed from the base of the PR and between 5faeb10 and 9a6a1bd.

📒 Files selected for processing (8)
  • docs/developers-guide.md
  • docs/execplans/2-6-1-intercept-acp-initialization.md
  • docs/users-guide.md
  • src/engine/connection/exec/acp_helpers.rs
  • src/engine/connection/exec/protocol.rs
  • src/engine/connection/exec/protocol_acp_bdd_tests.rs
  • src/engine/connection/exec/protocol_acp_tests.rs
  • src/engine/connection/exec/session.rs

Comment thread docs/users-guide.md Outdated
Comment thread src/engine/connection/exec/protocol_acp_tests.rs Outdated
Reflow the hosted-mode ACP user-guide paragraph without suppressing Markdown
line-length checks. Narrow the developer guide contract to the shipped
first-frame initialize rewrite behaviour, including empty capability object
removal.

Use a test-specific timeout in the oversized ACP frame regression so CI timing
is decoupled from production stdin settle timing.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented May 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following now been resolved?

src/engine/connection/exec/protocol_acp_tests.rs (1)

1-523: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift
Split this test module below the 400-line budget.
Bring this file back under the repository limit. It currently packs fixtures,

frame builders, forwarding harnesses, unit tests, shared BDD helpers, and the

BDD module include into 523 lines, which makes the ACP coverage harder to

maintain than the surrounding exec test layout.
Move the reusable frame builders and forwarding harness into a sibling helper

module, or move the exported BDD fixtures into the BDD file so this unit-test

module stays focused.
As per coding guidelines, "Files must not exceed 400 lines in length".

🤖 Prompt for AI Agents

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== Line count for protocol_acp_tests.rs ==="
wc -l src/engine/connection/exec/protocol_acp_tests.rs

echo ""
echo "=== Line count for sibling test files ==="
wc -l src/engine/connection/exec/protocol_acp_bdd_tests.rs 2>/dev/null || echo "protocol_acp_bdd_tests.rs not found"
wc -l src/engine/connection/exec/protocol_acp_forwarding_tests.rs 2>/dev/null || echo "protocol_acp_forwarding_tests.rs not found"

echo ""
echo "=== Last 30 lines of protocol_acp_tests.rs ==="
tail -30 src/engine/connection/exec/protocol_acp_tests.rs

echo ""
echo "=== Function/test list in protocol_acp_tests.rs ==="
grep -n "^fn \|^pub(super) fn \|^#\[test\]\|^mod \|^#\[path" src/engine/connection/exec/protocol_acp_tests.rs

Length of output: 2629


@leynos: Yes — this is resolved. protocol_acp_tests.rs now stands at 392 lines, inside the 400-line budget. The BDD infrastructure lives in protocol_acp_bdd_tests.rs (110 lines) and the forwarding harness in protocol_acp_forwarding_tests.rs (86 lines), both linked via #[path] directives at the foot of the unit-test file.

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Demote recoverable ACP frame parse and serialization failures to debug logs so
valid pass-through cases do not appear as operational warnings. Document the
per-session stdin forwarding ownership model and cancellation behaviour in the
ACP helper module.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/engine/connection/exec/acp_helpers.rs (1)

5-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize the remaining doc spelling to Oxford -ize.

Replace synchronisation, initialisation, re-serialises, and serialisation with synchronization, initialization, re-serializes, and serialization to match the repository rule.

✍️ Proposed fix
-//! and no synchronisation primitives are required.
+//! and no synchronization primitives are required.
...
-/// The JSON-RPC `method` value that identifies an ACP initialisation request.
+/// The JSON-RPC `method` value that identifies an ACP initialization request.
...
-/// `params.clientCapabilities`, then re-serialises the JSON preserving the
+/// `params.clientCapabilities`, then re-serializes the JSON preserving the
...
-/// serialisation failure, or if no capabilities were removed.
+/// serialization failure, or if no capabilities were removed.

As per coding guidelines, "Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar".

Also applies to: 25-25, 176-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/engine/connection/exec/acp_helpers.rs` around lines 5 - 10, Update the
top-level module doc comment in acp_helpers.rs (the crate-level/comment block
containing the forwarding task description) to use Oxford -ize spellings:
replace "synchronisation" with "synchronization", "initialisation" with
"initialization", "re-serialises" with "re-serializes", and "serialisation" with
"serialization"; apply the same substitutions to the other occurrences noted
(around lines ~25 and ~176-179) so all documentation in this file follows the
en-GB-oxendict (-ize) rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/engine/connection/exec/acp_helpers.rs`:
- Around line 5-10: Update the top-level module doc comment in acp_helpers.rs
(the crate-level/comment block containing the forwarding task description) to
use Oxford -ize spellings: replace "synchronisation" with "synchronization",
"initialisation" with "initialization", "re-serialises" with "re-serializes",
and "serialisation" with "serialization"; apply the same substitutions to the
other occurrences noted (around lines ~25 and ~176-179) so all documentation in
this file follows the en-GB-oxendict (-ize) rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 43deb9d6-e2d9-4376-9f3f-d8d1aaf6b904

📥 Commits

Reviewing files that changed from the base of the PR and between a29633c and b078dc9.

📒 Files selected for processing (1)
  • src/engine/connection/exec/acp_helpers.rs

@leynos

leynos commented May 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai pause

@coderabbitai

coderabbitai Bot commented May 1, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews paused.

Update ACP helper documentation and the related Clippy expectation reason to
use Oxford -ize spellings consistently across initialization, serialization,
and synchronization wording.
@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== acp_helpers.rs full ==="
cat src/engine/connection/exec/acp_helpers.rs

echo ""
echo "=== protocol_acp_bdd_tests.rs ==="
cat src/engine/connection/exec/protocol_acp_bdd_tests.rs 2>/dev/null || echo "FILE NOT FOUND"

echo ""
echo "=== issues 84-88 state ==="
for i in 84 85 86 87 88; do
  gh issue view $i --repo leynos/podbot --json title,state -q "\"#$i (\(.state)): \(.title)\""
done

echo ""
echo "=== run_protocol_session_with_io_async in exec test files ==="
rg -rn "run_protocol_session_with_io_async\|ExecSessionOptions" src/engine/connection/exec/ | head -20

echo ""
echo "=== CapabilityRestrictionPolicy / FrameTransformer in src ==="
rg -rn "CapabilityRestrictionPolicy\|FrameTransformer\|AcpMaskError" src/ || echo "NONE"

echo ""
echo "=== protocol_acp_forwarding_tests.rs ==="
cat src/engine/connection/exec/protocol_acp_forwarding_tests.rs 2>/dev/null || echo "FILE NOT FOUND"

Length of output: 16837


@leynos: Both in-scope items from the previous agent prompt are now applied. Here is the full updated status.


Status of each check

Check Resolved? Evidence
Testing (Unit And Behavioural) ⚠️ Out of scope run_protocol_session_with_io_async is absent from all exec test files; BDD steps still drive run_forwarding() directly → tracked in #84
Domain Architecture ⚠️ Out of scope CapabilityRestrictionPolicy, FrameTransformer, AcpMaskError do not exist; mask_acp_initialize_frame still manipulates serde_json::Value directly → tracked in #86
Observability ⚠️ In-scope portion resolved / ❌ remainder out of scope JSON parse and serialise failure arms now emit tracing::debug! (not warn!). Container ID in log fields, metrics counters, and #[instrument] → tracked in #88
Concurrency And State ⚠️ In-scope portion resolved / ❌ remainder out of scope Module-level //! concurrency model doc is present and documents ownership, cancellation safety, and absence of shared state. Task-abort and IO-failure tests → tracked in #87

No further in-scope work remains for this PR. All residual failures are covered by open issues #84, #86, #87, and #88.

@leynos
leynos merged commit 150ef83 into main May 2, 2026
4 checks passed
@leynos
leynos deleted the implement-acp-masking-jabwf2 branch May 2, 2026 00:04
@leynos

leynos commented May 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Have the following failed checks now been resolved?

If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures.

Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already.

❌ Failed checks (5 warnings)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning PR introduces new internal APIs and architectural changes, but the developers-guide repository layout is significantly outdated and does not document the new acp_helpers.rs module or three ACP-specific test modules. Update the repository layout diagram in docs/developers-guide.md section 3 to include acp_helpers.rs and the three ACP test modules, add field-level doc comments to rewrite_acp_initialize in both structs explaining their purpose.
Testing (Unit And Behavioural) ⚠️ Warning Unit tests validate mask_acp_initialize_frame comprehensively, but behavioural tests exercise test doubles instead of session-level protocol rewriting behaviour and integration. Add integration test creating ExecSessionOptions with rewriting enabled, providing stdin with blocked ACP frame, and verifying masked output at session boundary in both enabled and disabled states.
Testing (Property / Proof) ⚠️ Warning The PR lacks property-based testing for mathematical invariants: bounded buffering, line-ending preservation, and selective capability removal across arbitrary inputs. Introduce proptest for arbitrary byte sequences with random newlines, diverse JSON structures, and all line-ending variants; apply Kani bounded model checker to verify saturating subtraction arithmetic.
Domain Architecture ⚠️ Warning Domain logic for sandbox capability restrictions leaked into protocol adapter; business policy hardcoded as string constants in JSON manipulation code. Extract a domain type representing SandboxCapabilityPolicy that explicitly lists denied capabilities; pass policy as parameter to ACP adapter rather than hardcoding.
Observability ⚠️ Warning ACP masking implementation lacks essential observability: missing container_id context in logs, no metrics for operational visibility, no tracing spans for async task tracking, and no proper error categorisation for production diagnosis. Thread container_id through to acp_helpers with stable contextual field; add #[instrument(skip(...))] for span creation; introduce metrics for masked frames, capacity events, and failures; escalate error handling to distinguish recoverable failures from expected pass-through behaviour.

@coderabbitai

This comment was marked as resolved.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant