Skip to content

Remove ProductWorkflow from channel ingress - #6558

Merged
ilblackdragon merged 17 commits into
mainfrom
codex/reborn-product-surface-review-fixes-refresh-2
Jul 23, 2026
Merged

ilblackdragon merged 17 commits into
mainfrom
codex/reborn-product-surface-review-fixes-refresh-2

Conversation

@ilblackdragon

@ilblackdragon ilblackdragon commented Jul 23, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Merged latest origin/main (62a4907338a5f0d5a0cbad4001b3858e15077c38) into this PR branch.
  • Continued the ProductWorkflow removal by keeping channel/WebUI/CLI ingress on ProductSurface and moving API-only product operations through durable ProductSurface replay by activity/invocation ID.
  • Addressed review feedback for lost-response retries: extension lifecycle/setup, LLM provider upsert, and outbound preferences now use validated opaque client action IDs; generic ProductSurface mutations without an explicit action key now receive fresh activity IDs instead of permanent value-derived replay keys.
  • Fixed ProductSurface replay locking so queued waiters cannot race with lock cleanup, persisted operation-specific replay summaries, made unreadable optional summary sidecars fall back to legacy replay summaries, and added replay coverage for API-only operation success.
  • Addressed E2E review comments by adding Google Drive upload name/content readback and making GitHub release capability probes non-mutating.

Change Type

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI/Infrastructure
  • Security
  • Dependencies

Linked Issue

None.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all --benches --tests --examples --all-features -- -D warnings
  • cargo build
  • Relevant tests pass: see Commands run below.
  • cargo test --features integration if database-backed or integration behavior changed
  • Manual testing: inspected unresolved PR review threads and verified the branch is merged onto latest origin/main.
  • If a coding agent was used and supports it, review-pr or pr-shepherd --fix was run before requesting review: Not applicable; review comments were inspected and addressed manually.

Test Strategy

User behavior:
Product ingress uses ProductSurface as the product-facing boundary. Browser retries for extension setup/lifecycle, provider upsert, and outbound preference updates replay by opaque client action ID instead of request values or secrets. Generic product mutations without client action IDs avoid permanent value-derived replay caching.

Risk areas:

  • Model behavior
  • Browser
  • Side effect
  • Persistence
  • Security or permissions
  • External provider
  • Cross-component behavior

Tests added or updated:

  • Unit or contract: Product operation replay tests, unreadable product result summary sidecar replay fallback, WebUI handler activity-ID tests, WebUI ProductSurface contract tests, frontend API serialization tests, provider contract syntax/behavior helpers.
  • Reborn integration: targeted composition/product workflow tests for ProductSurface operation replay and LLM provider upsert request shape.
  • Recorded fixture: Not applicable: no model request-shape or tool-choice fixture changed.
  • Browser E2E: Reborn WebUI v2 Playwright smoke file passed after the latest-main merge.
  • Backend or runtime: clippy and targeted Rust tests for ironclaw_product_workflow, ironclaw_reborn_composition, and ironclaw_webui passed.
  • Live canary: Not applicable: no live provider credentials or external live canary path changed.

What the tests prove:
API-only ProductSurface operations can persist/replay operation-specific success summaries, unreadable optional summary sidecars do not fail replay of already-persisted product results, activity IDs are scoped to validated client action IDs where retry semantics are needed, generic mutations do not permanently cache repeated value payloads, frontend callers serialize generated/explicit client action IDs, Google Drive upload rewrites are verified by provider readback, and GitHub release probes do not seed provider state.

Commands run:

  • cargo fmt --all
  • cargo fmt --all -- --check
  • git diff --check
  • cargo test -p ironclaw_webui activity_id -- --nocapture
  • cargo test -p ironclaw_webui skill_content_and_mutations_use_product_surface -- --nocapture
  • cargo test -p ironclaw_webui set_outbound_preferences_dispatches_body_through_invoke -- --nocapture
  • cargo test -p ironclaw_webui --features test-support --test webui_v2_handlers_contract -- --nocapture
  • cargo test -p ironclaw_reborn_composition product_result_replay_ignores_unreadable_summary_sidecar -- --nocapture
  • cargo test -p ironclaw_reborn_composition product_operation_resolution_persists_replayable_success -- --nocapture
  • cargo test -p ironclaw_reborn_composition --features test-support --test webui_v2_e2e operator_llm_config::nearai_provider_save_persists_key_and_survives_resave -- --nocapture
  • cargo test -p ironclaw_product_workflow upsert_llm_provider_allows_loopback_base_url_for_self_hosted -- --nocapture
  • cargo clippy -p ironclaw_product_workflow -p ironclaw_reborn_composition -p ironclaw_webui --all-targets --all-features -- -D warnings
  • cargo clippy -p ironclaw_reborn_composition --all-targets --all-features -- -D warnings
  • corepack pnpm vitest run src/lib/api.test.ts src/pages/settings/lib/settings-api.test.ts
  • corepack pnpm lint:conventions
  • corepack pnpm typecheck
  • python3 -m py_compile tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py tests/e2e/scenarios/test_reborn_qa_trace_full_path.py
  • tests/e2e/.venv/bin/python -m pytest tests/e2e/scenarios/test_reborn_webui_v2_smoke.py -q (29 passed)

Note: corepack pnpm lint was attempted, but the package script shells out to pnpm and this environment exposes pnpm only through Corepack. The underlying script steps, lint:conventions and typecheck, both passed via corepack pnpm.

Security Impact

No new authentication bypass, network access, or secret exposure. Retry identities now use validated opaque client action IDs for product mutations that need replay, and raw API key values are not seeded into activity IDs. Product replay stores bounded result bytes under the existing scoped /product-results root.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: who can construct them? ProductSurface remains the product ingress boundary; WebUI action IDs are validated before activity-ID construction.
  • Untrusted content enters prompts only through an envelope/escaping primitive. Not applicable: no prompt construction path changed.
  • Hashes declare purpose; trust/binding/authenticity uses SHA-256/BLAKE3 or separate authenticity check. Activity IDs are purpose-scoped and caller/capability/action-bound; generic value hashing was removed from the fallback mutation path.
  • New/changed status, exit, policy, runtime, or error variants: downstream match sites audited. No variants added or changed.
  • Security/durability serde(default) fields fail closed or have migration tests. Optional client action fields are validated at ingress and covered by handler/frontend tests.
  • Queues/maps/buffers/counters have bounds and overflow-safe arithmetic. Product result replay reads are bounded by PRODUCT_RESULT_MAX_BYTES; per-activity locks now avoid removing entries while waiters may still exist.
  • Driver/operator-visible errors have stable class semantics (Transient, Permanent, Misconfigured, PolicyDenied or equivalent). Existing validation/error mapping retained.
  • Sandbox/native/host names accurately describe trust boundary. ProductSurface naming retained.

Database Impact

None. No schema migration is included. Durable ProductSurface replay uses the existing filesystem-backed scoped product result storage.

Blast Radius

Touches ProductSurface capability/operation invocation, WebUI product mutation activity IDs, frontend request serialization for setup/outbound/provider settings, and E2E provider trace/readback helpers. A regression would most likely appear as duplicate product mutation behavior, extension setup/lifecycle retry behavior, provider upsert retries, outbound preferences saves, or provider fixture readback/probe failures.

Rollback Plan

Revert this PR and restore the prior ProductSurface/ProductWorkflow ingress contract while replay semantics are revisited. No database schema rollback is required.

Review Follow-Through

  • IronLoop: addressed API-only extension_setup_submit durable replay, permanent value-derived activity keys, ProductSurface lock cleanup races, and Google Drive upload readback.
  • CodeRabbit: addressed generated setup action ID coverage, strongly typed client action IDs, API-key rotation replay identity, API-only operation replay summaries, unreadable summary sidecar replay fallback, setup-extension retry contract coverage, Google Drive readback, and non-mutating GitHub release probes.
  • Remaining GitHub review threads may still show unresolved until reviewers or bots mark them resolved after the latest push.

Review track: C (runtime/permissions/browser CI)

@github-actions github-actions Bot added size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 23, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@ironloopai

ironloopai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: ee36c1bdecf2325ef01a9930d07e0983253d18cc
Result: One or more review results were superseded by a newer PR head.
Next: Run @ironloopai review on the latest PR head.
Updated: 2026-07-23T18:16:31.136Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-23T17:03:00.375Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: 559ac4e. Previous verdict: Changes requested.
Recent activity
Time Reviewer State Detail
2026-07-23T16:19:27.986Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (8a28953).
2026-07-23T16:20:11.891Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head 8a28953.
2026-07-23T16:20:11.891Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-23T16:20:12.393Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-23T16:20:15.019Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (merge_ref) at 4ec025b.
2026-07-23T16:31:23.182Z ironloop/common-reviewer (reviewer) Result captured Changes requested; 1 blocking finding.
2026-07-23T16:31:23.182Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-23T17:03:00.375Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (559ac4e).
Available commands
  • @ironloopai help
  • @ironloopai agents
  • @ironloopai review
  • @ironloopai review --agent <agent>
Run metadata

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 09:13 Destroyed
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR propagates client_action_id through WebUI extension, outbound-preference, and LLM-provider mutations, derives deterministic activity IDs, adds replay and per-activity serialization, updates extension lifecycle mounting/removal, and expands frontend, Rust, integration, and E2E coverage.

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

Possibly related issues

Possibly related PRs

Suggested reviewers: benkurrek

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main change: removing ProductWorkflow from channel ingress.
Description check ✅ Passed The description follows the template and covers summary, type, validation, test strategy, security, rollback, and review notes.

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.

@github-actions

github-actions Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 86.24% (310399 / 359935 lines)
  floor:    86.27% (tolerance 0.5pp -> effective floor 85.77%)
  denominator: 359935 lines now vs 354049 at floor capture (+5886 lines, +1.66%) — not a material change

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

Reborn integration-tier coverage

Line coverage (Reborn crates): 86.24% — 310399 / 359935 lines

Per-crate breakdown (62 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_telegram_extension 34.88% 60 / 172
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_product_context 57.78% 26 / 45
ironclaw_observability 61.54% 16 / 26
ironclaw_authorization 62.98% 609 / 967
ironclaw_dispatcher 64.17% 77 / 120
ironclaw_memory 69.2% 773 / 1117
ironclaw_trust 73.21% 664 / 907
ironclaw_filesystem 73.53% 4545 / 6181
ironclaw_capabilities 74.07% 2717 / 3668
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_projects 76.48% 400 / 523
ironclaw_triggers 77.33% 2531 / 3273
ironclaw_mcp 77.56% 736 / 949
ironclaw_reborn_cli 78.12% 10632 / 13609
ironclaw_llm 78.63% 20824 / 26485
ironclaw_wasm 79.72% 735 / 922
ironclaw_process_sandbox 80.46% 671 / 834
ironclaw_memory_native 81.38% 3203 / 3936
ironclaw_first_party_extensions 82.21% 6610 / 8040
ironclaw_events 82.47% 1604 / 1945
ironclaw_telegram_v2_adapter 83.07% 2017 / 2428
ironclaw_processes 83.16% 933 / 1122
ironclaw_product_adapter_registry 83.43% 574 / 688
ironclaw_reborn_identity 83.8% 450 / 537
ironclaw_secrets 83.8% 2550 / 3043
ironclaw_reborn_config 84.17% 1962 / 2331
ironclaw_common 84.48% 1769 / 2094
ironclaw_product_adapters 84.65% 3308 / 3908
ironclaw_auth 85.01% 4011 / 4718
ironclaw_run_state 85.61% 458 / 535
ironclaw_network 85.97% 913 / 1062
ironclaw_reborn_event_store 86.51% 1251 / 1446
ironclaw_product_workflow 86.52% 16286 / 18824
ironclaw_hooks 86.58% 9930 / 11469
ironclaw_extensions 86.98% 3808 / 4378
ironclaw_threads 87.2% 4851 / 5563
ironclaw_host_api 87.55% 5407 / 6176
ironclaw_skills 87.6% 4471 / 5104
ironclaw_reborn_composition 87.63% 57591 / 65721
ironclaw_reborn_traces 88.11% 11972 / 13587
ironclaw_turns 88.62% 14579 / 16452
ironclaw_host_runtime 88.71% 18483 / 20835
ironclaw_reborn_openai_compat 88.92% 3580 / 4026
ironclaw_slack_extension 89.36% 2444 / 2735
ironclaw_extension_host 89.59% 2856 / 3188
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_webui 90.52% 9224 / 10190
ironclaw_resources 90.85% 4477 / 4928
ironclaw_event_streams 91.24% 1063 / 1165
ironclaw_runner 91.27% 17073 / 18707
ironclaw_loop_host 92.26% 16261 / 17625
ironclaw_attachments 93.06% 630 / 677
ironclaw_outbound 93.98% 3733 / 3972
ironclaw_agent_loop 94.93% 9840 / 10365
ironclaw_safety 95.15% 3749 / 3940
ironclaw_first_party_extension_ports 95.62% 3672 / 3840
ironclaw_runtime_policy 96.55% 811 / 840

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

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 3 0 3 c9cb601b92a9

Head: c9cb601b92a9b19ab74f6cc152c61acf158f327b
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

Changes requested: lifecycle retry handling is not idempotent, setup retries can re-run side effects, and the sidebar loses access to paginated threads.

Findings

Blocking: 3 / Notes: 0

Blocking findings

1. ❌ [MEDIUM] Lifecycle retries fail instead of replaying

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2277
Reusing a client action ID recreates the same ActivityId, which RuntimeProductCapabilityInvoker converts directly to the host InvocationId. The host run-state rejects a second invocation ID with InvocationAlreadyExists (the runtime's own tests explicitly require a fresh context for a second invocation), so a response-lost retry of install/activate/remove becomes a backend failure rather than an idempotent replay. Add durable replay/lookup behavior for completed lifecycle invocations, and cover a real runtime duplicate POST rather than only the stubbed handler test.

2. ❌ [MEDIUM] Setup action IDs do not deduplicate setup side effects

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2582
EXTENSION_SETUP_SUBMIT_CAPABILITY is handled by ProductOperationHandler directly, so this activity ID never reaches the runtime invocation/run-state path. The request's client_action_id is removed from input and a duplicate POST executes setup again. In particular, duplicate channel-secret setup calls ChannelConfigService::save, which treats every nonblank secret as stored and reactivates an active channel again. Persist and replay setup actions by client action ID (or route setup through an idempotent capability path), with a duplicate-POST test proving only one save/reactivation.

3. ❌ [MEDIUM] Removing pagination hides conversations after the first page

Location: crates/ironclaw_webui/frontend/src/pages/chat/hooks/useThreads.ts:20
This fetches only the default thread page, while the server default is 50 and returns next_cursor for additional pages. This change removes the hook's page appending and all sidebar load-more/search messaging, leaving the cursor unused. Users with more than 50 threads cannot select or search their older conversations from the sidebar. Restore cursor paging (and its UI/test coverage) or otherwise fetch and expose all accessible threads.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.
Inline review fallback

Inline comment projection fell back to a body-only PR Review because GitHub rejected the inline payload.
Reason: Unprocessable Entity: "Path could not be resolved" - https://docs.github.com/rest/pulls/reviews#create-a-review-for-a-pull-request

IronLoop preserved the inline review comment payloads below instead of dropping them.

Inline fallback 1: crates/ironclaw_webui/src/webui_v2/handlers.rs:2277

Reusing this derived activity ID is not a retry today: RuntimeProductCapabilityInvoker turns it into the host invocation ID, and the run-state rejects a second invocation with InvocationAlreadyExists. A response-lost install/activate/remove retry therefore fails instead of replaying. Please add durable replay/lookup semantics and exercise a duplicate POST against the real runtime.

Inline fallback 2: crates/ironclaw_webui/src/webui_v2/handlers.rs:2582

Setup is a direct ProductOperationHandler operation, so this activity ID never provides deduplication. The same client action can rerun credential/config writes; duplicate channel-secret setup also triggers another active-channel reactivation. Please persist/replay setup actions by client action ID (or route them through an idempotent path).

Inline fallback 3: crates/ironclaw_webui/frontend/src/pages/chat/hooks/useThreads.ts:20

The server defaults thread lists to 50 and returns next_cursor, but this change removes the page appender and all sidebar load-more wiring. Older threads become unselectable and unsearchable in the sidebar. Please retain cursor pagination and coverage.

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

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_webui/src/webui_v2/handlers.rs (1)

2729-2747: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Restore deterministic activity IDs for generic capability invocations.

invoke_product_capability() now passes ActivityId::new() — a fresh v4 ID per call — to invoke_product_capability_with_activity_id(). There are still 21 bare generic call sites, so retrying the same capability request (for example, set_settings_tools_auto_approve()) cannot reuse the same activity ID and can break the stable activity IDs needed for retry handling.

🤖 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 `@crates/ironclaw_webui/src/webui_v2/handlers.rs` around lines 2729 - 2747,
Update invoke_product_capability to derive and pass a deterministic ActivityId
for the serialized capability input instead of ActivityId::new(). Preserve the
existing invoke_product_capability_with_activity_id flow and ensure identical
generic capability requests produce the same activity ID across retries.
🤖 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
`@crates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsx`:
- Around line 43-52: The PROVIDER_DISPLAY_NAMES allowlist uses the wrong key for
the GitHub provider. Update the entry in providerDisplayName’s allowlist to use
the canonical “git_hub” provider ID while preserving the intended “GitHub”
display label.

In `@tests/e2e/scenarios/test_reborn_qa_trace_full_path.py`:
- Around line 915-937: Remove the single-use helpers
_emulate_google_supports_sheets and _emulate_slack_supports_extension_reads, and
inline their HTTP probe logic at their respective call sites in the retry/fetch
flow. Preserve the existing headers, URLs, timeout, and status-code checks while
eliminating the unnecessary indirection.
- Around line 638-650: Update the document rewrite logic around
created_document_content so content is derived per Google Docs document/upload
pair rather than once from the first google-docs__insert_text call in the trace.
Associate each google-docs__create_document and corresponding
google-drive__upload_file with its own inserted text, preserving distinct
content when multiple documents are created.

---

Outside diff comments:
In `@crates/ironclaw_webui/src/webui_v2/handlers.rs`:
- Around line 2729-2747: Update invoke_product_capability to derive and pass a
deterministic ActivityId for the serialized capability input instead of
ActivityId::new(). Preserve the existing
invoke_product_capability_with_activity_id flow and ensure identical generic
capability requests produce the same activity ID across retries.
🪄 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: 29b0b3d0-5680-47b5-a64f-8e483eb7ca5c

📥 Commits

Reviewing files that changed from the base of the PR and between 8f4d893 and c9cb601.

📒 Files selected for processing (39)
  • crates/ironclaw_product_workflow/src/commands.rs
  • crates/ironclaw_product_workflow/src/lib.rs
  • crates/ironclaw_product_workflow/src/reborn_services/extension_setup_credentials.rs
  • crates/ironclaw_product_workflow/src/webui_inbound.rs
  • crates/ironclaw_product_workflow/tests/product_commands_contract.rs
  • crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rs
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle_capabilities.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/outbound/mod.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/core.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/outbound_delivery.rs
  • crates/ironclaw_reborn_composition/src/webui/facade/tests.rs
  • crates/ironclaw_reborn_composition/src/webui/product_capability.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_serve.rs
  • crates/ironclaw_webui/frontend/src/lib/api.test.ts
  • crates/ironclaw_webui/frontend/src/lib/api.ts
  • crates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsx
  • crates/ironclaw_webui/frontend/src/pages/chat/lib/attachments.ts
  • crates/ironclaw_webui/frontend/src/pages/extensions/hooks/useExtensions-catalog.test.ts
  • crates/ironclaw_webui/frontend/src/pages/extensions/hooks/useExtensions.ts
  • crates/ironclaw_webui/frontend/src/pages/extensions/lib/extensions-api.test.ts
  • crates/ironclaw_webui/frontend/src/pages/extensions/lib/extensions-api.ts
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs
  • tests/e2e/conftest.py
  • tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py
  • tests/e2e/scenarios/test_reborn_qa_trace_full_path.py
  • tests/e2e/scenarios/test_reborn_slack_channel_e2e.py
  • tests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.py
  • tests/e2e/scenarios/test_reborn_webui_v2_extensions_api.py
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_approval.py
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_attachments.py
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_auth_flows.py
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_extensions.py
  • tests/e2e/scenarios/test_reborn_webui_v2_legacy_settings_search.py
  • tests/integration/webui_v2_product_api.rs
💤 Files with no reviewable changes (1)
  • tests/e2e/scenarios/test_reborn_webui_v2_automation_trace_outbound_api.py

Comment thread tests/e2e/scenarios/test_reborn_qa_trace_full_path.py Outdated
Comment thread tests/e2e/scenarios/test_reborn_qa_trace_full_path.py
@railway-app

railway-app Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 23, 2026 at 6:25 pm

…surface-review-fixes-refresh-2

# Conflicts:
#	crates/ironclaw_reborn_composition/src/factory.rs
#	crates/ironclaw_reborn_composition/src/runtime.rs
#	crates/ironclaw_reborn_composition/src/webui/product_capability.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 15:56 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

@ironloopai review --agent reviewer

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 2 0 2 d04cba851ab4

Head: d04cba851ab4ae7bc66b914a988bbb330afe9485
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

Two retry/idempotency regressions remain in ProductSurface mutations.

Findings

Blocking: 2 / Notes: 0

Blocking findings

1. ❌ [HIGH] Value-derived IDs turn repeat mutations into permanent replays

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2866
The new durable result lookup replays every previously successful ActivityId, while this helper derives that ID solely from the requested target. A user who sets preference A, then B, then A will have the third request replay the old A result without invoking the capability; the following query still returns B. generic_product_capability_activity_id has the same issue for skill and other generic mutations. Use a per-action retry key (or bounded/expiring replay semantics), and add an A→B→A regression test.

2. ❌ [HIGH] Extension setup retries bypass the durable replay path

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2583-2588
This passes the stable activity ID to ProductSurface, but builtin.extension_setup_submit is handled by ProductOperationHandler before RuntimeProductCapabilityInvoker is called. That path ignores activity_id, so a response-lost retry repeats manual-token/channel-config side effects instead of replaying the first result. Add operation-level durable idempotency or route setup through the replaying invoker, with a real-runtime duplicate-submit test.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

)))
}

fn outbound_preferences_activity_id(

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.

This activity ID is a permanent value-derived cache key now that successful ProductSurface calls replay /product-results. Setting A, then B, then A replays the first A result and skips the final mutation, leaving B active. The generic helper has the same issue; use a per-action retry key or bounded replay retention.

serde_json::Value::String(package_ref.id.as_str().to_string()),
);
let resolution = invoke_product_capability(
let resolution = invoke_product_capability_with_activity_id(

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.

builtin.extension_setup_submit is dispatched by ProductOperationHandler, which bypasses RuntimeProductCapabilityInvoker and ignores activity_id. A retry with this client action ID therefore re-submits setup side effects rather than replaying the durable result.

@ilblackdragon
ilblackdragon force-pushed the codex/reborn-product-surface-review-fixes-refresh-2 branch from d04cba8 to bf82ea7 Compare July 23, 2026 16:03
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 16:03 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

@ironloopai review --agent reviewer

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

Caution

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

⚠️ Outside diff range comments (1)
crates/ironclaw_reborn_composition/src/webui/product_capability.rs (1)

100-152: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Hold the activity lock only while mutating in-memory state.

_activity_guard crosses HostRuntime::invoke_capability(...) and filesystem reads before the later cast_update write, which violates crates/ironclaw_reborn_composition/**/*.rs: “Read-modify-write operations must use the shared bounded CAS helper rather than a process-local mutex held across backend I/O.” Build the capability request first, acquire the lock only around the persistence claim/replay path, and add a timeout to the whole idempotency attempt rather than a process-local unscoped AsyncMutex behind slow capability dispatch.

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 100 - 152, Refactor the capability invocation flow in the surrounding
method so `HostRuntime::invoke_capability` and filesystem/backend reads occur
before acquiring the activity lock. Use the shared bounded CAS helper for the
persistence claim/replay update, scope the lock only to the required in-memory
mutation, and apply a timeout across the complete idempotency attempt; remove
the current lock held across dispatch and `product_resolution`.

Source: Path instructions

🤖 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 `@crates/ironclaw_webui/frontend/src/lib/api.test.ts`:
- Around line 198-210: Extend the setupExtension test to cover the missing
clientActionId case: call setupExtension without clientActionId, then verify the
request body includes the generated client-action ID along with the action and
payload. Keep the existing caller-supplied ID coverage intact and assert the
generated ID’s expected format or value using the test’s available request data.

In `@crates/ironclaw_webui/src/webui_v2/handlers.rs`:
- Around line 2835-2840: Update extension_lifecycle_activity_id to accept a
reference to the parsed, strongly typed client action ID instead of &str, and
adjust its callers to pass that validated domain value without re-parsing or
accepting raw input.
- Around line 2820-2828: Update the activity identity generation around the
request presence flags so different api_key rotations produce distinct replay
identities without including raw key bytes, using an opaque action identity or
secret-store revision/fingerprint. Preserve the existing behavior for non-secret
fields, and add a handler-level test covering otherwise-identical updates with
different API keys.

---

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 100-152: Refactor the capability invocation flow in the
surrounding method so `HostRuntime::invoke_capability` and filesystem/backend
reads occur before acquiring the activity lock. Use the shared bounded CAS
helper for the persistence claim/replay update, scope the lock only to the
required in-memory mutation, and apply a timeout across the complete idempotency
attempt; remove the current lock held across dispatch and `product_resolution`.
🪄 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: 4e13badd-5bfa-4751-98ba-ff20bbe4a9ad

📥 Commits

Reviewing files that changed from the base of the PR and between c9cb601 and d04cba8.

📒 Files selected for processing (19)
  • crates/ironclaw_host_api/src/path.rs
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle.rs
  • crates/ironclaw_reborn_composition/src/extension_host/extension_lifecycle_capabilities.rs
  • crates/ironclaw_reborn_composition/src/factory.rs
  • crates/ironclaw_reborn_composition/src/local_dev_mounts.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/core.rs
  • crates/ironclaw_reborn_composition/src/runtime/tests/outbound_delivery.rs
  • crates/ironclaw_reborn_composition/src/webui/facade/tests.rs
  • crates/ironclaw_reborn_composition/src/webui/product_capability.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs
  • crates/ironclaw_webui/frontend/src/lib/api.test.ts
  • crates/ironclaw_webui/frontend/src/lib/api.ts
  • crates/ironclaw_webui/frontend/src/lib/sidebar-active-thread.test.ts
  • crates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsx
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs
  • tests/e2e/scenarios/test_reborn_qa_trace_full_path.py
  • tests/integration/webui_v2_product_api.rs

Comment thread crates/ironclaw_webui/frontend/src/lib/api.test.ts
Comment thread crates/ironclaw_webui/src/webui_v2/handlers.rs Outdated
Comment thread crates/ironclaw_webui/src/webui_v2/handlers.rs

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 3 0 3 bf82ea70d543

Head: bf82ea70d543fb53e67b2c97bd3d7c92eb53cfab
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

Found three blocking issues in retry handling and E2E verification.

Findings

Blocking: 3 / Notes: 0

Blocking findings

1. ❌ [HIGH] Setup submissions bypass durable replay

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2586
EXTENSION_SETUP_SUBMIT_CAPABILITY is handled as a ProductOperationHandler, so RebornServices::invoke executes it before RuntimeProductCapabilityInvoker, the sole /product-results replay path. Retrying the same client action after a lost response reruns channel configuration and credential submission rather than replaying the completed result. Add durable idempotency to this operation or route it through the replaying path.

2. ❌ [HIGH] Queued retries can split the per-activity lock

Location: crates/ironclaw_reborn_composition/src/webui/product_capability.rs:86
Removing the map entry does not account for callers that already cloned this mutex and are waiting. After an unpersisted invocation, such as a runtime failure or result-persistence error, a waiting retry proceeds with the old lock while a new retry creates a fresh lock and invokes the same activity concurrently. This can duplicate external side effects. Retain entries until no holders or waiters remain, or use a durable per-activity lease.

3. ❌ [MEDIUM] Docs-to-Drive trace rewriting no longer verifies uploaded content

Location: tests/e2e/scenarios/test_reborn_qa_trace_full_path.py:673
After this rewrite, the baseline and outcome checks recognize only Docs and Sheets creation. They therefore return before checking a normalized google-drive__upload_file, allowing the changed journey to pass even if the uploaded document name or content was not persisted. Update the provider readback checks to recognize Drive uploads and assert their content.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

let resolution = invoke_product_capability_with_activity_id(
state.services(),
caller.clone(),
EXTENSION_SETUP_SUBMIT_CAPABILITY,

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.

EXTENSION_SETUP_SUBMIT_CAPABILITY is handled by ProductOperationHandler, so it never reaches RuntimeProductCapabilityInvoker or its /product-results replay store. Retrying this client action after a lost response re-executes save_values and credential submission. Please add durable idempotency here or route setup through the replaying path.

.get(&activity_id)
.is_some_and(|current| Arc::ptr_eq(current, lock))
{
locks.remove(&activity_id);

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.

Removing the entry solely because it is the current Arc races with queued waiters. After an unpersisted error, a waiter can use this old mutex while a later retry creates a fresh mutex and invokes the same activity concurrently. Retain entries until no holders/waiters remain, or use a durable activity lease.

)
document_upload_index += 1
call["arguments"] = {
"name": arguments["title"],

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.

The baseline/outcome helpers only recognize Docs and Sheets creation, so after this rewrite they return without checking the Drive upload. Please add google-drive__upload_file readback and assert the uploaded name/content; otherwise this normalized journey can pass with missing or incorrect document content.

@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

Caution

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

⚠️ Outside diff range comments (1)
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py (1)

639-676: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve all text chunks for each rewritten document.

This assigns only the first google-docs__insert_text to each positional placeholder. Multiple inserts for one document are either discarded or assigned to another document, so the Drive upload no longer represents the recorded Docs operation. Associate inserts by their document reference and append chunks in trace order before rewriting the matching create call.

🤖 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/e2e/scenarios/test_reborn_qa_trace_full_path.py` around lines 639 -
676, Update the document content reconstruction around document_upload_contents
and the google-docs__create_document rewrite to associate
google-docs__insert_text calls with their document reference rather than
positional empty placeholders. Accumulate and append every text chunk in trace
order for each document, then use the complete accumulated content when
rewriting the matching create call as google-drive__upload_file.
🤖 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/e2e/scenarios/test_reborn_qa_trace_full_path.py`:
- Around line 663-676: Update _assert_google_provider_outcome() to recognize
rewritten google-drive__upload_file calls alongside
google-docs__create_document, then use the upload result to download the created
file and assert its provider state and expected content. Ensure _load_trace()
normalization still leads to read-back validation rather than returning when
create_call is absent.

---

Outside diff comments:
In `@tests/e2e/scenarios/test_reborn_qa_trace_full_path.py`:
- Around line 639-676: Update the document content reconstruction around
document_upload_contents and the google-docs__create_document rewrite to
associate google-docs__insert_text calls with their document reference rather
than positional empty placeholders. Accumulate and append every text chunk in
trace order for each document, then use the complete accumulated content when
rewriting the matching create call as google-drive__upload_file.
🪄 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: 66aa041a-fdf2-46f6-9d54-a6fd301b5c77

📥 Commits

Reviewing files that changed from the base of the PR and between d04cba8 and bf82ea7.

📒 Files selected for processing (4)
  • crates/ironclaw_webui/frontend/src/pages/chat/components/auth-oauth-card.tsx
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs
  • tests/e2e/scenarios/test_reborn_qa_trace_full_path.py

Comment thread tests/e2e/scenarios/test_reborn_qa_trace_full_path.py
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 16:19 Destroyed
@ilblackdragon

Copy link
Copy Markdown
Member Author

@ironloopai review --agent reviewer

@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

♻️ Duplicate comments (1)
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py (1)

644-692: 🎯 Functional Correctness | 🟠 Major

Restore provider readback for rewritten Docs uploads.

This change emits google-drive__upload_file, but _assert_google_provider_outcome() still searches only for google-docs__create_document and google-sheets__create_spreadsheet (Line 969-982). The normalized Docs flow can therefore return without checking the uploaded file, name, or content.

As per coding guidelines, provider tests must assert Emulate provider state/readback rather than only replay output.

🤖 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/e2e/scenarios/test_reborn_qa_trace_full_path.py` around lines 644 -
692, Update _assert_google_provider_outcome() to recognize the normalized
google-drive__upload_file call produced by the Docs replay flow, alongside the
existing document and spreadsheet cases. Assert the Emulate provider readback
for the uploaded file’s identifier, name, and content so rewritten Docs uploads
are validated rather than skipped.

Source: Coding guidelines

🤖 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/e2e/scenarios/test_emulate_reborn_provider_contracts.py`:
- Around line 29-47: Replace the mutating release POST in
_skip_if_github_release_writes_unavailable and the corresponding probe in
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py:938-948 with a
guaranteed-invalid, non-mutating capability request. Accept only 422 as
supported, skip on 403 or 404, and fail all other statuses; ensure this shared
capability contract runs before provider-operation baselines in both sites.

---

Duplicate comments:
In `@tests/e2e/scenarios/test_reborn_qa_trace_full_path.py`:
- Around line 644-692: Update _assert_google_provider_outcome() to recognize the
normalized google-drive__upload_file call produced by the Docs replay flow,
alongside the existing document and spreadsheet cases. Assert the Emulate
provider readback for the uploaded file’s identifier, name, and content so
rewritten Docs uploads are validated rather than skipped.
🪄 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: 40682f00-d6ad-4e59-b852-63419603fdbe

📥 Commits

Reviewing files that changed from the base of the PR and between bf82ea7 and 8a28953.

📒 Files selected for processing (2)
  • tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py
  • tests/e2e/scenarios/test_reborn_qa_trace_full_path.py

Comment thread tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py

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

❌ IronLoop Review: reviewer

Review at a glance

Verdict Blocking Notes Inline Head
❌ Changes requested 1 0 1 8a289536d97d

Head: 8a289536d97d0678b43753e3d169e0fa26385623
Next: Fix the blocking findings, push the PR branch, then re-run this reviewer.

Run details

Status: Current
Needs human: no
Needs validation: no

Summary

The setup retry path still re-executes side effects despite the new stable client action ID.

Findings

Blocking: 1 / Notes: 0

Blocking findings

1. ❌ [HIGH] Replay API-only extension setup by activity ID

Location: crates/ironclaw_webui/src/webui_v2/handlers.rs:2583-2589
This now supplies a stable activity ID, but builtin.extension_setup_submit is handled as an API-only ProductOperationHandler operation. That path invokes setup directly and returns a synthetic success resolution without consulting the durable /product-results replay store. A response-lost retry with the same client action ID therefore runs ChannelConfigFacade::save_values again (which can reactivate an active extension) and resubmits manual credentials. Put API-only setup behind durable activity-ID replay, or route it through the replaying runtime path, and add a real duplicate-request test that proves these side effects execute once.

Developer follow-up

After fixing this feedback:

  1. Push the fix to this PR branch.
  2. Re-run this reviewer with @ironloopai review --agent reviewer if you only changed this reviewer's findings.
  3. Re-run all reviewers with @ironloopai review when the fix may affect multiple areas.

caller.clone(),
EXTENSION_SETUP_SUBMIT_CAPABILITY,
input,
activity_id,

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.

extension_setup_submit is API-only, so RebornServices::invoke dispatches it directly and returns a synthetic success without consulting the durable runtime replay store. The same client action ID therefore reruns setup after a lost response, including config saves (which can reactivate an active extension) and manual-token submission. Please add durable replay for this path and a duplicate real-runtime request test.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 17:03 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 17:13 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: 1

Caution

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

⚠️ Outside diff range comments (2)
crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs (1)

5366-5419: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add a retry/replay assertion for setup_extension, matching install_extension's.

install_extension_invokes_lifecycle_capability_with_body_package_ref (5056-5109) and set_outbound_preferences_dispatches_body_through_invoke (3248-3319) both assert that an identical client_action_id retry reuses the same ProductSurface activity id. setup_extension_invokes_product_surface_capability only sends one request, so it doesn't prove the previously-flagged EXTENSION_SETUP_SUBMIT_CAPABILITY replay-bypass is actually fixed by the new invoke_product_operation dispatch path. Please add a second request + invoke_calls[0].2 == invoke_calls[1].2 assertion here.

🤖 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 `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs` around lines 5366
- 5419, Extend setup_extension_invokes_product_surface_capability with a second
identical POST using the same client_action_id, then assert both requests
succeed and invoke_calls contains two entries whose activity IDs (the third
tuple element) are equal. Preserve the existing payload, capability, response,
and view-query assertions while adding explicit retry/replay coverage for
setup_extension.
crates/ironclaw_webui/src/webui_v2/handlers.rs (1)

2566-2616: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add an HTTP-level retry regression test for setup_extension.

setup_extension_invokes_product_surface_capability sends only one request and checks that the capability call shape is correct, unlike install_extension_invokes_lifecycle_capability_with_body_package_ref. Per CLAUDE.md’s bug-fix regression-test invariant, add a two-request setup retry test that asserts the same activity/action/request shape survives the retry so saved setup work is replayed, not re-run.

🤖 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 `@crates/ironclaw_webui/src/webui_v2/handlers.rs` around lines 2566 - 2616, Add
an HTTP-level two-request retry regression test for setup_extension, modeled on
install_extension_invokes_lifecycle_capability_with_body_package_ref. Send the
same setup request twice and assert both capability invocations preserve the
identical activity ID, action/capability, package reference, and request body
shape, confirming the saved setup work is replayed rather than re-executed.
🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 154-186: Update invoke_product_operation to use the
caller-supplied summary when constructing the successful resolution, rather than
ignoring it. Thread summary into product_operation_resolution and replace its
hardcoded “capability completed” text with that value, ensuring the resulting
resolution persisted for replay retains each ProductOperation's success_summary.

---

Outside diff comments:
In `@crates/ironclaw_webui/src/webui_v2/handlers.rs`:
- Around line 2566-2616: Add an HTTP-level two-request retry regression test for
setup_extension, modeled on
install_extension_invokes_lifecycle_capability_with_body_package_ref. Send the
same setup request twice and assert both capability invocations preserve the
identical activity ID, action/capability, package reference, and request body
shape, confirming the saved setup work is replayed rather than re-executed.

In `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs`:
- Around line 5366-5419: Extend
setup_extension_invokes_product_surface_capability with a second identical POST
using the same client_action_id, then assert both requests succeed and
invoke_calls contains two entries whose activity IDs (the third tuple element)
are equal. Preserve the existing payload, capability, response, and view-query
assertions while adding explicit retry/replay coverage for setup_extension.
🪄 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: 5f9873dc-855f-43e7-901b-2eb06896c8f7

📥 Commits

Reviewing files that changed from the base of the PR and between 8a28953 and 559ac4e.

📒 Files selected for processing (15)
  • crates/ironclaw_product_workflow/src/lib.rs
  • crates/ironclaw_product_workflow/src/reborn_services.rs
  • crates/ironclaw_product_workflow/src/reborn_services/llm_config.rs
  • crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
  • crates/ironclaw_reborn_composition/src/llm_admin/llm_config_service.rs
  • crates/ironclaw_reborn_composition/src/webui/product_capability.rs
  • crates/ironclaw_webui/frontend/src/lib/api.test.ts
  • crates/ironclaw_webui/frontend/src/lib/api.ts
  • crates/ironclaw_webui/frontend/src/pages/settings/lib/settings-api.test.ts
  • crates/ironclaw_webui/frontend/src/pages/settings/lib/settings-api.ts
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs
  • tests/e2e/conftest.py
  • tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py
  • tests/e2e/scenarios/test_reborn_qa_trace_full_path.py

Comment thread crates/ironclaw_reborn_composition/src/webui/product_capability.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 17:23 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 17:38 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: 1

Caution

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

⚠️ Outside diff range comments (3)
crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs (1)

5063-5111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Retry assertions don't check the retry actually resent the same capability/input.

Only invoke_calls[0].0/.1 are checked against the expected capability id and input; invoke_calls[1].0/.1 are never asserted, so a regression that changes what the retry POST sends to invoke() (wrong capability, or a mutated input body) would pass this test silently. The setup_extension_invokes_product_surface_capability test right below (lines 5430-5433) checks both calls for capability and input — this test should mirror that.

♻️ Proposed fix
     assert_eq!(
         invoke_calls[0].0,
         CapabilityId::new(EXTENSION_INSTALL_CAPABILITY_ID).expect("capability id")
     );
     assert_eq!(
         invoke_calls[0].1,
         serde_json::json!({ "extension_id": "nearai-mcp" })
     );
+    assert_eq!(invoke_calls[1].0, invoke_calls[0].0);
+    assert_eq!(invoke_calls[1].1, invoke_calls[0].1);
     assert_eq!(
         invoke_calls[0].2, invoke_calls[1].2,
         "the client action id must survive response-lost retries as the ProductSurface activity id"
     );
🤖 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 `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs` around lines 5063
- 5111, Extend the retry assertions in the test around the two entries in
services.invoke_calls so invoke_calls[1].0 matches
EXTENSION_INSTALL_CAPABILITY_ID and invoke_calls[1].1 matches the expected
{"extension_id": "nearai-mcp"} input, mirroring the capability and input checks
used by setup_extension_invokes_product_surface_capability.
crates/ironclaw_reborn_composition/src/webui/product_capability.rs (2)

445-453: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Summary-persist failure surfaces a genuinely-successful, already-persisted operation as a hard error.

results.persist(...) (the durable success marker) succeeds, then results.persist_summary(...) runs with ?. If only the summary write fails, product_operation_resolution returns Err even though the operation completed and its result is durably recorded via the first persist call. The caller sees a failure and — per this PR's own client-action-id retry design — may mint a new client_action_id believing the prior attempt failed, causing the (possibly non-idempotent) underlying operation to run again. This defeats the retry-idempotency guarantee this PR is adding.

🐛 Proposed fix: don't let an auxiliary summary-write failure invalidate an already-durable result
     let result_ref = ResultRef::from_uuid(invocation_id.as_uuid());
     results.persist(scope, result_ref, Vec::new()).await?;
-    results.persist_summary(scope, result_ref, summary).await?;
+    // silent-ok: the durable success marker above already recorded this
+    // operation as done; replay falls back to a generic summary if this
+    // auxiliary write is missing, so failing here would wrongly report a
+    // succeeded, durably-persisted operation as failed to the caller.
+    if let Err(error) = results.persist_summary(scope, result_ref, summary).await {
+        tracing::warn!(%error, "product operation summary persist failed after result was persisted");
+    }

Based on coding guidelines, crates/**/*.rs: "justified fallbacks must include an inline // silent-ok: <reason> comment naming the operation."

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 445 - 453, Update product_operation_resolution so persist_summary failures
do not propagate as errors after the durable results.persist succeeds; treat the
summary write as a best-effort auxiliary operation while preserving the
successful Resolution return. Add the required inline // silent-ok: comment
explaining that summary persistence must not invalidate an already-durable
operation result.

Source: Coding guidelines


55-56: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don’t hold the per-activity async mutex across capability execution and result persistence

crates/**/*.rs requires not holding process-local/per-record async mutexes across filesystem/backend I/O. In both invoke and invoke_product_operation, the activity lock is acquired before results.replay(...)/host_runtime.invoke_capability(...)/operation execution and persists through product_resolution(...) or product_operation_resolution(...) persist calls. Scope the lock to the fast single-flight guard only, or move the locking down after the replay check so it is never held during capability/result I/O.

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 55 - 56, Update invoke and invoke_product_operation so activity_locks is
not held across results.replay, host_runtime.invoke_capability, operation
execution, or product resolution persistence. Perform replay checks before
acquiring the lock, and scope each lock only around the fast in-memory
single-flight guard; release it before all capability, backend, filesystem, and
result I/O.

Source: Path instructions

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 620-622: Update the summary handling in replay_product_result so
errors from replay_product_result_summary are treated like missing summaries:
fall back to fixed_summary("capability completed") instead of propagating them.
Preserve successful decoded summaries, and add an inline // silent-ok: comment
explaining that persisted successful results must remain replayable when summary
decoding or validation fails.

---

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 445-453: Update product_operation_resolution so persist_summary
failures do not propagate as errors after the durable results.persist succeeds;
treat the summary write as a best-effort auxiliary operation while preserving
the successful Resolution return. Add the required inline // silent-ok: comment
explaining that summary persistence must not invalidate an already-durable
operation result.
- Around line 55-56: Update invoke and invoke_product_operation so
activity_locks is not held across results.replay,
host_runtime.invoke_capability, operation execution, or product resolution
persistence. Perform replay checks before acquiring the lock, and scope each
lock only around the fast in-memory single-flight guard; release it before all
capability, backend, filesystem, and result I/O.

In `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs`:
- Around line 5063-5111: Extend the retry assertions in the test around the two
entries in services.invoke_calls so invoke_calls[1].0 matches
EXTENSION_INSTALL_CAPABILITY_ID and invoke_calls[1].1 matches the expected
{"extension_id": "nearai-mcp"} input, mirroring the capability and input checks
used by setup_extension_invokes_product_surface_capability.
🪄 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: 94727d14-7b82-41ca-b82d-8fced7d8cf38

📥 Commits

Reviewing files that changed from the base of the PR and between e715d6f and 8cebfb9.

📒 Files selected for processing (2)
  • crates/ironclaw_reborn_composition/src/webui/product_capability.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Comment thread crates/ironclaw_reborn_composition/src/webui/product_capability.rs Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6558 July 23, 2026 18:16 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.

Caution

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

⚠️ Outside diff range comments (3)
crates/ironclaw_reborn_composition/src/webui/product_capability.rs (3)

345-353: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include extension search in lifecycle mount selection.

is_extension_lifecycle_capability recognizes install/activate/remove but excludes EXTENSION_SEARCH_CAPABILITY_ID. The canonical search manifest declares ReadFilesystem, so product invocation gives it empty mounts and breaks lifecycle catalog search. Include search and extend the test through the product invocation caller, not only product_invocation_mounts.

As per coding guidelines, “Test through the caller when a predicate, classifier, or transform gates a side effect”; path instructions require the same.

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 345 - 353, Update is_extension_lifecycle_capability to also recognize
EXTENSION_SEARCH_CAPABILITY_ID, ensuring extension search receives the lifecycle
mounts required by its manifest. Add or extend coverage through the product
invocation caller, verifying the search capability gets the expected filesystem
mount rather than testing only product_invocation_mounts.

Sources: Coding guidelines, Path instructions


177-191: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make activity-lock cleanup cancellation-safe.

Cancellation during activity_lock.lock().await or operation.await drops the mutex guard but bypasses release_activity_lock; the map retains that activity ID forever. Use an RAII cleanup mechanism and add an aborted-invocation regression test.

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 177 - 191, Make the activity-lock lifecycle in the invocation flow
cancellation-safe by introducing an RAII cleanup guard immediately after
acquiring the lock, ensuring its drop path calls release_activity_lock for every
exit, including cancellation during activity_lock.lock().await or
operation.await. Update the replay and normal-result paths around
product_operation_resolution to avoid manual cleanup that can be bypassed, and
add a regression test verifying an aborted invocation removes the activity ID
from the lock map.

185-186: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not persist replay state only after the side effect.

operation can succeed while product_operation_resolution fails to persist. Retrying the same client action then re-executes the completed mutation because no durable replay record exists. Make the activity ID part of a durable idempotency/evidence protocol spanning the operation, and fault-test persistence failure followed by retry.

As per coding guidelines, “Side-effecting success requires durable or provider-issued evidence plus read-back verification.”

🤖 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 `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs` around
lines 185 - 186, Update the operation flow around operation.await and
product_operation_resolution so activity ID durability and idempotency span the
side effect, rather than persisting replay state only after resolution succeeds.
Record or obtain durable/provider-issued evidence and verify it by read-back
before treating the mutation as successful, allowing retries to reuse the
completed result without re-executing it. Add a fault test covering persistence
failure followed by retry.

Source: Coding guidelines

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

Outside diff comments:
In `@crates/ironclaw_reborn_composition/src/webui/product_capability.rs`:
- Around line 345-353: Update is_extension_lifecycle_capability to also
recognize EXTENSION_SEARCH_CAPABILITY_ID, ensuring extension search receives the
lifecycle mounts required by its manifest. Add or extend coverage through the
product invocation caller, verifying the search capability gets the expected
filesystem mount rather than testing only product_invocation_mounts.
- Around line 177-191: Make the activity-lock lifecycle in the invocation flow
cancellation-safe by introducing an RAII cleanup guard immediately after
acquiring the lock, ensuring its drop path calls release_activity_lock for every
exit, including cancellation during activity_lock.lock().await or
operation.await. Update the replay and normal-result paths around
product_operation_resolution to avoid manual cleanup that can be bypassed, and
add a regression test verifying an aborted invocation removes the activity ID
from the lock map.
- Around line 185-186: Update the operation flow around operation.await and
product_operation_resolution so activity ID durability and idempotency span the
side effect, rather than persisting replay state only after resolution succeeds.
Record or obtain durable/provider-issued evidence and verify it by read-back
before treating the mutation as successful, allowing retries to reuse the
completed result without re-executing it. Add a fault test covering persistence
failure followed by retry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0c1de0d3-f962-4c21-b5d5-a0ce965e0d44

📥 Commits

Reviewing files that changed from the base of the PR and between 8cebfb9 and ee36c1b.

📒 Files selected for processing (1)
  • crates/ironclaw_reborn_composition/src/webui/product_capability.rs

@ilblackdragon
ilblackdragon merged commit 1f8169d into main Jul 23, 2026
64 checks passed
@ilblackdragon
ilblackdragon deleted the codex/reborn-product-surface-review-fixes-refresh-2 branch July 23, 2026 19:01
BenKurrek added a commit that referenced this pull request Jul 23, 2026
…6520

Conflict resolution keeps main's structure and this PR's lifecycle model:
the no-Activate contract wins everywhere activate reappeared (handlers,
frontend api/hooks/tests, e2e scenarios, facade/product-capability
tests, product command parsing), while main's client-action-id mutation
contract replaces this branch's idempotency_key wire end-to-end (install
handler derives its activity id from the validated client action id and
still enters through install_extension_on_surface). The removal path
keeps main's snapshot-fallback unpublish for hosted-MCP republished
packages without resurrecting activation-state writes; the gesture-
idempotency contract test now pins both distinct-gesture divergence and
response-lost-retry stability of the client action id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WF1EiatUVLjKKs3eYGMbKV

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6558 — ee36c1bd Deployed Jul 23, 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: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant