Skip to content

fix(webui): scope workspace and memory views - #7062

Merged
serrrfirat merged 18 commits into
mainfrom
codex/webui-workspace-scoped-projection
Aug 4, 2026
Merged

serrrfirat merged 18 commits into
mainfrom
codex/webui-workspace-scoped-projection

Conversation

@serrrfirat

@serrrfirat serrrfirat commented Aug 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Scope WebUI Workspace and Memory presentation to the authenticated tenant/user instead of exposing shared storage paths.
  • Fail closed for signed/non-operator sessions and for missing scoped roots, while preserving local operator fallback.
  • Collapse internal memory wrapper directories and hide sidecar files from the user-facing view.

Reopening note

Reopens #5831 on top of current main. The prior branch had accumulated 5 merge commits keeping it in sync with main's ironclaw_webui_v2 → ironclaw_webui crate rename. This PR replays the same net 13-file change as a single clean commit on top of the latest main, with no merge commits.

Change Type

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

Linked Issue

Supersedes #5831 (closed).

Validation

  • cargo fmt --all -- --check
  • cargo clippy -p ironclaw_webui --all-features --all-targets -- -D warnings — to be run by CI.
  • cargo clippy -p ironclaw --all-features --all-targets -- -D warnings — to be run by CI.
  • Relevant tests pass — to be run by CI.
  • cargo build — Not applicable: the owning crate tests and all-target clippy compiled the affected targets.
  • cargo test --features integration — Not applicable: no database-backed behavior changed.
  • Manual testing — Not run; deterministic caller/API and presentation tests cover the changed seams.
  • Agent review workflow — Not run; final diff and required local checks were audited directly.

Test Strategy

User behavior: A signed hosted user sees only their scoped Workspace/Memory projection; missing scoped roots render empty, while local operator sessions retain raw workspace fallback.

Risk areas:

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

Tests added or updated:

  • Unit or contract: frontend workspace API tests cover caller scoping, fail-closed roots, memory projection, and source-thread browsing; WebUI handler contracts cover effective session flags; CLI unit test covers every deployment profile.
  • Reborn integration: composed-router tests cover serve-config propagation, multi-user auth, and composite signed-session versus operator-token behavior.
  • Recorded fixture: Not applicable: no model selection or tool arguments changed.
  • Browser E2E: Not applicable: the change is in deterministic API projection and session bootstrap logic, covered by frontend API/presentation tests and composed HTTP router tests; no new browser interaction was introduced.
  • Backend or runtime: Not applicable: no storage backend, schema, Docker, WASM, or external runtime behavior changed.
  • Live canary: Not applicable: no provider or real-model behavior changed.

What the tests prove: Tenant/user storage prefixes are hidden from presented paths; hosted and non-operator sessions cannot fall back to shared roots; local operators keep existing fallback; source-thread workspace routes remain isolated and functional after conflict resolution.

Commands run locally on the updated branch:

  • cargo fmt --all -- --check (clean)
  • cargo test -p ironclaw_extension_support apply_patch_rejects_probe_clean_document_path_after_full_read -- --nocapture (1 passed)
  • cargo test -p ironclaw_host_runtime --test first_party_coding_tools (41 passed)
  • cargo clippy -p ironclaw_extension_support --all-features --all-targets -- -D warnings (clean)

Security Impact

Changes file-view access presentation. Hosted/non-operator sessions now fail closed when caller identity or scoped roots are unavailable. No authentication bypass, secret handling, tool execution, or network policy is weakened.

Reborn Trust-Boundary Checklist

  • Public policy/evidence/trust-bearing types: no new public trust-bearing type; the existing authenticated caller and per-token capability determine the effective browser flag.
  • Untrusted content enters prompts only through an envelope/escaping primitive: N/A, no prompt path changed.
  • Hashes declare purpose: N/A, no hashing changed.
  • Status/policy/runtime/error variants audited: N/A, no variants changed.
  • Security/durability serde(default) fields fail closed: the new session feature defaults fail closed in the browser; no durable serde field added.
  • Queues/maps/buffers/counters bounded: N/A, none added.
  • Driver/operator-visible errors have stable class semantics: N/A, error vocabulary unchanged.
  • Sandbox/native/host names accurately describe trust boundary: scoped projection is explicitly a browser presentation gate; host/tool filesystem execution is unchanged.

Database Impact

None.

Blast Radius

WebUI session bootstrap, workspace/memory API projection, frontend workspace navigation, and CLI profile-to-WebUI configuration. Source-thread file browsing and artifact-export gating were explicitly preserved during the merge.

Rollback Plan

Revert this single commit. No schema or persisted-data rollback is required.

Review Follow-Through

CI must confirm changed-line coverage after the added CLI profile-matrix test. No known follow-up is required in this PR; tool/host execution scoping remains outside this UI-only change.


Review track: C (security boundary and hosted multi-user file visibility)

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

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 13:05 Destroyed
@railway-app

railway-app Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
ironclaw ✅ Success (View Logs) Web Aug 4, 2026 at 2:11 pm

@github-actions github-actions Bot added the size: L 200-499 changed lines label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Workspace access can be scoped to each caller, preventing cross-user file visibility or modification.
    • Workspace browsing, previews, searches, and writes now honor caller permissions.
    • New workspace roots appear empty, allowing work to begin without initialization errors.
    • Standalone deployments retain shared workspace behavior where appropriate.
  • Bug Fixes

    • Blocked path traversal and unauthorized cross-user access.
    • Improved handling of missing workspace directories and mount paths.
    • Prevented edits to binary documents and PDFs through file editing tools.
  • Tests

    • Added coverage for multi-user isolation, authorization modes, traversal protection, and workspace operations.

Walkthrough

The change adds caller-scoped workspace policy across deployment configuration, runtime mounts, WebUI handlers, frontend browsing, and filesystem root handling. It adds coverage for profile defaults, authentication modes, workspace isolation, traversal rejection, and fresh workspace roots.

Changes

Workspace projection

