Skip to content

feat(reborn): export caller-scoped QA run artifacts - #6344

Merged
serrrfirat merged 5 commits into
mainfrom
codex/reborn-run-artifacts
Jul 21, 2026
Merged

serrrfirat merged 5 commits into
mainfrom
codex/reborn-run-artifacts

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Jul 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • export one exact Reborn run as ironclaw.run_artifact.v1
  • authorize from the authenticated caller and select trajectory records and logs by caller-owned thread/run scope
  • add the architecture-targeted generic query read conduit plus typed run/log view descriptors
  • retire query_logs and query_operator_logs, shrinking the frozen facade from 89 methods on main to 88
  • add a run download action for finalized assistant replies
  • convert downloads into review-required single-turn fixture candidates without auto-blessing them

Security and deployment model

Thread and run IDs are locators only. Tenant, agent, project, and user authority come from the authenticated WebUI caller. Cross-user access returns an indistinguishable 404 before logs are queried.

Logs use the existing scoped operator-log service. They are bounded and process-local, so the artifact reports logs.complete=false and explicitly reports unavailable/truncated state. This stays provider-neutral and does not couple the product to Railway or trace-commons.

Provider signatures and reasoning are omitted. Message content, tool arguments, and log messages pass through the deterministic trace redactor; sensitive JSON keys use the canonical structured redactor.

QA workflow

  1. QA runs a normal manual scenario.
  2. On the finalized assistant reply, QA downloads that run.
  3. A developer runs scripts/import-reborn-run-artifact.py on the JSON.
  4. The importer creates a review-required single-turn candidate.
  5. The developer adds scenario assertions and hermetic external-service mocks/recordings before committing the fixture.

Stacked follow-up

PR #6346 adds complete multi-run thread export on top of this run-only vertical slice. This PR can be tested and merged independently first.

Verification

  • cargo test -p ironclaw_architecture --test reborn_facade_method_freeze_ratchet
  • cargo test -p ironclaw_product_workflow --test reborn_services_contract query_logs
  • cargo test -p ironclaw_product_workflow --test reborn_services_contract query_operator_logs
  • cargo test -p ironclaw_product_workflow --test reborn_services_contract run_artifact
  • cargo test -p ironclaw_webui --test webui_v2_handlers_contract logs
  • cargo test -p ironclaw_webui --test webui_v2_handlers_contract run_artifact
  • cargo test -p ironclaw_webui --test webui_v2_descriptors_contract
  • cargo clippy -p ironclaw_product_workflow -p ironclaw_webui --all-targets -- -D warnings
  • frontend pnpm typecheck
  • frontend pnpm vitest run src/pages/chat/components/message-bubble.test.ts
  • python3 scripts/test-import-reborn-run-artifact.py
  • scripts/ci/check-reborn-qa-fixtures.sh

@ironloopai

ironloopai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

🔎 IronLoop Review Status

Head: 117f9fbe84c9ece8f800416946d70fc93586dc9a
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-21T10:25:04.297Z

Current reviewers:

Reviewer State Verdict Findings Last update
ironloop/common-reviewer (reviewer) Superseded N/A N/A 2026-07-20T12:55:42.026Z
Reviewer summaries
Reviewer Detail
ironloop/common-reviewer (reviewer) Superseded by a newer PR head. New head: 9ae7af5. Previous verdict: Review declined.
Recent activity
Time Reviewer State Detail
2026-07-20T11:52:24.563Z ironloop/common-reviewer (reviewer) Queued Accepted review request for head 64400c3.
2026-07-20T11:52:24.563Z ironloop/common-reviewer (reviewer) Queued Waiting for this reviewer lane to become available.
2026-07-20T11:52:25.322Z ironloop/common-reviewer (reviewer) Started Reviewer worker started.
2026-07-20T11:52:27.943Z ironloop/common-reviewer (reviewer) Workspace ready Prepared isolated checkout (head_ref) at 64400c3.
2026-07-20T11:53:17.681Z ironloop/common-reviewer (reviewer) Result captured Skipped; 0 blocking findings.
2026-07-20T11:53:17.681Z ironloop/common-reviewer (reviewer) Completed Review completed and terminal status was persisted.
2026-07-20T12:55:42.026Z ironloop/common-reviewer (reviewer) Superseded A newer PR head replaced this review (9ae7af5).
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-6344 July 20, 2026 11:52 Destroyed
@github-actions github-actions Bot added scope: docs Documentation size: XL 500+ changed lines risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Jul 20, 2026
@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds caller-scoped ironclaw.run_artifact.v1 exports with deterministic redaction and bounded logs, routes WebUI reads through a unified view API, tests ownership and scoping, and adds a CLI that converts artifacts into trace candidates.

Changes

Run artifact export and unified view routing

Layer / File(s) Summary
Artifact contract and facade API
crates/ironclaw_product_workflow/src/..., FEATURE_PARITY.md, crates/ironclaw_architecture/tests/...
Defines artifact types and schema, adds the generic query facade, removes dedicated log methods, and updates parity and freeze records.
Scoped artifact construction
crates/ironclaw_product_workflow/src/reborn_services/run_artifact.rs, crates/ironclaw_product_workflow/tests/...
Selects one caller-owned run, filters replayable messages, redacts content and tool arguments, exports bounded logs, and tests ownership and scoping.
Unified log view routing
crates/ironclaw_product_workflow/src/reborn_services/log_views.rs, crates/ironclaw_webui/src/webui_v2/handlers.rs, crates/ironclaw_product_workflow/tests/...
Moves log validation and scoping behind paginated views and updates WebChat handlers and contracts to use the generic query path.
WebChat artifact wiring
crates/ironclaw_webui/src/webui_v2/handlers.rs, crates/ironclaw_webui/tests/...
Exposes the artifact handler and verifies URL thread and run identifiers reach the artifact view request.
Trace candidate importer
scripts/import-reborn-run-artifact.py, scripts/test-import-reborn-run-artifact.py
Validates redacted artifacts, groups tool calls and expected results into trace steps, derives metadata, writes candidates, and tests validation behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant WebChatV2
  participant RebornServices
  participant OperatorLogStore
  WebChatV2->>RebornServices: query RUN_ARTIFACT_VIEW with thread_id and run_id
  RebornServices->>OperatorLogStore: query bounded scoped logs
  OperatorLogStore-->>RebornServices: logs or unavailable status
  RebornServices-->>WebChatV2: redacted run artifact
Loading

Possibly related PRs

Suggested reviewers: ilblackdragon, benkurrek, henrypark133

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers summary, security, QA workflow, and verification, but misses most required template sections. Fill in the template sections: change type, linked issue, validation checklist, full test strategy, security impact, trust-boundary checklist, database impact, blast radius, rollback plan, and review follow-through.
✅ Passed checks (3 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 Conventional-commits style is used and the title matches the added run-artifact export feature.

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.

@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 Declined: reviewer

Review at a glance

Disposition Head
⏭️ Review declined 64400c36859a

Head: 64400c36859aff7a15e30257aab30765a7e03933
Reason: The diff changes 2,802 files (215,749 additions and 422,063 deletions), including broad architecture, CI, dependency, and test-suite rewrites unrelated to the stated feature.
Next: Split or provide a focused comparison containing only the run-artifact feature and its required tests; then rerun review against that focused base/head range.

Run details

Status: Current
Trustworthy review produced: no

Summary

Skipped: the supplied base-to-head comparison is a mega-diff, far broader than the stated run-artifact feature, and cannot be reviewed reliably as one change within the available scope.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a feature to export caller-owned, redacted run evidence bundles (under the ironclaw.run_artifact.v1 schema) containing messages, tool calls, and bounded process-local logs, along with a Python script to convert these artifacts into LLM trace candidates. It also removes the channel-connect command resolution from the chat interface. The review feedback highlights a critical issue where removing tokio::spawn in the Slack delivery hook makes external network I/O synchronous, which could block the main request flow. Additionally, the feedback suggests a performance optimization in redact_json to avoid redundant cloning, and robustness improvements in the Python import script to handle case-insensitive message kinds.

Important

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

Comment on lines +557 to +572
match self.current_driver().await {
Ok(Some(driver)) => driver.on_trigger_submitted(fire, run_id, scope).await,
Ok(None) => {
tracing::debug!(
%run_id,
"Slack dynamic triggered-run delivery skipped: Slack setup is not configured"
);
}
});
Err(error) => {
tracing::warn!(
%run_id,
%error,
"Slack dynamic triggered-run delivery skipped: delivery hook unavailable"
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Removing tokio::spawn here causes the Slack delivery hook to run synchronously within the main request/turn submission flow. Since Slack delivery involves external network I/O, this can introduce significant latency spikes or block the user-facing thread if the Slack API is slow, rate-limited, or experiencing downtime. It is highly recommended to keep this operation asynchronous by running it in a background task.

        let hook = self.clone();
        tokio::spawn(async move {
            match hook.current_driver().await {
                Ok(Some(driver)) => driver.on_trigger_submitted(fire, run_id, scope).await,
                Ok(None) => {
                    tracing::debug!(
                        %run_id,
                        "Slack dynamic triggered-run delivery skipped: Slack setup is not configured"
                    );
                }
                Err(error) => {
                    tracing::warn!(
                        %run_id,
                        %error,
                        "Slack dynamic triggered-run delivery skipped: delivery hook unavailable"
                    );
                }
            }
        });

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: OBSOLETE — the referenced Slack runtime file is absent from the current PR head.\n

Comment on lines +298 to +323
fn redact_json(
redactor: &DeterministicTraceRedactor,
input: &serde_json::Value,
) -> (serde_json::Value, bool) {
let keyed = redact_sensitive_json(input);
let redacted = match keyed {
serde_json::Value::String(value) => {
serde_json::Value::String(redactor.redact_text(&value).0)
}
serde_json::Value::Array(values) => serde_json::Value::Array(
values
.iter()
.map(|value| redact_json(redactor, value).0)
.collect(),
),
serde_json::Value::Object(values) => serde_json::Value::Object(
values
.iter()
.map(|(key, value)| (key.clone(), redact_json(redactor, value).0))
.collect(),
),
value => value,
};
let changed = redacted != *input;
(redacted, changed)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The current implementation of redact_json recursively calls redact_sensitive_json on every single node of the JSON tree. Since redact_sensitive_json is only useful for JSON Objects (to redact sensitive keys), calling it on strings, arrays, numbers, booleans, and nulls is highly inefficient and causes a massive amount of unnecessary cloning and memory allocations. We can optimize this by only calling redact_sensitive_json when the input is a serde_json::Value::Object.

fn redact_json(
    redactor: &DeterministicTraceRedactor,
    input: &serde_json::Value,
) -> (serde_json::Value, bool) {
    let redacted = match input {
        serde_json::Value::String(value) => {
            serde_json::Value::String(redactor.redact_text(value).0)
        }
        serde_json::Value::Array(values) => serde_json::Value::Array(
            values
                .iter()
                .map(|value| redact_json(redactor, value).0)
                .collect(),
        ),
        serde_json::Value::Object(_) => {
            let keyed = redact_sensitive_json(input);
            if let serde_json::Value::Object(keyed_values) = keyed {
                serde_json::Value::Object(
                    keyed_values
                        .into_iter()
                        .map(|(key, value)| (key, redact_json(redactor, &value).0))
                        .collect(),
                )
            } else {
                keyed
            }
        }
        value => value.clone(),
    };
    let changed = redacted != *input;
    (redacted, changed)
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in b3bb9ef: redaction now performs structural sensitive-key redaction once, then redacts text while consuming that result.\nVerification: product-workflow run-artifact tests and clippy passed.\n


def trace_candidate(artifact: dict[str, Any], model_override: str | None) -> dict[str, Any]:
messages = sorted(artifact["messages"], key=lambda item: item.get("sequence", 0))
user = next((item for item in messages if item.get("kind") == "user"), None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The kind field of a message is serialized from the Rust MessageKind enum. Depending on the serialization settings, it might be serialized as CamelCase (e.g., "User") rather than lowercase. To ensure robustness and prevent the import script from failing with a ValueError when processing CamelCase values, perform a case-insensitive comparison by converting the value to lowercase.

Suggested change
user = next((item for item in messages if item.get("kind") == "user"), None)
user = next((item for item in messages if str(item.get("kind") or "").lower() == "user"), None)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: INCORRECT — MessageKind uses snake_case serialization, so exported artifacts use user.\n

(
item
for item in reversed(messages)
if item.get("kind") == "assistant" and str(item.get("content", "")).strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Perform a case-insensitive comparison for the kind field here as well to ensure robustness against CamelCase serialization of the MessageKind enum.

Suggested change
if item.get("kind") == "assistant" and str(item.get("content", "")).strip()
if str(item.get("kind") or "").lower() == "assistant" and str(item.get("content", "")).strip()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: INCORRECT — MessageKind uses snake_case serialization, so exported artifacts use assistant.\n

@railway-app

railway-app Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Jul 21, 2026 at 10:38 am

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

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_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs (1)

2651-2654: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test name promises "without fetching connectable channels" but the fixture doesn't enforce it. Every other prompt test proves the negative with a throwing stub (e.g. throw new Error("ordinary prompts should not fetch connectable channels")); here fetchQuery happily runs queryFn, so a regression that re-introduced a connectable-channel fetch would pass silently.

♻️ Make the guarantee load-bearing
     queryClient: {
-      fetchQuery: async ({ queryFn }) => queryFn(),
+      fetchQuery: async () => {
+        throw new Error("slash connect prompts should not fetch connectable channels");
+      },
       invalidateQueries: () => {},
     },

As per coding guidelines: "Comments and documentation that promise guarantees must match the code and tests."

🤖 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_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs`
around lines 2651 - 2654, Update the queryClient fixture in the test for prompts
without fetching connectable channels so fetchQuery throws an error if invoked,
rather than executing queryFn. Preserve invalidateQueries as a no-op and match
the throwing-stub pattern used by the other prompt tests, making any
connectable-channel fetch regression fail.

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 `@crates/ironclaw_webui_v2_static/src/assets.rs`:
- Around line 261-265: Add a negative assertion in
chat_omits_connect_action_while_extensions_render_slack_setup_ui for the served
dist/app.js bundle, verifying it excludes ChannelConnectCard,
channelConnectAction, and dismissChannelConnectAction as appropriate. Use the
existing asset_text helper and preserve the source-module assertions.

In `@crates/ironclaw_webui_v2/src/handlers/run_artifact.rs`:
- Around line 1-5: Update the handlers in run_artifact.rs to extract
WebUiV2Capabilities through Axum’s Extension extractor, importing the capability
type from its existing module. Add this required extension to each affected
handler’s parameters so requests missing it are rejected rather than processed.

In `@scripts/import-reborn-run-artifact.py`:
- Around line 74-94: Widen the exception handling fence in main() around
trace_candidate so malformed nested artifact data raising KeyError or TypeError
follows the existing stderr error and return-code-2 path. Preserve the current
ValueError handling and avoid changing trace_candidate’s field access behavior.
- Around line 73-119: Update the step-building logic in the
tool_groups/assistant flow so pending_results from the final tool-call group
cannot be silently discarded when assistant is None. Either attach those
unconsumed results to the last step or raise ValueError for tool calls without a
trailing assistant reply, while preserving the existing no-replayable-message
guard behavior.
- Line 13: Centralize the run-artifact schema and redaction-pipeline constants
in the owning run_artifact module, exposing the pipeline identifier as a public
constant alongside RUN_ARTIFACT_SCHEMA. Update
scripts/import-reborn-run-artifact.py and the webui_v2_handlers_contract tests
to consume the shared/generated contract values instead of hardcoding either
literal, preserving validation against the same contract across languages.

In `@scripts/test-import-reborn-run-artifact.py`:
- Around line 15-39: Extend
test_groups_parallel_calls_and_attaches_results_to_next_step with coverage for
two sequential provider tool-call groups, asserting each group’s
expected_tool_results attaches to the following step correctly. Add a separate
case where the artifact ends immediately after a tool-call step without a
finalized assistant message, and assert the expected trailing-results behavior
from trace_candidate.

---

Outside diff comments:
In
`@crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs`:
- Around line 2651-2654: Update the queryClient fixture in the test for prompts
without fetching connectable channels so fetchQuery throws an error if invoked,
rather than executing queryFn. Preserve invalidateQueries as a no-op and match
the throwing-stub pattern used by the other prompt tests, making any
connectable-channel fetch regression fail.
🪄 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: 04736fa3-f93a-4bef-8706-fd0a5ac39331

📥 Commits

Reviewing files that changed from the base of the PR and between 792da03 and 64400c3.

⛔ Files ignored due to path filters (1)
  • tests/fixtures/llm_traces/reborn_qa/README.md is excluded by !tests/fixtures/**
📒 Files selected for processing (28)
  • FEATURE_PARITY.md
  • crates/ironclaw_product_workflow/src/lib.rs
  • crates/ironclaw_product_workflow/src/reborn_services.rs
  • crates/ironclaw_product_workflow/src/reborn_services/run_artifact.rs
  • crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
  • crates/ironclaw_reborn_composition/src/slack_host_beta/runtime_setup.rs
  • crates/ironclaw_webui_v2/CLAUDE.md
  • crates/ironclaw_webui_v2/src/descriptors.rs
  • crates/ironclaw_webui_v2/src/handlers.rs
  • crates/ironclaw_webui_v2/src/handlers/run_artifact.rs
  • crates/ironclaw_webui_v2/src/lib.rs
  • crates/ironclaw_webui_v2/src/router.rs
  • crates/ironclaw_webui_v2/tests/webui_v2_descriptors_contract.rs
  • crates/ironclaw_webui_v2/tests/webui_v2_handlers_contract.rs
  • crates/ironclaw_webui_v2_static/src/assets.rs
  • crates/ironclaw_webui_v2_static/static/js/lib/api.js
  • crates/ironclaw_webui_v2_static/static/js/lib/channel-connect.js
  • crates/ironclaw_webui_v2_static/static/js/lib/channel-connect.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/chat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/channel-connect-card.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/channel-connect-card.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/message-bubble.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/message-bubble.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/useChat-send.test.mjs
  • scripts/import-reborn-run-artifact.py
  • scripts/test-import-reborn-run-artifact.py
💤 Files with no reviewable changes (6)
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/channel-connect-card.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/components/channel-connect-card.js
  • crates/ironclaw_webui_v2_static/static/js/lib/channel-connect.test.mjs
  • crates/ironclaw_webui_v2_static/static/js/lib/channel-connect.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/hooks/useChat.js
  • crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/chat.test.mjs

Comment thread crates/ironclaw_webui_v2_static/src/assets.rs
Comment on lines +1 to +5
use axum::Json;
use axum::extract::{Extension, Path, State};
use ironclaw_product_workflow::{
RebornRunArtifact, RebornRunArtifactRequest, WebUiAuthenticatedCaller,
};

@coderabbitai coderabbitai Bot Jul 20, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail closed on missing WebUiV2Capabilities.

As per path instructions for WebUI v2, handlers must fail closed when the WebUiV2Capabilities extension is missing. Extracting the extension ensures Axum enforces its presence and rejects the request if it is omitted.

🔒️ Proposed fix to enforce capabilities
 use ironclaw_product_workflow::{
-    RebornRunArtifact, RebornRunArtifactRequest, WebUiAuthenticatedCaller,
+    RebornRunArtifact, RebornRunArtifactRequest, WebUiAuthenticatedCaller, WebUiV2Capabilities,
 };
 use serde::Deserialize;

 // ...

 pub async fn get_run_artifact(
     State(state): State<WebUiV2State>,
     Extension(caller): Extension<WebUiAuthenticatedCaller>,
+    Extension(_capabilities): Extension<WebUiV2Capabilities>,
     Path(path): Path<RunArtifactPath>,
 ) -> Result<Json<RebornRunArtifact>, WebUiV2HttpError> {

Also applies to: 18-22

🤖 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_v2/src/handlers/run_artifact.rs` around lines 1 - 5,
Update the handlers in run_artifact.rs to extract WebUiV2Capabilities through
Axum’s Extension extractor, importing the capability type from its existing
module. Add this required extension to each affected handler’s parameters so
requests missing it are rejected rather than processed.

Source: Path instructions

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed; no code change: OBSOLETE — this refers to the former WebUI v2 crate path; the current caller-authorized read route is in ironclaw_webui.\n

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Candidate run-artifact handlers =='
fd -a 'run_artifact\.rs$' crates || true

printf '%s\n' '== Route registrations and handler references =='
rg -n -C 4 --glob '*.rs' \
  '(get_run_artifact|run-artifact|run_artifact|WebUiV2Capabilities|WebUiAuthenticatedCaller)' \
  crates/ironclaw_webui crates/ironclaw_webui_v2 2>/dev/null || true

printf '%s\n' '== Handler outlines =='
for file in $(fd -a 'run_artifact\.rs$' crates); do
  ast-grep outline "$file" --items all --type function --view expanded || true
done

Length of output: 201073


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '== Active run-artifact handler =='
cat -n crates/ironclaw_webui/src/webui_v2/handlers/run_artifact.rs

printf '%s\n' '== Active route registration and extension layers =='
rg -n -C 8 --glob '*.rs' \
  'get_run_artifact|run-artifact|run_artifact|Extension\(.*WebUiV2Capabilities|WebUiV2Capabilities' \
  crates/ironclaw_webui/src/webui_v2/router.rs \
  crates/ironclaw_webui/src/webui_v2 \
  | head -n 400

printf '%s\n' '== Focused run-artifact contract test =='
sed -n '1920,1985p' crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Length of output: 40034


@serrrfirat, agreed the original crates/ironclaw_webui_v2 path is obsolete. However, the underlying finding still applies to the migrated route: crates/ironclaw_webui/src/webui_v2/handlers/run_artifact.rs Line 19 extracts WebUiAuthenticatedCaller but not WebUiV2Capabilities.

The route is registered at crates/ironclaw_webui/src/webui_v2/router.rs Lines 193-196, and the current contract test supplies the capability extension, so it does not cover the missing-extension boundary. Per the WebUI v2 fail-closed invariant, this handler should require the extension as well. I’m retaining the finding with the corrected path.


✏️ Learnings added
Learnt from: serrrfirat
Repo: nearai/ironclaw PR: 6344
File: crates/ironclaw_webui_v2/src/handlers/run_artifact.rs:0-0
Timestamp: 2026-07-21T08:19:09.687Z
Learning: The active caller-authorized WebChat v2 run-artifact route is `crates/ironclaw_webui/src/webui_v2/handlers/run_artifact.rs::get_run_artifact`; the former `crates/ironclaw_webui_v2` crate path is obsolete.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread scripts/import-reborn-run-artifact.py
Comment thread scripts/import-reborn-run-artifact.py
Comment thread scripts/import-reborn-run-artifact.py
Comment thread scripts/test-import-reborn-run-artifact.py
@serrrfirat
serrrfirat force-pushed the codex/reborn-run-artifacts branch from 64400c3 to 9ae7af5 Compare July 20, 2026 12:55
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 20, 2026 12:55 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

🤖 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 `@FEATURE_PARITY.md`:
- Line 334: Update the lower-priority trajectory-export entry near the existing
status listing to match the 🚧 parity status on the Trajectory export row, or
clarify its scope so it does not contradict the documented WebChat v2
capability. Keep the documentation aligned with the implemented and tested
guarantees, including the remaining follow-up limitations.
🪄 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: a15782eb-10d4-42bc-bab2-15909b90c1d9

📥 Commits

Reviewing files that changed from the base of the PR and between 64400c3 and 9ae7af5.

📒 Files selected for processing (2)
  • FEATURE_PARITY.md
  • crates/ironclaw_architecture/tests/reborn_facade_method_freeze_ratchet.rs

Comment thread FEATURE_PARITY.md
@github-actions

github-actions Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Coverage ratchet

Ratchet mode: ENFORCING

RATCHET PASS: global
  observed: 86.37% (320226 / 370760 lines)
  floor:    85.3% (tolerance 0.5pp -> effective floor 84.8%)
  denominator: 370760 lines now vs 320188 at floor capture (+50572 lines, +15.79%) — material change (>5%)

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

Reborn integration-tier coverage

Line coverage (Reborn crates): 86.37% — 320226 / 370760 lines

Per-crate breakdown (65 crates, lowest-covered first)
Crate Line % Covered / Total
ironclaw_prompt_envelope 0% 0 / 88
ironclaw_scripts 0% 0 / 345
ironclaw_event_projections 43.31% 673 / 1554
ironclaw_observability 61.54% 16 / 26
ironclaw_channel_host 62.08% 185 / 298
ironclaw_authorization 62.46% 604 / 967
ironclaw_dispatcher 62.88% 83 / 132
ironclaw_mcp 65.63% 611 / 931
ironclaw_filesystem 68.64% 4139 / 6030
ironclaw_memory 69.2% 773 / 1117
ironclaw_reborn_migration 71.87% 2407 / 3349
ironclaw_trust 72.88% 661 / 907
ironclaw_wasm_limiter 74.6% 47 / 63
ironclaw_extractors 74.72% 538 / 720
ironclaw_capabilities 74.79% 2513 / 3360
ironclaw_projects 76.48% 400 / 523
ironclaw_reborn_cli 76.54% 9890 / 12922
ironclaw_triggers 77.33% 2531 / 3273
ironclaw_llm 78.43% 20568 / 26224
ironclaw_product_context 78.57% 11 / 14
ironclaw_wasm_product_adapters 80.36% 1448 / 1802
ironclaw_process_sandbox 80.65% 671 / 832
ironclaw_first_party_extensions 81.06% 5965 / 7359
ironclaw_memory_native 81.17% 3195 / 3936
ironclaw_events 81.95% 1594 / 1945
ironclaw_telegram_extension 82.08% 4414 / 5378
ironclaw_network 82.98% 673 / 811
ironclaw_reborn_event_store 83.03% 1169 / 1408
ironclaw_reborn_identity 83.59% 433 / 518
ironclaw_processes 83.76% 939 / 1121
ironclaw_secrets 83.79% 2548 / 3041
ironclaw_wasm 84.44% 1069 / 1266
ironclaw_reborn_config 84.66% 2152 / 2542
ironclaw_product_workflow 84.89% 11373 / 13397
ironclaw_auth 84.97% 3279 / 3859
ironclaw_run_state 85.61% 458 / 535
ironclaw_channel_delivery 86.11% 1383 / 1606
ironclaw_common 86.66% 1741 / 2009
ironclaw_threads 87.08% 4844 / 5563
ironclaw_skills 87.58% 4470 / 5104
ironclaw_slack_v2_adapter 87.89% 2024 / 2303
ironclaw_extensions 87.93% 2913 / 3313
ironclaw_product_adapter_registry 88.06% 531 / 603
ironclaw_product_adapters 88.1% 3384 / 3841
ironclaw_reborn_traces 88.2% 11946 / 13544
ironclaw_turns 88.48% 14408 / 16284
ironclaw_hooks 88.62% 9597 / 10829
ironclaw_reborn_openai_compat 88.79% 3778 / 4255
ironclaw_host_runtime 88.8% 17752 / 19991
ironclaw_host_api 88.88% 4645 / 5226
ironclaw_webui 89.41% 7768 / 8688
ironclaw_reborn_composition 89.53% 73202 / 81767
ironclaw_telegram_v2_adapter 89.65% 2712 / 3025
ironclaw_approvals 90.18% 1598 / 1772
ironclaw_conversations 90.39% 3123 / 3455
ironclaw_event_streams 90.82% 1009 / 1111
ironclaw_runner 91.2% 17007 / 18649
ironclaw_resources 91.67% 4477 / 4884
ironclaw_loop_host 92.29% 16104 / 17450
ironclaw_attachments 93.06% 630 / 677
ironclaw_agent_loop 94.81% 9467 / 9985
ironclaw_safety 95.15% 3749 / 3940
ironclaw_outbound 95.52% 3451 / 3613
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

@serrrfirat
serrrfirat force-pushed the codex/reborn-run-artifacts branch from 9ae7af5 to f2319dd Compare July 20, 2026 13:40
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 20, 2026 13:40 Destroyed
@serrrfirat serrrfirat changed the title feat(reborn): export caller-scoped QA run artifacts feat(reborn): export caller-scoped QA trajectory artifacts Jul 20, 2026
@serrrfirat
serrrfirat force-pushed the codex/reborn-run-artifacts branch from f2319dd to 9ae7af5 Compare July 20, 2026 13:47
@serrrfirat serrrfirat changed the title feat(reborn): export caller-scoped QA trajectory artifacts feat(reborn): export caller-scoped QA run artifacts Jul 20, 2026
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 21, 2026 08:17 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 21, 2026 08:37 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 21, 2026 09:59 Destroyed
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-6344 July 21, 2026 10:25 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

🤖 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/src/webui_v2/handlers.rs`:
- Around line 1989-2008: Extract the repeated view-query dispatch from
query_operator_logs and query_logs into a shared async query_view helper. Have
it serialize the request, construct RebornViewQuery with the supplied view_id
and cursor, call state.services().query, and deserialize page.payload; update
both handlers to delegate to it while preserving their existing request and
response types.
🪄 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: 77242222-d0d2-4aa2-bd3c-7afce5e4d25a

📥 Commits

Reviewing files that changed from the base of the PR and between d063baa and 117f9fb.

📒 Files selected for processing (7)
  • crates/ironclaw_architecture/tests/reborn_facade_method_freeze_ratchet.rs
  • crates/ironclaw_product_workflow/src/lib.rs
  • crates/ironclaw_product_workflow/src/reborn_services.rs
  • crates/ironclaw_product_workflow/src/reborn_services/log_views.rs
  • crates/ironclaw_product_workflow/tests/reborn_services_contract.rs
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Comment on lines +1989 to 2008
Query(mut query): Query<RebornOperatorLogsQuery>,
) -> Result<Json<RebornOperatorCommandPlaneResponse>, WebUiV2HttpError> {
require_operator_webui_config(capabilities)?;
let response = state.services().query_operator_logs(caller, query).await?;
let cursor = query.cursor.take();
let params = serde_json::to_value(query).map_err(RebornServicesError::internal_from)?;
let page = state
.services()
.query(
caller,
RebornViewQuery {
view_id: OPERATOR_LOGS_VIEW.id.to_string(),
params,
cursor,
},
)
.await?;
let response =
serde_json::from_value(page.payload).map_err(RebornServicesError::internal_from)?;
Ok(Json(response))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated view-query boilerplate.

query_operator_logs and query_logs both now repeat the same "take cursor → serialize params → build RebornViewQuery → .query() → deserialize page.payload" sequence, differing only in view id and request/response types. This pattern will keep growing as more views (e.g. run-artifact elsewhere in the stack) route through the generic conduit.

♻️ Proposed helper to de-duplicate the view-query dispatch
async fn query_view<Req: Serialize, Resp: DeserializeOwned>(
    state: &WebUiV2State,
    caller: WebUiAuthenticatedCaller,
    view_id: &str,
    request: Req,
    cursor: Option<String>,
) -> Result<Resp, WebUiV2HttpError> {
    let params = serde_json::to_value(request).map_err(RebornServicesError::internal_from)?;
    let page = state
        .services()
        .query(
            caller,
            RebornViewQuery {
                view_id: view_id.to_string(),
                params,
                cursor,
            },
        )
        .await?;
    serde_json::from_value(page.payload).map_err(RebornServicesError::internal_from)
}
-    let cursor = query.cursor.take();
-    let params = serde_json::to_value(query).map_err(RebornServicesError::internal_from)?;
-    let page = state
-        .services()
-        .query(
-            caller,
-            RebornViewQuery {
-                view_id: OPERATOR_LOGS_VIEW.id.to_string(),
-                params,
-                cursor,
-            },
-        )
-        .await?;
-    let response =
-        serde_json::from_value(page.payload).map_err(RebornServicesError::internal_from)?;
+    let cursor = query.cursor.take();
+    let response = query_view(&state, caller, OPERATOR_LOGS_VIEW.id, query, cursor).await?;
     Ok(Json(response))

Also applies to: 2014-2051

🤖 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 1989 - 2008,
Extract the repeated view-query dispatch from query_operator_logs and query_logs
into a shared async query_view helper. Have it serialize the request, construct
RebornViewQuery with the supplied view_id and cursor, call
state.services().query, and deserialize page.payload; update both handlers to
delegate to it while preserving their existing request and response types.

@serrrfirat
serrrfirat merged commit 8084414 into main Jul 21, 2026
66 checks passed
@serrrfirat
serrrfirat deleted the codex/reborn-run-artifacts branch July 21, 2026 12:56
BenKurrek added a commit that referenced this pull request Jul 21, 2026
Ninth fold, additive: the run-artifact export/import facade methods, log
query views, and the WebUI run-artifact route land verbatim; conflicts were
export/import list reflows resolved to this tree's surface (retired
connectable-channels rail stays retired) plus main's new identifiers, and
both contract suites keep both sides' tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot mentioned this pull request Jul 22, 2026
15 of 29 tasks

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-6344 — 117f9fbe Deployed Jul 21, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: low Changes to docs, tests, or low-risk modules scope: docs Documentation size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant