Tasks: Unified Group-Based Access Control for Shareable Resources
Input: Design documents from docs/docs/specs/2026-06-03-unified-shareable-resource-rbac/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/, db-migration.md
Tests: INCLUDED. This is RBAC/security code; the spec mandates them โ SC-006 (existing agent/KB suites pass unchanged), FR-007 (drift check), FR-031 (model parity), plus per-story acceptance tests. Write/adjust tests alongside each phase.
Organization: Phases follow plan.md Phases AโG (the dependency order), each mapped to its user story. Phase A (the shared module) is both User Story 1 and the foundational blocker for every later phase.
Format: [ID] [P?] [Story] Description with file pathโ
- [P]: parallelizable (different files, no dependency on an incomplete task)
- [Story]: US1โUS6 from spec.md
- All paths are repo-relative to
/Users/kkantesa/ai-platform-engineering/
Story โ Plan-Phase โ Priority mapโ
| Story | Plan phase | Priority | Theme |
|---|---|---|---|
| US1 | A | P1 | Shared module (foundational) |
| US2 | B | P1 | Creator/owner split |
| US4 | C | P1 | RAG parent_kb inheritance |
| US5 | D | P2 | RAG persistence + UI |
| US6 | E | P2 | MCP tool parity + can_call |
| US3 | F | P2 | Ownership transfer |
| โ | G | โ | Migration, tests, RBAC docs (polish) |
Phase 1: Setupโ
Purpose: Confirm the working environment and baselines before changes.
- T001 Confirm branch
2026-06-03-unified-shareable-resource-rbacis checked out and based onmain; rungit statusclean. - T002 [P] Establish UI test baseline: run
make caipe-ui-testsand record the passing suite list (for SC-006 regression comparison) โ note results in the PR description later. - T003 [P] Establish OpenFGA model baseline: run the model-parity test and
scripts/validate-rbac-matrix.py; record green state. - T004 [P] Build RAG
commonvenv if needed (ai_platform_engineering/knowledge_bases/rag/common) per CLAUDE.md worktree rules, so pytest can run on the Python model changes.
Checkpoint: Baselines captured; any later regression is attributable to this work.
Phase 2 (Plan Phase A): User Story 1 โ Shared Access-Control Module (Priority: P1) ๐ฏ MVP + FOUNDATIONALโ
Goal: One reusable module (reconciler core, route helper, persistence mixin, UI component, model template + drift check) that any resource composes for group-based access control. Agent + KB are refactored onto it with no behavior change.
Independent Test: Existing agent + KB reconciler/route/UI suites pass unchanged after refactor (SC-006); a unit test drives a hypothetical new type through the core and gets the expected tuple diff.
โ ๏ธ CRITICAL: No later phase may begin until this phase is complete โ every other phase consumes this module.
Tests for User Story 1 (write/extend first)โ
- T005 [P] [US1] Add unit tests for the generalized reconciler core in
ui/src/lib/rbac/__tests__/shareable-resource.test.ts: owner+shared writes, revoke deletes on unshare,previousOwnerTeamSlugtransfer deletes,creatorwritten-once and never deleted,parentKnowledgeBaseIdedge,extraMemberRelations, dedup/idempotency, invalid-slug drop. - T006 [P] [US1] Add unit tests for
handleShareableResourceWrite(route helper) covering: creator set-once from session, owner-immutability rejection whenallowOwnerTransferis false, previous-set read from config, persist called with next state. - T007 [P] [US1] Add a shareable-type drift test in
ui/src/lib/rbac/__tests__/(orscripts/): for each shareable type, assertcreator: [user]present and absent from everycan_*, and authored/chart parity for that type (FR-007 / contract C5).
Implementation for User Story 1โ
- T008 [US1] Generalize
buildOwnedResourceWithSharedTeamsDiffintobuildShareableResourceTupleDiff+reconcileShareableResourceinui/src/lib/rbac/openfga-owned-resources.tsper contractreconciler-and-route.mdR1: addcreatorSubject,previousOwnerTeamSlug,parentKnowledgeBaseId; keepextraMemberRelations. - T009 [US1] Refactor
buildKnowledgeBaseRelationshipTupleDiffto delegate to the core (preserve itsreader+ingestor+managermember set) inui/src/lib/rbac/openfga-owned-resources.ts. - T010 [US1] Refactor
buildAgentRelationshipTupleDiffto delegate to the core inui/src/lib/rbac/openfga-agent-tools.ts, keeping agent-specificglobalUserAccess(user:*) andtoolcalleredges layered on top (depends on T008). - T011 [US1] Create the route-orchestration helper
handleShareableResourceWriteinui/src/lib/rbac/shareable-resource.tsper contract R2 (validate membership โ capture creator โ load previous from config โ reconcile โ persist; reject owner change unlessallowOwnerTransfer). - T012 [P] [US1] Create the Pydantic
OwnedResourceMixin(creator_subject,owner_subject,owner_team_slug,shared_with_teams) inai_platform_engineering/knowledge_bases/rag/common/src/common/models/rag.py(or a shared module it imports) with validation/normalization. - T013 [P] [US1] Create the
<TeamOwnershipFields>React component inui/src/components/rbac/TeamOwnershipFields.tsxper contractui-component.md(owner picker disabled-on-edit, share multi-select, effective-access preview, not-a-member transfer confirm, read-only creator display; controlled, button-save). - T014 [US1] Refactor
DynamicAgentEditorto render<TeamOwnershipFields>instead of its inline owner/share controls inui/src/components/dynamic-agents/DynamicAgentEditor.tsx; confirm no UX/behavior change (depends on T013). - T015 [US1] Document the canonical shareable-type model template as a comment block / reference in
deploy/openfga/model.fga(and note it in the RBAC docs later) so new types start from it (contract C-template). - T016 [US1] Run the agent + KB existing suites (
make caipe-ui-teststargeted) and confirm zero behavioral change vs. the T002 baseline (SC-006).
Checkpoint: Shared module exists and is proven faithful; later phases compose it.
Phase 3 (Plan Phase B): User Story 2 โ Provenance Without Lingering Authority (Priority: P1)โ
Goal: Audit-only creator relation on shareable types; team manager
carries authority; creator written on create, never in any can_*.
Independent Test: Creating a resource writes a creator tuple; revoking the
creator's team membership removes their management ability while the creator
record remains; no can_* resolves via creator.
Tests for User Story 2โ
- T017 [P] [US2] Extend the drift test (T007) to assert
creatoris present onagent,knowledge_base,data_source,mcp_tooland referenced by no permission. - T018 [P] [US2] Add an OpenFGA model assertion test:
Check(user:C, can_manage, <type>:X)is FALSE when onlyuser:C creator <type>:Xexists (creator grants nothing).
Implementation for User Story 2โ
- T019 [US2] Add
define creator: [user]toagent,knowledge_base,data_source,mcp_toolindeploy/openfga/model.fga(contract C1); ensure it appears in nocan_*. - T020 [US2] Mirror the
creatorrelation intocharts/ai-platform-engineering/charts/openfga/authorization-model.jsonfor the same 4 types, keeping parity (FR-031). - T021 [US2] Ensure
reconcileShareableResourcewritesuser:<creatorSubject> creator <type>:<id>on create and the route helper passessession.subas creator on first write (verify against T005/T006; wire any missing call site inui/src/lib/rbac/shareable-resource.ts). - T022 [US2] Run model-parity + drift tests; confirm green.
Checkpoint: Provenance recorded; authority is team-based; creator is inert in authz.
Phase 4 (Plan Phase C): User Story 4 โ RAG Datasource Access That Enforces (Priority: P1)โ
Goal: data_source read/ingest/manage inherit from knowledge_base via
parent_kb; the inheritance edge is written on create; the PR #1703 mirror is
retired; user:* public read preserved.
Independent Test: Grant a team read on a KB; a member can query the corresponding data source (inheritance) with no mirrored data_source tuples; a non-member cannot.
Tests for User Story 4โ
- T023 [P] [US4] Add OpenFGA assertion test:
Check(team-member, can_read, data_source:X)is TRUE when onlyteam:t#member reader knowledge_base:X+data_source:X parent_kb knowledge_base:Xexist (inheritance), and likewisecan_ingest/can_managefrom the matching KB grants. - T024 [P] [US4] Add a parity test confirming the
X from Y(tuple-to-userset) form round-trips betweenmodel.fgaand the chart JSON (contract C2 verification). - T025 [P] [US4] Update reconciler tests to assert the
parent_kbedge is written on datasource create and that no per-teamdata_sourcereader/manager tuples are written (mirror retired).
Implementation for User Story 4โ
- T026 [US4] Add
parent_kb: [knowledge_base]and rewritecan_read/can_ingest/can_managewith... or <perm> from parent_kbondata_sourceindeploy/openfga/model.fga(contract C2). - T027 [US4] Mirror the
data_sourcechanges (parent_kb relation + tupleToUserset children) intocharts/ai-platform-engineering/charts/openfga/authorization-model.json(FR-031). - T028 [US4] Ensure
user:*reader is present onknowledge_baseanddata_source(contract C3); if PR #1703 has not merged, add it here. - T029 [US4] Write the
data_source:<id> parent_kb knowledge_base:<id>edge on datasource creation inui/src/app/api/rag/[...path]/route.ts(via the shared reconcilerparentKnowledgeBaseId). - T030 [US4] Retire the mirror: remove
mirrorKnowledgeBaseDiffToDataSourceusage and thereconcileDataSourceRelationshipsper-team-grant calls fromui/src/app/api/rag/[...path]/route.ts,ui/src/app/api/rag/kbs/[id]/sharing/route.ts, andui/src/app/api/admin/teams/[id]/kb-assignments/route.ts(FR-020); delete now-dead mirror code inui/src/lib/rbac/openfga-owned-resources.ts. - T031 [US4] Update/remove mirror-specific tests (e.g.
openfga-kb-shared-teams.test.tsmirror assertions, sharing-route + kb-assignments data_source mirror assertions) to reflect inheritance. - T032 [US4] Verify the BFF datasource query/invoke filter (
ui/src/app/api/rag/[...path]/route.tsloadReadableDatasourceIds/constrainSearchBody) resolvesdata_source#can_readthrough inheritance (no code change expected; add a test if feasible).
Checkpoint: KB grants enforce on data sources via inheritance; mirror gone.
Phase 5 (Plan Phase D): User Story 5 โ Datasource Ownership & Sharing Parity (Priority: P2)โ
Goal: Persist owner/shared/creator on DataSourceInfo; the sharing GET
returns the real owner; owner picker at create (immutable on edit); sharing UI
uses the shared component with revoke-on-unshare.
Independent Test: Create a datasource with an owner team, share with a second team, unshare it โ persisted config and effective access reflect each step; owner is shown (not blank).
Tests for User Story 5โ
- T033 [P] [US5] Add pytest for
DataSourceInfo(de)serialization with the new mixin fields and backward-compat defaults inai_platform_engineering/knowledge_bases/rag/common/.../tests/. - T034 [P] [US5] Update
ui/src/app/api/rag/kbs/__tests__/sharing-route.test.ts: GET returns realowner_team_slug+creator_subject; PUT acceptsowner_team_slugon first-set and rejects owner change (non-transfer). - T035 [P] [US5] Update
ui/src/components/rag/__tests__/panel tests for the shared<TeamOwnershipFields>(owner shown, share add/remove, button save).
Implementation for User Story 5โ
- T036 [US5] Add
OwnedResourceMixintoDataSourceInfoinai_platform_engineering/knowledge_bases/rag/common/src/common/models/rag.py; persist/read the fields inmetadata_storage.py. - T037 [US5] Populate owner from config in the sharing GET (replace the
loadOwnerTeamSlug โ nullstub) and returncreator_subjectinui/src/app/api/rag/kbs/[id]/sharing/route.ts(contract A1). - T038 [US5] Accept
owner_team_slugon the sharing PUT and route it throughhandleShareableResourceWrite(first-set only; transfer handled in Phase F) inui/src/app/api/rag/kbs/[id]/sharing/route.ts. - T039 [US5] Capture
owner_team_slug+creator_subjectat datasource creation and persist to config inui/src/app/api/rag/[...path]/route.ts(contract A2). - T040 [US5] Add the owner
TeamPickerto the datasource create flow and render<TeamOwnershipFields>inui/src/components/rag/IngestView.tsxand refactorui/src/components/rag/KbSharingPanel.tsxto consume it (contract U4).
Checkpoint: Datasources have agent-parity ownership/sharing with accurate display.
Phase 6 (Plan Phase E): User Story 6 โ Custom MCP Tool Parity (Priority: P2)โ
Goal: Persist owner/shared/creator on MCPToolConfig; owner/share UI in the
tool dialog; BFF reconcile on POST/PUT/DELETE; enforce can_call on invoke.
Independent Test: Create a tool with owner team A, share with B; a member of A/B can invoke, a non-member is denied at invoke; delete leaves no orphan tuples.
Tests for User Story 6โ
- T041 [P] [US6] Add pytest for
MCPToolConfig(de)serialization with mixin fields + defaults. - T042 [P] [US6] Add BFF route tests: POST/PUT reconcile owner/shared; DELETE removes all
mcp_tool:<id>grants (no orphans);can_callgate denies a non-member invoke and allows a member/agent invoke (contract A4/A5). - T043 [P] [US6] Add/extend
mcp_toolreconciler tests for owner+shared (reader+usermember relations) and creator.
Implementation for User Story 6โ
- T044 [US6] Add
OwnedResourceMixintoMCPToolConfiginmodels/rag.py; persist/read inmetadata_storage.py. - T045 [US6] Add owner picker + share multi-select via
<TeamOwnershipFields>toToolFormDialoginui/src/components/rag/MCPToolsView.tsx(contract U4). - T046 [US6] Wire create (POST) to capture owner/creator, validate membership, persist, and reconcile in
ui/src/app/api/rag/[...path]/route.ts(contract A4). - T047 [US6] Wire update (PUT) to read previous from config and reconcile the diff in
ui/src/app/api/rag/[...path]/route.ts. - T048 [US6] Add DELETE reconciliation removing all
mcp_tool:<id>grants (owner, shared, creator) inui/src/app/api/rag/[...path]/route.ts(FR-028 โ closes the orphan-tuple gap). - T049 [US6] Add the
mcp_tool#can_callCheck on the invoke path before forwarding to/v1/mcp/invokeinui/src/app/api/rag/[...path]/route.ts, handling bothuser:<sub>andagent:<id>principals; deny with a tool-specific 403 (contract A5, FR-029).
Checkpoint: Custom MCP tools have full parity and enforced invocation.
Phase 7 (Plan Phase F): User Story 3 โ Ownership Transfer (Priority: P2)โ
Goal: Transfer owner team via the shared helper, guarded by owner-team admin/org admin, with a not-a-member UI confirmation; creator retained.
Independent Test: Transfer AโB as a team-A admin; A loses grants, B gains them, creator unchanged; non-member transferor is warned; unauthorized caller denied.
Tests for User Story 3โ
- T050 [P] [US3] Reconciler test: transfer (previousOwnerTeamSlug โ ownerTeamSlug) deletes old-owner grants, writes new-owner grants, leaves
creatoruntouched (extend T005). - T051 [P] [US3] Route test: transfer authorized for owner-team admin / org admin, denied otherwise; persists new owner (contract A3).
- T052 [P] [US3] Component test:
<TeamOwnershipFields>requires the not-a-member confirmation beforeonTransfer(..., true)(FR-015).
Implementation for User Story 3โ
- T053 [US3] Add the transfer path in
handleShareableResourceWrite/ a dedicated transfer handler:allowOwnerTransfer: true, passpreviousOwnerTeamSlug, guard withcan_manageorbypassForOrgAdmin(isOrgAdmin) inui/src/lib/rbac/shareable-resource.ts+resource-authz.ts(contract A3/R3). - T054 [US3] Expose transfer on the datasource sharing route and the MCP tool PUT route (
owner_team_slugchange +?transfer/confirm_not_member) inui/src/app/api/rag/...; and enable it for agents inui/src/app/api/dynamic-agents/route.ts(readbody.owner_team_slug, pass new + previous to the reconciler). - T055 [US3] Wire the transfer affordance + not-a-member confirm in
<TeamOwnershipFields>and surface it in all three editors (agent, datasource, MCP tool).
Checkpoint: All three resource types support guarded ownership transfer via one path.
Phase 8 (Plan Phase G): Polish โ Migration, Tests, RBAC Docs (Cross-Cutting)โ
Purpose: Backfills, full-suite verification, and mandatory RBAC living-doc updates.
- T056 [P] Write the idempotent
parent_kbbackfill script (one edge per existing datasource, enumerated from the RAG metadata store) per db-migration ยง3, inscripts/. - T057 [P] Write the idempotent
creator-from-ownerbackfill script (writecreator, retainowner) per db-migration ยง4 (research FR-012 option b), inscripts/. - T058 [P] Extend
scripts/validate-rbac-matrix.py(and the shareable-type drift check) to cover the new relations/permissions. - T059 Update RBAC living docs (FR-030):
docs/docs/security/rbac/architecture.md(data_source inheritance,creator, mcp_toolcan_call, transfer),workflows.md(inheritance + transfer + invoke-gate sequences),file-map.md(new files:shareable-resource.ts,TeamOwnershipFields.tsx, backfills) andindex.mdif the big-picture/threat model changes. - T060 Run full gates:
make lint,make test,make caipe-ui-tests, model-parity,scripts/validate-rbac-matrix.py,scripts/validate-rbac-doc.py; confirm green and no SC-006 regression vs. T002 baseline. - T061 [P] Execute the quickstart.md verification walkthroughs (RAG inheritance, MCP parity, transfer+provenance) as manual acceptance.
Checkpoint: Feature complete, migrated, fully tested, documented.
Dependencies & Execution Orderโ
Hard ordering (each blocks the next):
Phase 1 Setup
โโโบ Phase 2 / US1 (shared module) โโ FOUNDATIONAL, blocks all below
โโโบ Phase 3 / US2 (creator) [needs core + model]
โโโบ Phase 4 / US4 (parent_kb) [needs model; independent of US2]
โ โโโบ Phase 5 / US5 (RAG persist+UI) [needs US4 + mixin]
โโโบ Phase 6 / US6 (MCP parity) [needs core + mixin; independent of RAG]
โโโบ Phase 7 / US3 (transfer) [needs core + at least one resource wired]
โโโบ Phase 8 / G (migration, docs, full gates)
- US2 and US4 are independent (both P1, both only need Phase 2) โ can run in parallel.
- US6 (MCP) is independent of US4/US5 (RAG) โ can run in parallel once Phase 2 is done.
- US3 (transfer) should land after the core + at least the agent path are wired (it reuses the agent reconciler's existing
previousOwnerTeamSlug). - Phase 8 is last (backfills assume the model is live; docs/gates verify the whole).
Parallel Opportunitiesโ
- Within Phase 2: T005/T006/T007 (tests) โฅ; T012 (Python mixin) โฅ T013 (React component) โฅ the TS reconciler work (T008).
- Across stories after Phase 2: a RAG track (US4โUS5) and an MCP track (US6) can proceed concurrently by different contributors; US2 (creator) can land alongside either.
- Phase 8: T056/T057/T058 (scripts) โฅ; docs (T059) โฅ scripts.
Implementation Strategyโ
- MVP = Phase 2 (US1) + Phase 3 (US2) + Phase 4 (US4). That delivers the reusable module, the provenance fix, and the RAG enforcement fix โ the three P1 stories โ and is independently shippable.
- Increment 2 = US5 + US6 (P2 parity for RAG UI and MCP tools).
- Increment 3 = US3 (transfer) + Phase 8 (backfills, docs, full gates).
- Honor the FR-032 sequencing note: if PR #1703 is still open, prefer amending it
to introduce
parent_kb; if merged, Phase 4/T030 deletes the mirror instead.