Skip to content

feat(stress): add API capacity workload - #5781

Merged
serrrfirat merged 20 commits into
mainfrom
codex/pr5726-api-capacity-stress-harness
Jul 8, 2026
Merged

serrrfirat merged 20 commits into
mainfrom
codex/pr5726-api-capacity-stress-harness

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

  • add an api-user-capacity scenario to ironclaw_stress for full API user flows plus concurrent read pressure
  • add an optional OpenAI-compatible mock LLM sidecar for API-capacity runs
  • add read-mix, request timeout, polling, setup-concurrency, and user-token inputs for capacity sweeps
  • move synchronous resource-governor calls in async mixed user-turn stress onto blocking workers so the harness does not self-deadlock under c100

Stacking

Stacked on #5726 (codex/hst-postgres-v2-03-runtime-stores). This PR is harness-only; storage/runtime optimizations should follow after this lands or while stacked above it.

Verification

  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target-96d3 cargo test -p ironclaw_stress
  • git diff --check
  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target-96d3 cargo build -p ironclaw_stress --release

Notes

This is intentionally split before further hillclimbing so future benchmark and optimization PRs can cite a stable workload shape.

@ironloopai

ironloopai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 0898d1ced91e5dedaa9523cf8000fe4266ad687b
Result: No reviewer jobs are scheduled yet.
Next: Run @ironloopai review to start reviewers.
Updated: 2026-07-08T13:24:31.719Z

Current reviewers:

Reviewer State Verdict Findings Last update
none Queued N/A No reviewer jobs scheduled yet. N/A
Reviewer summaries
Reviewer Detail
none No reviewer jobs scheduled yet.
Recent activity
Time Reviewer State Detail
N/A N/A Waiting No progress events recorded yet.
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent-id-or-alias>
  • @ironloopai status
Run metadata

Admission: webhook accepted the request and IronLoop persisted review state before this projection.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ad6f5170-7ef8-41a1-ac46-401703b4d368

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
📝 Walkthrough

Walkthrough

Adds an api-user-capacity stress scenario with API harness, mock LLM, CLI/summary wiring, and validation. Separately, adds optional stage tracing in user_turn.rs and moves governor reserve/reconcile/release work onto blocking threads.

Changes

API Capacity Stress Scenario

Layer / File(s) Summary
Dependencies and data contracts
tools/ironclaw_stress/Cargo.toml, tools/ironclaw_stress/src/api_capacity.rs
Adds reqwest and expanded tokio features, and defines the API capacity summary and mock-LLM result types.
Run orchestration and windowing
tools/ironclaw_stress/src/api_capacity.rs
Implements the top-level API capacity run, measured window coordination, virtual-user loop, read-worker scheduling, and full message flow with optional assistant-finalization polling.
HTTP harness, identity loading, and metrics
tools/ironclaw_stress/src/api_capacity.rs
Implements the reqwest-based API harness, concurrent user setup, bearer-token parsing, read-mix routing, latency aggregation, payload shaping, and unit tests for routing and interval clamping.
Mock LLM server
tools/ironclaw_stress/src/api_capacity.rs
Implements mock LLM startup, connection handling, completion responses, and low-level HTTP parsing/writing helpers.
CLI args, dispatch, and validation
tools/ironclaw_stress/src/main.rs, tools/ironclaw_stress/src/process_pressure.rs, tools/ironclaw_stress/src/resource_ops.rs, tools/ironclaw_stress/src/tests.rs
Adds the api-user-capacity scenario, CLI fields, summary plumbing, dispatch routing, validation, and the process-pressure/resource stage mappings.
Human summary rendering
tools/ironclaw_stress/src/human.rs
Adds conditional API capacity overview rendering and the per-endpoint latency table helper.

Estimated code review effort: 4 (Complex) | ~60 minutes

User-Turn Stage Tracing and Blocking Governor Calls

Layer / File(s) Summary
Stage trace helper and instrumentation
tools/ironclaw_stress/src/user_turn.rs
Introduces the stage_trace helper and inserts start/done trace calls around load_context, resource_reserve, model_wait, write_assistant, and resource_reconcile, while moving governor calls to blocking threads.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

  • nearai/ironclaw#5313: Extends the same ironclaw_stress scenario plumbing, including scenario dispatch and stage mapping.
  • nearai/ironclaw#5455: Touches the same user_turn.rs finalized-assistant write path and adjacent stress-harness execution flow.

Suggested reviewers: think-in-universe, italic-jinxin

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary and verification, but omits required template sections like Change Type, Linked Issue, and several risk/rollback sections. Add the missing template sections, especially Change Type, Linked Issue, Security Impact, Blast Radius, Rollback Plan, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific, matches the API capacity feature, and follows Conventional Commits style.
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.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5781 July 7, 2026 17:27 Destroyed
@github-actions github-actions Bot added scope: dependencies Dependency updates size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 7, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new api-user-capacity stress testing scenario to the ironclaw_stress tool, adding the api_capacity module to simulate virtual users and background read workers, along with a built-in mock LLM sidecar. Key feedback highlights several critical robustness issues: potential division-by-zero panics if the user count is zero or if the mock LLM jitter is set to maximum; integer overflow and memory exhaustion risks with large HTTP content lengths; and a resource leak vulnerability when blocking resource governor tasks are cancelled. Additionally, the reviewer suggested using tokio::time::interval to accurately maintain target QPS under load and improving HTTP response handling to prevent network errors from being masked as JSON decoding failures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +443 to +456
let aggregate_qps = args.api_read_qps_per_user * args.users as f64;
let worker_count = read_worker_count(args).max(1) as f64;
let sleep = if aggregate_qps <= 0.0 {
Duration::from_secs(3600)
} else {
Duration::from_secs_f64((worker_count / aggregate_qps).max(0.001))
};
let mut api_samples = Vec::new();
let mut operation_index = 0_u64;
loop {
tokio::select! {
_ = stop_reads.notified() => break,
_ = tokio::time::sleep(sleep) => {}
}

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.

high

Sleeping for a constant duration in the read worker loop does not compensate for request latency, which will significantly throttle the achieved QPS under load. Additionally, if args.api_read_qps_per_user is extremely small, Duration::from_secs_f64 can panic on overflow. Using tokio::time::interval with MissedTickBehavior::Delay allows the loop to dynamically compensate for request latency and maintain the target QPS accurately.

    let aggregate_qps = args.api_read_qps_per_user * args.users as f64;
    let worker_count = read_worker_count(args).max(1) as f64;
    let sleep = if aggregate_qps <= 0.0 {
        Duration::from_secs(3600)
    } else {
        let sleep_secs = (worker_count / aggregate_qps).max(0.001);
        if sleep_secs.is_nan() || sleep_secs.is_infinite() || sleep_secs > 3600.0 {
            Duration::from_secs(3600)
        } else {
            Duration::from_secs_f64(sleep_secs)
        }
    };
    let mut interval = tokio::time::interval(sleep);
    interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
    let mut api_samples = Vec::new();
    let mut operation_index = 0_u64;
    loop {
        tokio::select! {
            _ = stop_reads.notified() => break,
            _ = interval.tick() => {}
        }

Comment on lines +577 to +589
let status = response.status();
let text = response.text().await.unwrap_or_default();
if !status.is_success() {
Err(FailureCause::new(
format!("api_http_status_{}", status.as_u16()),
name,
truncate_detail(format!("{url}: {text}")),
))
} else {
serde_json::from_str::<Value>(&text).map_err(|error| {
FailureCause::new("api_json_decode", name, format!("{url}: {error}"))
})
}

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.

medium

When validating or processing HTTP responses, check for successful status codes (e.g., 2xx) first to short-circuit and avoid the performance overhead of parsing the response body as JSON. Additionally, using unwrap_or_default() on response.text().await can hide actual network or timeout errors that occur during the body download, reporting them as JSON decode errors instead. It is better to check the status code first, and then match on the result of response.text().await to distinguish between a failure to retrieve the body and a failure to parse a successfully received body.

                let status = response.status();
                if !status.is_success() {
                    let text = response.text().await.unwrap_or_default();
                    return Err(FailureCause::new(
                        format!("api_http_status_{}", status.as_u16()),
                        name,
                        truncate_detail(format!("{url}: {text}")),
                    ));
                }
                match response.text().await {
                    Ok(text) => {
                        serde_json::from_str::<Value>(&text).map_err(|error| {
                            FailureCause::new("api_json_decode", name, format!("{url}: {error}"))
                        })
                    }
                    Err(error) => Err(FailureCause::new("api_http_body", name, error)),
                }
References
  1. When validating or processing HTTP responses, check for successful status codes (e.g., 2xx) first to short-circuit and avoid the performance overhead of parsing the response body as JSON.

Comment on lines +656 to +657
users.sort_by_key(|user| user.index);
Ok(users)

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.

medium

If args.users is 0, setup_users will return an empty Vec<ApiUser> without error, which subsequently causes division-by-zero panics downstream (e.g., in run_read_worker). Adding a check to ensure at least one user is successfully set up prevents these panics.

    if users.is_empty() {
        return Err("No users were successfully set up. Ensure --users is greater than 0.".to_string());
    }
    users.sort_by_key(|user| user.index);
    Ok(users)

Comment on lines +1000 to +1010
let content_length = parse_content_length(&headers).unwrap_or(0);
while request.len() < header_end + 4 + content_length {
let read = stream
.read(&mut buffer)
.await
.map_err(|error| error.to_string())?;
if read == 0 {
break;
}
request.extend_from_slice(&buffer[..read]);
}

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.

medium

If content_length is extremely large, header_end + 4 + content_length can overflow usize (causing a panic in debug mode), and the loop can read unbounded amounts of data into memory (causing out-of-memory exhaustion). Enforcing a reasonable maximum limit on content_length prevents these issues.

    let content_length = parse_content_length(&headers).unwrap_or(0);
    if content_length > 10 * 1024 * 1024 {
        write_mock_response(&mut stream, 413, "text/plain", b"payload too large").await?;
        return Ok(());
    }
    while request.len() < header_end + 4 + content_length {
        let read = stream
            .read(&mut buffer)
            .await
            .map_err(|error| error.to_string())?;
        if read == 0 {
            break;
        }
        request.extend_from_slice(&buffer[..read]);
    }

Comment on lines +1106 to +1111
fn deterministic_jitter_ms(request_index: u64, jitter_ms: u64) -> u64 {
request_index
.wrapping_mul(1_103_515_245)
.wrapping_add(12_345)
% (jitter_ms + 1)
}

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.

medium

If jitter_ms is u64::MAX, jitter_ms + 1 will overflow to 0, causing a division-by-zero panic during the modulo operation. Using checked_add or saturating_add prevents this panic.

fn deterministic_jitter_ms(request_index: u64, jitter_ms: u64) -> u64 {
    let divisor = jitter_ms.checked_add(1).unwrap_or(u64::MAX);
    (request_index
        .wrapping_mul(1_103_515_245)
        .wrapping_add(12_345))
        % divisor
}

Comment on lines 1464 to 1478
async fn reserve_resources(
governor: Arc<dyn ResourceGovernor>,
scope: ResourceScope,
) -> Result<ResourceReservation, OperationFailure> {
governor
.reserve(scope, resource_ops::estimate())
tokio::task::spawn_blocking(move || governor.reserve(scope, resource_ops::estimate()))
.await
.map_err(|error| {
OperationFailure::new(
"resource_worker_join",
"resource_reserve",
format!("resource reserve worker failed: {error}"),
)
})?
.map_err(|error| resource_failure("resource_reserve", error))
}

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.

medium

When offloading synchronous resource governor calls to tokio::task::spawn_blocking, if the outer async future is cancelled (e.g., due to a timeout or task abort), the JoinHandle is dropped but the blocking task continues to run in the background. Once it completes, the returned ResourceReservation is dropped without being reconciled or released, leading to a permanent resource leak in the governor. Consider using a guard or registering the reservation ID in a way that ensures cleanup even if the outer future is cancelled.

@railway-app

railway-app Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the ironclaw-pr-5781 environment in ironclaw-ci-preview

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 8, 2026 at 1:35 pm

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5781 July 7, 2026 20:20 Destroyed
Base automatically changed from codex/hst-postgres-v2-03-runtime-stores to main July 8, 2026 08:17
@serrrfirat
serrrfirat marked this pull request as ready for review July 8, 2026 10:41
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5781 July 8, 2026 10:41 Destroyed

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/ironclaw_stress/src/api_capacity.rs`:
- Line 597: The body-read in the response handling path is swallowing transport
errors by converting them to an empty string. Update the response processing
logic around the `response.text().await` call in `api_capacity.rs` to propagate
the read failure as its own `FailureCause` instead of defaulting silently. Keep
the failure distinct from `api_json_decode` so the harness records the correct
cause, and apply the same fail-loud pattern anywhere else in the same
response-to-text flow.
- Around line 454-492: In run_read_worker, the stop signal handling should not
rely solely on Notify::notified() because a worker that is busy in an API call
can miss notify_waiters() and loop forever; switch to a shutdown mechanism that
is remembered across waits (for example, check a shared atomic/flag before each
iteration and after each request, or use a cancellation primitive that
persists). Also, when collecting API samples from the harness calls in
run_read_worker, do not swallow response body-read failures with
unwrap_or_default; propagate the error so TaskResult only records valid samples
and failures are visible to the caller.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3c77664a-fb9a-44cd-8d98-7ad458ff6e97

📥 Commits

Reviewing files that changed from the base of the PR and between 6d84b2e and 277b6b2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !**/Cargo.lock
📒 Files selected for processing (8)
  • tools/ironclaw_stress/Cargo.toml
  • tools/ironclaw_stress/src/api_capacity.rs
  • tools/ironclaw_stress/src/human.rs
  • tools/ironclaw_stress/src/main.rs
  • tools/ironclaw_stress/src/process_pressure.rs
  • tools/ironclaw_stress/src/resource_ops.rs
  • tools/ironclaw_stress/src/tests.rs
  • tools/ironclaw_stress/src/user_turn.rs

Comment thread tools/ironclaw_stress/src/api_capacity.rs
Comment thread tools/ironclaw_stress/src/api_capacity.rs Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5781 July 8, 2026 13:07 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed the review comments in 9c241e8706eb24095c88a12cdaaf55265bbdb905.

Fixed Review Feedback

Reviewer Direct quote Fix
gemini-code-assist “Sleeping for a constant duration” Switched read workers to tokio::time::interval with delayed missed ticks and clamped invalid/huge intervals.
coderabbitai “stop signal handling should not rely solely on Notify::notified()” Added remembered ReadShutdown state so workers observe shutdown before waiting and after in-flight reads.
gemini-code-assist “using unwrap_or_default() on response.text().await can hide” Body read errors now produce distinct api_http_body failure causes.
coderabbitai “don't swallow the body-read error” Same api_http_body handling records the correct failure bucket.
gemini-code-assist “content_length is extremely large” Added a 1 MiB mock LLM request body limit and checked request-length arithmetic before reading.
gemini-code-assist “jitter_ms + 1 will overflow” Replaced the unchecked add with saturating_add(1) and added max-ceiling coverage.
gemini-code-assist “If args.users is 0” No code change: current validate_args rejects --users 0 before setup_users; line 993 returns --users must be greater than 0.
gemini-code-assist “blocking task continues to run in the background” No code change in this commit: that cancellation path is broader than the API capacity harness fixes and does not have an observed harness cancellation path here.

Validation

  • cargo fmt
  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target-pr5781 cargo test -p ironclaw_stress
  • git diff --check
  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target-pr5781 cargo clippy -p ironclaw_stress --all-targets -- -D warnings
  • CARGO_TARGET_DIR=/Volumes/NVME/ironclaw-target-pr5781 cargo build -p ironclaw_stress --release

GitHub checks: pending after push. PR remains merge-conflicting against main.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-5781 July 8, 2026 13:23 Destroyed
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.16% (282152 / 331314 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 331314 lines now vs 320188 at floor capture (+11126 lines, +3.47%) — not a material change

⚠️ 3 Reborn crate(s) have 0 int-tier coverage (target: 0) — ironclaw_prompt_envelope, ironclaw_scripts, ironclaw_skill_learning

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.16% — 282152 / 331314 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 347
ironclaw_skill_learning 0% 0 / 61
ironclaw_wasm_sandbox_core 7.37% 7 / 95
ironclaw_runtime_policy 33.2% 80 / 241
ironclaw_event_projections 43.34% 673 / 1553
ironclaw_run_state 52.36% 222 / 424
ironclaw_authorization 53.54% 461 / 861
ironclaw_triggers 59.99% 1736 / 2894
ironclaw_observability 61.54% 16 / 26
ironclaw_webui_v2 63.12% 2543 / 4029
ironclaw_mcp 63.15% 581 / 920
ironclaw_reborn_cli 63.41% 3816 / 6018
ironclaw_reborn_migration 67.01% 1172 / 1749
ironclaw_memory 67.12% 747 / 1113
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_filesystem 67.44% 3815 / 5657
ironclaw_trust 72.88% 661 / 907
ironclaw_capabilities 74.08% 1658 / 2238
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.61% 958 / 1284
ironclaw_extractors 74.72% 538 / 720
ironclaw_first_party_extensions 77.62% 5411 / 6971
ironclaw_llm 77.88% 19479 / 25013
ironclaw_product_context 78.57% 11 / 14
ironclaw_wasm_product_adapters 80.58% 1510 / 1874
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_reborn_openai_compat 80.95% 956 / 1181
ironclaw_memory_native 81.86% 3226 / 3941
ironclaw_wasm 82.54% 950 / 1151
ironclaw_secrets 82.7% 2791 / 3375
ironclaw_events 83.47% 1762 / 2111
ironclaw_processes 84.06% 965 / 1148
ironclaw_turns 84.24% 13099 / 15549
ironclaw_host_api 85.17% 2549 / 2993
ironclaw_product_workflow 85.74% 10624 / 12391
ironclaw_projects 85.92% 659 / 767
ironclaw_network 86.12% 670 / 778
ironclaw_threads 86.45% 4021 / 4651
ironclaw_common 86.59% 1472 / 1700
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_auth 86.96% 2995 / 3444
ironclaw_reborn_config 86.98% 1730 / 1989
ironclaw_reborn_identity 87.03% 557 / 640
ironclaw_product_adapters 87.29% 3207 / 3674
ironclaw_skills 87.37% 4337 / 4964
ironclaw_hooks 87.84% 9916 / 11289
ironclaw_product_adapter_registry 87.96% 526 / 598
ironclaw_reborn_traces 88.23% 11707 / 13268
ironclaw_extensions 88.26% 2631 / 2981
ironclaw_reborn_composition 88.77% 69708 / 78529
ironclaw_host_runtime 88.94% 17522 / 19700
ironclaw_reborn 89.14% 15703 / 17616
ironclaw_conversations 90% 2924 / 3249
ironclaw_approvals 90.51% 1507 / 1665
ironclaw_event_streams 91.48% 1009 / 1103
ironclaw_loop_support 92.46% 14723 / 15924
ironclaw_resources 93.05% 4607 / 4951
ironclaw_attachments 93.06% 630 / 677
ironclaw_reborn_webui_ingress 93.19% 2217 / 2379
ironclaw_telegram_v2_adapter 94.01% 2447 / 2603
ironclaw_agent_loop 94.58% 8776 / 9279
ironclaw_safety 94.8% 3668 / 3869
ironclaw_first_party_extension_ports 95% 3094 / 3257
ironclaw_outbound 95.59% 3556 / 3720

This table itself is informational and never gates the PR on its own — not the percentage, not the per-crate holes, not the 0-coverage callout. A separate coverage ratchet (dry-run until enforce=true; see tests/integration/coverage-floor.toml) can fail the build on specific configured floors.

Exemptions (4 entry/entries excluded from the accounting above)
Module / Crate Reason Issue
crate: ironclaw_embeddings v1-only: consumed only by root ironclaw (src/app.rs, src/tools/builtin/memory.rs, src/workspace/mod.rs, src/config/{mod,embeddings}.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_gateway v1-only: consumed only by root ironclaw (src/channels/web/platform/static_files.rs, src/channels/web/handlers/frontend.rs); no crates/* dependents. Covered by "Tests (Legacy)". #5657
crate: ironclaw_oauth v1-only: consumed only by root ironclaw (src/auth/oauth.rs); no crates/* dependents. Crate's own doc comment confirms v1-only. Covered by "Tests (Legacy)". #5657
crate: ironclaw_tui v1-only: consumed only by root ironclaw (src/main.rs, src/channels/tui.rs); no crates/* dependents. Crate's own doc comment confirms it bridges INTO v1, not Reborn. Covered by "Tests (Legacy)". #5657

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-5781 — 0898d1ce Deployed Jul 8, 2026 by railway-app[bot]
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: low Changes to docs, tests, or low-risk modules scope: dependencies Dependency updates size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant