Repository navigation
feat: add permission-aware cross-workspace access - #275
Conversation
|
Too many files changed for review. ( Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (25)
📒 Files selected for processing (159)
Disabled knowledge base sources:
WalkthroughThis change adds policy-based cross-workspace authorization across daemon, APIs, CLI, task and tool services, coordination endpoints, session spawning, and web session deep links. It also adds session-owner projections, consent and audit handling, workspace canonicalization, and extensive unit and integration coverage. ChangesWorkspace access policy
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Agent
participant API
participant WorkspaceAccessPolicy
participant TaskService
participant AuditStore
Agent->>API: Submit workspace-scoped request
API->>WorkspaceAccessPolicy: Authorize(actor, target, seam)
WorkspaceAccessPolicy->>AuditStore: Emit access decision
WorkspaceAccessPolicy-->>API: Allowed or denied decision
API->>TaskService: Create or claim task
TaskService-->>Agent: Response
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/api/core/agent_handlers.go (1)
70-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winNormalize workspace-origin entry IDs in the shared resolver.
ListAgentsandworkspaceAgentDefnow patchWorkspaceIDper caller, butListAgentCatalogtransformsresolved.Entriesdirectly. Stampresolved.WorkspaceIDonto workspace-origin entries here, then remove the duplicate caller-side patches.🤖 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 `@internal/api/core/agent_handlers.go` around lines 70 - 76, Update the shared resolver around ListAgentCatalog to assign resolved.WorkspaceID to each workspace-origin entry before transforming resolved.Entries into agent definitions. Remove the duplicate WorkspaceID patching from ListAgents and workspaceAgentDef, leaving those callers to consume the normalized entries.internal/daemon/native_workspace_input_binder.go (1)
170-182: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winBind and authorize native workspace inputs with the stable workspace ID.
bindNativeWorkspaceFieldwritesresolvedWorkspace.IDto the bound payload, butResolvedWorkspace.WorkspaceIDis the stable workspace identity and tests separate these namespaces.authorizeWorkspaceTargetcompares the bound target againstscope.WorkspaceIDfrom the caller/workspace source, which also uses that stable ID in existing coverage. Compare/assign the stable ID there to avoid denying own-workspace calls when the two IDs diverge.🤖 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 `@internal/daemon/native_workspace_input_binder.go` around lines 170 - 182, Update bindNativeWorkspaceField to compare and assign ResolvedWorkspace.WorkspaceID instead of ResolvedWorkspace.ID, matching authorizeWorkspaceTarget and the caller scope’s stable workspace identity. Preserve the existing requested-versus-trusted selection and error handling.
🧹 Nitpick comments (14)
internal/agentidentity/identity.go (1)
172-177: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPreserve the policy error instead of swallowing it.
policy.Authorizefailures (store/transport errors, cancelled context) are indistinguishable from real denials here, and the returned hint tells the operator to changepermissions.modeeven when the cause was infrastructural. Join the underlying error so callers and logs keep the root cause while the deny decision stays fail-closed.♻️ Suggested change
- if policy != nil { - decision, err := policy.Authorize(ctx, req) - if err == nil && decision.Allowed { - return nil - } - } - return identityError( - ErrIdentityUnauthorized, - "identity_unauthorized", - "agent session does not belong to the requested workspace", - workspaceaccess.DenialHint, - ) + var authorizeErr error + if policy != nil { + decision, err := policy.Authorize(ctx, req) + if err == nil && decision.Allowed { + return nil + } + authorizeErr = err + } + return identityError( + errors.Join(ErrIdentityUnauthorized, authorizeErr), + "identity_unauthorized", + "agent session does not belong to the requested workspace", + workspaceaccess.DenialHint, + )Note:
errors.Join(err, nil)returns justerr, so the allowed path and the plain-denial path stay unchanged.🤖 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 `@internal/agentidentity/identity.go` around lines 172 - 177, Update the policy authorization handling around policy.Authorize in the identity flow to preserve any non-nil authorization error while still returning a fail-closed denial. Join the underlying error with the existing denial error, ensuring allowed requests and plain policy denials retain their current behavior.Source: Coding guidelines
internal/daemon/daemon_mock_agents_integration_test.go (1)
1438-1457: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
requireLatestHostedMCPStdioServerduplicatesrequireHostedMCPStdioServerexcept for iteration order.Consider a single helper with a direction flag (or one that always returns the latest, if earliest-match had no semantic requirement) to avoid the two copies drifting.
🤖 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 `@internal/daemon/daemon_mock_agents_integration_test.go` around lines 1438 - 1457, Consolidate requireLatestHostedMCPStdioServer with the existing requireHostedMCPStdioServer helper instead of maintaining duplicate MCP server-search logic. Add a direction or latest-selection option only if the existing helper must preserve earliest-match behavior, while retaining the current failure message and latest-session semantics.internal/task/task_resource_authorizer.go (1)
59-79: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPolicy authorization errors are silently swallowed.
taskWorkspaceAccessAlloweddiscards the error frompolicy.Authorizeand only returns a boolean. Fail-closed-on-error is the right default for security, but the actual error (e.g., a misconfigured or unreachable policy backend) is never logged, making it indistinguishable from a legitimate deny when debugging denied cross-workspace task access.As per path instructions for
internal/**/*.go, error handling and logging conventions should be followed when editing production Go underinternal/.♻️ Suggested logging addition
func taskWorkspaceAccessAllowed( ctx context.Context, policy workspaceaccess.Policy, actor ActorContext, targetWorkspaceID string, ) bool { if policy == nil { return false } decision, err := policy.Authorize(ctx, workspaceaccess.Request{ Actor: workspaceaccess.ActorRef{ Kind: workspaceaccess.ActorKind(actor.Actor.Kind.Normalize()), SessionID: strings.TrimSpace(actor.Scope.SessionID), WorkspaceID: strings.TrimSpace(actor.Scope.WorkspaceID), Operator: actor.Scope.Operator, }, TargetWorkspaceID: strings.TrimSpace(targetWorkspaceID), Seam: workspaceaccess.SeamTask, }) + if err != nil { + logger.FromContext(ctx).Warn("workspace access policy authorization failed", "error", err) + return false + } return err == nil && decision.Allowed }🤖 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 `@internal/task/task_resource_authorizer.go` around lines 59 - 79, Update taskWorkspaceAccessAllowed to log the error returned by policy.Authorize before preserving the existing fail-closed behavior of returning false. Follow the repository’s established error-logging convention for internal Go code and keep legitimate denied decisions returning false without logging an error.Source: Path instructions
web/src/systems/session/lib/session-owner-query.ts (1)
14-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winQuery key/options factory not co-located in
lib/query-options.ts.
sessionOwnerKeys/sessionOwnerOptionsare defined in a newsession-owner-query.tsfile rather than inlib/query-options.ts. The in-code rationale (keeping the owner projection out ofsessionKeysper ADR-004 to avoid workspace-cache leakage) is sound, but the same isolation could be achieved by exporting a separate key/options factory fromlib/query-options.tsitself, satisfying the co-location guideline while preserving the namespace separation.As per coding guidelines, "Co-locate query keys and query functions in
lib/query-options.ts; reuse option/key factories... and reconcile mutations through canonical keys."🤖 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 `@web/src/systems/session/lib/session-owner-query.ts` around lines 14 - 35, The session-owner query keys and options are in a separate session-owner-query module instead of the shared query-options module. Move the sessionOwnerKeys, SessionOwnerDialogState, and sessionOwnerOptions definitions into lib/query-options.ts, preserving their separate namespace from sessionKeys and keeping sessionOwnerOptions’ query key, fetchSessionOwner query function, stale time, and enabled behavior unchanged; update imports and usages accordingly.Source: Coding guidelines
internal/daemon/native_workspace_input_binder.go (1)
248-250: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPreserve the policy error cause in the denial.
errfromAuthorizeis dropped and replaced with a generic denial, so infrastructure failures (mode source unavailable, audit store down) become indistinguishable from genuine policy denials during triage.♻️ Wrap the cause
if err != nil { - return nativeWorkspaceAccessDeniedError(descriptor.ID) + return fmt.Errorf("%w: authorize workspace %q: %w", + nativeWorkspaceAccessDeniedError(descriptor.ID), targetWorkspaceID, err) }🤖 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 `@internal/daemon/native_workspace_input_binder.go` around lines 248 - 250, Update the Authorize error path in the native workspace input binding flow to preserve err when constructing the denial, wrapping the original cause while retaining the nativeWorkspaceAccessDeniedError context. Ensure infrastructure failures remain distinguishable from genuine policy denials and keep the existing successful authorization path unchanged.internal/daemon/native_tools_test.go (1)
1095-1103: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThese assertions no longer test authorization; the messages claim otherwise.
adapteris built with onlyWorkspaces— no binder, no policy — so resolvingws-2from aws-1scope succeeds simply becausenativeCallerWorkspaceInputno longer rejects foreign refs. The failure messages ("want binder-authorized ws-2") suggest a binder authorized the call, which is misleading for future readers. Consider renaming to reflect that the raw adapter defers authorization to the registry seam.♻️ Clarify intent
if err != nil || foreignResolved.WorkspaceID != "ws-2" { - t.Fatalf("nativeResolvedWorkspace(foreign) = %#v, %v, want binder-authorized ws-2", foreignResolved, err) + t.Fatalf( + "nativeResolvedWorkspace(foreign) = %#v, %v, want ws-2 resolved (authorization deferred to the registry binder)", + foreignResolved, + err, + ) }Also applies to: 1146-1153
🤖 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 `@internal/daemon/native_tools_test.go` around lines 1095 - 1103, Rename the assertions around adapter.nativeResolvedWorkspace in the affected test cases to reflect raw adapter resolution and registry-seam authorization, not binder authorization. Update the failure messages to remove “binder-authorized” language while preserving the expected ws-2 result and existing assertion behavior.internal/daemon/workspace_access_audit.go (1)
67-67: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winDetached audit write has no deadline and runs synchronously on the authorization path.
context.WithoutCancel(ctx)correctly prevents request cancellation from dropping the ledger append, but it also removes the caller's deadline. SinceDefaultPolicy.Authorizeemits synchronously (internal/workspaceaccess/default_policy.goLines 43-61), a wedged or lock-contended event store blocks every workspace-access decision indefinitely. Reattach a bounded deadline.🛡️ Reattach a deadline
- if err := e.store.WriteEventSummary(context.WithoutCancel(ctx), store.EventSummary{ + writeCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), workspaceAccessAuditWriteTimeout) + defer cancel() + if err := e.store.WriteEventSummary(writeCtx, store.EventSummary{Plus a package-level constant:
const workspaceAccessAuditWriteTimeout = 5 * time.SecondAs per coding guidelines, "Work that outlives an HTTP/UDS request must use
context.WithoutCancel(ctx)...; reattach a deadline when needed."🤖 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 `@internal/daemon/workspace_access_audit.go` at line 67, Update the audit write in the surrounding workspace access audit function to derive a context with WithoutCancel(ctx) and apply a five-second timeout using the package-level workspaceAccessAuditWriteTimeout constant before calling WriteEventSummary. Preserve the detached context behavior while ensuring the synchronous authorization path cannot block indefinitely, and release the derived context when the write completes.Source: Coding guidelines
internal/daemon/workspace_access_prompt_bridge.go (1)
107-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUnderlying
outcome.Validate()error is discarded from the returned message.The error from
outcome.Validate()is checked but its details are dropped in favor of a generic message. Wrapping it would aid debugging malformed ACP outcomes.♻️ Optional improvement
- if err := outcome.Validate(); err != nil || outcome.Selected == nil { - return false, errors.New("daemon: workspace access prompt returned no selected outcome") - } + if err := outcome.Validate(); err != nil { + return false, fmt.Errorf("daemon: workspace access prompt outcome invalid: %w", err) + } + if outcome.Selected == nil { + return false, errors.New("daemon: workspace access prompt returned no selected outcome") + }🤖 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 `@internal/daemon/workspace_access_prompt_bridge.go` around lines 107 - 109, Update the validation branch in the workspace access prompt bridge to preserve and wrap the error returned by outcome.Validate() when validation fails, while retaining the existing generic error for a nil outcome.Selected case. Ensure malformed ACP outcome details are included in the returned error message.web/src/systems/os/components/os-route-hold.tsx (1)
12-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove route coordination out of the component layer.
OsRouteHoldis route orchestration, not a presentational component. Move this effect into the decision route or a route-level hook.As per coding guidelines, “UI components must be pure and presentational; orchestration and data fetching belong at route/page level.”
🤖 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 `@web/src/systems/os/components/os-route-hold.tsx` around lines 12 - 20, Move the route-hold orchestration from OsRouteHold into the decision route or an equivalent route-level hook, including the useLocation values and coordinator hold/release effect. Remove the component-layer effect and ensure the route still holds on mount or route changes and releases on cleanup.Source: Coding guidelines
web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx (2)
240-256: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHarness components are declared after
builder'sreturn.Hoisting makes this valid, but the returned object followed by unreachable-looking declarations is easy to misread. Moving the harness components above the
return(or to module scope, parameterized by the route handles) would read better.🤖 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 `@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx` around lines 240 - 256, Move the AgentsFleetRouteHarness and AgentDetailRouteHarness declarations above the builder’s return statement, keeping their existing behavior and route-handle references unchanged.
306-309: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse the exported owner/context types instead of restating them inline.
{ permalinkError?: string; sessionOwner?: { sessionId; workspaceId; workspaceName } }duplicatesSessionPermalinkRouteContext/SessionOwnerDialogState. Importing those types keeps the harness failing (at typecheck) if the production projection changes shape.♻️ Suggested change
- const context = permalinkRoute.useRouteContext() as { - permalinkError?: string; - sessionOwner?: { sessionId: string; workspaceId: string; workspaceName: string }; - }; + const context = permalinkRoute.useRouteContext() as Pick< + SessionPermalinkRouteContext, + "permalinkError" | "sessionOwner" + >;🤖 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 `@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx` around lines 306 - 309, Update the route-context typing near permalinkRoute.useRouteContext to import and reuse the exported SessionPermalinkRouteContext and SessionOwnerDialogState types instead of duplicating the permalinkError and sessionOwner structure inline. Keep the test context aligned with the production route projection so type changes are detected automatically.web/src/routes/_app/session.$id.tsx (1)
40-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDialog wiring duplicated with the session leaf route.
This
OsRouteHold+SessionWorkspaceSwitchDialog+ confirm/cancel navigation block is byte-for-byte the same shape asweb/src/routes/_app/agents.$name.sessions.$id.tsx(lines 59-80) apart from thetotarget. A small composite (owner + re-enter target) in@/systems/sessionwould keep both seams from diverging as the confirmation flow evolves.🤖 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 `@web/src/routes/_app/session`.$id.tsx around lines 40 - 56, The session workspace-switch confirmation UI is duplicated between the session route and agent session route. Extract a shared composite in the session system that accepts the owner and re-entry navigation target, encapsulates OsRouteHold, SessionWorkspaceSwitchDialog, and confirm/cancel navigation, then replace both route-local blocks with that component while preserving each route’s existing target.internal/session/spawn_test.go (1)
96-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider moving this subtest out of
TestValidatePermissionSubset.The scenario exercises
Manager.Spawncross-workspace authorization (policy invocation + workspace binding + per-workspace cap), notValidatePermissionSubset. Hosting it in a dedicated top-level test (e.g.TestSpawnWorkspaceAccess) keeps the invariant and owning suite aligned and makes failures easier to attribute.Also,
policy.calls != 2pins an internalprepareSpawndetail; asserting the recorded requests plus a non-zero call count would be less brittle.As per path instructions, "Before creating tests, identify the invariant, owning layer, and canonical suite; extend the existing suite rather than creating duplicate standalone regressions."
🤖 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 `@internal/session/spawn_test.go` around lines 96 - 157, Move the cross-workspace scenario out of TestValidatePermissionSubset into the dedicated top-level Manager.Spawn workspace-access suite, such as TestSpawnWorkspaceAccess, preserving assertions for authorization, workspace binding, and per-workspace limits. Replace the exact policy.calls == 2 assertion with a non-zero call-count check while retaining validation of every recorded request against wantRequest.Source: Path instructions
web/src/routes/_app/-agent-session-route-loader.ts (1)
53-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winForeign-session resolution rule implemented twice. Both deep-link seams re-implement the same policy — cached foreign owner check, active-workspace detail miss, owner projection lookup, compare owner workspace against the active one — differing only in how a miss is signalled. One shared helper in
@/systems/sessionkeeps the rule (and its "owner projection only, never foreign detail before confirm" invariant) in a single place.
web/src/routes/_app/-agent-session-route-loader.ts#L53-L69: replaceresolveForeignSessionwith the shared helper, mapping a miss to{ status: "not-found" }.web/src/routes/_app/-session-permalink-route.ts#L117-L126: replaceresolveForeignPermalinkwith the same helper, throwingSessionNotFoundErroron a miss.🤖 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 `@web/src/routes/_app/-agent-session-route-loader.ts` around lines 53 - 69, Replace resolveForeignSession in web/src/routes/_app/-agent-session-route-loader.ts lines 53-69 with the shared `@/systems/session` helper, mapping a miss to { status: "not-found" }. Replace resolveForeignPermalink in web/src/routes/_app/-session-permalink-route.ts lines 117-126 with the same helper, throwing SessionNotFoundError on a miss; preserve the owner-projection-only invariant.
🤖 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 `@internal/api/core/tool_errors.go`:
- Around line 119-121: The workspace-access-denied branch in
internal/api/core/tool_errors.go (lines 119-121) must stop forwarding
ToolError.Message and instead return a stable vetted public denial message.
Update the related assertions in internal/api/core/tools_test.go (lines 300-316)
to expect the canonical sanitized message and verify that a secret-bearing
internal message is not present.
In `@internal/api/udsapi/routes.go`:
- Line 357: Apply handlers.AuthorizeNetworkWorkspaceAccess to the
/network-coordination route group as well as workspaceNetwork, ensuring its read
and mutation endpoints enforce the cross-workspace check. Add a UDS test
covering a denied agent accessing a coordination endpoint and verify the request
is rejected.
In `@internal/cli/client_network_credentials.go`:
- Around line 33-49: Update newNetworkStatusCommand so NetworkStatus receives
the workspace-scoped credentials established after resolveNetworkWorkspaceRef,
rather than calling client.NetworkStatus directly with the unwrapped command
context. Reuse the existing credential setup path used by doNetworkJSON or pass
the wrapped context into NetworkStatus, preserving the current status request
behavior.
In `@internal/cli/workspace_test.go`:
- Around line 483-496: Extend the assertions in the workspace resolution test
around executeRootCommand and decoded workspaceDetailOutput to verify that
decoded.Workspace.ID equals "ws-foreign" for the cross-workspace cases. Keep the
existing ResolutionSource assertion and ensure the test validates the selected
workspace outcome, not only its resolution source.
In `@internal/daemon/daemon_mock_agents_integration_test.go`:
- Around line 1058-1089: Strengthen the “Should consume allow-once consent only
for one call” subtest so it verifies the first grant is consumed rather than
approving both calls explicitly. After the initial call through
callWorkspaceAccessToolWithDecision, make the second call via the bare
callWorkspaceAccessTool or otherwise assert that it triggers a new
prompt/denial, including the relevant permission-event or message validation so
the test fails if consent becomes session-wide.
In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 264-273: Stop fail-open authorization in
nativeWorkspaceScopeHasOperatorAuthority and the session-status path: require
explicit scope.Operator or a dedicated internal-actor kind instead of treating
nativeWorkspaceScopeUnbound as authoritative, and make nil sessions or Status
failures return an error/sentinel that causes authorizeWorkspaceTarget to deny
rather than trust scope.WorkspaceID or scope.AgentName. Apply these changes at
internal/daemon/native_workspace_input_binder.go lines 264-273 (anchor) and
285-296 (sibling).
In `@internal/session/spawn.go`:
- Around line 256-260: Update the spawn authorization flow around
resolver.Resolve and policy.Authorize to preserve validation/backend failures as
actionable errors rather than converting them to spawnWorkspaceAccessDenied.
Return a validation error for workspace-ref resolution failures, matching the
existing parent-workspace resolution handling, and propagate or wrap
authorizeErr when policy evaluation fails. Keep spawnWorkspaceAccessDenied only
for successfully resolved references that policy explicitly denies.
In `@internal/task/manager_integration_test.go`:
- Around line 414-452: The test must exercise automation authorization while a
run remains claimable. Update the flow around the agent and automation calls in
the integration test to either perform the automation claim first or enqueue a
separate queued run for it, then assert ErrPermissionDenied and the
corresponding policy request before verifying the agent claim and its propagated
request.
In `@internal/tools/dispatch_test.go`:
- Around line 746-760: Extend the denial-path assertions after the second
registry.Call in the test to verify policy authorization was consulted: assert
policy.calls == 2 and validate the canonical authorization request shape using
the policy’s recorded request data. Keep the existing
ReasonWorkspaceAccessDenied and provider-not-called assertions.
In `@web/src/systems/os/lib/routing-coordinator.ts`:
- Around line 319-330: Update startRouteReconciliation to clear the matching
routeReconciliation and call reportAuthoritativeState when outcome.accepted is
false, rather than returning with pending state. Also handle rejected
outcome.completion promises with the same token-guarded cleanup and
authoritative-state reporting used for successful completion, while preserving
protection against clearing a newer reconciliation.
---
Outside diff comments:
In `@internal/api/core/agent_handlers.go`:
- Around line 70-76: Update the shared resolver around ListAgentCatalog to
assign resolved.WorkspaceID to each workspace-origin entry before transforming
resolved.Entries into agent definitions. Remove the duplicate WorkspaceID
patching from ListAgents and workspaceAgentDef, leaving those callers to consume
the normalized entries.
In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 170-182: Update bindNativeWorkspaceField to compare and assign
ResolvedWorkspace.WorkspaceID instead of ResolvedWorkspace.ID, matching
authorizeWorkspaceTarget and the caller scope’s stable workspace identity.
Preserve the existing requested-versus-trusted selection and error handling.
---
Nitpick comments:
In `@internal/agentidentity/identity.go`:
- Around line 172-177: Update the policy authorization handling around
policy.Authorize in the identity flow to preserve any non-nil authorization
error while still returning a fail-closed denial. Join the underlying error with
the existing denial error, ensuring allowed requests and plain policy denials
retain their current behavior.
In `@internal/daemon/daemon_mock_agents_integration_test.go`:
- Around line 1438-1457: Consolidate requireLatestHostedMCPStdioServer with the
existing requireHostedMCPStdioServer helper instead of maintaining duplicate MCP
server-search logic. Add a direction or latest-selection option only if the
existing helper must preserve earliest-match behavior, while retaining the
current failure message and latest-session semantics.
In `@internal/daemon/native_tools_test.go`:
- Around line 1095-1103: Rename the assertions around
adapter.nativeResolvedWorkspace in the affected test cases to reflect raw
adapter resolution and registry-seam authorization, not binder authorization.
Update the failure messages to remove “binder-authorized” language while
preserving the expected ws-2 result and existing assertion behavior.
In `@internal/daemon/native_workspace_input_binder.go`:
- Around line 248-250: Update the Authorize error path in the native workspace
input binding flow to preserve err when constructing the denial, wrapping the
original cause while retaining the nativeWorkspaceAccessDeniedError context.
Ensure infrastructure failures remain distinguishable from genuine policy
denials and keep the existing successful authorization path unchanged.
In `@internal/daemon/workspace_access_audit.go`:
- Line 67: Update the audit write in the surrounding workspace access audit
function to derive a context with WithoutCancel(ctx) and apply a five-second
timeout using the package-level workspaceAccessAuditWriteTimeout constant before
calling WriteEventSummary. Preserve the detached context behavior while ensuring
the synchronous authorization path cannot block indefinitely, and release the
derived context when the write completes.
In `@internal/daemon/workspace_access_prompt_bridge.go`:
- Around line 107-109: Update the validation branch in the workspace access
prompt bridge to preserve and wrap the error returned by outcome.Validate() when
validation fails, while retaining the existing generic error for a nil
outcome.Selected case. Ensure malformed ACP outcome details are included in the
returned error message.
In `@internal/session/spawn_test.go`:
- Around line 96-157: Move the cross-workspace scenario out of
TestValidatePermissionSubset into the dedicated top-level Manager.Spawn
workspace-access suite, such as TestSpawnWorkspaceAccess, preserving assertions
for authorization, workspace binding, and per-workspace limits. Replace the
exact policy.calls == 2 assertion with a non-zero call-count check while
retaining validation of every recorded request against wantRequest.
In `@internal/task/task_resource_authorizer.go`:
- Around line 59-79: Update taskWorkspaceAccessAllowed to log the error returned
by policy.Authorize before preserving the existing fail-closed behavior of
returning false. Follow the repository’s established error-logging convention
for internal Go code and keep legitimate denied decisions returning false
without logging an error.
In
`@web/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsx`:
- Around line 240-256: Move the AgentsFleetRouteHarness and
AgentDetailRouteHarness declarations above the builder’s return statement,
keeping their existing behavior and route-handle references unchanged.
- Around line 306-309: Update the route-context typing near
permalinkRoute.useRouteContext to import and reuse the exported
SessionPermalinkRouteContext and SessionOwnerDialogState types instead of
duplicating the permalinkError and sessionOwner structure inline. Keep the test
context aligned with the production route projection so type changes are
detected automatically.
In `@web/src/routes/_app/-agent-session-route-loader.ts`:
- Around line 53-69: Replace resolveForeignSession in
web/src/routes/_app/-agent-session-route-loader.ts lines 53-69 with the shared
`@/systems/session` helper, mapping a miss to { status: "not-found" }. Replace
resolveForeignPermalink in web/src/routes/_app/-session-permalink-route.ts lines
117-126 with the same helper, throwing SessionNotFoundError on a miss; preserve
the owner-projection-only invariant.
In `@web/src/routes/_app/session`.$id.tsx:
- Around line 40-56: The session workspace-switch confirmation UI is duplicated
between the session route and agent session route. Extract a shared composite in
the session system that accepts the owner and re-entry navigation target,
encapsulates OsRouteHold, SessionWorkspaceSwitchDialog, and confirm/cancel
navigation, then replace both route-local blocks with that component while
preserving each route’s existing target.
In `@web/src/systems/os/components/os-route-hold.tsx`:
- Around line 12-20: Move the route-hold orchestration from OsRouteHold into the
decision route or an equivalent route-level hook, including the useLocation
values and coordinator hold/release effect. Remove the component-layer effect
and ensure the route still holds on mount or route changes and releases on
cleanup.
In `@web/src/systems/session/lib/session-owner-query.ts`:
- Around line 14-35: The session-owner query keys and options are in a separate
session-owner-query module instead of the shared query-options module. Move the
sessionOwnerKeys, SessionOwnerDialogState, and sessionOwnerOptions definitions
into lib/query-options.ts, preserving their separate namespace from sessionKeys
and keeping sessionOwnerOptions’ query key, fetchSessionOwner query function,
stale time, and enabled behavior unchanged; update imports and usages
accordingly.
🪄 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: CHILL
Plan: Pro Plus
Run ID: 3ed53d45-af72-4460-be06-92d82c3adaef
⛔ Files ignored due to path filters (40)
docs/_memory/glossary.mdis excluded by!**/*.mddocs/qa/bugs/BUG-0028.mdis excluded by!**/*.mddocs/qa/bugs/BUG-20260719-autonomous-progress-unobservable.mdis excluded by!**/*.mddocs/qa/bugs/BUG-20260729-coordination-cli-drops-agent-identity.mdis excluded by!**/*.mddocs/qa/bugs/BUG-20260729-nearest-workspace-case-alias.mdis excluded by!**/*.mddocs/qa/charters/CH-cross-workspace-consent-audit.mdis excluded by!**/*.mddocs/qa/charters/CH-cross-workspace-mode-seams.mdis excluded by!**/*.mddocs/qa/charters/CH-foreign-session-deep-link.mdis excluded by!**/*.mddocs/qa/charters/CH-workspace-binding-canary.mdis excluded by!**/*.mddocs/qa/journeys/J-11-return-to-running-session.mdis excluded by!**/*.mddocs/qa/journeys/J-12-open-session-fast.mdis excluded by!**/*.mddocs/qa/journeys/J-cross-workspace-access.mdis excluded by!**/*.mddocs/qa/journeys/J-open-foreign-session.mdis excluded by!**/*.mddocs/qa/journeys/J-operate-workspace-context.mdis excluded by!**/*.mddocs/qa/reports/2026-07-29-cross-workspace-access.mdis excluded by!**/*.mddocs/qa/scenarios/ET-native-workspace-scope-isolation.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-session-cross-workspace-confirm.mdis excluded by!**/*.mddocs/qa/scenarios/ET-web-session-deep-link-isolation.mdis excluded by!**/*.mddocs/qa/scenarios/ET-workspace-access-mode-matrix.mdis excluded by!**/*.mddocs/qa/scenarios/ET-workspace-access-prompt-outcomes.mdis excluded by!**/*.mddocs/qa/scenarios/MS-workspace-resolution-chain.mdis excluded by!**/*.mddocs/qa/scenarios/RT-073.mdis excluded by!**/*.mddocs/qa/scenarios/TA-053.mdis excluded by!**/*.mdinternal/testutil/acpmock/testdata/workspace_access_fixture.jsonis excluded by!**/*.jsonopenapi/compozy.jsonis excluded by!**/*.jsonpackages/site/content/runtime/cli-reference/spawn.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/agents/spawning.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/autonomy/safe-spawn.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/configuration/config-toml.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/hooks/event-catalog.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/sessions/permissions.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/workspaces/index.mdxis excluded by!**/*.mdxpackages/site/content/runtime/core/workspaces/resolver.mdxis excluded by!**/*.mdxskills/compozy/references/agent-definitions.mdis excluded by!**/*.mdskills/compozy/references/native-tools.mdis excluded by!**/*.mdweb/e2e/__tests__/os-shell.spec.tsis excluded by!web/e2e/**web/e2e/__tests__/settings-transport.spec.tsis excluded by!web/e2e/**web/e2e/fixtures/selectors.tsis excluded by!**/fixtures/**,!web/e2e/**web/src/generated/compozy-openapi.d.tsis excluded by!**/generated/**,!**/generated/**,!**/*.d.tsweb/src/systems/session/routes/session-permalink.stories.tsxis excluded by!**/*.stories.tsx
📒 Files selected for processing (178)
internal/agentidentity/identity.gointernal/agentidentity/identity_test.gointernal/api/contract/agents.gointernal/api/contract/session_owner.gointernal/api/core/agent_channels_internal_test.gointernal/api/core/agent_definition_resolution.gointernal/api/core/agent_fleet_catalog.gointernal/api/core/agent_handlers.gointernal/api/core/agent_identity.gointernal/api/core/agent_spawn.gointernal/api/core/agent_spawn_test.gointernal/api/core/agent_task_claim_criteria.gointernal/api/core/base_handlers.gointernal/api/core/handlers_agents.gointernal/api/core/handlers_test.gointernal/api/core/network_coordination_public_test.gointernal/api/core/network_workspace_access.gointernal/api/core/session_owner.gointernal/api/core/tasks_internal_test.gointernal/api/core/test_helpers_test.gointernal/api/core/tool_errors.gointernal/api/core/tools_test.gointernal/api/httpapi/agent_context_test.gointernal/api/httpapi/handlers.gointernal/api/httpapi/handlers_test.gointernal/api/httpapi/routes.gointernal/api/httpapi/server.gointernal/api/httpapi/server_handler_config.gointernal/api/httpapi/session_routes.gointernal/api/spec/registry_sessions.gointernal/api/testutil/workspace_access_stub.gointernal/api/udsapi/agent_identity_test.gointernal/api/udsapi/handler_config.gointernal/api/udsapi/handlers_test.gointernal/api/udsapi/routes.gointernal/api/udsapi/server.gointernal/api/udsapi/server_handler_config.gointernal/api/udsapi/server_handlers.gointernal/api/udsapi/session_routes.gointernal/api/udsapi/transport_parity_integration_test.gointernal/automation/dispatch_retry.gointernal/automation/dispatch_test.gointernal/cli/agent_identity.gointernal/cli/agent_identity_test.gointernal/cli/agent_kernel_metadata.gointernal/cli/agent_kernel_test.gointernal/cli/client_network.gointernal/cli/client_network_coordination.gointernal/cli/client_network_credentials.gointernal/cli/helpers_test.gointernal/cli/network.gointernal/cli/network_client_test.gointernal/cli/network_coordination_cmd.gointernal/cli/network_coordination_output.gointernal/cli/network_public_surfaces_test.gointernal/cli/network_test.gointernal/cli/session_event_output.gointernal/cli/session_test.gointernal/cli/spawn.gointernal/cli/workspace_resolution.gointernal/cli/workspace_test.gointernal/daemon/boot.gointernal/daemon/boot_hook_observers.gointernal/daemon/boot_memory_session.gointernal/daemon/boot_tool_registry.gointernal/daemon/boot_tool_registry_providers.gointernal/daemon/boot_workspace_access.gointernal/daemon/daemon_mock_agents_integration_test.gointernal/daemon/daemon_review_and_fix_e2e_integration_test.gointernal/daemon/daemon_software_delivery_e2e_integration_test.gointernal/daemon/daemon_test.gointernal/daemon/native_automation_tools_test.gointernal/daemon/native_bridge_catalog_tools.gointernal/daemon/native_bundle_resource_tools.gointernal/daemon/native_create_tools.gointernal/daemon/native_hook_helpers.gointernal/daemon/native_hook_query_tools.gointernal/daemon/native_loop_tools_support.gointernal/daemon/native_loop_tools_test.gointernal/daemon/native_session_list.gointernal/daemon/native_task_list.gointernal/daemon/native_tool_approval_grants_test.gointernal/daemon/native_tool_authored_paths.gointernal/daemon/native_tool_observe_helpers.gointernal/daemon/native_tool_registry_skills.gointernal/daemon/native_tool_scope.gointernal/daemon/native_tools_test.gointernal/daemon/native_workspace_input_binder.gointernal/daemon/prompt_sections.gointernal/daemon/runtime_dependencies.gointernal/daemon/runtime_deps.gointernal/daemon/server_options.gointernal/daemon/task_runtime_boot.gointernal/daemon/tool_approval_boot.gointernal/daemon/tool_approval_bridge_test.gointernal/daemon/tool_approval_grants_integration_test.gointernal/daemon/workspace_access_audit.gointernal/daemon/workspace_access_consent.gointernal/daemon/workspace_access_integration_test.gointernal/daemon/workspace_access_mode_source.gointernal/daemon/workspace_access_prompt_bridge.gointernal/daemon/workspace_access_test.gointernal/events/components.gointernal/events/registry_mcp_auth.gointernal/events/registry_test.gointernal/events/registry_workspace_access.gointernal/extension/host_api_integration_test.gointernal/extension/host_api_test.gointernal/extension/manager_integration_test.gointernal/mcp/hosted_proxy.gointernal/mcp/hosted_proxy_test.gointernal/mcp/hosted_proxy_transport_error.gointernal/session/manager_permissions_test.gointernal/session/manager_types.gointernal/session/manager_workspace_access.gointernal/session/query_metadata.gointernal/session/session.gointernal/session/session_info.gointernal/session/spawn.gointernal/session/spawn_permissions.gointernal/session/spawn_test.gointernal/store/globaldb/global_db_task_event_tx_test.gointernal/store/globaldb/global_db_test.gointernal/task/actors.gointernal/task/lease_claim_hooks.gointernal/task/lease_manager_claim.gointernal/task/lease_test.gointernal/task/manager.gointernal/task/manager_constructor.gointernal/task/manager_integration_test.gointernal/task/manager_test.gointernal/task/manager_workspace_access.gointernal/task/task_resource_authorizer.gointernal/testutil/e2e/runtime_harness_transport.gointernal/tools/dispatch.gointernal/tools/dispatch_authorization.gointernal/tools/dispatch_test.gointernal/tools/dispatch_workspace_access.gointernal/tools/reason.gointernal/tools/registry.gointernal/tools/tool.gointernal/workspace/coordination_commands.gointernal/workspace/coordination_commands_test.gointernal/workspace/discovery.gointernal/workspace/resolver_test.gointernal/workspaceaccess/audit.gointernal/workspaceaccess/default_policy.gointernal/workspaceaccess/default_policy_contracts.gointernal/workspaceaccess/default_policy_test.gointernal/workspaceaccess/default_policy_validation.gointernal/workspaceaccess/policy.gomagefiles/boundaries.goweb/src/routeTree.gen.tsweb/src/routes/_app/-agent-session-route-loader.tsweb/src/routes/_app/-session-permalink-route.tsweb/src/routes/_app/-session-workspace-switch.tsweb/src/routes/_app/__tests__/-agent-session-deeplink.router.integration.test.tsxweb/src/routes/_app/__tests__/-route-preloading.integration.test.tsxweb/src/routes/_app/agents.$name.index.tsxweb/src/routes/_app/agents.$name.sessions.$id.tsxweb/src/routes/_app/agents.$name.tsxweb/src/routes/_app/agents.index.tsxweb/src/routes/_app/agents.tsxweb/src/routes/_app/session.$id.tsxweb/src/storybook/route-story-registry.tsweb/src/systems/os/components/index.tsweb/src/systems/os/components/os-route-hold.tsxweb/src/systems/os/index.tsweb/src/systems/os/lib/__tests__/routing-coordinator.test.tsweb/src/systems/os/lib/routing-coordinator.tsweb/src/systems/session/adapters/session-owner-api.tsweb/src/systems/session/components/session-workspace-switch-dialog.tsxweb/src/systems/session/index.tsweb/src/systems/session/lib/__tests__/session-deeplink-search.test.tsweb/src/systems/session/lib/session-deeplink-search.tsweb/src/systems/session/lib/session-owner-query.tsweb/src/systems/session/mocks/handlers.tsweb/src/systems/session/types.ts
💤 Files with no reviewable changes (2)
- internal/api/core/agent_task_claim_criteria.go
- internal/mcp/hosted_proxy.go
| if hasToolReason(toolErr.ReasonCodes, toolspkg.ReasonWorkspaceAccessDenied) { | ||
| payload.Message = strings.TrimSpace(toolErr.Message) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Do not expose arbitrary denial messages.
Line 119 bypasses safeToolErrorMessage and returns toolErr.Message verbatim. Since workspace denials are 403, maskInternal does not redact it; any sensitive detail attached to a ToolError with this reason reaches the API response. Emit a canonical vetted denial message (or a dedicated vetted public-message field) instead.
internal/api/core/tool_errors.go#L119-L121: replace directtoolErr.Messageforwarding with a stable safe workspace-access denial message.internal/api/core/tools_test.go#L300-L316: assert the sanitized canonical message and verify a secret-bearing internal message is absent.
As per coding guidelines, “Never expose raw … secret bindings in logs, APIs, errors, messages, SSE, UI, or memory.”
📍 Affects 2 files
internal/api/core/tool_errors.go#L119-L121(this comment)internal/api/core/tools_test.go#L300-L316
🤖 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 `@internal/api/core/tool_errors.go` around lines 119 - 121, The
workspace-access-denied branch in internal/api/core/tool_errors.go (lines
119-121) must stop forwarding ToolError.Message and instead return a stable
vetted public denial message. Update the related assertions in
internal/api/core/tools_test.go (lines 300-316) to expect the canonical
sanitized message and verify that a secret-bearing internal message is not
present.
Source: Coding guidelines
| private startRouteReconciliation( | ||
| pending: RouteReconciliation, | ||
| outcome: WindowManagerOpenOutcome | ||
| ): void { | ||
| if (!outcome.accepted) return; | ||
| this.routeReconciliation = { ...pending, inFlight: true }; | ||
| void outcome.completion.then(() => { | ||
| if (this.routeReconciliation?.token !== pending.token) return; | ||
| this.routeReconciliation = null; | ||
| this.reportAuthoritativeState(); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
A rejected or failed open leaves routeReconciliation pending forever, muting authoritative URL sync.
Two failure paths never clear the pending entry:
if (!outcome.accepted) return;keepsrouteReconciliationset withinFlight: false.outcome.completionhas no rejection handler; a rejected completion skips thethencallback, soinFlight: truesticks.
Because reportAuthoritativeState returns early whenever routeReconciliation !== null (Line 124), the coordinator then stops truing the URL up to authoritative focus for the rest of the session — and in the rejected-outcome case it re-attempts openOrFocus on every authoritative frame.
🛡️ Proposed fix
private startRouteReconciliation(
pending: RouteReconciliation,
outcome: WindowManagerOpenOutcome
): void {
- if (!outcome.accepted) return;
+ if (!outcome.accepted) {
+ if (this.routeReconciliation?.token === pending.token) this.routeReconciliation = null;
+ return;
+ }
this.routeReconciliation = { ...pending, inFlight: true };
- void outcome.completion.then(() => {
- if (this.routeReconciliation?.token !== pending.token) return;
- this.routeReconciliation = null;
- this.reportAuthoritativeState();
- });
+ void outcome.completion
+ .catch(() => false)
+ .then(() => {
+ if (this.routeReconciliation?.token !== pending.token) return;
+ this.routeReconciliation = null;
+ this.reportAuthoritativeState();
+ });
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private startRouteReconciliation( | |
| pending: RouteReconciliation, | |
| outcome: WindowManagerOpenOutcome | |
| ): void { | |
| if (!outcome.accepted) return; | |
| this.routeReconciliation = { ...pending, inFlight: true }; | |
| void outcome.completion.then(() => { | |
| if (this.routeReconciliation?.token !== pending.token) return; | |
| this.routeReconciliation = null; | |
| this.reportAuthoritativeState(); | |
| }); | |
| } | |
| private startRouteReconciliation( | |
| pending: RouteReconciliation, | |
| outcome: WindowManagerOpenOutcome | |
| ): void { | |
| if (!outcome.accepted) { | |
| if (this.routeReconciliation?.token === pending.token) this.routeReconciliation = null; | |
| return; | |
| } | |
| this.routeReconciliation = { ...pending, inFlight: true }; | |
| void outcome.completion | |
| .catch(() => false) | |
| .then(() => { | |
| if (this.routeReconciliation?.token !== pending.token) return; | |
| this.routeReconciliation = null; | |
| this.reportAuthoritativeState(); | |
| }); | |
| } |
🤖 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 `@web/src/systems/os/lib/routing-coordinator.ts` around lines 319 - 330, Update
startRouteReconciliation to clear the matching routeReconciliation and call
reportAuthoritativeState when outcome.accepted is false, rather than returning
with pending state. Also handle rejected outcome.completion promises with the
same token-guarded cleanup and authoritative-state reporting used for successful
completion, while preserving protection against clearing a newer reconciliation.
Checkpoint via cy-loop-tasks (iteration 2, phase B mode=tasks). Focused gates: 2172 race tests across 5 packages; 4 tagged integration cases; make lint and make boundaries passed. Full make verify is reserved for the final workflow gate. Co-Authored-By: Codex <[email protected]>
Checkpoint via cy-loop-tasks (iteration 3, phase B mode=tasks). Co-Authored-By: Codex <[email protected]>
Checkpoint via cy-loop-tasks (iteration 4, phase B mode=tasks). Co-Authored-By: Codex <[email protected]>
a2ccd05 to
8de642d
Compare
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
Summary
This PR delivers the complete seven-task cross-workspace access program. It replaces scattered
hard-deny behavior with one permission-mode-driven policy, wires that policy through every shipped
agent access seam, adds a safe foreign-session deep-link flow, and co-ships the public contracts,
documentation, official Compozy skill guidance, QA plans, and durable verification evidence.
The operator-facing policy is intentionally simple:
approve-alldeny-allapprove-readsNo new config key, database table, migration, durable grant, Settings section, or standalone access
management API is introduced. The existing session
permissions.moderemains the single source oftruth.
Release notes
public spawn targets through the API/UDS contract and
compozy spawn --workspace.foreign session details never enter workspace-scoped caches before confirmation.
workspace.access_*audit events, and denialsuse consistent, actionable reasons across native tools, CLI, HTTP, and UDS.
What changed
1. Central workspace-access policy
internal/workspaceaccessas the single policy boundary with canonical target validation,actor-kind gating, fail-closed mode lookup, session-consent evaluation, and one best-effort audit
record per decision.
audit emission, ACP permission prompts, session-stop cleanup, and fail-closed dependency wiring.
widened by workspace permission mode.
ws_<16 lowercase hex>andCrockford ULIDs), while resolving aliases and paths before authorization.
2. Enforcement across runtime and agent-managed surfaces
automation, and Network agent commands through the shared policy on workspace mismatch.
scopes unavailable to identified sessions, including callers that supply an explicit home
workspace.
leakage on denied requests.
effective mode; added the explicit
workspacefield to the public spawn contract and CLI.credentials across the coordination CLI's UDS transport.
3. Prompt, consent, denial, and audit behavior
allow_once,allow_session,reject_once, andreject_sessionforapprove-readssessions.and are cleared on session stop or daemon restart. Timeouts, transport failures, and invalid
answers deny without persisting consent.
preserving existing status codes, sentinels, and 404 masking where those surfaces already use it.
the existing event/log readers without turning audit data into authorization state.
4. Session-owner projection and Web deep links
GET /api/sessions/:session_id/ownerwith byte-identical HTTP/UDS behavior and the minimalthree-field response
{session_id, workspace_id, workspace_name}; unknown sessions remain 404.workspace-switch confirmation; confirm uses the existing switch barrier and opens the exact
session, while cancel preserves the current workspace and arrangement.
remains with the session route, and hardened
RoutingCoordinatorso an older asynchronous focuscompletion cannot overwrite a newer accepted route.
and browser E2E coverage using existing
@compozy/uiprimitives.5. Review remediation and live-QA fixes
requested cap. The second round shipped with
SHIPstatus.binder/authorizer drift while retaining operator and daemon behavior.
bounded and production files below the repository cap.
become operator calls (
4ef8e8c).lexical containment as the fast path (
4e81f17).gaps exposed by runtime E2E and the final monorepo verification.
6. Documentation and durable QA assets
and CLI reference documentation to match runtime behavior.
skills/compozy/references for permission modes,native-tool scope behavior, prompts, consent lifetime, and audit visibility.
addressed bug records, and the dated run report.
untestedwhere required for the next independent QAcycle rather than treating implementation verification as tracker retesting.
Architecture and data decisions
extension, automation, Network-peer, and daemon actors do not gain mode-based widening.
no compatibility or migration path.
does not change the decision.
Compozy Impact Audit
reasons, prompt handling, and focused tests. No tool IDs, toolsets, descriptors, I/O schemas,
schema digests, risk flags, availability diagnostics, or capability gates changed.
SDK, MCP sidecar, or config lifecycle was added. Existing extension/hosted-MCP fixtures were
updated only to preserve fail-closed runtime wiring. The official bundled skill documents the
changed public behavior.
and keyed by session ID. Actor-home workspace IDs now propagate through CLI/HTTP/UDS/core/task/
automation/native paths. Foreign session details are not read or cached before Web confirmation,
and the global owner projection exposes only three fields. No new persisted data can leak across
workspaces.
skills/compozy/references/agent-definitions.mdandskills/compozy/references/native-tools.mdfor mode behavior, target selection, native scope,prompts, consent, denials, and auditing.
Verification
make verify— PASS, exit 0 after both review rounds.make lint— PASS, zero warnings/errors.make codegen-check— PASS.make test-e2e-runtime— PASS.make test-e2e-web— PASS, unchanged 113-case catalog.integration, API HTTP/UDS parity, native authority, task/automation, and review regressions.
reload/back/forward/multi-tab behavior, and pre-confirmation network isolation.
clean: trueand no surviving daemon, Web, observer, browser, tmux, orwatcher processes.
Detailed durable report: Cross-workspace access QA run.
Known limitations and QA disclosure
calls are trusted as operator requests. A same-UID malicious managed session can exploit that local
trust boundary, and middleware without agent identity cannot guarantee a workspace-access audit.
Closing this requires the future containment/operator-authority architecture, not another local
seam patch.
enforced containment. No agent-writable grant or access toggle was added by this PR.
Northstar Pay run completed 12/12 tasks and 19 Network sends but its strict report remains
FAIL on pre-existing, out-of-scope C11/C17 playbook gaps: 0/3 disruption probes delivered,
1/2 review cycles completed, no evidenced resolved disagreement, and missing runtime-owned progress
projection. The report preserves these facts instead of conflating them with feature coverage.
Scope
8,645insertions,682deletions).part of this change.
Summary by CodeRabbit
New Features
--workspaceoption.Bug Fixes