Tasks: Workflow RBAC on CAS (re-implementation of #1751)
Date: 2026-06-07
Stacked on: PR #1770 (CAS foundation) β branch feat/centralized-authz-service
Supersedes: PR #1751
Companions: ../2026-06-06-cas-implementation/spec.md Β· architecture.md Β· research.md
Goalβ
Re-implement #1751 (workflow RBAC: visibility, run access, team sharing, execution authz, editor UX) on top of CAS β and resolve the #1751 review feedback.
Status β 2026-06-07β
Implemented on local branch feat/workflow-rbac-on-cas (to be split β #1770 CAS core + #1771 workflow).
- Phase 1 (PAP) β
PolicyAdmin+ grant/revoke shipped in CAS core; grants exposed at/api/admin/authz/grants(admin-gated + meta-authz "caller manages the resource"),cas_grantaudit. Path differs from the planned/api/authz/v1/grants. - Phase 2 (read/use) β data model from the #1751 merge;
workflow-runs(read/delete/start) andworkflow-configsread-path re-pointed onto CAS viafilterAccessible+ org-admin bypass. β οΈ PUT/DELETE configs stay on the owner/visibility model β CAS write/delete grants aren't modeled yet. - Phase 3 (per-step gate) β
authorize(user, step.agent_id, use)runs in the sharedexecuteSteps()loop (covers start and resume); DENY fails the step. Tested. - Phase 4 (DA) β
workflow_execution_authz.pyandopenfga_authz.pydeleted. DA is now a thin PEP:auth/authz.pydelegates every agent-use decision to CAS ({AUTHZ_SERVICE_URL}/api/authz/v1/decisions, OBO). No in-process OpenFGA, no bypass mirror, no DA-side Mongo audit β CAS is the single PDP. Fails closed if CAS is unreachable. Tested. - Phase 6 (editor UX) β landed via the #1751 merge.
- Phase 7 β
domains/workflow.tsstub dropped; Access Manager/Diagnostics dead code removed; missing tests added (Phase 3/4 + bearer fallback).
Remaining: Phase 5 (modal β grant API, cosmetic); split branch β PRs #1770/#1771 + DCO sign-off; reply to #1751 threads.
Addresses #1751 review commentsβ
| Comment | Resolution here |
|---|---|
@subbaksh on workflow_execution_authz.py β "Workflows are a UI-server concept; RBAC for invocation should be in the UI server, not DA." | Per-step agent-use gate moves into the BFF executeSteps(); workflow_execution_authz.py is deleted. DA reads no workflow_configs. |
@subbaksh on workflow-da-auth.ts β "Why send user/email in a header and have DA trust it?" | Workflow authz no longer depends on X-User-Context. Header survives only for get_user_context() email; cleanup tracked in #1753. |
code-quality bot β unused import in workflow-run-access.ts | File is replaced by CAS authorize / filterAccessible. |
Architecture noteβ
The BFF executeSteps() orchestrates the workflow and invokes agents one step at a time (/api/v1/chat/stream/start per step). So the per-step CAS gate runs in-process in the BFF β no DAβCAS cross-process delegation, no decision tokens, no shared run registry. Direct agent chat still migrates DA's openfga_authz.py β CAS HTTP check.
Decisions (locked)β
- Standing grants β sharing writes
teamβagenttuples; global workflows write auser:* can_use agentwildcard. - CAS owns grants via an intent-based PAP (
PolicyAdmin); meta-authz: caller must manage the agent being granted. - Full #1751 parity (including editor UX).
- Drop the
domains/workflow.tsdelegation stub.
Phasesβ
Phase 1 β CAS grant API (PAP)β
-
PolicyAdmininterface +OpenFgaPolicyAdmin(tuple write/delete transport) -
grant()/revoke()inlib/authz;GrantIntentcontract type -
POST/DELETE /api/authz/v1/grantsβ JWKS verify + meta-authz (caller manages resource) -
cas_grantaudit event - unit + contract tests
Phase 2 β Workflow data model + BFF read/useβ
-
visibility/shared_with_teams/owner_idon workflow config (via #1751 merge) -
workflow-configsread-path β CAS; runs read/delete/start β CAS (PUT/DELETE configs stay on owner/visibility) - list filtering via
filterAccessible
Phase 3 β BFF per-step execution gate (addresses @subbaksh #3)β
-
authorize({user, step.agent_id, use})inexecuteSteps()beforechat/stream/start - same gate covers resume (shared
executeSteps()loop) - DENY β fail step with a clean reason; audit
Phase 4 β DA cleanup (addresses @subbaksh #3 + #2)β
- delete
workflow_execution_authz.py+ its test -
openfga_authz.pyremoved entirely β replaced by a thinauth/authz.pythat delegates every agent-use decision to CAS (POST {AUTHZ_SERVICE_URL}/api/authz/v1/decisions, OBO bearer). No in-process OpenFGA check, no org-admin-bypass mirror, no DA-side Mongo audit β CAS is the single PDP and recordscas_decision. Fails closed (503) when CAS is unreachable orAUTHZ_SERVICE_URLis unset. -
X-User-Contextreduced to email-only; link #1753
Phase 5 β Share / modal via CAS grant APIβ
-
WorkflowAgentAccessModalβPOST /api/authz/v1/grants(teamβagent, globalβuser:*) - remove direct OpenFGA admin writes from the modal path
Phase 6 β Editor UX parityβ
- unsaved-changes badge + discard dialog
-
WorkflowCanvas/WorkflowSidebar/WorkflowStepSidebar/WorkflowToolbar
Phase 7 β Cleanup + verifyβ
- drop
domains/workflow.tsstub - fix unused import (bot comment) + remove Access Manager/Diagnostics dead code
- Phase 3/4 + bearer-fallback tests added; targeted suites green (full-suite coverage pass pending)
- reply to #1751 threads; mark #1751 superseded by this PR