Skip to content

Add cargo audit to CI#200

Draft
leynos wants to merge 26 commits into
mainfrom
feat/cargoauditmakefileci
Draft

Add cargo audit to CI#200
leynos wants to merge 26 commits into
mainfrom
feat/cargoauditmakefileci

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented May 24, 2026

Summary

This branch adds a Rust dependency-audit gate so CI can fail on cargo-audit
violations before vulnerable dependency paths are merged. It introduces a
Corbusier-style manifest walk in Makefile, installs cargo-audit in test.yml, and upgrades the dependency graph in Cargo.toml and Cargo.lock to clear the failing audit paths.

No roadmap task, issue, or execplan is associated with this branch.

Review walkthrough

Validation

  • make audit: passed. The target still reports allowed warnings for upstream paths that cargo-audit classifies as warnings, while the Rustls/WebPKI advisory ignores are centralised in Makefile.
  • cargo nextest run --workspace --features test-helpers --profile default tools::wasm::runtime::tests::test_enable_compilation_cache_with_explicit_dir tools::wasm::runtime::tests::test_enable_compilation_cache_label_isolation: passed.
  • make all: passed, including format checks, all Clippy variants, 4,068 workspace tests, and the standalone GitHub tool tests.
  • git diff --check: passed.

Notes

  • The remaining ignored cargo-audit advisories are limited to libSQL's current hyper-rustls / Rustls stack and are kept in one Makefile variable so they can be removed when libSQL updates that dependency path.
  • fuzz/Cargo.toml now declares its own empty workspace so cargo audit can process the fuzz manifest during the repository-wide manifest walk.

Summary by Sourcery

Introduce automated Rust dependency auditing in CI and update runtime dependencies to align with the new tooling requirements.

New Features:

  • Add a repository-wide Rust dependency audit Makefile target and supporting configuration.
  • Add a dedicated GitHub Actions job to run cargo-audit and gate rollup on dependency audit success.

Enhancements:

  • Upgrade libsql, wasmtime/wasmtime-wasi, bollard, AWS Bedrock SDK, and testcontainers-modules dependencies to newer versions and adjust code to their updated APIs.
  • Update WASM tooling and channel wrappers to the newer wasmtime component model and WASI APIs, including cache configuration and resource limits.
  • Refine Docker sandbox and orchestrator integrations to use the updated bollard query parameter and model types.
  • Adjust WASI view implementations and WIT compatibility tests for the new wasmtime-wasi interfaces and linker helpers.
  • Ensure fuzzing crate participates correctly in manifest-based tooling by declaring its own empty workspace.

Build:

  • Introduce centralized cargo-audit advisory ignores and a manifest-walking audit command to support dependency checks across all Rust crates in the repo.

CI:

  • Add a CI dependency-audit job that installs cargo-audit, runs the new Makefile audit target, and wires its result into the existing rollup gating.

Tests:

  • Update and extend Docker-based e2e tests and WIT compatibility tests to work with the upgraded wasmtime and bollard APIs.

Add a Corbusier-style `make audit` target that walks Rust manifests
and runs `cargo audit` for each one. Install `cargo-audit` in CI and
include the audit job in the test roll-up.

Upgrade the vulnerable dependency paths found by the new target,
including Wasmtime, libSQL, Bollard, testcontainers, and AWS Bedrock
SDK feature selection. Keep the remaining libSQL Rustls/WebPKI
advisory ignores centralised until upstream moves off the older TLS
stack.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b887a099-9a88-459c-ac53-0ed84f908d0f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cargoauditmakefileci

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

@github-actions github-actions Bot added scope: tool/wasm WASM tool sandbox scope: orchestrator Container orchestrator scope: ci CI/CD workflows scope: dependencies Dependency updates scope: channel/wasm WASM channel runtime scope: sandbox Docker sandbox size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels May 24, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 24, 2026

Reviewer's Guide

Adds a Rust dependency audit gate to CI using cargo-audit, centralizes audit ignores, and upgrades key Rust dependencies (wasmtime, bollard, libsql, AWS SDK, testcontainers) along with the necessary code adaptations in WASM tooling and Docker orchestration to satisfy the new audit and dependency APIs.

Flow diagram for new cargo-audit CI gate

flowchart TD
    A[GitHub_Actions_test_workflow] --> B[Install_cargo-audit]
    B --> C[Run_make_audit]
    C --> D[rust-audit_Makefile_target]
    D --> E[find_Cargo.toml_manifests]
    E --> F[Per_manifest: cd_manifest_dir]
    F --> G[Run_CARGO_AUDIT_with_AUDIT_FLAGS]
    G --> H[Fail_or_pass_CI_based_on_cargo-audit_result]
Loading

File-Level Changes

Change Details Files
Introduce repository-wide cargo-audit support and CI gate.
  • Add configurable cargo-audit command and centralized advisory ignores in Makefile, including a manifest-walking audit target
  • Define a new GitHub Actions audit job that installs cargo-audit, runs make audit, and wires the job into the rollup gate
Makefile
.github/workflows/test.yml
Upgrade and adapt WASM runtime and wrappers to wasmtime/wasmtime-wasi 44.
  • Bump wasmtime and wasmtime-wasi versions and enable cache feature in Cargo.toml
  • Switch WasiView implementations to return WasiCtxView instead of separate ctx/table references
  • Update add_to_linker calls to use wasmtime_wasi::p2 and new HasSelf-aware signatures
  • Adjust host stub function signatures and cache configuration API usages to match wasmtime 44 behavior and error types
Cargo.toml
src/tools/wasm/runtime.rs
src/tools/wasm/wrapper.rs
src/tools/wasm/wrapper/metadata.rs
src/channels/wasm/wrapper.rs
tests/tools_and_config/wit_compat.rs
src/tools/wasm/limits.rs
Upgrade bollard and related Docker orchestration code to the newer API.
  • Change container config types to ContainerCreateBody and updated CreateContainerOptions, LogsOptions, and WaitContainerOptions shapes
  • Adapt start/stop/remove/pull/list Docker calls to new query_parameters option types and optional fields
  • Adjust label maps and filter structures to use owned Strings and Option-wrapped fields
  • Update reaper e2e tests to use new types and explicit generic parameters for inspect/remove calls
Cargo.toml
src/orchestrator/job_manager/docker_helpers.rs
src/orchestrator/job_manager/docker.rs
src/orchestrator/reaper.rs
src/orchestrator/reaper/tests/e2e.rs
src/sandbox/container.rs
src/sandbox/container_docker.rs
Refresh other dependencies to clear audit failures and support new stacks.
  • Upgrade libsql to 0.9 and testcontainers-modules to 0.15 in Cargo.toml
  • Constrain aws-sdk-bedrockruntime default features to use the Tokio runtime and default HTTPS client while avoiding older TLS paths
  • Declare a standalone workspace in fuzz/Cargo.toml so cargo-audit can process it during manifest walks
  • Regenerate Cargo.lock to align with updated dependency graph
Cargo.toml
fuzz/Cargo.toml
Cargo.lock

Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 24, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

src/channels/wasm/wrapper.rs

Comment on file

use wasmtime::Store;
use wasmtime::component::Linker;
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
use wasmtime::component::{HasSelf, Linker};

❌ New issue: Low Cohesion
This module has at least 3 different responsibilities amongst its 68 functions, threshold = 3

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 24, 2026

@coderabbitai Please suggest a fix for the the underlying issues in the codebase resulting in the following audit violations and provide an AI coding agent prompt for the fix:

Run make audit
  make audit
  shell: /usr/bin/bash -e {0}
  env:
    CARGO_HOME: /home/runner/.cargo
    CARGO_INCREMENTAL: 0
    CARGO_TERM_COLOR: always
find . \
	\( -path '*/target/*' -o -path '*/node_modules/*' -o -path '*/.venv/*' \) -prune -o \
	-name Cargo.toml -exec sh -c 'set -e; for manifest do \
		manifest_dir=$(dirname "$manifest"); \
		printf "Auditing Rust manifest %s\n" "$manifest"; \
		(cd "$manifest_dir" && /home/runner/.cargo/bin/cargo audit --ignore RUSTSEC-2026-0049 --ignore RUSTSEC-2026-0098 --ignore RUSTSEC-2026-0099 --ignore RUSTSEC-2026-0104); \
	done' sh {} +
Auditing Rust manifest ./channels-src/telegram/Cargo.toml
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./channels-src/discord/Cargo.toml
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./channels-src/slack/Cargo.toml
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (54 crate dependencies)
Auditing Rust manifest ./channels-src/whatsapp/Cargo.toml
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/gmail/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/google-slides/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/telegram/Cargo.toml
    Updating crates.io index
     Locking 94 packages to latest compatible versions
      Adding generic-array v0.14.7 (available: v0.14.9)
      Adding getrandom v0.3.4 (available: v0.4.2)
      Adding grammers-crypto v0.8.0 (available: v0.9.0)
      Adding grammers-mtproto v0.8.0 (available: v0.9.0)
      Adding grammers-tl-types v0.8.0 (available: v0.9.0)
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (95 crate dependencies)
Auditing Rust manifest ./tools-src/google-calendar/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/google-drive/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/web-search/Cargo.toml
    Updating crates.io index
     Locking 84 packages to latest compatible versions
      Adding wit-bindgen v0.41.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (85 crate dependencies)
Auditing Rust manifest ./tools-src/github/Cargo.toml
    Updating crates.io index
     Locking 84 packages to latest compatible versions
      Adding wit-bindgen v0.41.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (85 crate dependencies)