Layer / File(s) Summary
Policy and WebUI contract
crates/ironclaw_reborn_composition/src/deployment.rs, crates/ironclaw_reborn_cli/src/commands/serve.rs, crates/ironclaw_webui/src/webui_v2/*, crates/ironclaw_webui/frontend/src/app/*, crates/ironclaw_webui/frontend/src/layout/*
Deployment profiles and serve resolve workspace scoping. WebUI session metadata and outlet context expose the effective projection setting.
Frontend workspace access
crates/ironclaw_webui/frontend/src/pages/workspace/*
The frontend passes caller identity and projection settings into workspace and memory path resolution.
Filesystem projection handlers
crates/ironclaw_webui/src/webui_v2/handlers.rs
Workspace handlers prefix caller paths, reject traversal, and normalize mount-relative responses.
Runtime mount policy and caller resolution
crates/ironclaw_reborn_composition/src/runtime_mounts.rs, crates/ironclaw_reborn_composition/src/runtime/*, crates/ironclaw_reborn_composition/src/factory/*, crates/ironclaw_reborn_composition/src/host_access_assembly.rs
Runtime capabilities, approvals, attachments, and production builds resolve shared or per-caller workspace mounts.
Missing mount-root behavior
crates/ironclaw_filesystem/src/scoped.rs, crates/ironclaw_host_runtime/src/invocation_services.rs, crates/extensions/ironclaw_extension_support/src/coding/*
Authorized missing mount roots return empty listings or directory stats. Deeper missing paths retain error behavior.
Isolation validation
crates/ironclaw_reborn_composition/src/runtime/capability_host/workspace_scoping_tests.rs, crates/ironclaw_reborn_composition/tests/*, tests/integration/group_multiuser/*, crates/ironclaw_filesystem/tests/*
Tests cover per-caller isolation, shared standalone behavior, fresh roots, attachment placement, and multi-user scenarios.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Suggested reviewers: benkurrek

Sequence Diagram(s)

sequenceDiagram
  participant Serve
  participant RuntimeComposition
  participant WorkspaceFilesystem
  participant WebUiV2
  participant WorkspaceBrowser
  Serve->>RuntimeComposition: enable caller-scoped workspace policy
  RuntimeComposition->>WorkspaceFilesystem: resolve tenant/user workspace mount
  Serve->>WebUiV2: propagate projection setting
  WorkspaceBrowser->>WebUiV2: request workspace path
  WebUiV2->>WorkspaceFilesystem: query caller-prefixed path
  WorkspaceFilesystem-->>WebUiV2: return scoped result
  WebUiV2-->>WorkspaceBrowser: return mount-relative path
Loading
🚥 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 uses Conventional Commits style and accurately summarizes the main workspace and memory scoping change.
Description check ✅ Passed The description includes all required sections, explains the security impact, documents validation, and provides test strategy, blast radius, and rollback details.

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 added risk: low Changes to docs, tests, or low-risk modules contributor: core 20+ merged PRs labels Aug 3, 2026
@ironloopai

ironloopai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Review · PR #7062

🟢 Completed · Review submitted

1 actionable findings →

The change breaks the Workspace/Memory browser by adding tenant/user storage prefixes on top of an API that is already caller-scoped server-side. The new frontend tests pass because their mocks encode the incorrect raw-storage API contract.

Automatic · PR opened + CI failed · attempt 1 of 3 · completed in 2m 28s

Run details
  • Repository: nearai/ironclaw
  • Base: main at 9ad5709
  • Head: codex/webui-workspace-scoped-projection at 411c751
  • Created: Aug 3, 2026, 1:10 PM UTC
  • Updated: Aug 3, 2026, 1:12 PM UTC
  • Run: 14148217-ffe0-4862-9ae0-e9d9ac245b7d
  • Latest attempt: 1 · Completed · c0ffe32f-245e-49e7-8017-c9c78731ed18

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

🤖 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/workspace/lib/workspace-api.test.ts`:
- Line 135: Update the affected hosted test calls to listWorkspace in the
workspace API tests to explicitly pass requireScopedWorkspace: true alongside
currentUser. Keep requireScopedWorkspace: false only for the local-fallback
test, ensuring the hosted workspace cases exercise the production required-scope
behavior rather than the module default.
- Around line 279-293: Update the affected workspace security tests around
readWorkspaceFile to record observed stat request paths in the fetch handler
instead of asserting there. Constrain assert.rejects to the expected error, then
assert after rejection that the unscoped shared path was never requested while
the scoped path behavior remains covered; apply the same pattern to both test
cases.

In `@crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts`:
- Around line 196-206: The workspace path helpers re-resolve scoped roots and
memory directories for every file operation, causing redundant sequential
`/fs/list` requests. Update `resolveWorkspacePath` and `resolveMemoryPath` to
memoize resolved prefixes/collapsed directories keyed by `{currentUser,
requireScopedWorkspace}`, or propagate the listing result’s `actualPath` and
reuse it for stat and content URLs; ensure subsequent `readWorkspaceFile`
operations avoid repeating the hierarchy traversal.
- Around line 285-316: Change the default for requireScopedWorkspace from false
to true in resolveDirectory, resolveFilePath, listWorkspace, and
readWorkspaceFile so omitted options use scoped behavior. Update tests that rely
on permissive behavior to pass requireScopedWorkspace: false explicitly, while
preserving explicit true and false handling.
- Around line 145-206: Enforce tenant/user workspace containment at the server
boundary for the `/fs/list`, `/fs/stat`, `/fs/content`, and equivalent
project-file handlers, rather than relying on the frontend functions
`resolveWorkspaceRootWithOptions`, `resolveWorkspaceDirectory`, and
`resolveWorkspacePath`. Validate every client-supplied path against the
authenticated tenant/user prefix and return 403 for cross-user paths; add an
integration test covering that rejection.
🪄 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: 587fcacf-1111-47ee-8751-56b8edc8bc0c

📥 Commits

Reviewing files that changed from the base of the PR and between 9ad5709 and 411c751.

📒 Files selected for processing (13)
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_serve.rs
  • crates/ironclaw_webui/frontend/src/app/app.tsx
  • crates/ironclaw_webui/frontend/src/app/auth.ts
  • crates/ironclaw_webui/frontend/src/layout/gateway-layout.tsx
  • crates/ironclaw_webui/frontend/src/pages/workspace/hooks/useWorkspaceBrowser.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/workspace-page.tsx
  • crates/ironclaw_webui/src/webui_serve.rs
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/src/webui_v2/router.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Comment thread crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts Outdated
Comment thread crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts Outdated
Comment thread crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts Outdated

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

🔍 Review complete · PR #7062

⚠️ 1 finding · 1 blocking

The change breaks the Workspace/Memory browser by adding tenant/user storage prefixes on top of an API that is already caller-scoped server-side. The new frontend tests pass because their mocks encode the incorrect raw-storage API contract.

Findings

  1. 🔴 High · Do not prepend storage ownership paths to the already caller-scoped filesystem API — crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts:244-250
    Details are attached to the relevant diff.
Validation and technical details
  • Reviewed the complete refs/ironloop/base...refs/ironloop/head comparison across all 13 changed files and applicable crate guidance.
  • Traced /api/webchat/v2/fs/{list,stat,content} through WebUI handlers, product authorize_browse_scope, FilesystemBrowseReader, and MountScopedFilesystemReader.
  • Verified existing webui_filesystem_memory_mount_is_scoped_to_authenticated_user composed E2E coverage establishes mount-relative, server-scoped behavior.
  • git diff --check refs/ironloop/base...refs/ironloop/head completed cleanly.
  • Frontend Vitest suite passed: 124 files, 1,056 tests. TypeScript tsc --noEmit completed successfully; Node 24.18.0 emitted an engine warning because the package requests Node 22.22.x.
  • Base: main
  • Head: codex/webui-workspace-scoped-projection at 411c751
  • Run: 14148217-ffe0-4862-9ae0-e9d9ac245b7d

Comment on lines +244 to +250
let actualPath = prefix;
let response;
try {
response = await fetchFsList(MEMORY_MOUNT, actualPath);
} catch (error) {
if (!isNotFound(error)) throw error;
response = emptyDirectoryResponse();

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 · Do not prepend storage ownership paths to the already caller-scoped filesystem API

The /fs/* handlers already bind the authenticated caller, derive an authorized ResourceScope, and return mount-relative paths. Existing composed E2E coverage confirms that requesting mount=memory at the root returns the caller's MEMORY.md, while another caller cannot access the raw tenant/user path. This code instead starts every Memory lookup at tenants/{tenant}/users/{user} (and applies the same assumption to Workspace), so hosted users query that prefix inside their already-scoped mount. On the real backend this produces a 404/empty view; file previews similarly stat/download doubly-prefixed paths. Keep tenant/user prefix resolution authoritative on the server and consume only the mount-relative paths returned by /fs/*. The frontend tests should mock that existing contract rather than a raw shared tree.

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.

Valid finding — fixed in 03c5084.

The /fs/{list,stat,content} handlers bind the authenticated caller and return mount-relative, server-scoped paths (already proven by the composed test webui_filesystem_memory_mount_is_scoped_to_authenticated_user in crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs, which shows user B gets 404 for user A's MEMORY.md and for raw tenants/.../users/alice/... paths). The frontend was incorrectly prepending tenants/{tenant}/users/{user} on top of that server-scoped API.

The frontend now consumes the existing server-scoped contract directly:

  • No tenant/user prefix is built for any /fs/* request. Paths are mount-relative.
  • currentUser is retained only as a fail-closed identity gate when requireScopedWorkspace is true — missing identity renders empty listings and short-circuits file reads rather than requesting a raw mount root.
  • requireScopedWorkspace defaults to true at the exported entry points so an omitted option cannot enable raw-root behavior.
  • Memory collapse-walk over the caller's own mount-relative tree is kept so internal agents/<agent>/projects/_none wrappers stay hidden; every request stays mount-relative.

No server logic was added — the server is already the authority for caller scope. Frontend tests now mock that mount-relative contract.

@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 13:17 Destroyed
serrrfirat added a commit that referenced this pull request Aug 3, 2026
…orts

Address review feedback on #7062 (reopened from #5831):

- The /fs/{list,stat,content} handlers bind the authenticated caller and
  return mount-relative, server-scoped paths (covered by the existing
  webui_filesystem_memory_mount_is_scoped_to_authenticated_user composed
  test). The frontend now consumes that contract directly: it no longer
  builds tenants/{tenant}/users/{user} prefixes for /fs/* requests.
  currentUser is retained only as a fail-closed identity gate when
  requireScopedWorkspace is true; missing identity renders empty listings
  and short-circuits file reads instead of requesting a raw mount root.
- Default requireScopedWorkspace to true at the exported entry points
  (listWorkspace, readWorkspaceFile) and internal resolvers so an omitted
  option cannot silently enable raw-root behavior.
- Keep memory collapse-walk over the caller's own mount-relative tree so
  the internal agents/<agent>/projects/_none wrapper directories stay
  hidden; all requests remain mount-relative.
- Gate the three test-support-only imports in composition runtime.rs so
  default-feature clippy no longer reports them unused.
- Update workspace-api tests to mock the server-scoped, mount-relative
  contract: hosted calls expect mount-relative paths, requireScopedWorkspace
  is explicit, and assert.rejects records/constrains rejections instead of
  asserting inside the fetch handler.
@serrrfirat
serrrfirat force-pushed the codex/webui-workspace-scoped-projection branch from 3e24420 to 03c5084 Compare August 3, 2026 13:43
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 13:43 Destroyed
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Update — review feedback addressed (03c5084)

Thanks @ironloopai and @coderabbitai. All actionable findings fixed; CI root cause identified and fixed.

ironloopai blocking finding ( High — raw storage prefix on a caller-scoped API)

Valid. The /fs/{list,stat,content} handlers bind the authenticated caller and return mount-relative, server-scoped paths. This is already proven by the composed test webui_filesystem_memory_mount_is_scoped_to_authenticated_user (crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs): user B gets 404 for user A's MEMORY.md and for raw tenants/.../users/alice/... paths. The frontend was incorrectly prepending tenants/{tenant}/users/{user} on top of that server-scoped API.

Fix: the frontend now consumes the existing server-scoped contract directly:

  • No tenant/user prefix is built for any /fs/* request — all paths are mount-relative.
  • currentUser is retained only as a fail-closed identity gate when requireScopedWorkspace is true (missing identity → empty listing / short-circuited read, no raw mount request).
  • requireScopedWorkspace defaults to true at the exported entry points.
  • Memory collapse-walk over the caller's own mount-relative tree is kept; every request stays mount-relative.

No server logic was added — the server is already the authority for caller scope.

CodeRabbit findings

  1. Hosted tests now pass requireScopedWorkspace: true explicitly; false only for local fallback. ✅
  2. assert.rejects security tests record forbidden paths in the handler and assert after rejection, with rejection constrained to the expected error. ✅
  3. Server-enforced /fs/* bounds: not applicable — server already rejects cross-user paths (covered by the composed test cited above). ✅
  4. Per-file re-resolution round trips: moot — prefix resolution removed; file reads use the mount-relative path directly (memory walks the collapse tree once for the parent). ✅
  5. requireScopedWorkspace default flipped to true at exported entry points. ✅

CI — Code Style / clippy failure root cause

The Clippy (all-features) job failed on ironclaw_reborn_composition with 3 unused import errors for ChannelConnectionNoticePolicy, AdminUserService, and ChannelConfigProductService in runtime.rs.

Root cause: these imports are used only inside #[cfg(any(test, feature = "test-support"))] blocks. The CI clippy matrix runs a default leg (no --all-features) that compiles the lib without test-support, which eliminates the usages and leaves the imports unused. This is pre-existing on main but only surfaces in CI when ironclaw_reborn_composition is in the changed-packages set — which this PR triggers via tests/webui_v2_serve.rs.

Fix: gated the three imports with #[cfg(any(test, feature = "test-support"))] so they are only present when their usages are. Verified locally with both legs:

  • cargo clippy -p ironclaw_reborn_composition --lib --bins -- -D warnings ✅
  • cargo clippy -p ironclaw_reborn_composition --lib --bins --all-features -- -D warnings ✅

Validation run locally

  • cargo fmt --all -- --check ✅
  • cargo clippy -p ironclaw_reborn_composition -p ironclaw_webui --lib --bins -- -D warnings ✅
  • cargo clippy -p ironclaw_reborn_composition -p ironclaw_webui --lib --bins --all-features -- -D warnings ✅
  • cargo test -p ironclaw_webui --all-features ✅
  • cargo test -p ironclaw_reborn_composition --test webui_v2_serve ✅ (64 pass)
  • pnpm typecheck ✅
  • pnpm test ✅ (124 files, 1054 tests)

Branch rebased onto current main (9ad57098); 2 commits, no merge commits.

@ironloopai

ironloopai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Caution

Command line has extra arguments

The command line may contain only the IronLoop mention and command name.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- repository guidance ---'
fd -HI '^(CLAUDE|AGENTS)\.md$|^rules$' . -t f | head -40
for f in $(fd -HI '^(CLAUDE|AGENTS)\.md$' . -t f | head -10); do
  echo "--- $f ---"
  sed -n '1,220p' "$f"
done

echo '--- changed files ---'
git diff --name-only HEAD~2..HEAD

echo '--- workspace API outline ---'
ast-grep outline crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts --items all

echo '--- workspace API relevant implementation ---'
rg -n -C 8 'tenant|user|requireScopedWorkspace|currentUser|/fs/|listWorkspace|readWorkspaceFile|memory' \
  crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts

echo '--- frontend tests for scoped versus fallback behavior ---'
rg -n -C 5 'requireScopedWorkspace|tenants/|users/|/fs/|assert\.rejects|forbidden' \
  crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts

echo '--- server e2e scope test ---'
rg -n -C 8 'webui_filesystem_memory_mount_is_scoped_to_authenticated_user|tenants/|MEMORY\.md' \
  crates/ironclaw_reborn_composition/tests/webui_v2_e2e.rs

echo '--- test-only Rust imports and usages ---'
rg -n -C 5 'ChannelConnectionNoticePolicy|AdminUserService|ChannelConfigProductService|cfg\(any\(test, feature = "test-support"\)\)' \
  crates/ironclaw_reborn_composition/src/runtime.rs

echo '--- patch summary ---'
git diff --check HEAD~2..HEAD
git diff --stat HEAD~2..HEAD

Length of output: 154067


@serrrfirat Verified.

The frontend now sends only mount-relative paths to /fs/*. It fails closed when scoped projection requires identity. The server remains the authority for tenant and user scope.

This preserves the AGENTS.md invariant: preserve tenant/user scope and do not expose backend paths across public surfaces.

The composed E2E test confirms that cross-user and raw storage paths receive 404. The frontend tests cover the mount-relative contract and the no-request identity gate.

The three runtime.rs imports are now gated with the same condition as their usages. This fixes the default-feature clippy failure.

No new actionable finding from this update.

🐇 ✅


✏️ Learnings added
Learnt from: serrrfirat
URL: https://github.com/nearai/ironclaw/pull/7062

Timestamp: 2026-08-03T13:47:01.096Z
Learning: For `crates/ironclaw_webui` WebUI filesystem endpoints, `/api/webchat/v2/fs/{list,stat,content}` resolve tenant and user scope server-side from the authenticated caller. Frontend callers must use mount-relative paths only. In `frontend/src/pages/workspace/lib/workspace-api.ts`, `currentUser` is only a fail-closed identity gate when `requireScopedWorkspace` is true; it must not be used to construct `tenants/{tenant}/users/{user}` path prefixes.

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.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts`:
- Around line 218-237: Update resolveMemoryDirectory to initialize actualPath at
the memory mount root rather than relativePath, then let the existing
splitRelative(relativePath) loop descend through each segment exactly once. Add
a regression test for listWorkspace on a memory subdirectory with currentUser
and requireScopedWorkspace enabled, asserting the expected /fs/list request
paths.
- Around line 96-101: Update userScopedPrefix to return a boolean indicating
whether both tenant_id and user_id are present after normalization, removing the
tenants/.../users/... path construction. Keep scopedUserUnavailable using the
predicate’s truthiness and ensure no caller expects or re-appends a formatted
prefix.
🪄 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: f8d7ccd6-3f8d-4c11-9805-57616671acde

📥 Commits

Reviewing files that changed from the base of the PR and between 411c751 and 03c5084.

📒 Files selected for processing (14)
  • crates/ironclaw_reborn_cli/src/commands/serve.rs
  • crates/ironclaw_reborn_composition/src/runtime.rs
  • crates/ironclaw_reborn_composition/tests/webui_v2_serve.rs
  • crates/ironclaw_webui/frontend/src/app/app.tsx
  • crates/ironclaw_webui/frontend/src/app/auth.ts
  • crates/ironclaw_webui/frontend/src/layout/gateway-layout.tsx
  • crates/ironclaw_webui/frontend/src/pages/workspace/hooks/useWorkspaceBrowser.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/workspace-page.tsx
  • crates/ironclaw_webui/src/webui_serve.rs
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/src/webui_v2/router.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Comment thread crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts Outdated
Comment thread crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts Outdated
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 15:30 Destroyed
@github-actions github-actions Bot added size: XL 500+ changed lines and removed size: L 200-499 changed lines labels Aug 3, 2026
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Update — workspace browser leak fixed (490f191)

Root cause

You could see other users' documents in Home because the standalone /fs/* browser mount (scoped_browse_mount_view in runtime_mounts.rs) maps the Workspace alias to a shared /projects/workspace target for every caller. Memory is caller-scoped server-side (scoped_memory_target), but Workspace was not — so a hosted user listing mount=workspace saw the whole shared tree, including other users' artifacts. The frontend was already mount-relative after the previous fix; the leak was server-side.

Fix

Confine Workspace reads to the caller's own subtree (tenants/{tenant}/users/{user}) in the WebUI /fs/{list,stat,content} handlers, gated on the same effective scoped-projection flag the session endpoint reports (state.workspace_requires_scoped_projection() || !capabilities.operator_webui_config):

  • Scoped on (hosted / non-operator): handlers prepend tenants/{tenant}/users/{user} before forwarding to the product layer, and strip that prefix from any path the product layer echoes back. The browser keeps round-tripping mount-relative paths and never sees the storage ownership prefix. A cross-user path from a scoped caller becomes tenants/<caller>/users/<caller>/tenants/.../other and 404s inside the caller's own subtree.
  • Scoped off (local / operator): raw shared workspace root served unchanged, so single-user workspaces stay visible.

Operator capability alone does not bypass a deployment that has set the scoped-projection flag — consistent with the existing session-feature logic. Operator bypass only applies when the deployment flag is off (local mode).

Memory needs no change — it is already caller-scoped server-side (proven by webui_filesystem_memory_mount_is_scoped_to_authenticated_user).

Relationship to #5927

Complementary, not overlapping:

  • fix(coding): virtualize the workspace root #5927 scopes agent tool/shell workspace writes to the same per-user subtree (/projects/workspace/tenants/{tenant}/users/{user}) and adds scoped_workspace_mount_view for the local-dev process port. It does not touch the WebUI browser mount.
  • This PR (7062) scopes the WebUI browser read surface.

Both are needed for end-to-end per-user workspace isolation: 5927 makes agent writes land per-user; 7062 makes the browser only show the caller's subtree. They merge independently.

CodeRabbit follow-ups on 03c5084 (also in this commit)

  1. Critical — resolveMemoryDirectory seeded the walk with relativePath and then appended every segment again, doubling any non-root memory subdirectory (notes → notes/notes). Fixed: seed at the mount root, descend once. Regression test added.
  2. Nitpick — userScopedPrefix built a tenants/{tenant}/users/{user} string whose only consumer read truthiness. Replaced with a boolean hasCallerIdentity predicate so no future caller can re-prepend the prefix.

Validation

  • cargo fmt --all -- --check ✅
  • cargo clippy -p ironclaw_webui --all-targets --all-features -- -D warnings ✅
  • cargo clippy -p ironclaw_reborn_composition --lib --bins (default + all-features) ✅
  • cargo test -p ironclaw_webui --all-features ✅ (incl. 4 new scoped-workspace contract tests)
  • cargo test -p ironclaw_reborn_composition --test webui_v2_serve ✅ (64)
  • pnpm typecheck + pnpm test ✅ (124 files, 1055 tests; +1 memory-subdirectory regression)

New server-side contract tests

  • browse_fs_dir_prefixes_workspace_path_with_scoped_projection — scoped non-operator list forwards tenants/{tenant}/users/{user} to the product layer.
  • browse_fs_dir_scopes_workspace_for_non_operator_even_when_state_flag_off — non-operator is always scoped.
  • browse_fs_dir_keeps_raw_workspace_root_for_operator_fallback — operator + flag off keeps raw root.
  • stat_fs_path_prefixes_workspace_path_with_scoped_projection — stat prefixes and echoes back mount-relative.

@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

🤖 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 920-928: Update strip_workspace_prefix so it strips prefix only
when path exactly equals prefix or the prefix is immediately followed by a path
separator; otherwise return the existing trimmed path unchanged. Preserve the
current separator trimming for valid prefixed paths and avoid matching
identifier prefixes such as “alice” in “alice2”.
- Around line 894-918: Update workspace_served_path to validate the trimmed
requested path before prepending the scoped prefix, rejecting any path
containing a parent-directory component (“..”) and preventing
RebornFsStatRequest or RebornFsReadRequest dispatch for unsafe input. Preserve
the existing scoped-path construction and passthrough behavior for valid paths
and other mounts.

In `@crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs`:
- Around line 8007-8041: Add a regression test alongside
stat_fs_path_prefixes_workspace_path_with_scoped_projection for the read_fs_file
route under scoped projection. Use caller user-alpha, enable workspace scoped
projection, request the workspace file through the download endpoint, and assert
the stub receives the caller-subtree-prefixed path while the returned file.path
is stripped to the mount-relative path.
🪄 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: 577053c9-4ccd-40a9-a329-008850208d56

📥 Commits

Reviewing files that changed from the base of the PR and between 03c5084 and 490f191.

📒 Files selected for processing (4)
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.test.ts
  • crates/ironclaw_webui/frontend/src/pages/workspace/lib/workspace-api.ts
  • crates/ironclaw_webui/src/webui_v2/handlers.rs
  • crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs

Comment thread crates/ironclaw_webui/src/webui_v2/handlers.rs
Comment thread crates/ironclaw_webui/src/webui_v2/handlers.rs
Comment thread crates/ironclaw_webui/tests/webui_v2_handlers_contract.rs
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 15:38 Destroyed
Scope WebUI Workspace and Memory presentation to the authenticated
tenant/user instead of exposing shared storage paths. Fail closed for
signed/non-operator sessions and for missing scoped roots, while
preserving local operator fallback. Collapse internal memory wrapper
directories and hide sidecar files from the user-facing view.
…orts

Address review feedback on #7062 (reopened from #5831):

- The /fs/{list,stat,content} handlers bind the authenticated caller and
  return mount-relative, server-scoped paths (covered by the existing
  webui_filesystem_memory_mount_is_scoped_to_authenticated_user composed
  test). The frontend now consumes that contract directly: it no longer
  builds tenants/{tenant}/users/{user} prefixes for /fs/* requests.
  currentUser is retained only as a fail-closed identity gate when
  requireScopedWorkspace is true; missing identity renders empty listings
  and short-circuits file reads instead of requesting a raw mount root.
- Default requireScopedWorkspace to true at the exported entry points
  (listWorkspace, readWorkspaceFile) and internal resolvers so an omitted
  option cannot silently enable raw-root behavior.
- Keep memory collapse-walk over the caller's own mount-relative tree so
  the internal agents/<agent>/projects/_none wrapper directories stay
  hidden; all requests remain mount-relative.
- Gate the three test-support-only imports in composition runtime.rs so
  default-feature clippy no longer reports them unused.
- Update workspace-api tests to mock the server-scoped, mount-relative
  contract: hosted calls expect mount-relative paths, requireScopedWorkspace
  is explicit, and assert.rejects records/constrains rejections instead of
  asserting inside the fetch handler.
The standalone /fs/* browser mount (scoped_browse_mount_view) maps the
Workspace alias to a shared /projects/workspace target, so a hosted user
could list other users' workspace artifacts through the WebUI. Memory is
already caller-scoped server-side; Workspace was not.

Confine Workspace reads to the caller's own subtree
(tenants/{tenant}/users/{user}) in the WebUI /fs/{list,stat,content}
handlers when the effective scoped-projection flag is true (deployment
flag OR non-operator caller). The flag reuses the same effective value
the session endpoint reports. Operator fallback with scoped projection
off keeps the raw shared workspace root so local/single-user workspaces
stay visible.

The handlers prepend the per-caller prefix before forwarding to the
product layer and strip it from any path the product layer echoes back,
so the browser keeps round-tripping mount-relative paths and never sees
the storage ownership prefix. A cross-user path from a scoped caller
becomes tenants/<caller>/users/<caller>/tenants/.../other and 404s
inside the caller's own subtree.

Complementary to #5927, which scopes agent tool/shell workspace writes
to the same per-user subtree. This PR scopes the browser read surface.

Also addresses CodeRabbit follow-up on 03c5084:
- resolveMemoryDirectory now seeds the walk at the mount root instead of
  relativePath, which doubled any non-root memory subdirectory.
- userScopedPrefix is reduced to a boolean hasCallerIdentity predicate so
  no future caller can re-prepend a tenant/user path string.
- Regression test for memory subdirectory navigation.

Server-side contract tests cover: scoped workspace list prefixes the
caller subtree; non-operator is scoped even when the state flag is off;
operator fallback with the flag off keeps the raw root; scoped stat
echoes the mount-relative path.
@serrrfirat
serrrfirat force-pushed the codex/webui-workspace-scoped-projection branch from 5ebee49 to e7214a1 Compare August 3, 2026 19:06
@railway-app
railway-app Bot temporarily deployed to ironclaw-ci-preview / ironclaw-pr-7062 August 3, 2026 19:06 Destroyed
Merged via the queue into main with commit be33ae1 Aug 4, 2026
47 checks passed
@serrrfirat
serrrfirat deleted the codex/webui-workspace-scoped-projection branch August 4, 2026 14:39
BenKurrek added a commit that referenced this pull request Aug 4, 2026
One conflict, in composition/src/runtime.rs: main independently added the same
three `#[cfg(any(test, feature = "test-support"))]` gates #7117 added for #7119.
Kept our explanatory comment; verified no import was duplicated (each of the
three appears exactly once with exactly one gate).

ALLOWLIST recounted off the compiler after #7155 touched the file
("ALLOWLIST grew to 123 entries" with the constant set to 0) — still **123**,
so the baseline is unchanged and correct.

WIP: the changed-lines coverage gate that dequeued this PR is NOT yet addressed.
BenKurrek added a commit that referenced this pull request Aug 4, 2026
⚠ WIP: pushed under session-time pressure. Conflicts are resolved and the
reintroduced-name scan is clean, but this merge has NOT been compiled or
tested locally. Treat CI as the first verification.

Resolved 5 conflicts, all content-vs-rename, union taken in each:
- extension_host_assembly.rs: main dropped `ScopedFilesystem` and switched the
  reader to `inbound_filesystem`; kept both plus our renames.
- root/profile.rs: main added `workspace_scoped_per_caller`; kept it with our
  renamed `ironclaw_event_store` return type.
- runtime/capability_host.rs: main added `WorkspaceMountPolicy`; kept it with
  our renamed `ironclaw_assistant` import.
- capability_host/workspace_scoping_tests.rs: git flagged this as a
  file-location conflict — main ADDED it under the pre-rename directory. Taken
  at the renamed path.
- CHECKLIST.md: append-only; main's amendments kept, our renames re-applied.

The scan earned its keep again: this conflict-free merge reintroduced 20 old
crate names with ZERO conflicts — 6 real `ironclaw_reborn_composition::` code
references (serve.rs, profile_acceptance.rs, group_constructors.rs) that would
not have compiled, 9 path-keyed rows in
`tests/integration/changed-coverage-exemptions.toml` (which would have gone
quiet, not loud), and 5 doc references. All repointed; code/config zone is
back to 0.
BenKurrek added a commit that referenced this pull request Aug 4, 2026
`Check formatting` (step 6 of Fast deterministic checks) went red on
cca5884: the merge was pushed under time pressure without running fmt.

Only the two files whose crate references I rewrote by hand are affected —
`ironclaw_reborn_composition` -> `ironclaw_composition` is 9 characters
shorter, so call sites that were wrapped at the old width now fit on one line.
No semantic change.
BenKurrek added a commit that referenced this pull request Aug 4, 2026
…44392)

Merging main @ be33ae1 into this branch brought #7062's +371 production
LOC of composition wiring, and the new absolute-mass gate correctly went
red against its own merge context (44392 observed vs 44021+150 effective
ceiling — the exact failure CI showed). Re-measured on the merged tree with
the gate's own counter and re-seeded to current, not padded, per the
manifest's ratchet convention. Gate + its 76-case self-test green locally;
both new architecture gates (same-layer inventory, vendor census) pass on
the merged tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
serrrfirat added a commit that referenced this pull request Aug 7, 2026
…space mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what #7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.
serrrfirat added a commit that referenced this pull request Aug 8, 2026
…space mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what #7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.
pull Bot pushed a commit to Stars1233/ironclaw that referenced this pull request Aug 10, 2026
* fix(composition): read landed attachments through the per-caller workspace mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what nearai#7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.

* test(playwright): reconcile legacy WebUI v2 suites to the fetch-based SSE transport

event-source-plus (fetch/ReadableStream). The legacy suites still faked
window.EventSource, so the app never opened a stream and every
duration-1/duration-4 legacy test that emitted frames failed with "no
EventSource stream is open".

- Extract the smoke suite's proven fetch fake into
  install_fake_v2_event_stream() in reborn_webui_harness, extended to
  record request URLs and headers for reconnect assertions.
- Port all seven legacy scenario files onto it, updating cursor/token
  assertions to the header contract (Authorization bearer,
  Last-Event-ID) instead of the retired token/after_cursor query
  params.
- legacy_skills delete: use the shared in-app confirmation dialog
  instead of a native browser dialog.
- legacy_dom_resource_limits reconnect-timer: assert the pending
  reconnect is cancelled when the tab hides (the fetch transport
  schedules retries internally).
- legacy_rendering: assert no live onerror/iframe/img nodes instead of
  substring-scanning escaped text.
- extensions_api: restore the nearai#6520 wire contract (retired
  authenticated/active/needs_setup/has_auth/onboarding_state booleans
  must be absent).
- tool_execution truncated-tool test: expect model_output_truncated
  failure per nearai#6845's no-recovery contract instead of an assistant
  recovery message.
- streaming_run_control_api: drop the stream=true assertion for the
  OpenAI-compatible mock, which rides the buffered fallback since
  nearai#7120 (rig-core cannot distinguish a complete stream from a truncated
  one).
Kampouse pushed a commit to Kampouse/ironclaw that referenced this pull request Aug 13, 2026
* fix(composition): read landed attachments through the per-caller workspace mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what nearai#7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.

* test(playwright): reconcile legacy WebUI v2 suites to the fetch-based SSE transport

event-source-plus (fetch/ReadableStream). The legacy suites still faked
window.EventSource, so the app never opened a stream and every
duration-1/duration-4 legacy test that emitted frames failed with "no
EventSource stream is open".

- Extract the smoke suite's proven fetch fake into
  install_fake_v2_event_stream() in reborn_webui_harness, extended to
  record request URLs and headers for reconnect assertions.
- Port all seven legacy scenario files onto it, updating cursor/token
  assertions to the header contract (Authorization bearer,
  Last-Event-ID) instead of the retired token/after_cursor query
  params.
- legacy_skills delete: use the shared in-app confirmation dialog
  instead of a native browser dialog.
- legacy_dom_resource_limits reconnect-timer: assert the pending
  reconnect is cancelled when the tab hides (the fetch transport
  schedules retries internally).
- legacy_rendering: assert no live onerror/iframe/img nodes instead of
  substring-scanning escaped text.
- extensions_api: restore the nearai#6520 wire contract (retired
  authenticated/active/needs_setup/has_auth/onboarding_state booleans
  must be absent).
- tool_execution truncated-tool test: expect model_output_truncated
  failure per nearai#6845's no-recovery contract instead of an assistant
  recovery message.
- streaming_run_control_api: drop the stream=true assertion for the
  OpenAI-compatible mock, which rides the buffered fallback since
  nearai#7120 (rig-core cannot distinguish a complete stream from a truncated
  one).
Kampouse pushed a commit to Kampouse/ironclaw that referenced this pull request Aug 13, 2026
* fix(composition): read landed attachments through the per-caller workspace mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what nearai#7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.

* test(playwright): reconcile legacy WebUI v2 suites to the fetch-based SSE transport

event-source-plus (fetch/ReadableStream). The legacy suites still faked
window.EventSource, so the app never opened a stream and every
duration-1/duration-4 legacy test that emitted frames failed with "no
EventSource stream is open".

- Extract the smoke suite's proven fetch fake into
  install_fake_v2_event_stream() in reborn_webui_harness, extended to
  record request URLs and headers for reconnect assertions.
- Port all seven legacy scenario files onto it, updating cursor/token
  assertions to the header contract (Authorization bearer,
  Last-Event-ID) instead of the retired token/after_cursor query
  params.
- legacy_skills delete: use the shared in-app confirmation dialog
  instead of a native browser dialog.
- legacy_dom_resource_limits reconnect-timer: assert the pending
  reconnect is cancelled when the tab hides (the fetch transport
  schedules retries internally).
- legacy_rendering: assert no live onerror/iframe/img nodes instead of
  substring-scanning escaped text.
- extensions_api: restore the nearai#6520 wire contract (retired
  authenticated/active/needs_setup/has_auth/onboarding_state booleans
  must be absent).
- tool_execution truncated-tool test: expect model_output_truncated
  failure per nearai#6845's no-recovery contract instead of an assistant
  recovery message.
- streaming_run_control_api: drop the stream=true assertion for the
  OpenAI-compatible mock, which rides the buffered fallback since
  nearai#7120 (rig-core cannot distinguish a complete stream from a truncated
  one).
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
* fix(webui): scope workspace and memory views

Scope WebUI Workspace and Memory presentation to the authenticated
tenant/user instead of exposing shared storage paths. Fail closed for
signed/non-operator sessions and for missing scoped roots, while
preserving local operator fallback. Collapse internal memory wrapper
directories and hide sidecar files from the user-facing view.

* fix(webui): consume mount-relative /fs/* paths and gate test-only imports

Address review feedback on nearai#7062 (reopened from nearai#5831):

- The /fs/{list,stat,content} handlers bind the authenticated caller and
  return mount-relative, server-scoped paths (covered by the existing
  webui_filesystem_memory_mount_is_scoped_to_authenticated_user composed
  test). The frontend now consumes that contract directly: it no longer
  builds tenants/{tenant}/users/{user} prefixes for /fs/* requests.
  currentUser is retained only as a fail-closed identity gate when
  requireScopedWorkspace is true; missing identity renders empty listings
  and short-circuits file reads instead of requesting a raw mount root.
- Default requireScopedWorkspace to true at the exported entry points
  (listWorkspace, readWorkspaceFile) and internal resolvers so an omitted
  option cannot silently enable raw-root behavior.
- Keep memory collapse-walk over the caller's own mount-relative tree so
  the internal agents/<agent>/projects/_none wrapper directories stay
  hidden; all requests remain mount-relative.
- Gate the three test-support-only imports in composition runtime.rs so
  default-feature clippy no longer reports them unused.
- Update workspace-api tests to mock the server-scoped, mount-relative
  contract: hosted calls expect mount-relative paths, requireScopedWorkspace
  is explicit, and assert.rejects records/constrains rejections instead of
  asserting inside the fetch handler.

* fix(webui): scope workspace browser to caller subtree server-side

The standalone /fs/* browser mount (scoped_browse_mount_view) maps the
Workspace alias to a shared /projects/workspace target, so a hosted user
could list other users' workspace artifacts through the WebUI. Memory is
already caller-scoped server-side; Workspace was not.

Confine Workspace reads to the caller's own subtree
(tenants/{tenant}/users/{user}) in the WebUI /fs/{list,stat,content}
handlers when the effective scoped-projection flag is true (deployment
flag OR non-operator caller). The flag reuses the same effective value
the session endpoint reports. Operator fallback with scoped projection
off keeps the raw shared workspace root so local/single-user workspaces
stay visible.

The handlers prepend the per-caller prefix before forwarding to the
product layer and strip it from any path the product layer echoes back,
so the browser keeps round-tripping mount-relative paths and never sees
the storage ownership prefix. A cross-user path from a scoped caller
becomes tenants/<caller>/users/<caller>/tenants/.../other and 404s
inside the caller's own subtree.

Complementary to nearai#5927, which scopes agent tool/shell workspace writes
to the same per-user subtree. This PR scopes the browser read surface.

Also addresses CodeRabbit follow-up on 03c5084:
- resolveMemoryDirectory now seeds the walk at the mount root instead of
  relativePath, which doubled any non-root memory subdirectory.
- userScopedPrefix is reduced to a boolean hasCallerIdentity predicate so
  no future caller can re-prepend a tenant/user path string.
- Regression test for memory subdirectory navigation.

Server-side contract tests cover: scoped workspace list prefixes the
caller subtree; non-operator is scoped even when the state flag is off;
operator fallback with the flag off keeps the raw root; scoped stat
echoes the mount-relative path.

* style: apply rustfmt to workspace scoping handlers and tests

* fix(webui): reject parent traversal in scoped workspace projection

Address CodeRabbit follow-up on the workspace-scoping handlers:

- workspace_served_path now rejects any path containing a '..' segment
  before prepending the caller prefix, so a request like
  '../other-user/secret' can never become
  'tenants/{tenant}/users/{user}/../other-user'. The product layer
  rejects traversal too, but this keeps the escape attempt from being
  dispatched at all.
- Add a read_fs_file scoped-projection regression test mirroring the
  list/stat coverage: asserts the download command receives the
  caller-subtree-prefixed path.
- Add a parent-traversal rejection test asserting a 400 and that the
  product layer is never reached.

Rebased onto current main (resolved webui_v2_serve.rs conflict from
nearai#6780/nearai#7050/nearai#7033).

* fix(webui): wire per-caller workspace writes + address review 4847764083

Hosted non-admin users saw an empty workspace because the browser read the
per-user subtree (tenants/{tenant}/users/{user}) while agent tool/attachment
writes still landed in the shared /projects/workspace root. Wire the agent
read-write workspace filesystem to a per-caller resolver so writes land in
the caller's own subtree, mirroring memory scoping. The WebUI browser reads
the same subtree, so a hosted user sees, reads, and writes only their own
artifacts.

Composition (runtime_mounts.rs):
- Add scoped_workspace_mount_view(scope, permissions) mapping WORKSPACE_ALIAS
  to /projects/workspace/tenants/{tenant}/users/{user}.
- read_write_workspace_filesystem now uses ScopedFilesystem::new with that
  resolver (per-call, per-caller) instead of the shared fixed view, so each
  authenticated caller keys its own subtree and the shared root is never
  exposed for writes.

WebUI handlers (review 4847764083):
- Extract workspace_scoped_projection_required(state, capabilities) and call
  it from both get_session and workspace_projection_for so /session and /fs/*
  never drift on the policy.
- workspace_projection_for now fails closed (returns Err) when scoped
  projection is required but the caller identity cannot key a subtree, instead
  of returning None (which served the shared root). Validated newtypes make
  empty identity unreachable in practice; the guard is defense in depth.
- strip_workspace_prefix strips only an exact prefix or prefix/ boundary, not
  sibling prefixes like tenants/.../users/alice2.
- webui_serve.rs doc comment: remove presentation-layer-only wording; the
  flag controls /fs/* workspace path selection, not only UI display.

Contract tests (webui_v2_handlers_contract.rs):
- Stub FS_LIST_VIEW now echoes entry paths under the served (prefixed) root,
  so the handler's prefix stripping is observable.
- Add browse_fs_dir_strips_prefixed_entry_paths_under_scoped_projection
  asserting response entries are mount-relative.
- Existing scoped/traversal/raw-fallback/stat/read tests preserved.

* fix(composition): scope agent workspace writes per caller on hosted profiles

PR nearai#7062 scoped the WebUI workspace browser (and, in 0707e57, the WebUI
attachment handle) to /projects/workspace/tenants/{tenant}/users/{user} on
hosted profiles, but the agent still WROTE to the shared root: a hosted user
saw an empty workspace because the browser read one subtree while every agent
write landed in another.

The read and write sides were deciding independently. Introduce ONE
composition-owned decision --- DeploymentConfig::workspace_scoped_per_caller
(true for every profile except Standalone/StandaloneUnrestricted) --- carried
to the runtime as runtime_mounts::WorkspaceMountPolicy, and route every lane
through it:

- Grant minting: RefreshingLoopCapabilityPortFactory::create_capability_port
  now resolves the workspace MountView per run from the same ResourceScope the
  skill mounts already key off, so every `mounts = "workspace"` capability
  (write_file, read_file, apply_patch, list_dir, glob, grep, shell, http.save,
  attach_workspace_file_to_reply, ...) resolves paths inside the caller's own
  subtree. Previously it used the composition-wide ambient view.
- Approval lease terms: PolicyApprovalLeaseTermsProvider mints workspace
  mounts from the gate's own ResourceScope, so approving one user's write no
  longer leases the shared root. Scoping failure denies rather than falling
  back.
- Channel-inbound attachment lander: production channel_host_source built a
  fixed shared view while its test-support twin used the scoped resolver. Both
  now call one owner, runtime_mounts::read_write_workspace_filesystem, and the
  project_filesystem reader is built over the same handle so reader and lander
  address the same subtree.
- WebUI attachment handle: read_write_workspace_filesystem scoped
  UNCONDITIONALLY after 0707e57, which relocated standalone uploads into
  tenants/local/users/... away from the raw root the standalone agent and
  browser use. It now follows the same policy bit.

Standalone keeps the ambient shared view including the raw host-home aliases
local coding profiles depend on; those are never scoped.

The CLI's profile_requires_scoped_workspace_projection now delegates to
RebornCompositionProfile::workspace_scoped_per_caller, so the browser
projection and the write lanes cannot drift.

Regression tests (each verified failing before the fix):
- workspace_scoping_tests: a hosted-profile turn writing /workspace/note.txt
  through the production capability port lands under the caller's subtree, two
  callers writing the same relative path do not collide, and nothing lands in
  the shared root; the standalone counterpart still lands at the shared root.
  Fails pre-fix with the file absent from the caller subtree.
- runtime::approval: per-caller lease terms key the gate's own subtree and two
  callers get different targets. Fails pre-fix against the shared view.
- runtime_mounts: the shared read-write handle owner resolves different roots
  per policy branch, pinning the WebUI/channel/C-ATTACH lanes together.
- serve.rs: the browser projection flag agrees with the composition write
  policy for every profile.

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

* fix(composition): close workspace read/write mismatch on multi-user standalone

Follow-up to the per-caller workspace scoping commit, addressing two gaps the
deeper lane trace surfaced.

1. The scoping decision had TWO derivations, so a host override was silently
   dropped. `production_build_assembly` re-derived it from the profile
   (`context.profile.workspace_scoped_per_caller()`) instead of reading the
   deployment the host assembled. The build context now carries the resolved
   boolean, so the deployment is the only source. Caught by the new e2e test
   below, which wrote to the shared root despite the override being set.

2. Profile alone was the wrong gate. The WebUI browser confines EVERY
   non-operator caller's Workspace reads to `tenants/{tenant}/users/{user}`
   (`workspace_scoped_projection_required` = state flag OR not-operator), so a
   standalone-composed deployment with a multi-user authenticator --- exactly
   what `serve` builds when SSO is on --- still read a per-user subtree while
   the agent wrote shared. That is the reported bug, alive outside the hosted
   profiles.

   `RebornHostBindings::with_workspace_scoped_per_caller` lets the assembling
   host raise the deployment's decision (raise-only; a hosted profile stays
   scoped). `serve` now computes the value ONCE as `profile default || SSO on`
   and feeds it to both `build_reborn_runtime` and
   `WebuiServeConfig::with_workspace_requires_scoped_projection`, so the
   browser and the write lanes can no longer disagree in any configuration the
   binary builds.

Lanes verified against the trace findings, no change needed:
- Grant view and execution mounts already come from ONE resolved value:
  `create_capability_port` resolves the per-caller view and passes it to both
  `visible_capability_request` (grant.constraints.mounts) and
  `with_execution_mounts` (context.mounts), so the authorization obligation
  subset check (`next.is_subset_of(&context.mounts)`) compares a view against
  itself. Proven end to end by the write_file dispatch test, which completes
  with verdict Success and lands bytes in the caller subtree --- a helper-only
  test would have missed the obligation wrapper.
- Sandbox process lane: `resolve_grant` strips the source virtual root and
  joins the remainder as subdirectories, creating read-write targets on demand,
  so a nested `/projects/workspace/tenants/{t}/users/{u}` grant binds the
  caller's own directory inside the trusted source. Newly pinned by
  `per_caller_workspace_grant_binds_the_callers_subdirectory`. The default
  per-`RebornSandboxScopeKey` workdir bind (no `/workspace` in the view) is
  untouched.
- `MountScopedRootFilesystem` for the ScopedVirtual backend is built from the
  same request mounts, so its containment re-check follows automatically.
- WASM has no filesystem write host-call; nothing to change.

Regression tests:
- `agent_workspace_writes_are_visible_to_their_owner_and_hidden_from_other_users`
  (webui_v2_e2e) --- two-user WebUI over a standalone-composed runtime with
  scoping raised: user A's agent writes a workspace file, A lists it through
  `/fs/list`, and user B neither lists nor reads it. Fails before the fix (user
  A's listing 404s because the write landed in the shared root).
- `per_caller_workspace_grant_binds_the_callers_subdirectory` (host runtime
  sandbox mounts).
- Standalone integration suites still assert the FLAT workspace root and pass
  unchanged (group_journeys, group_multiuser, group_approvals, group_memory,
  integration_attach, integration_tool_call, webui_v2_product_api).

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

* fix(composition): gate test-only workspace view accessor and single-source the mount policy

The `Fast deterministic checks` gate failed on
`crates/ironclaw_reborn_composition/src/factory/test_support.rs`:
`workspace_mounts_for_test` carried an `.expect()` without an item-level
`#[cfg(any(test, feature = "test-support"))]`, so `check_no_panics.py`
classified it as production code. The parent module gate is not what the
scanner reads. Gate the accessor like every sibling in the file, and drop
the duplicated `#[cfg]` attribute that landed on
`workspace_mount_policy_for_test`. `Code Style` was only a rollup of this
same failure.

Review follow-ups on the same scoping change:

- `WorkspaceMountPolicy::resolve` is now the single constructor for the
  per-caller/shared decision. `host_access_assembly` and
  `production_backend_assembly` each re-implemented the branch, so a
  deployment could have drifted into a scoped view on one write lane and
  an ambient shared view on another.
- Annotate the `.ok()?` in `read_write_workspace_filesystem`'s `Shared`
  branch per `.claude/rules/error-handling.md`; the mount view is built
  from compile-time constants with no caller input.
- Use `tokio::fs::canonicalize` in the sandbox-mount test so the async
  test no longer blocks a Tokio worker.

Behavior is unchanged; the existing scoping tests
(`workspace_scoping_tests`, `webui_v2_e2e`) cover both policy branches
through the production seam.

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

* fix(composition): give a fresh caller an empty workspace instead of a hard error

Production report (hosted volume container, profile local-dev + SSO, so
workspace scoping is active): `builtin.list_dir` and `builtin.glob` on
/workspace failed `operation_failed` for the caller `reborn-cli`. Grants were
correct --- /workspace mapped to
/projects/workspace/tenants/reborn-cli/users/reborn-cli --- but that directory
had never been created, so the agent was unusable for every new user.

Root cause: a shared deployment's /projects/workspace is created once at
composition (`build_host_access`). Per-caller scoping introduced a root with no
equivalent step. The local backend returns `NotFound`, and the coding tools map
`NotFound` to `operation_error`, so the very first read of an untouched
workspace was a hard failure rather than an empty listing.

Fix: `runtime_mounts::ensure_caller_workspace_root` creates the caller's own
workspace root, called once per run from
`RefreshingLoopCapabilityPortFactory::create_capability_port` --- the single
seam that already resolves the caller's scope for grant minting, so the path
comes from their own mount view and never from tool input.

Creating was preferred over softening `NotFound`:
- it fixes reads and writes together, where mapping NotFound to empty would
  have had to be repeated in list_dir, glob, grep and the sensitive-path
  pre-check, each an opportunity to drift;
- error mapping is left untouched everywhere else, so `NotFound` is not
  weakened for any other path (the scoping brief's memory-safety constraint);
- it mirrors the sandbox lane, which already `create_dir_all`s its per-scope
  workspace in `prepare_workspace`.

It is a no-op for shared deployments, whose root composition already created,
and failure is logged and swallowed --- a run must not die because a directory
the first write would create anyway could not be pre-created.

Also fixes the `reborn_struct_test_support_ratchet` failure that c84c9c5
introduced: gating `workspace_mounts_for_test` for `check_no_panics.py` made it
a `#[cfg]`-gated member on a production struct, which is what that ratchet
counts. Converting it to a gated free function satisfies both gates. Verified
the ratchet fails at c84c9c5 with this change stashed.

Regression test `fresh_caller_reads_an_empty_workspace_then_writes_into_it`:
a caller whose subtree was never created lists an empty workspace, globs and
greps cleanly, then writes --- including into a nested path whose parents do
not exist --- and reads the file back from their own subtree. Fails before the
fix with exactly the production error
(`RecoverableFailure { error_kind: OperationFailed }`) on the first list_dir.

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

* fix: read a never-written workspace mount root as empty at every lane's own seam

Relocates the fresh-caller fix (544cebb) from a per-run eager
create_dir_all in create_capability_port to the read paths themselves, per
review: the eager create covered only the agent lane (a fresh user opening
the WebUI Workspace tab before any run still 404'd), was best-effort with a
swallowed failure, and paid a per-run cost to fix a read problem.

The rule, applied once per lane at the seam that knows the mount root: an
authorized caller's mount ROOT exists by definition --- it is the namespace
their grant names, not a path they chose --- so a backend that has nothing
under it yet reads as an EMPTY directory. Anything deeper keeps reporting
NotFound.

- Coding tools (the agent lane, which resolves grant mounts against the raw
  HostWorkspace filesystem): `ResolvedPath::is_mount_root` +
  `list_dir_empty_if_missing_root`; list_dir's sensitive-stat guard, glob's
  walk, and grep's root stat tolerate exactly the missing root. The dead
  `deny_sensitive_existing_path` helper is deleted.
- `ScopedFilesystem` (WebUI browse/attachment handles, channel lander):
  list/stat resolve root-aware and read a missing mount root as empty.
- `MountScopedRootFilesystem` (ScopedVirtual backend): same rule.
- WebUI browse handler: a scoped caller's own projection ROOT (their
  `tenants/{t}/users/{u}` subtree under the shared workspace mount) lists as
  empty on NotFound --- it is authorization-derived, not user input. Deeper
  paths keep their 404.

Regression coverage: `fresh_caller_reads_an_empty_workspace_then_writes_into_it`
(agent lane, drives the production capability port; fails before this change),
and the two-user webui_v2_e2e test's fresh-user assertion is tightened from
`OK || NOT_FOUND` to `OK` + zero entries --- the NOT_FOUND arm it used to
tolerate was this bug.

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

* fix(cli): scope workspace writes whenever serve can mint a non-operator caller

Review finding on 56dc411: gating the write-side raise on `sso_enabled` was
provably too narrow. `serve` installs the admin-API token minter on every
start, that minter stamps `operator = false` by design, and the no-SSO branch
wires a `SessionAuthenticator` so those bearers validate. The WebUI browser
confines every non-operator caller's Workspace reads to their own subtree, so
on Standalone + SSO off, a `POST /admin/users` user got scoped reads over a
shared write root: the exact empty-workspace mismatch this branch exists to
close, still open through a different door.

`serve` now raises `with_workspace_scoped_per_caller_services(true)`
unconditionally, and the browser flag is READ BACK from the deployment the
runtime actually received (`runtime_input.config()`) instead of being
recomputed locally --- the raise is a request, composition owns the answer, and
the two sides can no longer drift (this also closes the silent-no-op gap where
a raise on a `services: None` input vanished while the local recomputation
proceeded). The now-unused `profile_requires_scoped_workspace_projection` /
`workspace_scoped_per_caller` helpers are deleted and `composition_profile`
returns to private.

Regression tests: `serve_scopes_workspace_writes_even_on_standalone_without_sso`
pins the raise + read-back pair on the Standalone profile;
`workspace_scoping_default_follows_deployment_profile` keeps pinning the
per-profile defaults that the raise starts from.

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

* fix: address review comments on the fresh-workspace read path

- A slash-only ?path= names the same projected workspace root as an empty
  path; the fresh-caller empty-listing fallback now keys on the trimmed
  path, and the two-user e2e pins path=/ returning OK + zero entries.
- Mirror the parent-traversal rejection test across stat_fs_path and
  read_fs_file: all three /fs routes share workspace_served_path's `..`
  guard, so all three are pinned to 400 without reaching the product layer.
- The workspace-scoping test helper now fails loudly on result-store errors
  instead of collapsing them into a missing output, and the fresh-caller
  test asserts glob and grep return empty file sets rather than bare
  success.

Review comments 3711525396, 3707056909, 3711525388 on PR nearai#7062.

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

* test: integration-tier coverage for per-caller workspace scoping + gate residue exemptions

The merge-queue run for this branch (actions/runs/30905070584) failed the
changed-line coverage gate at 74.77%: the per-caller scoping and
never-written-root behavior was pinned only by #[cfg(test)] unit modules,
which the integration-tier lcov excludes. Close the gap with real coverage at
the tiers the gate measures, and exempt the residue the lcov structurally
cannot observe.

New coverage:
- tests/integration/group_multiuser/scenario_scoped_workspace_isolation.rs
  over the new RebornIntegrationGroup::multiuser_scoped_workspace(): a
  runtime built with the same scoping raise `serve` applies, capability
  grants confined to the caller's own tenants/{tenant}/users/{user} subtree
  via the production resolver (new test-support export
  scoped_workspace_mount_view_for_test). Drives through real turns: a fresh
  caller's list_dir/glob/grep read an EMPTY workspace instead of erroring; a
  GATED write is approved (per-caller lease) and lands on disk in the
  caller's own subtree with parents created, never at the shared root; the
  written subtree lists/greps back; a missing SUB-path stays a hard error.
- HostRuntimeHarnessOptions::with_workspace_scoped_per_caller() — the
  harness seam applying RebornRuntimeInput::with_workspace_scoped_per_caller_services,
  so the integration tier can compose a scoped deployment at all.
- filesystem_contract: never_written_mount_root_reads_as_empty_but_subpaths_stay_not_found
  pins ScopedFilesystem's list/list_bounded/stat mount-root rule at its
  owning seam (a tests/ binary, visible to the gate's lcov).
- profile_acceptance: workspace_scoping_default_per_profile pins the
  per-profile default the serve raise starts from.

Exemptions (tests/integration/changed-coverage-exemptions.toml, tracked in
issue nearai#7142): the create_capability_port async-fn body (llvm-cov attributes
it to the signature line — 114 hits — and emits no body DA records, the
nearai#6963 class), the serve command body + webui_serve builder (unit-tier-only),
the ScopedVirtual-only MountScopedRootFilesystem arm, defensive error arms,
and a handful of match-arm construction counters whose driving tests assert
the behavior. Verified against the queue run's own merged lcov unioned with
a local llvm-cov run of the new binaries: changed-line coverage 98.74%
(gate exit 0) without base-lcov subtraction.

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

* test: address review comments on the scoped-workspace coverage

- filesystem_contract: pin the WRITE half of the never-written-root rule at
  the owning tier — the first write materializes the root and parents, the
  root then lists the entry, and the bytes read back (review 3712779771).
- group_constructors: state the SINGLE-CALLER constraint on
  multiuser_scoped_workspace loudly — the grant view is resolved once for the
  canonical subject while dispatch resolves owners per run, so a second
  .with_actor_id thread would hold the wrong subtree grant; cross-actor
  isolation is pinned where grants are per-caller by construction
  (review 3712779794).
- harness options: cite the symbol the harness actually calls
  (RebornRuntimeInput::with_workspace_scoped_per_caller_services), not the
  host-bindings raise (review 3712779803).

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

* fix(coding): reject document paths in apply_patch

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
l3ocifer pushed a commit to l3ocifer/frick-ironclaw that referenced this pull request Sep 3, 2026
* fix(composition): read landed attachments through the per-caller workspace mount

/projects/workspace/tenants/{tenant}/users/{user}, but the loop-host
attachment_read_port still read through the shared read-only fixed view
(services.workspace_filesystem), which resolves the workspace root. A
landed image therefore came back NotFound at model-gateway time and was
silently dropped, so vision-capable model payloads lost every inline
image (the duration-4 Playwright attachment failure).

Wire the read port over the same per-caller scoped handle the WebUI
lander uses (runtime_mounts::read_write_workspace_filesystem), mirroring
what nearai#7062 already did for the channel-host assembly. Under the Shared
policy the handle is byte-identical to the old fixed view; under
PerCaller it now resolves the caller's subtree.

* test(playwright): reconcile legacy WebUI v2 suites to the fetch-based SSE transport

event-source-plus (fetch/ReadableStream). The legacy suites still faked
window.EventSource, so the app never opened a stream and every
duration-1/duration-4 legacy test that emitted frames failed with "no
EventSource stream is open".

- Extract the smoke suite's proven fetch fake into
  install_fake_v2_event_stream() in reborn_webui_harness, extended to
  record request URLs and headers for reconnect assertions.
- Port all seven legacy scenario files onto it, updating cursor/token
  assertions to the header contract (Authorization bearer,
  Last-Event-ID) instead of the retired token/after_cursor query
  params.
- legacy_skills delete: use the shared in-app confirmation dialog
  instead of a native browser dialog.
- legacy_dom_resource_limits reconnect-timer: assert the pending
  reconnect is cancelled when the tab hides (the fetch transport
  schedules retries internally).
- legacy_rendering: assert no live onerror/iframe/img nodes instead of
  substring-scanning escaped text.
- extensions_api: restore the nearai#6520 wire contract (retired
  authenticated/active/needs_setup/has_auth/onboarding_state booleans
  must be absent).
- tool_execution truncated-tool test: expect model_output_truncated
  failure per nearai#6845's no-recovery contract instead of an assistant
  recovery message.
- streaming_run_control_api: drop the stream=true assertion for the
  OpenAI-compatible mock, which rides the buffered fallback since
  nearai#7120 (rig-core cannot distinguish a complete stream from a truncated
  one).

This branch was successfully deployed

No deployments
ironclaw-ci-preview / ironclaw-pr-7062 — 1de78b9c Deployed Aug 4, 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.

2 participants