fix(reborn): keep OAuth auth gates visible without auth URL - #5067
Conversation
|
🚅 Deployed to the ironclaw-pr-5067 environment in ironclaw-ci-preview
|
📝 WalkthroughWalkthroughUpdates OAuth challenge kind from ChangesOAuth unavailable URL handling
Auth gate cancellation after approval fix
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Code Review
This pull request updates the OAuth challenge handling to preserve the OAuthUrl challenge kind even when the authorization URL is absent (e.g., when the provider is unconfigured or unavailable), allowing the UI to render an OAuth-specific unavailable state. In the WebUI, the OAuth card button is no longer disabled when the URL is missing; instead, clicking it displays a "Service Unavailable" error message. The review feedback suggests using the translation helper t() for this error message to support internationalization.
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.
There was a problem hiding this comment.
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
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.js`:
- Around line 52-55: The hardcoded error message "Service Unavailable" in the
setError() call within the hasHttpsAuthorizationUrl validation block is not
internationalized, while the rest of the component consistently uses the t()
i18n hook for all user-facing text. Replace the hardcoded string with t() using
an appropriate i18n key (e.g., "auth.service_unavailable" or similar), then add
this new key with translations to all i18n resource files (en.js, es.js, fr.js,
etc.) to maintain consistency with the component's existing i18n pattern.
🪄 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: 823daf37-bb06-4fb0-8904-5eca79a9463f
⛔ Files ignored due to path filters (1)
crates/ironclaw_webui_v2_static/static/dist/app.jsis excluded by!**/dist/**
📒 Files selected for processing (5)
crates/ironclaw_product_adapters/src/outbound.rscrates/ironclaw_reborn_composition/src/auth_prompt.rscrates/ironclaw_reborn_composition/src/projection/tests/turn_stream_auth.rscrates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.jscrates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs
|
@claude review |
|
✅ Codex review for current head I reviewed the OAuth auth-gate projection/UI change and did not find a new blocking correctness or security issue beyond the existing automated i18n comments on the hardcoded Local verification from an isolated worktree at the PR head:
Not ready for final human-review guidance yet: Claude review is now requested, CI still has pending Reborn/clippy/gateway jobs, and the i18n review thread remains unresolved. |
|
✅ Codex follow-up for current head I reviewed the new Local verification from isolated PR worktree:
Still not ready for final human-review guidance: the existing i18n threads on |
|
@claude review |
|
✅ Codex follow-up for current head Reviewed the new Focused local verification from an isolated PR worktree:
Still not ready for final human-review guidance: CI is pending for this new head, Claude has been re-requested for the current runtime changes, and the existing i18n review threads on |
think-in-universe
left a comment
There was a problem hiding this comment.
❌ Codex follow-up for current head 096a9ac4332bc2c4c15f4a0999d04730bc1a4dd6.
I found one blocking packaging issue in the latest i18n fix: the source module now uses authGate.serviceUnavailable, but the committed WebUI v2 bundle still contains the old hardcoded "Service Unavailable" path. Since static/index.html loads /v2/dist/app.js, this fix will not reach the shipped UI until the bundle is regenerated and committed.
Focused checks run locally:
cargo test -p ironclaw_webui_v2_static --test i18n_consistency— passednode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs— passedrgover source +static/dist/app.js— found the stale bundle string
I did not re-request Claude for this small follow-up commit; the remaining issue is concrete and localized.
|
Fixed in 937b6e6: rebuilt and committed the WebUI v2 static/dist bundle so the shipped app now uses authGate.serviceUnavailable instead of the stale hardcoded string.\n\nVerified with:\n- bash crates/ironclaw_webui_v2_static/frontend/build.sh --no-vendor\n- rg over static/dist for the old "Service Unavailable" string\n- node --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs\n- cargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistency |
think-in-universe
left a comment
There was a problem hiding this comment.
✅ Codex follow-up for current head 937b6e6b09f73bd893c6ed92295481adce87130a.
The stale WebUI v2 bundle blocker from my previous review is resolved: static/dist/app.js now calls authGate.serviceUnavailable, the old hardcoded "Service Unavailable" path is gone, and the locale chunk imports point at the rebuilt chunk names. I did not find a new blocking issue in this rebuild-only follow-up.
Focused checks run locally:
cargo test -p ironclaw_webui_v2_static --test i18n_consistency— passedcargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistency— passednode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjs crates/ironclaw_webui_v2_static/static/js/lib/i18n.test.mjs— passedrgoverstatic/dist— confirmed the stale hardcoded string is gone and the rebuilt chunks are referenced
This is not an approval or final human-review guidance yet; CI is still pending on the new head.
Superseded by Codex follow-up on head 937b6e6; the stale WebUI v2 bundle issue was fixed and the review thread is resolved.
There was a problem hiding this comment.
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
`@crates/ironclaw_reborn_composition/src/projection/tests/live_progress_stream.rs`:
- Around line 232-241: The test is brittle because it only inspects
state.items.first() when filtering for
ProductProjectionItem::CapabilityActivity, which means if item ordering changes,
the CapabilityActivity could be missed. Instead of using filter_map with
state.items.first(), iterate through all items in the state.items collection to
find the CapabilityActivity variant. Additionally, add an assertion on the
runtime field of the CapabilityActivity to ensure the newly projected terminal
field is properly covered by the test, fulfilling the requirement to test
through the actual caller and its side effects.
🪄 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: 10137701-a506-41c5-8b37-158e9c11367a
⛔ Files ignored due to path filters (1)
crates/ironclaw_webui_v2_static/static/dist/app.jsis excluded by!**/dist/**
📒 Files selected for processing (22)
crates/ironclaw_agent_loop/src/executor/gates.rscrates/ironclaw_agent_loop/src/executor/tests.rscrates/ironclaw_event_streams/src/types.rscrates/ironclaw_product_workflow/src/auth_interaction/service.rscrates/ironclaw_product_workflow/tests/auth_interaction_contract.rscrates/ironclaw_reborn_composition/src/projection/live_progress.rscrates/ironclaw_reborn_composition/src/projection/tests/live_progress_stream.rscrates/ironclaw_webui_v2_static/static/js/i18n/ar.jscrates/ironclaw_webui_v2_static/static/js/i18n/de.jscrates/ironclaw_webui_v2_static/static/js/i18n/en.jscrates/ironclaw_webui_v2_static/static/js/i18n/es.jscrates/ironclaw_webui_v2_static/static/js/i18n/fr.jscrates/ironclaw_webui_v2_static/static/js/i18n/hi.jscrates/ironclaw_webui_v2_static/static/js/i18n/ja.jscrates/ironclaw_webui_v2_static/static/js/i18n/ko.jscrates/ironclaw_webui_v2_static/static/js/i18n/pt-BR.jscrates/ironclaw_webui_v2_static/static/js/i18n/uk.jscrates/ironclaw_webui_v2_static/static/js/i18n/zh-CN.jscrates/ironclaw_webui_v2_static/static/js/pages/chat/components/auth-oauth-card.jscrates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.jscrates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat-input.test.mjscrates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs
|
Fixed the live progress stream test nit in de663ef: the test now scans all projection items for the capability activity instead of only state.items.first(), and it asserts the projected runtime field.\n\nVerified with:\n- cargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --lib\n- cargo fmt --check |
|
✅ Codex follow-up for current head Reviewed the new Focused verification from an isolated PR worktree at this head:
I did not re-request Claude for this test-only follow-up. This is still not final human-review guidance because CI and automated review are pending for the new head. |
|
✅ Codex final-review guidance No blocking findings from my latest review of Human reviewers should still focus on:
I’m not approving or merging; this is ready for human review. |
…#5067) The OAuth auth gate (AuthOauthCard, challengeKind "oauth_url") previously disabled the Authorize button and silently no-op'd when the gateway had no HTTPS authorization URL yet — a dead gate with no explanation. Port of upstream #5067, adapted to the desktop's diverged component: - keep the button actionable; on click without a valid HTTPS URL, show an inline "Service unavailable" alert instead of doing nothing - clear the error when the gate changes (authorizationUrl/gateRef/runId) - security guard intact (issue #4112): openAuth still rejects missing/ non-HTTPS URLs before window.open, href stays conditional i18n: add authGate.serviceUnavailable to all 11 locales with upstream's translations; bump i18n-completeness expected en key count 1079 -> 1080. Surgical/additive only — no other upstream divergence pulled. Upstream's net-new modules (workspace-breadcrumb/directory, automations-empty-state) were evaluated and skipped: the desktop already covers them via its own diverged workspace UI and EmptyPanel-based automations empty state. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… pack, triggered auth delivery, attachments, golden/synthetic expansions (#5610) * test(reborn): doc/text + multi-attachment coverage (W4-ATTACH-VARIANTS) Adds submit_turn_with_attachments (generalizes the image-only submit_turn_with_image_attachment to N attachments of any mime type) and two int-tier tests: a text/plain attachment's extracted text reaching the model, and two attachments in one turn both reaching the model with distinct index ordinals. Closes the doc/multi-attachment gap in C-ATTACH (only single-image coverage existed before). * test(reborn): W4-AUTHGATE-WIRE — runtime-401 provider-gate + cancel-no-replay (wave-4 row 1) Pins the #5174/#5180 bug class (empty credential_requirements leaving AuthPromptView.provider null, "Could not save the token" with no network request) through the FULL scripted-gateway integration harness — a tier below the existing crate-level pins, which drive CapabilityHost::invoke_json or HostRuntimeServices::invoke_capability directly and never exercise the real submit_turn -> BlockedAuth wire the WebUI depends on. - tests/reborn_integration_auth_gate.rs: new runtime_401_after_injection_populates_provider_credential_requirement (github credential resolves OK but the runtime HTTP call 401s; asserts the resulting BlockedAuth gate's credential_requirements carries provider=github + ManualToken setup), cancel_blocked_auth_gate_leaves_no_stale_replay (cancelling a BlockedAuth run lands directly on Cancelled with no active worker, and the SAME real gate ref can no longer resume it afterward — closes the #5067/#4957 class of gates staying "live"), and deny_auth_gate_rejects_a_non_auth_gate_ref_prefix (negative companion). Flip-check: temporarily bypassed the host.rs enrichment call site, confirmed the flagship test fails with the exact pre-fix empty-list shape, restored (crates/ironclaw_capabilities/src/host.rs left byte-identical — no production diff). - tests/support/reborn/harness.rs: RecordingNetworkHttpEgress gains an additive FIFO status_queue (default empty -> unchanged hardcoded-200 behavior) + install_network_status_script accessor. Needed because GithubIssueTools' real WASM HTTP call flows through the network-egress lane, not the runtime-egress lane the existing ScriptedHttpResponse matcher scripts (try_with_host_http_egress overwrites the runtime port — see reborn_integration_secret_injection.rs's module doc) — the prior double had no way to script a non-200 status on that lane at all. - tests/support/reborn/builder.rs: with_github_network_status(status) builder method (FIFO) threading github_network_statuses through RebornCapabilityBackend::install. - tests/support/reborn/capability_backend.rs: wires keyed_http_responses (previously dropped for this backend) and the new github_network_statuses into the GithubIssueTools install arm; no-op for existing empty-vec callers. - tests/support/reborn/assertions.rs: assert_network_egress_count, sibling of assert_egress_count for the network-lane call-count proofs above. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): unknown extension_id fails extension_install safely (W4-EXT-MANIFEST-ERR) Narrowed from the originally-scoped manifest-content arms (schema mismatch/reserved id/forbidden trust level): extension_install's only input is a catalog-resolved extension_id over a fixed, compile-time-embedded bundled catalog, so raw manifest TOML never reaches ManifestV2Error validation through this capability in production. The one reachable, wired arm is an unknown extension_id, which fails Failed{invalid_input} rather than panicking or no-oping. * test(reborn): W4-PROVIDER-VALIDATE — password/traceback caller-gap coverage #5001 (PinchBench bucket D) removed the crude SENSITIVE_PROVIDER_TEXT_MARKERS substring scan on provider reasoning/response_reasoning/signature text (bare words like "password"/"traceback" were false-positive-rejected, driving retry/give-up loops); the entropy-based LeakDetector is the real guard now. That contract was pinned only at the private free-function level (capability_port/provider_validation.rs's own unit test calling validate_provider_tool_call directly) — the #5001 caller gap. Adds provider_tool_call_registration_accepts_password_and_traceback_reasoning_text in crates/ironclaw_loop_support/src/capability_port.rs's existing test module, alongside the crate's other caller-level `port.validate_provider_tool_call(&call)` tests: drives the REAL production caller (LoopCapabilityPort::validate_provider_tool_call / register_provider_tool_call / invoke_capability on HostRuntimeLoopCapabilityPort, the same port the agent loop calls) with "password"/"traceback" in all three metadata fields, and proves genuine acceptance through to a real Completed dispatch (not just a non-error return). Flip-check: temporarily bloated response_reasoning past PROVIDER_METADATA_TEXT_MAX_BYTES to confirm the assertion mechanism discriminates a genuine rejection (fails with the expected "exceeds 16384 bytes" error), then restored the password/traceback content. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): W4-MCP-SSO-WIRING — NEAR AI host-managed fallback through build_reborn_services #5439 fixed NEAR AI MCP token resolution for SSO users: a Google-SSO user in the same tenant/agent as the boot owner, with no NEAR AI token of their own, now falls back to the host-managed (boot-owner) NEAR AI credential instead of being prompted for one. That contract was pinned only at the private rule/selector level (product_auth_runtime_credentials/tests.rs never calls build_reborn_services) — the composition-wiring gap this row targets. Adds local_dev_nearai_runtime_selection_falls_back_to_host_managed_account_for_sso_user to extension_lifecycle_capabilities_auth_tests.rs (extending the existing in-crate #[cfg(test)] composition-test file — same pattern as the sibling github manual-token test above, template: product_auth_refresh_composition.rs's "drive build_reborn_services directly" style). Drives ONLY the public surface: build_reborn_services (local-dev always derives nearai_mcp_host_managed_scope from the boot owner, so no live NEAR AI config injection is needed) plus the crate-internal runtime_credential_account_selection_service() accessor this file already had precedent for calling. Two discriminating arms on one composed `services`: an SSO user in the owner's tenant/agent (different project -- local-dev's host scope is project-unscoped by design) resolves via fallback; an SSO user under a different tenant does not (CredentialMissing) -- proving the positive arm is a real scope match, not the selector always succeeding. Flip-check: temporarily short-circuited RebornProductAuthServices::runtime_credential_account_selection_service to always return the un-decorated selector (pre-#5439 behavior), confirmed the new test's positive arm fails with CredentialMissing, restored (auth.rs left byte-identical -- no production diff). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): C-SYNTH deferred arms — AmbiguousSkill seeding + project_create fault-injection (wave-4 lane C) Two carry-over arms deferred from wave-3 PR #5584: - skill_activate AmbiguousSkill: seed a system-scoped AND a user-scoped skill sharing one name (both SkillTrust::Trusted per FilesystemSkillBundleRoot::system/user) so the real validate_explicit_mentions_are_unambiguous reject path fires end-to-end, not just at the skill_activation.rs unit-test level. New seed_user_skill_for_test harness helper (additive, mirrors seed_system_skill_for_test). - project_create fault-injection: new FaultInjectingProjectService test double (project_service_fault.rs) wrapping the real ProjectService at the production-wired Arc<dyn ProjectService> seam, forcing ProjectServiceError::Denied for a sentinel project name and delegating everything else to the real store. New project_tools_with_fault_injection()/project_lifecycle_fault_injected() harness+group constructors (additive). Deliberately NOT ProjectServiceError::Unavailable/Internal: investigation found both route through DefaultRecoveryStrategy's capability-retry branch, whose retry re-dispatch hits a real, confirmed production bug for provider-tool-call-originated invocations under local-dev composition — LocalDevCapabilityIo::resolve_capability_input rejects the reused input_ref on the retry with InvalidInvocation/"capability input ref was not staged for this loop run", collapsing the documented "retry twice, then a model-visible Failed" contract into an immediate terminal driver_unavailable. Documented in project_service_fault.rs; reported separately (not fixed — production change, out of this lane's scope). Both flip-checked (mutated seed/fault-injection to prove discriminating failure) and reverted before commit. * test(reborn): golden payload expansions — parallel tool_calls, image attachment, gated-turn resume (wave-4 lane C) Three scenario expansions to tests/reborn_integration_golden_payload.rs (carry-over from wave-3 PR #5584): - golden_parallel_tool_calls: new RebornScriptedReply::tool_calls([..]) constructor (additive to reply.rs) scripts ONE assistant response with TWO tool_calls[] entries, pinning that multiple calls in one turn each get a distinct id and each following tool-role message's tool_call_id lines up in order — a shape the existing single-call golden_tool_call_feedback can't exercise. - golden_image_attachment_turn: an inline image landed through the real submit_inbound_with_attachments entry point (RebornIntegrationGroup::attachment_tools()), routed through a vision-pattern model id, pinning the multimodal ContentPart::ImageUrl data: URL alongside the text part byte-for-byte. - golden_gated_turn_approve: a real BlockedApproval gate raised, approved, and resumed (RebornIntegrationGroup::live_approvals()), snapshotting BOTH inference calls around the gate — proving the resume doesn't drop, duplicate, or reorder accumulated turn history. Two normalization fixes to golden.rs, both needed for these scenarios to be reproducible (discovered while authoring, not pre-existing regressions): - Attachment-landing scenarios embed today's real UTC date in the landed project path (chrono::Utc::now(), no test seam) — added a second <DATE> filter alongside the existing loop-start-clock <TIMESTAMP> filter, or the image golden would bit-rot on every day boundary. - Tool-call ids come from a NEXT_TOOL_CALL_ID counter shared by every test in this one compiled binary; running more than one tool-call-scripting golden test concurrently (the default `cargo test` thread pool) makes the raw id values order-dependent. Added normalize_tool_call_ids: renumbers every call-<N> to a canonical call-1, call-2, … in order of first appearance per rendered payload, preserving the id/tool_call_id linkage the golden actually cares about without depending on the racy raw value. Confirmed behavior-preserving for the four pre-existing snapshots (no diff) and confirmed the race is fixed (5 consecutive full-suite green runs). Flip-checked (forced two parallel tool_calls to share one id; golden correctly failed) and reverted before commit. * test(reborn): W4-ASK-EACH-ONCE — ask-each-time approval resumes exactly once #5306 fixed an unresumable BlockedApproval loop: require_approval_for_profile_policy checked the explicit ask_each_time override (and the hard-floor force-approval class) BEFORE consulting the matching one-shot approval lease a resume carries, so an approved AskEachTime-gated resume re-hit the ask_each_time branch and re-gated instead of completing. Only a Python E2E test (test_tool_approval.py) exercised this class before; no Rust harness coverage existed. Adds scenario_ask_each_time_resumes_once.rs to the reborn_group_approvals binary (both approvals_group_e2e and its libsql variant), run LAST because it installs a persistent, group-wide ToolPermissionOverride::AskEachTime override on builtin.write_file that would force-gate every sibling scenario's plain-Ask-mode writes. Submits under the override, approves the resulting BlockedApproval gate, and proves the resume reaches Completed in ONE round trip with the write actually persisted — plus a companion "resumes exactly once" proof that re-approving the same now-resolved gate_ref fails NotPending (not a fresh re-raised gate). tests/support/reborn/harness.rs: adds a generic tool_permission_overrides: Option<Arc<dyn ToolPermissionOverrideStore>> field (mirrors the existing auto_approve_settings field's pattern — populated only by new_with_options, None elsewhere) and set_ask_each_time_override_for_test, generalizing disable_outbound_target_set_tool's override-store access beyond outbound_target_tools() to any host-runtime-backed harness/group. Flip-check: temporarily restored the pre-#5306 check order in profile_approval_authorization.rs (ask_each_time/hard-floor before the one-shot lease), confirmed the new scenario fails (the approved write never persists), restored (file left byte-identical — no production diff). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(reborn): triggered-origin chained gated journey (wave-4 lane C) Carry-over from wave-3 PR #5584: a triggered fire whose run raises a BlockedApproval gate, gets resolved, then CHAINS into a SECOND BlockedApproval gate in the SAME run (the post-resume model call issues another gated tool call instead of finalizing), driven through submit_triggered_turn_scripted (E-TRIGGERED-SUBMIT). New scenario_triggered_chained_gate::run_chained_approve, registered as its own live_approvals group in reborn_group_triggers::triggered_gate_group. Re-reads TurnOriginKind::ScheduledTrigger fresh at the coordinator boundary at THREE checkpoints (first park, second/chained park, final Completed) — not just trusting the initial TriggeredSubmission — closing the gap that a resume path rebuilding product_context from a non-trigger-aware default on the SECOND hop would otherwise slip through undetected. Also asserts both gate_refs are genuinely distinct, both chained writes persisted, and the final reply persisted in the trigger's own thread. Flip-checked (asserted the wrong origin kind; the checkpoint helper correctly failed with the real ScheduledTrigger value in the diagnostic) and reverted before commit. Also folds in `cargo fmt` whitespace-only fixes surfaced while formatting this new file (golden.rs, harness.rs, reply.rs, and two golden/skill-activate test files touched by prior lane-C commits) — no semantic change, reran their test bins green after formatting. * test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C) Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted hand-mirrored ConversationContentRefMaterializer::materialize_prompt (trigger_resolve_request + record_trigger_prompt + the content-ref shape, field-by-field) instead of reusing it, and — as flagged — deliberately SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged as a drift trap (trusted-trigger materialization is an ownership boundary, AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")] materializer helper returning (TriggerMaterializedPrompt, TurnScope) living beside the real materializer, held out of #5584 as a fast-follow with this exact shape. New production-crate (test-support-gated, compiles out of default builds) surface in ironclaw_reborn_composition: - trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test, #[cfg(any(test, feature = "test-support"))] — runs the REAL production pipeline via ConversationContentRefMaterializer::materialize_prompt (authorize + validate + resolve + record + content-ref), then an idempotent second resolve_or_create_binding_with_trusted_scope call (safe — same request, same already-created binding) to also return the TurnScope the trait method computes internally but never exposes. Plus two crate-tier unit tests: positive (returned scope/content-ref match an independent ground-truth resolve) and negative (an unsafe prompt is rejected by the REAL safety validator). - test_support/trigger_materializer.rs: pub, feature="test-support"-gated thin wrapper re-exported from test_support/mod.rs — the established wrap_project_create_capability_for_test-style pattern. tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now calls this ONE production-owned helper instead of hand-mirroring; deletes ~90 net lines of duplicated resolve/thread-record/content-ref logic. Verified default-features build of ironclaw_reborn_composition stays warning-free (function/import correctly compile out). Flip-checked at the INTEGRATION level (not just the new crate-unit tests): forced an injection-pattern prompt through submit_triggered_turn_scripted — every triggered-gate scenario correctly failed with "rejected by safety scan", proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt is now closed. Reverted before commit. All touched integration test bins (reborn_group_triggers, reborn_integration_triggered_submit, plus every other wave-4 lane-C bin) rerun green after the extraction. * test(reborn): W4-TRIGSLACK-SETTLE — auth-gate coverage for TriggeredRunDeliveryDriver TriggeredRunDeliveryDriver was exercised by exactly one crate-tier test (triggered_approval_prompt_route_resolves_dm_approve_on_foreign_scope), covering only the approval-gate path. Add the auth-gate twin: a BlockedAuth triggered run whose auth-prompt preference resolves to the creator's DM must carry the OAuth setup link (triggered_auth_prompt_route_delivers_dm_setup_link_on_foreign_scope), mirroring slack_dm_delivers_auth_prompt_with_setup_link_after_immediate_ack's assertion shape but driven through the real triggered-delivery driver. TriggeredRunDeliveryDriver only ever targets the creator's personal DM (never a channel), so there is no literal "channel" arm to mirror slack_channel_auth_prompt_omits_setup_link_after_immediate_ack. The discriminating negative arm instead exercises the driver's own send-time OAuth-DM backstop (triggered_auth_prompt_oauth_target_not_dm_suppresses_setup_link_and_cancels_run): when the resolved auth-prompt target is not a personal DM, the setup link must never be posted and the blocked run must be cancelled instead. ScriptedTriggerCoordinator gains an additive new_with_first_poll constructor (script an arbitrary first-poll status/gate_ref instead of the hardcoded BlockedApproval/GATE pair) and a functional cancel_run (previously unreachable!, since the approval-only scenario never called it) to support the OAuth-not-DM arm. Test code only; no production changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(reborn): extract trigger-prompt materializer test-support helper (wave-4 lane C) Committed follow-up on PR #5584's review thread: submit_triggered_turn_scripted hand-mirrored ConversationContentRefMaterializer::materialize_prompt (trigger_resolve_request + record_trigger_prompt + the content-ref shape, field-by-field) instead of reusing it, and — as flagged — deliberately SKIPPED authorize_trigger_fire and validate_trusted_trigger_prompt. Flagged as a drift trap (trusted-trigger materialization is an ownership boundary, AGENTS.md:61); the review agreed the fix is a #[cfg(feature = "test-support")] materializer helper returning (TriggerMaterializedPrompt, TurnScope) living beside the real materializer, held out of #5584 as a fast-follow with this exact shape. New production-crate (test-support-gated, compiles out of default builds) surface in ironclaw_reborn_composition: - trigger_poller_trusted_submit.rs: materialize_trigger_prompt_for_test, #[cfg(any(test, feature = "test-support"))] — runs the REAL production pipeline via ConversationContentRefMaterializer::materialize_prompt (authorize + validate + resolve + record + content-ref), then an idempotent second resolve_or_create_binding_with_trusted_scope call (safe — same request, same already-created binding) to also return the TurnScope the trait method computes internally but never exposes. Plus two crate-tier unit tests: positive (returned scope/content-ref match an independent ground-truth resolve) and negative (an unsafe prompt is rejected by the REAL safety validator). - test_support/trigger_materializer.rs: pub, feature="test-support"-gated thin wrapper re-exported from test_support/mod.rs — the established wrap_project_create_capability_for_test-style pattern. tests/support/reborn/triggered_submit.rs: submit_triggered_turn_scripted now calls this ONE production-owned helper instead of hand-mirroring; deletes ~90 net lines of duplicated resolve/thread-record/content-ref logic. Verified default-features build of ironclaw_reborn_composition stays warning-free (function/import correctly compile out). Flip-checked at the INTEGRATION level (not just the new crate-unit tests): forced an injection-pattern prompt through submit_triggered_turn_scripted — every triggered-gate scenario correctly failed with "rejected by safety scan", proving the old hand-mirrored path's skip of validate_trusted_trigger_prompt is now closed. Reverted before commit. All touched integration test bins (reborn_group_triggers, reborn_integration_triggered_submit, plus every other wave-4 lane-C bin) rerun green after the extraction. * test(reborn): review fixes — consolidate slack e2e poll helpers, cite #5608 in fault-injection rationale Factor the three near-identical bounded-poll-for-chat.postMessage helpers in slack_serve/e2e_tests.rs into one predicate-parameterized wait_for_post_messages_matching, and replace "Lane C final report" citations with the filed issue (#5608) in the local-dev retry-path rationale comments. * test(reborn): address wave4 review comments * test(reborn): relax auth gate harness wait --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Service Unavailablefrom the OAuth card only after the authorize action is clicked.pending_auth_resumeand clearingpending_approval_resumeat the auth gate.RUNuntil refresh.Closes #5066
Closes #5070
Tests
npm run buildnode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/gates.test.mjsnode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjsnode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChatEvents.test.mjsnode --test crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/*.test.mjscargo test -p ironclaw_webui_v2_static --features webui-v2-beta --test i18n_consistencycargo test -p ironclaw_reborn_composition webui_event_stream_keeps_oauth_requirement_as_oauth_prompt_without_url --features webui-v2-betacargo fmt --checkcargo test -p ironclaw_agent_loop auth_resume_after_approval_carries_resume_token_and_approval_request_idcargo test -p ironclaw_product_workflow denied_auth_without_flow_recordcargo test -p ironclaw_reborn stamp_resume_disposition_fails_closed_when_both_slots_match_last_gatecargo test -p ironclaw_reborn_composition webui_event_stream_projects_live_tool_failure --features webui-v2-beta