Auditing Rust manifest ./tools-src/slack/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/google-docs/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./tools-src/google-sheets/Cargo.toml
    Updating crates.io index
     Locking 42 packages to latest compatible versions
      Adding wit-bindgen v0.36.0 (available: v0.57.1)
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (43 crate dependencies)
Auditing Rust manifest ./Cargo.toml
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1098 security advisories (from /home/runner/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (922 crate dependencies)
Crate:     wasmtime-wasi
Version:   44.0.1
Title:     WASI path_open(TRUNCATE) bypasses `FilePerms::WRITE` host restriction
error: 1 vulnerability found!
warning: 5 allowed warnings found
Date:      2026-05-21
ID:        RUSTSEC-2026-0149
URL:       https://rustsec.org/advisories/RUSTSEC-2026-0149
Severity:  7.5 (high)
Solution:  Upgrade to >=24.0.9, <25.0.0 OR >=36.0.10, <37.0.0 OR >=44.0.2, <45.0.0 OR >=45.0.0
Dependency tree:
wasmtime-wasi 44.0.1
└── ironclaw 0.18.0

Crate:     bincode
Version:   1.3.3
Warning:   unmaintained
Title:     Bincode is unmaintained
Date:      2025-12-16
ID:        RUSTSEC-2025-0141
URL:       https://rustsec.org/advisories/RUSTSEC-2025-0141
Dependency tree:
bincode 1.3.3
└── libsql 0.9.30
    └── ironclaw 0.18.0

Crate:     proc-macro-error
Version:   1.0.4
Warning:   unmaintained
Title:     proc-macro-error is unmaintained
Date:      2024-09-01
ID:        RUSTSEC-2024-0370
URL:       https://rustsec.org/advisories/RUSTSEC-2024-0370
Dependency tree:
proc-macro-error 1.0.4
└── rstest-bdd-macros 0.5.0
    └── ironclaw 0.18.0

Crate:     rustls-pemfile
Version:   2.2.0
Warning:   unmaintained
Title:     rustls-pemfile is unmaintained
Date:      2025-11-28
ID:        RUSTSEC-2025-0134
URL:       https://rustsec.org/advisories/RUSTSEC-2025-0134
Dependency tree:
rustls-pemfile 2.2.0
└── rustls-native-certs 0.7.3
    └── hyper-rustls 0.25.0
        └── libsql 0.9.30
            └── ironclaw 0.18.0

Crate:     libyml
Version:   0.0.5
Warning:   unsound
Title:     `libyml::string::yaml_string_extend` is unsound and unmaintained
Date:      2025-09-11
ID:        RUSTSEC-2025-0067
URL:       https://rustsec.org/advisories/RUSTSEC-2025-0067
Dependency tree:
libyml 0.0.5
└── serde_yml 0.0.12
    └── ironclaw 0.18.0

Crate:     serde_yml
Version:   0.0.12
Warning:   unsound
Title:     serde_yml crate is unsound and unmaintained
Date:      2025-09-11
ID:        RUSTSEC-2025-0068
URL:       https://rustsec.org/advisories/RUSTSEC-2025-0068
Dependency tree:
serde_yml 0.0.12
└── ironclaw 0.18.0

make: *** [Makefile:57: rust-audit] Error 1
Error: Process completed with exit code 2.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 24, 2026

@coderabbitai please add a tracking issue to ultimately replace serde_norway with serdy_saphyr

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh issue create \
  --repo leynos/axinite \
  --title "Replace serde_norway with serde_saphyr for YAML deserialisation" \
  --assignee leynos \
  --body "## Summary

The interim fix in `#200` replaces the unsound, unmaintained \`serde_yml\` crate (RUSTSEC-2025-0067, RUSTSEC-2025-0068) with \`serde_norway\`. This issue tracks the follow-on work to replace \`serde_norway\` with \`serde_saphyr\`, which is based on the actively maintained \`saphyr\` YAML parser and provides a fully pure-Rust implementation with no C FFI.

## Motivation

\`serde_saphyr\` is the recommended long-term replacement for unmaintained YAML crates in the Rust ecosystem. Moving to it eliminates any residual advisory risk that may arise from \`serde_norway\`'s dependency tree and aligns the codebase with the upstream community direction.

## Affected files

- \`Cargo.toml\` — swap \`serde_norway\` for \`serde_saphyr\`
- \`src/skills/parser.rs\` — update import and \`from_str\` call site
- \`src/skills/tests.rs\` — update import and both \`from_str\` call sites

## Acceptance criteria

- \`serde_norway\` is removed from \`Cargo.toml\` and replaced with \`serde_saphyr\`.
- All \`serde_norway::from_str\` call sites are updated to \`serde_saphyr::from_str\` (API is compatible).
- \`cargo check --workspace\` passes.
- \`make audit\` passes with no new advisories.

## References

- PR: `#200`
- Comment thread: https://github.com/leynos/axinite/pull/200#issuecomment-0 (audit violation discussion)

Requested by `@leynos`."

Length of output: 45


@leynos: Tracking issue created — #201 — assigned to you. It covers the swap of serde_norway for serde_saphyr across Cargo.toml, src/skills/parser.rs, and src/skills/tests.rs, with acceptance criteria requiring a clean cargo check --workspace and make audit.

Move the WASM channel wrapper's host store, conversion helpers,
credential resolution, and attachment helpers into focused child modules.
Keep the generated WIT bindings and public wrapper API in `mod.rs` so
callers can continue to use `crate::channels::wasm::wrapper::*`.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Swap the direct `serde_yml` dependency for `serde_yaml` so cargo
audit no longer reports the unmaintained `serde_yml` and `libyml`
advisories.

Add a central temporary audit ignore for the unpublished Wasmtime
WASI `path_open(TRUNCATE)` fix so the audit gate remains actionable
until the upstream patch release is available on crates.io.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Low Cohesion

src/channels/wasm/wrapper/mod.rs:

What lead to degradation?

This module has at least 23 different responsibilities amongst its 43 functions, threshold = 3

Why does this problem occur?

Cohesion is a measure of how well the elements in a file belong together. CodeScene measures cohesion using the LCOM4 metric (Lack of Cohesion Measure). With LCOM4, the functions inside a module are related if a) they access the same data members, or b) they call each other. High Cohesion is desirable as it means that all functions are related and likely to represent the same responsibility. Low Cohesion is problematic since it means that the module contains multiple behaviors. Low Cohesion leads to code that's harder to understand, requires more tests, and very often become a coordination magnet for developers.

How to fix it?

Look to modularize the code by splitting the file into more cohesive units; functions that belong together should still be located together. A common refactoring is EXTRACT CLASS.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# low_cohesion_example.js
 
 var userLayer = connectUsers(myConnectionProperties);
 
-var chessEngine = startEngine(gameProperties);
+// [Refactoring: moved the data related to chess to a new chessGame.js module]
 
+// The module contains login related functionality that forms one behaviour: all
+// code is related since it either a) uses the same data, or b) calls the same functions.
 export function login(newUser) {
   val authenticated = userLayer.authenticate(newUser);
   traceLoginFor(authenticated);
    // ...some code...
 }
 
-// playChess seems like a very unrelated responsibility.
-// Should it really be within the same module?
-
-export function playChess(loggedInUser) {
-   var board = chessEngine.newBoard();
-
-   return newGameOn(board, loggedInUser);
-}
+// [Refactoring: moved playChess to a new chessGame.js module
+// As a result of this refactoring, the module maintains a
+// single behavior where all code and data is related: high cohesion.]

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

String Heavy Function Arguments

src/channels/wasm/wrapper/store.rs:

What lead to degradation?

In this module, 47.4% of all arguments to its 14 functions are strings. The threshold for string arguments is 39.0%

Why does this problem occur?

String is a generic type that fail to capture the constraints of the domain object it represents. In this module, 47 % of all function arguments are string types.

How to fix it?

Heavy string usage indicates a missing domain language. Introduce data types that encapsulate the semantics. For example, a user_name is better represented as a constrained User type rather than a pure string, which could be anything.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Complex Method

src/channels/wasm/wrapper/convert.rs: status_to_wit

What lead to degradation?

status_to_wit has a cyclomatic complexity of 11, threshold = 9

Why does this problem occur?

A Complex Method has a high cyclomatic complexity. The recommended threshold for the Rust language is a cyclomatic complexity lower than 9.

How to fix it?

There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using EXTRACT FUNCTION. Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the DECOMPOSE CONDITIONAL refactoring.

Helpful refactoring examples

This code health issue has been solved before in this project. Here are some examples for inspiration:

undefined

# wit_compat.rs (8a29e56)
 
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 
-/// Search paths for WASM artifacts produced by cargo-component.
-fn find_wasm_artifact(source_dir: &Path, crate_name: &str) -> Option<PathBuf> {
-    let artifact_name = crate_name.replace('-', "_");
-
-    // Crate-local target dir (CI, default cargo)
-    for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-        let candidate = source_dir
-            .join("target")
-            .join(target_triple)
-            .join("release")
-            .join(format!("{artifact_name}.wasm"));
-        if candidate.exists() {
-            return Some(candidate);
-        }
-    }
-
-    // Shared target dir (CARGO_TARGET_DIR env)
-    if let Ok(shared) = std::env::var("CARGO_TARGET_DIR") {
-        for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-            let candidate = Path::new(&shared)
-                .join(target_triple)
-                .join("release")
-                .join(format!("{artifact_name}.wasm"));
-            if candidate.exists() {
-                return Some(candidate);
-            }
-        }
-    }
-
-    // Common shared target location (~/.cargo/shared-target)
-    if let Some(home) = dirs::home_dir() {
-        let shared = home.join(".cargo/shared-target");
-        if shared.exists() {
-            for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-                let candidate = shared
-                    .join(target_triple)
-                    .join("release")
-                    .join(format!("{artifact_name}.wasm"));
-                if candidate.exists() {
-                    return Some(candidate);
-                }
-            }
-        }
-    }
-
-    None
-}
-
 /// Parse registry manifests to discover all WASM extensions.
     for ext in &tool_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,
     for ext in &channel_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 25, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Excess Number of Function Arguments

src/channels/wasm/wrapper/store.rs: near.http_request

What lead to degradation?

near.http_request has 6 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Rust language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

Move the large wrapper test module into focused child modules for
approval, attachments, channel behaviour, cloning, conversion, dispatch,
and store coverage.

Keep the existing test bodies and expectations while reducing the
cohesion burden on a single test file.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Move placeholder replacement, unresolved-placeholder warnings, and
redaction replacement into focused helpers in the WASM channel store.

Keep the public store methods and logging behaviour unchanged while
reducing their branch-heavy bodies.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Complex Method

src/channels/wasm/wrapper/dispatch.rs: WasmChannel.process_emitted_messages, WasmChannel.dispatch_emitted_messages

What lead to degradation?

WasmChannel.process_emitted_messages has a cyclomatic complexity of 10, threshold = 9
WasmChannel.dispatch_emitted_messages has a cyclomatic complexity of 9, threshold = 9

Why does this problem occur?

A Complex Method has a high cyclomatic complexity. The recommended threshold for the Rust language is a cyclomatic complexity lower than 9.

How to fix it?

There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using EXTRACT FUNCTION. Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the DECOMPOSE CONDITIONAL refactoring.

Helpful refactoring examples

This code health issue has been solved before in this project. Here are some examples for inspiration:

undefined

# wit_compat.rs (8a29e56)
 
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 
-/// Search paths for WASM artifacts produced by cargo-component.
-fn find_wasm_artifact(source_dir: &Path, crate_name: &str) -> Option<PathBuf> {
-    let artifact_name = crate_name.replace('-', "_");
-
-    // Crate-local target dir (CI, default cargo)
-    for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-        let candidate = source_dir
-            .join("target")
-            .join(target_triple)
-            .join("release")
-            .join(format!("{artifact_name}.wasm"));
-        if candidate.exists() {
-            return Some(candidate);
-        }
-    }
-
-    // Shared target dir (CARGO_TARGET_DIR env)
-    if let Ok(shared) = std::env::var("CARGO_TARGET_DIR") {
-        for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-            let candidate = Path::new(&shared)
-                .join(target_triple)
-                .join("release")
-                .join(format!("{artifact_name}.wasm"));
-            if candidate.exists() {
-                return Some(candidate);
-            }
-        }
-    }
-
-    // Common shared target location (~/.cargo/shared-target)
-    if let Some(home) = dirs::home_dir() {
-        let shared = home.join(".cargo/shared-target");
-        if shared.exists() {
-            for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-                let candidate = shared
-                    .join(target_triple)
-                    .join("release")
-                    .join(format!("{artifact_name}.wasm"));
-                if candidate.exists() {
-                    return Some(candidate);
-                }
-            }
-        }
-    }
-
-    None
-}
-
 /// Parse registry manifests to discover all WASM extensions.
     for ext in &tool_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,
     for ext in &channel_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,

Excess Number of Function Arguments

src/channels/wasm/wrapper/dispatch.rs: WasmChannel.dispatch_emitted_messages

What lead to degradation?

WasmChannel.dispatch_emitted_messages has 6 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Rust language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Large Method

src/channels/wasm/wrapper/tests/dispatch.rs: test_dispatch_emitted_messages_preserves_attachments

What lead to degradation?

test_dispatch_emitted_messages_preserves_attachments has 70 lines, threshold = 70

Why does this problem occur?

Overly long functions make the code harder to read. The recommended maximum function length for the Rust language is 70 lines of code. Severity: Brain Method - Complex Method - Long Method.

How to fix it?

We recommend to be careful here -- just splitting long functions don't necessarily make the code easier to read. Instead, look for natural chunks inside the functions that expresses a specific task or concern. Often, such concerns are indicated by a Code Comment followed by an if-statement. Use the EXTRACT FUNCTION refactoring to encapsulate that concern.

Excess Number of Function Arguments

src/channels/wasm/wrapper/tests/dispatch.rs: assert_attachment

What lead to degradation?

assert_attachment has 8 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Rust language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Code Duplication

src/channels/wasm/wrapper/tests/channel.rs:

What lead to degradation?

The module contains 4 functions with similar structure: test_typing_task_cancelled_on_approval_needed,test_typing_task_cancelled_on_awaiting_approval_status,test_typing_task_cancelled_on_done,test_typing_task_persists_on_tool_started

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

Code Duplication

src/channels/wasm/wrapper/tests/convert.rs:

What lead to degradation?

The module contains 13 functions with similar structure: test_status_to_wit_auth_completed_failure,test_status_to_wit_auth_completed_success,test_status_to_wit_done,test_status_to_wit_done_case_insensitive and 9 more functions

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Code Duplication

channels-src/telegram/src/lib.rs:

What lead to degradation?

introduced similar code in: audio_attachment,document_attachment,photo_attachment,sticker_attachment and 2 more functions

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 26, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Bumpy Road Ahead

src/channels/wasm/wrapper/store.rs: ChannelStoreData.redact_credentials

What lead to degradation?

ChannelStoreData.redact_credentials has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

Why does this problem occur?

A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.
A bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is levels of conditionals.

How to fix it?

Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the Bumpy Road code health issue.
A Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The EXTRACT FUNCTION refactoring is the primary response.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Update the WIT package versions and matching runtime constants after the
interface changes in this branch.

Bump each published tool and channel registry entry whose extension source
changed so the version-bump CI check can enforce compatibility metadata.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Introduce a dispatch context parameter object for polling callbacks and
share emitted-message conversion plus rate-limited sending between the two
WASM channel dispatch paths.

Keep the internal process API intact while reducing complexity and argument
count in the dispatch module.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Replace the broad attachment assertion helper with an expected-value
parameter object and move repeated attachment fixture construction into a
single helper.

Keep the dispatch test expectations unchanged while removing the local
Clippy argument-count suppression.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Introduce a media specification parameter object for Telegram attachments
and route each per-media helper through one common attachment constructor.

Keep the existing MIME defaults, filename handling, and duration mapping
unchanged while removing the repeated constructor pattern.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Add shared helpers for typing-task lifecycle assertions and WIT status
conversion setup in the WASM wrapper tests.

Keep the existing test coverage and assertions while removing repeated
setup code from the channel and convert test modules.
codescene-delta-analysis[bot]

This comment was marked as outdated.

Move the empty-secret guards in `redact_credentials` into iterator
filters so the loop bodies stay unconditional.

Keep the redaction behaviour unchanged while removing the remaining
nested conditional blocks from the method.
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.

Code Health Improved (7 files improve in Code Health)

Gates Failed
New code is healthy (3 new files with code health below 10.00)
Enforce critical code health rules (1 file with Bumpy Road Ahead)
Enforce advisory code health rules (5 files with Large Method, Complex Method, Code Duplication)

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
New code is healthy Violations Code Health Impact
status.rs 2 rules 9.00 Suppress
convert.rs 1 rule 9.10 Suppress
convert.rs 1 rule 9.57 Suppress
Enforce critical code health rules Violations Code Health Impact
status.rs 1 critical rule 9.00 Suppress
Enforce advisory code health rules Violations Code Health Impact
status.rs 1 advisory rule 9.00 Suppress
convert.rs 1 advisory rule 9.10 Suppress
convert.rs 1 advisory rule 9.57 Suppress
lib.rs 1 advisory rule 8.86 → 8.84 Suppress
lib.rs 1 advisory rule 3.03 → 3.29 Suppress
View Improvements
File Code Health Impact Categories Improved
wrapper.rs 5.73 → 6.07 Low Cohesion, Complex Method, Excess Number of Function Arguments
metadata.rs 9.69 → 10.00 Excess Number of Function Arguments
mod.rs 4.30 → 8.11 Lines of Code in a Single File, Complex Method, Bumpy Road Ahead, Excess Number of Function Arguments, Code Duplication, Large Assertion Blocks
lib.rs 8.44 → 8.57 Bumpy Road Ahead
lib.rs 3.03 → 3.29 Complex Method, Large Method, Bumpy Road Ahead, Overall Code Complexity, Deep, Nested Complexity
lib.rs 6.84 → 6.94 Bumpy Road Ahead, Code Duplication
setup.rs 9.36 → 10.00 Large Method

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Large Method

src/channels/wasm/wrapper/tests/dispatch.rs: test_dispatch_emitted_messages_preserves_attachments

What lead to degradation?

test_dispatch_emitted_messages_preserves_attachments has 70 lines, threshold = 70

Why does this problem occur?

Overly long functions make the code harder to read. The recommended maximum function length for the Rust language is 70 lines of code. Severity: Brain Method - Complex Method - Long Method.

How to fix it?

We recommend to be careful here -- just splitting long functions don't necessarily make the code easier to read. Instead, look for natural chunks inside the functions that expresses a specific task or concern. Often, such concerns are indicated by a Code Comment followed by an if-statement. Use the EXTRACT FUNCTION refactoring to encapsulate that concern.

Excess Number of Function Arguments

src/channels/wasm/wrapper/tests/dispatch.rs: assert_attachment

What lead to degradation?

assert_attachment has 8 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Rust language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/channels/wasm/wrapper/tests/dispatch.rs ==="
cat src/channels/wasm/wrapper/tests/dispatch.rs 2>/dev/null || {
  echo "File not found"
  fd -a 'dispatch.rs' .
}

echo
echo "=== Relevant symbols ==="
rg -n -C 5 'fn assert_attachment|test_dispatch_emitted_messages_preserves_attachments|struct ExpectedAttachment|build_test_attachments|dispatch_emitted_messages' src/channels/wasm/wrapper/tests/dispatch.rs 2>/dev/null || true

Length of output: 12928



@leynos

File: src/channels/wasm/wrapper/tests/dispatch.rs
Symbols: test_dispatch_emitted_messages_preserves_attachments, assert_attachment, ExpectedAttachment, build_test_attachments, dispatch_messages_for_test (new), assert_preserved_attachments (new)


Fix

Keep the existing ExpectedAttachment<'_> parameter object. It already resolves the reported assert_attachment issue: the current function has 2 arguments, not 8:

fn assert_attachment(
    attachment: &crate::channels::IncomingAttachment,
    expected: &ExpectedAttachment<'_>,
)

Treat that part of the CodeScene report as stale. Address the remaining Large Method issue by extracting the repeated dispatch-test setup and the attachment verification block:

  1. Add dispatch_messages_for_test(messages) to own the channel setup, DispatchContext, call to WasmChannel::dispatch_emitted_messages, and receive queue.
  2. Add assert_preserved_attachments(&msg) to own the two ExpectedAttachment assertions.
  3. Rewrite test_dispatch_emitted_messages_preserves_attachments into a short arrange/act/assert test.

This reduces the test body well below the 70-line threshold and preserves the named parameter object that already fixed the excessive argument count.


AI coding-agent prompt

Fix the remaining Large Method issue in
`src/channels/wasm/wrapper/tests/dispatch.rs`.

## Context

The file already contains:

- `ExpectedAttachment<'a>`
- `assert_attachment(attachment, expected)`
- `build_test_attachments()`

Do not replace these. They already fix the old "assert_attachment has 8
arguments" report; `assert_attachment` now has only 2 arguments.

The remaining issue is that
`test_dispatch_emitted_messages_preserves_attachments` is still too long.
Extract cohesive test helpers instead of changing test behaviour.

## Step 1 — Add `dispatch_messages_for_test`

Add this private async helper near the other test helpers, after
`build_test_attachments`:

```rust
async fn dispatch_messages_for_test(
    messages: Vec<crate::channels::wasm::host::EmittedMessage>,
) -> (
    Result<(), crate::channels::wasm::WasmChannelError>,
    tokio::sync::mpsc::Receiver<crate::channels::IncomingMessage>,
) {
    let (tx, rx) = tokio::sync::mpsc::channel(10);
    let message_tx = Arc::new(tokio::sync::RwLock::new(Some(tx)));

    let rate_limiter = Arc::new(tokio::sync::RwLock::new(
        crate::channels::wasm::host::ChannelEmitRateLimiter::new(
            crate::channels::wasm::capabilities::EmitRateLimitConfig::default(),
        ),
    ));

    let last_broadcast_metadata = Arc::new(tokio::sync::RwLock::new(None));
    let result = WasmChannel::dispatch_emitted_messages(
        "test-channel",
        messages,
        DispatchContext {
            message_tx: message_tx.as_ref(),
            rate_limiter: rate_limiter.as_ref(),
            last_broadcast_metadata: last_broadcast_metadata.as_ref(),
            settings_store: None,
        },
    )
    .await;

    (result, rx)
}
```

If the concrete error type path differs, import it or use the exact type
reported by the compiler. Do not change the semantics of the helper.

## Step 2 — Add `assert_preserved_attachments`

Add this private helper below `dispatch_messages_for_test`:

```rust
fn assert_preserved_attachments(msg: &crate::channels::IncomingMessage) {
    assert_eq!(msg.attachments.len(), 2);

    // Verify first attachment
    assert_attachment(
        &msg.attachments[0],
        &ExpectedAttachment {
            id: "photo123",
            mime_type: "image/jpeg",
            filename: Some("cat.jpg"),
            size_bytes: Some(50_000),
            source_url: Some("https://api.telegram.org/file/photo123"),
            storage_key: None,
            extracted_text: None,
        },
    );

    // Verify second attachment
    assert_attachment(
        &msg.attachments[1],
        &ExpectedAttachment {
            id: "doc456",
            mime_type: "application/pdf",
            filename: Some("report.pdf"),
            size_bytes: Some(120_000),
            source_url: None,
            storage_key: Some("store/doc456"),
            extracted_text: Some("Report contents..."),
        },
    );
}
```

Move the existing attachment assertions verbatim into this helper. Preserve all
expected values.

## Step 3 — Rewrite `test_dispatch_emitted_messages_preserves_attachments`

Replace the body of `test_dispatch_emitted_messages_preserves_attachments` with:

```rust
#[tokio::test]
async fn test_dispatch_emitted_messages_preserves_attachments() {
    use crate::channels::wasm::host::EmittedMessage;

    let messages = vec![
        EmittedMessage::new("user1", "Check these files")
            .with_attachments(build_test_attachments()),
    ];

    let (result, mut rx) = dispatch_messages_for_test(messages).await;

    assert!(result.is_ok());

    let msg = rx.try_recv().expect("Should receive message");
    assert_eq!(msg.content, "Check these files");
    assert_preserved_attachments(&msg);
}
```

## Step 4 — Optional clean-up

If the same dispatch setup remains duplicated in other tests in this file,
update them to call `dispatch_messages_for_test` too, but do not change their
assertions or test names.

## Constraints

- Do not alter any assertion value, field name, test name, or message content.
- Keep `ExpectedAttachment<'_>` and `assert_attachment` as the parameter-object
  fix for the old excessive-argument report.
- Do not reintroduce positional expected attachment arguments.
- Keep all helpers private; do not add `pub`.
- Do not reformat unrelated code.
- Run `cargo check --workspace` after applying the changes.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented May 27, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Complex Method

src/channels/wasm/wrapper/dispatch.rs: WasmChannel.process_emitted_messages, WasmChannel.dispatch_emitted_messages

What lead to degradation?

WasmChannel.process_emitted_messages has a cyclomatic complexity of 10, threshold = 9
WasmChannel.dispatch_emitted_messages has a cyclomatic complexity of 9, threshold = 9

Why does this problem occur?

A Complex Method has a high cyclomatic complexity. The recommended threshold for the Rust language is a cyclomatic complexity lower than 9.

How to fix it?

There are many reasons for Complex Method. Sometimes, another design approach is beneficial such as a) modeling state using an explicit state machine rather than conditionals, or b) using table lookup rather than long chains of logic. In other scenarios, the function can be split using EXTRACT FUNCTION. Just make sure you extract natural and cohesive functions. Complex Methods can also be addressed by identifying complex conditional expressions and then using the DECOMPOSE CONDITIONAL refactoring.

Helpful refactoring examples

This code health issue has been solved before in this project. Here are some examples for inspiration:

undefined

# wit_compat.rs (8a29e56)
 
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 
 
-/// Search paths for WASM artifacts produced by cargo-component.
-fn find_wasm_artifact(source_dir: &Path, crate_name: &str) -> Option<PathBuf> {
-    let artifact_name = crate_name.replace('-', "_");
-
-    // Crate-local target dir (CI, default cargo)
-    for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-        let candidate = source_dir
-            .join("target")
-            .join(target_triple)
-            .join("release")
-            .join(format!("{artifact_name}.wasm"));
-        if candidate.exists() {
-            return Some(candidate);
-        }
-    }
-
-    // Shared target dir (CARGO_TARGET_DIR env)
-    if let Ok(shared) = std::env::var("CARGO_TARGET_DIR") {
-        for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-            let candidate = Path::new(&shared)
-                .join(target_triple)
-                .join("release")
-                .join(format!("{artifact_name}.wasm"));
-            if candidate.exists() {
-                return Some(candidate);
-            }
-        }
-    }
-
-    // Common shared target location (~/.cargo/shared-target)
-    if let Some(home) = dirs::home_dir() {
-        let shared = home.join(".cargo/shared-target");
-        if shared.exists() {
-            for target_triple in &["wasm32-wasip2", "wasm32-wasip1", "wasm32-wasi"] {
-                let candidate = shared
-                    .join(target_triple)
-                    .join("release")
-                    .join(format!("{artifact_name}.wasm"));
-                if candidate.exists() {
-                    return Some(candidate);
-                }
-            }
-        }
-    }
-
-    None
-}
-
 /// Parse registry manifests to discover all WASM extensions.
     for ext in &tool_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,
     for ext in &channel_extensions {
-        let wasm_path = match find_wasm_artifact(&ext.source_dir, &ext.crate_name) {
+        let wasm_path = match ironclaw::registry::artifacts::find_wasm_artifact(
+            &ext.source_dir,
+            &ext.crate_name,
+            "release",
+        ) {
             Some(p) => p,

Excess Number of Function Arguments

src/channels/wasm/wrapper/dispatch.rs: WasmChannel.dispatch_emitted_messages

What lead to degradation?

WasmChannel.dispatch_emitted_messages has 6 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Rust language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Tip

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

@leynos

File: src/channels/wasm/wrapper/dispatch.rs
Symbols: WasmChannel::process_emitted_messages, WasmChannel::dispatch_emitted_messages, DispatchContext (new), convert_emitted_to_incoming (new), apply_emitted_metadata (new), send_with_rate_limit (new)


Fix

Extract the shared per-message dispatch pipeline and introduce a parameter object for polling dispatch state.

Both methods currently perform the same responsibilities inline:

  1. acquire a message sender,
  2. convert EmittedMessage into IncomingMessage,
  3. apply optional metadata,
  4. update broadcast metadata,
  5. rate-limit,
  6. send to the agent queue,
  7. stop when the receiver is closed.

Move steps 2, 3, and 5 into helpers. Wrap the four state handles passed to dispatch_emitted_messages in DispatchContext<'_>. This drops dispatch_emitted_messages below the argument threshold and lowers both methods below the cyclomatic-complexity threshold.


AI coding-agent prompt

Fix the CodeScene issues in `src/channels/wasm/wrapper/dispatch.rs`.

## Target issues

- Complex Method:
  - `WasmChannel::process_emitted_messages`
  - `WasmChannel::dispatch_emitted_messages`
- Excess Number of Function Arguments:
  - `WasmChannel::dispatch_emitted_messages`

## Goal

Reduce both methods' cyclomatic complexity below 9 and reduce
`dispatch_emitted_messages` to no more than 4 arguments.

Do not change public behaviour. Preserve all tracing messages, metadata
handling, rate-limiting semantics, and send-loop behaviour.

## Step 1 — Add `DispatchContext`

In `src/channels/wasm/wrapper/dispatch.rs`, add this struct near the top of the
file, after the `use` declarations:

```rust
/// Bundles the per-channel async state required when dispatching messages
/// from the polling path.
pub(super) struct DispatchContext<'a> {
    pub(super) message_tx: &'a tokio::sync::RwLock<Option<mpsc::Sender<IncomingMessage>>>,
    pub(super) rate_limiter:
        &'a tokio::sync::RwLock<crate::channels::wasm::host::ChannelEmitRateLimiter>,
    pub(super) last_broadcast_metadata: &'a tokio::sync::RwLock<Option<String>>,
    pub(super) settings_store: Option<&'a Arc<dyn crate::db::SettingsStore>>,
}
```

Use the existing imports in the file where possible. Add missing imports for
`Arc`, `IncomingMessage`, `mpsc`, or `SettingsStore` only if the file does not
already import them.

## Step 2 — Extract `convert_emitted_to_incoming`

Add this private helper above the `impl WasmChannel` block:

```rust
fn convert_emitted_to_incoming(channel_name: &str, emitted: &EmittedMessage) -> IncomingMessage {
    let mut msg = IncomingMessage::new(channel_name, &emitted.user_id, &emitted.content);

    if let Some(name) = emitted.user_name.clone() {
        msg = msg.with_user_name(name);
    }

    if let Some(thread_id) = emitted.thread_id.clone() {
        msg = msg.with_thread(thread_id);
    }

    if !emitted.attachments.is_empty() {
        let attachments = emitted
            .attachments
            .iter()
            .map(|attachment| crate::channels::IncomingAttachment {
                id: attachment.id.clone(),
                kind: crate::channels::AttachmentKind::from_mime_type(&attachment.mime_type),
                mime_type: attachment.mime_type.clone(),
                filename: attachment.filename.clone(),
                size_bytes: attachment.size_bytes,
                source_url: attachment.source_url.clone(),
                storage_key: attachment.storage_key.clone(),
                extracted_text: attachment.extracted_text.clone(),
                data: attachment.data.clone(),
                duration_secs: attachment.duration_secs,
            })
            .collect();

        msg = msg.with_attachments(attachments);
    }

    msg
}
```

Move the existing conversion code into this helper. Preserve every field mapping.

## Step 3 — Extract `apply_emitted_metadata`

Add this private helper above the `impl WasmChannel` block:

```rust
fn apply_emitted_metadata(
    mut msg: IncomingMessage,
    emitted: &EmittedMessage,
) -> (IncomingMessage, bool) {
    if let Ok(metadata) = serde_json::from_str(&emitted.metadata_json) {
        msg = msg.with_metadata(metadata);
        return (msg, true);
    }

    (msg, false)
}
```

The boolean tells callers whether metadata parsed successfully, so they can keep
the existing side-effect:

- `process_emitted_messages` calls `self.update_broadcast_metadata(...)`
- `dispatch_emitted_messages` calls `do_update_broadcast_metadata(...)`

## Step 4 — Extract `send_with_rate_limit`

Add this private async helper above the `impl WasmChannel` block:

```rust
async fn send_with_rate_limit(
    channel_name: &str,
    msg: IncomingMessage,
    tx: &mpsc::Sender<IncomingMessage>,
    limiter: &mut crate::channels::wasm::host::ChannelEmitRateLimiter,
) -> Result<bool, WasmChannelError> {
    if !limiter.check_and_record() {
        tracing::warn!(channel = %channel_name, "Message emission rate limited");
        return Err(WasmChannelError::EmitRateLimited {
            name: channel_name.to_string(),
        });
    }

    tracing::info!(
        channel = %channel_name,
        user_id = %msg.user_id,
        content_len = msg.content.len(),
        attachment_count = msg.attachments.len(),
        "Sending message to agent"
    );

    if tx.send(msg).await.is_err() {
        tracing::error!(channel = %channel_name, "Failed to send message, channel closed");
        return Ok(false);
    }

    tracing::info!(channel = %channel_name, "Message successfully sent to agent queue");
    Ok(true)
}
```

Return `Ok(false)` when the channel is closed so callers can break out of the
loop exactly as before.

## Step 5 — Rewrite `process_emitted_messages`

Replace the body of `WasmChannel::process_emitted_messages` with:

```rust
pub(super) async fn process_emitted_messages(
    &self,
    messages: Vec<EmittedMessage>,
) -> Result<(), WasmChannelError> {
    tracing::info!(
        channel = %self.name,
        message_count = messages.len(),
        "Processing emitted messages from WASM callback"
    );

    if messages.is_empty() {
        tracing::debug!(channel = %self.name, "No messages emitted");
        return Ok(());
    }

    let tx_guard = self.message_tx.read().await;
    let Some(tx) = tx_guard.as_ref() else {
        tracing::error!(
            channel = %self.name,
            count = messages.len(),
            "Messages emitted but no sender available - channel may not be started!"
        );
        return Ok(());
    };

    let mut rate_limiter = self.rate_limiter.write().await;

    for emitted in messages {
        let msg = convert_emitted_to_incoming(&self.name, &emitted);
        let (msg, has_metadata) = apply_emitted_metadata(msg, &emitted);

        if has_metadata {
            self.update_broadcast_metadata(&emitted.metadata_json).await;
        }

        if !send_with_rate_limit(&self.name, msg, tx, &mut rate_limiter).await? {
            break;
        }
    }

    Ok(())
}
```

Keep the function signature unchanged.

## Step 6 — Rewrite `dispatch_emitted_messages`

Change the signature from the current 6-argument form to:

```rust
pub(super) async fn dispatch_emitted_messages(
    channel_name: &str,
    messages: Vec<EmittedMessage>,
    ctx: DispatchContext<'_>,
) -> Result<(), WasmChannelError>
```

Replace the body with:

```rust
pub(super) async fn dispatch_emitted_messages(
    channel_name: &str,
    messages: Vec<EmittedMessage>,
    ctx: DispatchContext<'_>,
) -> Result<(), WasmChannelError> {
    tracing::info!(
        channel = %channel_name,
        message_count = messages.len(),
        "Processing emitted messages from polling callback"
    );

    let tx_guard = ctx.message_tx.read().await;
    let Some(tx) = tx_guard.as_ref() else {
        tracing::error!(
            channel = %channel_name,
            count = messages.len(),
            "Messages emitted but no sender available - channel may not be started!"
        );
        return Ok(());
    };

    let mut limiter = ctx.rate_limiter.write().await;

    for emitted in messages {
        let msg = convert_emitted_to_incoming(channel_name, &emitted);
        let (msg, has_metadata) = apply_emitted_metadata(msg, &emitted);

        if has_metadata {
            do_update_broadcast_metadata(
                channel_name,
                &emitted.metadata_json,
                ctx.last_broadcast_metadata,
                ctx.settings_store,
            )
            .await;
        }

        if !send_with_rate_limit(channel_name, msg, tx, &mut limiter).await? {
            break;
        }
    }

    Ok(())
}
```

## Step 7 — Update callers

Search for every call site:

```text
WasmChannel::dispatch_emitted_messages
dispatch_emitted_messages(
```

Update each call from the old positional state arguments to `DispatchContext`.

For example, in `src/channels/wasm/wrapper/polling.rs`, replace:

```rust
WasmChannel::dispatch_emitted_messages(
    &channel_name,
    messages,
    &message_tx,
    &rate_limiter,
    &last_broadcast_metadata,
    settings_store.as_ref(),
)
.await?;
```

with:

```rust
WasmChannel::dispatch_emitted_messages(
    &channel_name,
    messages,
    DispatchContext {
        message_tx: &message_tx,
        rate_limiter: &rate_limiter,
        last_broadcast_metadata: &last_broadcast_metadata,
        settings_store: settings_store.as_ref(),
    },
)
.await?;
```

Add this import in `polling.rs` if needed:

```rust
use super::dispatch::DispatchContext;
```

If the local values are `Arc<RwLock<...>>`, pass references using
`.as_ref()` only if required by the compiler. Keep the resulting types aligned
with `DispatchContext<'_>`.

## Step 8 — Update tests

Update any tests that call `WasmChannel::dispatch_emitted_messages` directly.

Replace the old call shape:

```rust
WasmChannel::dispatch_emitted_messages(
    "test-channel",
    messages,
    &message_tx,
    &rate_limiter,
    &last_broadcast_metadata,
    None,
)
.await
```

with:

```rust
WasmChannel::dispatch_emitted_messages(
    "test-channel",
    messages,
    DispatchContext {
        message_tx: message_tx.as_ref(),
        rate_limiter: rate_limiter.as_ref(),
        last_broadcast_metadata: last_broadcast_metadata.as_ref(),
        settings_store: None,
    },
)
.await
```

Import `DispatchContext` in the affected test module:

```rust
use crate::channels::wasm::wrapper::dispatch::DispatchContext;
```

If `dispatch` is private to `wrapper`, import through the nearest accessible
module path or expose `DispatchContext` as `pub(super)` from `dispatch.rs` and
re-export it for tests under `#[cfg(test)]`.

## Constraints

- Do not change message conversion semantics.
- Do not drop metadata updates.
- Do not alter rate-limiter behaviour.
- Do not remove or change tracing message text.
- Keep `process_emitted_messages` signature unchanged.
- Keep `DispatchContext`, `convert_emitted_to_incoming`, `apply_emitted_metadata`,
  and `send_with_rate_limit` internal to the wrapper module.
- Do not reformat unrelated code.
- Run `cargo check --workspace` after applying the change.

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

Labels

contributor: core 20+ merged PRs risk: medium Business logic, config, or moderate-risk modules scope: channel/wasm WASM channel runtime scope: ci CI/CD workflows scope: dependencies Dependency updates scope: docs Documentation scope: orchestrator Container orchestrator scope: sandbox Docker sandbox scope: tool/builder Dynamic tool builder scope: tool/wasm WASM tool sandbox size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant