Skip to content

test(reborn): StaleSurface same-run refresh pin + extension-remove channel-cleanup integration coverage - #6055

Closed
henrypark133 wants to merge 3 commits into
mainfrom
coverage-p2-salvage
Closed

henrypark133 wants to merge 3 commits into
mainfrom
coverage-p2-salvage

Conversation

@henrypark133

Copy link
Copy Markdown
Collaborator

What this adds

Integration-tier coverage only — no production behavior change. Follow-up to the harness port-seam program (#5950, #6026): now that the integration harness consumes production's LocalDev capability-port factory, these tests pin two previously-uncovered production paths through it.

1. StaleSurface same-run refresh (new scenario 6 in reborn_group_extensions)

One run performs builtin.extension_install → builtin.extension_activate → google-calendar.list_calendars. Pins the production refresh mechanism end-to-end: SurfaceTrackingLoopCapabilityPort::capability_may_change_visible_surface clears the cached surface on activate, and the next loop iteration's visible_capabilities rebuild picks up the newly-activated extension — the capability dispatches in the same run instead of failing as stale/unknown.

Mutation-verified: forcing capability_may_change_visible_surface to false fails exactly this scenario (capability "google-calendar.list_calendars" was not invoked).

2. Extension-remove channel cleanup (extended scenario 7 in reborn_group_extensions)

#5851 unified channel cleanup on remove (extension_lifecycle.rs::remove → cleanup_channel_before_remove → disconnect_channel_for_cleanup) but shipped with zero default-CI coverage (its positive crate test is slack-v2-host-beta-gated). This backfills at the integration tier, both directions in one scenario:

  • Positive: removing slack (RemovableChannelCleanup::Required) disconnects the channel exactly once.
  • Negative: removing google-drive (credentialed, non-channel → IfConnectionFacadeSupportsChannel, no connection-map key) fires zero disconnects.

Final assertion is exact equality disconnects == [(user, "slack")], so both regressions are discriminating: neutralizing cleanup_channel_before_remove (reproducing #5851's original bug) drops the slack tuple; forcing should_disconnect = true unconditionally adds a google-drive tuple. Both mutations were run and failed this scenario only.

Support changes

  • tests/integration/support/doubles/recording_channel_connection_facade.rs: recording ChannelConnectionFacade double (salvaged from the parked ext-remove worktree; its production changes were superseded by Unify Slack cleanup for extension remove tool #5851 and are NOT included).
  • RebornServices::set_channel_connection_facade_for_test (#[cfg(feature = "test-support")]): fills the existing channel_connection_facade_slot OnceLock that production already carries — zero new production logic. Shared pub(crate) recipe fill_channel_connection_facade_slot backs both this accessor and the production RebornRuntime::set_channel_connection_facade so the two can't drift.

Investigated and deliberately not covered here

  • External-tools park/resume: register/submit/resume lives on the OpenAI-compat serve HTTP surface (openai_compat_serve.rs), unreachable via submit_turn; production chat wires an empty InMemoryExternalToolCatalog by design. Full loop is covered at crate tier where the surface actually lives. Adding harness injection would be test-only wiring for a path production chat never runs.
  • Real CapabilitySurfaceProfileResolver: production LocalDev resolver is a private AllowAllCapabilitySurfaceResolver with a single branchless behavior the existing harness double reproduces exactly; swapping it in requires new public surface for zero behavioral difference.
  • Issue Channel disconnect on extension removal is broken for generic ExternalChannel extensions (non-Slack) #5953 (generic ExternalChannel disconnect gap) remains open, out of scope.

Verification

  • cargo test --test reborn_group_extensions: 13/13 (scenarios 1–7 inside extensions_group_e2e)
  • Spot-runs: reborn_integration_surface_disclosure 17/17, reborn_integration_tool_call 23/23, reborn_integration_wiring_parity 17/17
  • cargo clippy --all --benches --tests --examples --all-features: zero warnings
  • cargo test -p ironclaw_reborn_composition --features test-support --lib: 1137/1137
  • Feature-matrix compile check on the shared recipe: no/test-support/slack-v2-host-beta/both — warning-free

🤖 Generated with Claude Code

henrypark133 and others added 2 commits July 13, 2026 10:34
…el cleanup (#5950/#6026 salvage, #5851 salvage)

A1: prove RefreshingLocalDevCapabilityPort's surface refresh makes a
just-activated extension's capability dispatchable within the SAME run
(install -> activate -> dispatch in one turn), not just across a new run
like scenario 5 already covered.

B: salvage int-tier coverage for #5851's extension-remove channel cleanup
(extension_lifecycle.rs::remove -> cleanup_channel_before_remove ->
disconnect_channel_for_cleanup), previously only pinned by a
slack-v2-host-beta-gated crate test. Adds a test-support accessor to fill
the harness's deferred ChannelConnectionFacade slot, plus positive (slack,
Required-kind, unconditional disconnect) and negative (google-drive,
IfConnectionFacadeSupportsChannel, facade-gated disconnect) scenarios.

All four new scenarios are mutation-verified against their production call
sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vage

Fold scenario 8 (google-drive, no-disconnect) into scenario 7 as a second
install/remove pair in the shared group, keeping the negative case exact-
equality-checked against the same disconnects() vector instead of standing
up a second group boot for one assertion (consolidate-don't-proliferate).

Extract the byte-identical facade-slot-fill body shared by
RebornRuntime::set_channel_connection_facade (prod, slack-v2-host-beta) and
the test-only accessor into one RebornServices::fill_channel_connection_facade_slot
recipe so the two can never drift, and condense two over-long module/doc
comments to invariant + issue-ref.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 13, 2026 17:59

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6055 July 13, 2026 17:59 Destroyed
@github-actions github-actions Bot added size: S 10-49 changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Removing a channel extension now correctly disconnects supported channel connections while leaving unsupported connections unchanged.
    • Newly activated Google Calendar capabilities are available immediately within the same workflow.
  • Tests

    • Added coverage for channel disconnection behavior during extension removal.
    • Added integration coverage confirming Google Calendar installation, activation, and tool usage succeed together.

Walkthrough

The change centralizes channel facade slot initialization, adds test-support access, introduces a recording facade double, and expands integration coverage for channel-extension removal cleanup and same-run Google Calendar activation and dispatch.

Changes

Extension lifecycle behavior

Layer / File(s) Summary
Centralize facade slot filling
crates/ironclaw_reborn_composition/src/factory.rs, crates/ironclaw_reborn_composition/src/runtime.rs, crates/ironclaw_reborn_composition/src/runtime/test_support.rs
RebornServices now owns idempotent channel facade slot filling; runtime and test-support callers delegate to it, with tests covering occupied and unavailable slots.
Add recording channel facade
tests/integration/support/doubles/*
The integration test double exposes configured channel connections and records disconnect calls.
Exercise channel removal cleanup
tests/integration/group_extensions/*
The group-extension suite runs Slack and Google Drive removal scenarios and verifies conditional disconnect behavior.
Verify same-run activation visibility
tests/integration/extension_visibility.rs
The integration test verifies Google Calendar installation, activation, capability dispatch, and the resulting reply within one run.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: copilot, serrrfirat, benkurrek, ilblackdragon, hanakannzashi

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers intent and verification, but it omits most required template sections such as Change Type, Linked Issue, and Rollback Plan. Rewrite it to match the template: add Summary bullets, Change Type, Linked Issue, Security Impact, Trust-Boundary Checklist, Database Impact, Blast Radius, Rollback Plan, Review Follow-Through, and Review track.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is conventional-commit styled and accurately summarizes the two integration coverage additions.
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.

@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 refactors the channel-connection facade slot management by centralizing its setup in RebornServices and introducing test-only accessors. It also adds integration tests to verify channel disconnection on extension removal and capability surface refreshing within a single run. The review feedback suggests simplifying the test double by removing a redundant Arc indirection, cleaning up assertions in the new integration tests to avoid unnecessary cloning, and serializing tests that modify shared global state using a mutex to prevent race conditions.

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 +17 to +32
#[derive(Default)]
pub(crate) struct RecordingChannelConnectionFacade {
connections: HashMap<String, bool>,
disconnects: Arc<Mutex<Vec<(String, String)>>>,
}

impl RecordingChannelConnectionFacade {
pub(crate) fn with_connections(entries: &[(&str, bool)]) -> Self {
Self {
connections: entries
.iter()
.map(|(channel, connected)| ((*channel).to_string(), *connected))
.collect(),
disconnects: Arc::new(Mutex::new(Vec::new())),
}
}

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

The disconnects field is wrapped in an Arc (Arc<Mutex<Vec<(String, String)>>>), but RecordingChannelConnectionFacade itself is already instantiated and shared via Arc<RecordingChannelConnectionFacade> (and coerced to Arc<dyn ChannelConnectionFacade>) in the test. This results in a redundant double-Arc indirection.

We can simplify this by removing the inner Arc and making disconnects a direct Mutex<Vec<(String, String)>>.

Suggested change
#[derive(Default)]
pub(crate) struct RecordingChannelConnectionFacade {
connections: HashMap<String, bool>,
disconnects: Arc<Mutex<Vec<(String, String)>>>,
}
impl RecordingChannelConnectionFacade {
pub(crate) fn with_connections(entries: &[(&str, bool)]) -> Self {
Self {
connections: entries
.iter()
.map(|(channel, connected)| ((*channel).to_string(), *connected))
.collect(),
disconnects: Arc::new(Mutex::new(Vec::new())),
}
}
#[derive(Default)]
pub(crate) struct RecordingChannelConnectionFacade {
connections: HashMap<String, bool>,
disconnects: Mutex<Vec<(String, String)>>,
}
impl RecordingChannelConnectionFacade {
pub(crate) fn with_connections(entries: &[(&str, bool)]) -> Self {
Self {
connections: entries
.iter()
.map(|(channel, connected)| ((*channel).to_string(), *connected))
.collect(),
disconnects: Mutex::new(Vec::new()),
}
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 62bc6c8 — inner Arc removed, disconnects is now a direct Mutex<Vec<..>>.

Comment on lines +104 to +113
let expected_user = capability_harness.capability_user_id().as_str().to_string();
let disconnects = facade.disconnects();
if disconnects != vec![(expected_user.clone(), "slack".to_string())] {
return Err(format!(
"builtin.extension_remove must disconnect the run owner's slack channel binding \
exactly once (Required cleanup) and never google-drive \
(IfConnectionFacadeSupportsChannel, key absent); expected \
[({expected_user:?}, \"slack\")], got {disconnects:?}"
)
.into());

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

We can simplify the assertion and avoid cloning expected_user by defining the expected vector directly and asserting against it. This also makes the error message cleaner by formatting the entire expected vector.

Suggested change
let expected_user = capability_harness.capability_user_id().as_str().to_string();
let disconnects = facade.disconnects();
if disconnects != vec![(expected_user.clone(), "slack".to_string())] {
return Err(format!(
"builtin.extension_remove must disconnect the run owner's slack channel binding \
exactly once (Required cleanup) and never google-drive \
(IfConnectionFacadeSupportsChannel, key absent); expected \
[({expected_user:?}, \"slack\")], got {disconnects:?}"
)
.into());
let expected_user = capability_harness.capability_user_id().as_str().to_string();
let disconnects = facade.disconnects();
let expected = vec![(expected_user, "slack".to_string())];
if disconnects != expected {
return Err(format!(
"builtin.extension_remove must disconnect the run owner's slack channel binding \
exactly once (Required cleanup) and never google-drive \
(IfConnectionFacadeSupportsChannel, key absent); expected \
{expected:?}, got {disconnects:?}"
)
.into());
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 62bc6c8 — expected vec built once, compared directly, error message formats {expected:?}.

Comment on lines +31 to +33
if !services.set_channel_connection_facade_for_test(facade.clone()) {
return Err("channel-connection facade slot already filled or no local runtime".into());
}

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

Since services is shared across all scenarios in extensions_group_e2e(), setting the OnceLock via set_channel_connection_facade_for_test modifies shared global state. To prevent race conditions and flakiness when tests are run in parallel, ensure that tests modifying this shared global state are serialized using a mutex.

References
  1. Tests that modify shared global state should be serialized using a mutex to prevent race conditions and flakiness when run in parallel.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Declined — no parallelism exists here. All group scenarios run sequentially inside the single extensions_group_e2e #[tokio::test] (report.record(scenario::run(&g).await) in order), and channel_connection_facade_slot is a per-runtime-instance Arc<OnceLock<..>> (constructed per RebornLocalRuntimeServices build), not process-global state. A serializing mutex would guard against a race that cannot occur.

@github-actions

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 85.38% (296976 / 347845 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 347845 lines now vs 320188 at floor capture (+27657 lines, +8.64%) — material change (>5%)

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

Reborn integration-tier coverage

Line coverage (Reborn crates): 85.38% — 296976 / 347845 lines

Per-crate breakdown (63 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_runtime_policy 31.75% 80 / 252
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_run_state 53.07% 225 / 424
ironclaw_authorization 53.89% 464 / 861
ironclaw_triggers 59.89% 1792 / 2992
ironclaw_observability 61.54% 16 / 26
ironclaw_webui_v2 62.93% 2679 / 4257
ironclaw_mcp 63.03% 578 / 917
ironclaw_reborn_cli 66.2% 4492 / 6785
ironclaw_reborn_migration 67.09% 1215 / 1811
ironclaw_filesystem 67.1% 3833 / 5712
ironclaw_dispatcher 67.15% 92 / 137
ironclaw_memory 69.2% 773 / 1117
ironclaw_trust 72.88% 661 / 907
ironclaw_capabilities 74.39% 1685 / 2265
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_reborn_event_store 74.67% 958 / 1283
ironclaw_extractors 74.72% 538 / 720
ironclaw_first_party_extensions 78.23% 5439 / 6953
ironclaw_llm 78.36% 20328 / 25941
ironclaw_product_context 78.57% 11 / 14
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_wasm_product_adapters 80.71% 1448 / 1794
ironclaw_memory_native 81.22% 3205 / 3946
ironclaw_secrets 82.7% 2791 / 3375
ironclaw_wasm 82.72% 996 / 1204
ironclaw_events 82.86% 1765 / 2130
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_auth 83.87% 3078 / 3670
ironclaw_reborn_config 84.06% 1814 / 2158
ironclaw_processes 84.44% 993 / 1176
ironclaw_common 84.85% 1490 / 1756
ironclaw_turns 84.94% 13575 / 15982
ironclaw_host_api 85.17% 2664 / 3128
ironclaw_product_workflow 85.56% 10836 / 12665
ironclaw_projects 85.92% 659 / 767
ironclaw_threads 86.07% 4404 / 5117
ironclaw_network 86.12% 670 / 778
ironclaw_slack_v2_adapter 86.79% 1806 / 2081
ironclaw_product_adapters 86.98% 3207 / 3687
ironclaw_skills 87.58% 4470 / 5104
ironclaw_hooks 87.75% 9917 / 11302
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_reborn_traces 88.19% 11946 / 13546
ironclaw_host_runtime 88.53% 17271 / 19509
ironclaw_reborn_composition 89.03% 76509 / 85941
ironclaw_extensions 89.03% 2864 / 3217
ironclaw_runner 89.28% 16696 / 18700
ironclaw_approvals 89.41% 1587 / 1775
ironclaw_reborn_openai_compat 89.46% 3768 / 4212
ironclaw_conversations 90.33% 3120 / 3454
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_loop_host 92.51% 14755 / 15950
ironclaw_resources 92.75% 4732 / 5102
ironclaw_attachments 93.06% 630 / 677
ironclaw_reborn_webui_ingress 93.19% 2217 / 2379
ironclaw_telegram_v2_adapter 93.62% 2511 / 2682
ironclaw_agent_loop 94.57% 8789 / 9294
ironclaw_safety 94.88% 3671 / 3869
ironclaw_first_party_extension_ports 95.24% 3343 / 3510
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 (3 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_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

@railway-app

railway-app Bot commented Jul 13, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 13, 2026 at 10:29 pm

@henrypark133 henrypark133 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review (multi-agent)

Intent: Add integration coverage for same-run stale-surface refresh and extension-removal channel cleanup without changing production behavior.

Stats: 2 findings (from 2 raw, 2 after dedup) across 2 files. Reviewers run: security, performance, tests. Reviewer dispatch unavailable: bugs, conventions, local-patterns, maintainability, approach (agent-thread capacity). Body-only: 0.

Tests

  1. Medium Facade slot failure paths are untested (crates/ironclaw_reborn_composition/src/factory.rs:542-552, confidence 90) — anchor: crates/ironclaw_reborn_composition/src/factory.rs:542.

    The new shared setter returns false when no local runtime exists and when the OnceLock is already occupied, but current tests only assert the first successful set. These branches protect production/test wiring and idempotence semantics.

  2. Medium Keep the single-thread scenario out of the group test (tests/integration/group_extensions/main.rs:27, confidence 100) — anchor: tests/integration/CLAUDE.md:411-412.

    same_run_activation_refresh_dispatches submits and asserts one thread only, while the integration-test guide requires one-thread scenarios to be flat tests; group tests are reserved for shared state or shared-runtime behavior.

Arc::clone(&self.secret_store)
}

/// Fill the deferred per-caller channel-connection facade slot

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Facade slot failure paths are untested.

The new shared setter returns false when no local runtime exists and when the OnceLock is already occupied, but current tests only assert the first successful set. These branches protect production/test wiring and idempotence semantics.

Fix: Add a focused factory test covering no-local-runtime and already-filled-slot cases.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 62bc6c8 — added fill_channel_connection_facade_slot_rejects_second_fill_and_keeps_first (second fill → false, first facade's Arc pointer identity preserved) and fill_channel_connection_facade_slot_without_local_runtime_returns_false (via RebornServices::disabled(), fail-closed).

);

// Scenario 6 (harness-port-seam P2, A1): install -> activate -> dispatch
// within ONE run, proving the RefreshingLocalDevCapabilityPort surface

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Medium — Keep the single-thread scenario out of the group test.

The new same_run_activation_refresh_dispatches scenario submits and asserts one thread only, but the integration-test guide requires scenarios that submit and assert in one thread to be flat tests, reserving groups for shared state or shared-runtime behavior.

Fix: Move this scenario to its own flat integration test binary, or make it exercise shared group state/runtime behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 62bc6c8 — scenario moved out of the group into the flat tests/integration/extension_visibility.rs binary per the guide rule (one-thread submit+assert = flat test). Discriminating property preserved (google-calendar inactive at start, absent from initial surface); mutation re-verified after the move: forcing capability_may_change_visible_surface to false fails exactly this test.

…ation, slot failure-path pins

- recording_channel_connection_facade.rs: drop redundant inner Arc<Mutex<..>>
  (the facade is already shared via the outer Arc callers hold).
- scenario_remove_channel_extension_disconnects_channel.rs: build an
  `expected` vec and compare/format against it instead of re-cloning
  expected_user into the message.
- Move the single-thread same-run-activation-refresh scenario out of
  group_extensions into a flat #[tokio::test] in extension_visibility.rs,
  per tests/integration/CLAUDE.md; delete the scenario file + its
  group_extensions/main.rs registration.
- factory.rs: add two focused tests pinning
  fill_channel_connection_facade_slot's failure paths — second fill on an
  occupied slot returns false and keeps the first facade, and no local
  runtime returns false rather than panicking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 13, 2026 22:21
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6055 July 13, 2026 22:21 Destroyed

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added size: M 50-199 changed lines and removed size: S 10-49 changed lines labels Jul 13, 2026

@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: 1

🤖 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 `@tests/integration/extension_visibility.rs`:
- Around line 76-126: Reduce the body of
same_run_activation_refresh_dispatches_newly_activated_capability by extracting
the scripted replies and credential seeding into a focused helper, while
preserving the existing lifecycle setup and assertions. Keep the test’s main
flow in the documented build → submit_turn → assert shape, with the helper
returning or preparing the configured harness before submission.
🪄 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: 18a022a5-b3cb-4fce-b037-cf9f8f595184

📥 Commits

Reviewing files that changed from the base of the PR and between 06718e9 and 62bc6c8.

📒 Files selected for processing (5)
  • crates/ironclaw_reborn_composition/src/factory.rs
  • tests/integration/extension_visibility.rs
  • tests/integration/group_extensions/main.rs
  • tests/integration/group_extensions/scenario_remove_channel_extension_disconnects_channel.rs
  • tests/integration/support/doubles/recording_channel_connection_facade.rs

Comment on lines +76 to +126
#[tokio::test]
async fn same_run_activation_refresh_dispatches_newly_activated_capability() {
let group = RebornIntegrationGroup::extension_lifecycle()
.await
.expect("extension-lifecycle group builds");
let h = group
.thread("ext-same-run-activation-refresh")
.script([
RebornScriptedReply::tool_call(
"builtin.extension_install",
json!({"extension_id": "google-calendar"}),
),
RebornScriptedReply::tool_call(
"builtin.extension_activate",
json!({"extension_id": "google-calendar"}),
),
RebornScriptedReply::tool_call("google-calendar.list_calendars", json!({})),
RebornScriptedReply::text("calendars listed"),
])
.build()
.await
.expect("thread builds");
// Credential material must exist BEFORE submit_turn — activation's
// credential gate resolves inline only if an account is already seeded,
// else the run would park at BlockedAuth instead of completing this turn.
h.seed_capability_credential_account(
"google",
"itest google calendar",
&[GOOGLE_CALENDAR_READONLY_SCOPE, GOOGLE_CALENDAR_EVENTS_SCOPE],
)
.await
.expect("credential account seeds");

h.submit_turn("install, activate, and list my calendars")
.await
.expect("turn completes");
h.assert_tool_result_contains("\"installed\":true")
.await
.expect("install reported success");
h.assert_tool_result_contains("\"activated\":true")
.await
.expect("activate reported success");
// The discriminating proof: dispatch reached the capability port in the
// SAME run that activated it, i.e. the surface refresh fired.
h.assert_tool_invoked("google-calendar.list_calendars")
.await
.expect("newly-activated capability dispatched within the same run");
h.assert_reply_contains("calendars listed")
.await
.expect("run completed with the expected reply");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test body far exceeds the flat-integration-test size guidance.

The function body runs ~48 lines against the documented "keep the body small and readable (about 3–12 lines)" convention for tests/integration/*.rs. The multi-step lifecycle chain (install → activate → seed credential → dispatch → 4 assertions) plausibly justifies more than 12 lines, but consider extracting the script/credential setup into a small helper to bring the submit_turn/assert portion closer to the documented shape.

As per path instructions, "Write Reborn integration tests as flat tests/integration/<name>.rs binaries that follow the build → submit_turn → assert shape, use RebornIntegrationHarness, and keep the body small and readable (about 3–12 lines)."

🤖 Prompt for 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.

In `@tests/integration/extension_visibility.rs` around lines 76 - 126, Reduce the
body of same_run_activation_refresh_dispatches_newly_activated_capability by
extracting the scripted replies and credential seeding into a focused helper,
while preserving the existing lifecycle setup and assertions. Keep the test’s
main flow in the documented build → submit_turn → assert shape, with the helper
returning or preparing the configured harness before submission.

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6055 — 62bc6c8a Deployed Jul 13, 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 size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants