| Short primer: Keycloak JWTs prove identity/context while OpenFGA proves relationship access | docs/docs/security/rbac/jwt-and-openfga.md |
Deep-dive: where the OpenFGA permission union is evaluated, what gets stored vs. computed (no can_* tuples are ever written), and a worked end-to-end Probe-button example walking the model, atom tuples, BFF gate code path, and OpenFGA server-side union evaluation | docs/docs/security/rbac/openfga-permission-evaluation.md |
Shared team-membership sync helpers β resolveKeycloakUserSubject (emailβKeycloak sub), writeTeamMembershipTuples (writes user:<sub>#{member,admin} team:<slug>), and mongoRoleToOpenFgaRelations (creator gets both admin and member). Both POST /api/admin/teams (team creation) and POST /api/admin/teams/[id]/members (add member) MUST go through these helpers so OpenFGA and canonical Mongo team_membership_sources stay in sync; teams.members[] is legacy and the Teams dialog reconstructs visible members from active source rows. The original team-creation route used to skip the OpenFGA write, leaving team:<slug>#can_use false for the creator. | ui/src/lib/rbac/team-membership-sync.ts, ui/src/app/api/admin/teams/route.ts, ui/src/app/api/admin/teams/[id]/members/route.ts, ui/src/components/admin/TeamDetailsDialog.tsx, ui/src/app/api/admin/teams/__tests__/team-creation-openfga-sync.test.ts, ui/src/components/admin/__tests__/TeamDetailsDialog.members.test.tsx |
Issue #1509 β scoped team admins (members with role=owner or role=admin) can manage their own team end-to-end without holding the platform-wide organization:<org>#admin tuple. All six team-mutation routes (PATCH/DELETE /api/admin/teams/[id], PUT /api/admin/teams/[id]/roles, PUT /api/admin/teams/[id]/resources, POST/DELETE /api/admin/teams/[id]/members, POST /api/admin/teams/[id]/openfga/reconcile) go through requireTeamMembershipManagementPermission(session, actorEmail, team) in team-admin-guards.ts, which first tries the platform-admin bypass (admin_ui#admin) and falls back to isScopedTeamAdmin(actorEmail, team). | ui/src/lib/rbac/team-admin-guards.ts, ui/src/app/api/admin/teams/[id]/route.ts, ui/src/app/api/admin/teams/[id]/roles/route.ts, ui/src/app/api/admin/teams/[id]/resources/route.ts, ui/src/app/api/admin/teams/[id]/members/route.ts, ui/src/app/api/admin/teams/[id]/openfga/reconcile/route.ts, ui/src/app/api/admin/teams/[id]/__tests__/team-admin-edit.test.ts, ui/src/app/api/admin/teams/[id]/members/__tests__/membership-sources.test.ts |
Team OpenFGA sync diagnostic β computeTeamMembershipSyncReport is a pure function that compares team_membership_sources against OpenFGA tuples on team:<slug> and reports each active row as synced, pending (no Keycloak sub resolved yet), drifted (sub resolved but tuple missing), or unknown (OpenFGA unreachable). readTeamOpenFgaTuples is the IO wrapper that reads tuples one team at a time. GET /api/admin/teams/[id] decorates its response with a top-level openfga_sync field consumed by the Teams settings dialog (banner on the Details tab + per-member badge on the Members tab). POST /api/admin/teams/[id]/openfga/reconcile is the on-demand repair endpoint β same auth as add-member (team admin or platform admin) β that re-resolves missing subjects and writes any missing tuples. | ui/src/lib/rbac/team-openfga-sync-status.ts, ui/src/app/api/admin/teams/[id]/route.ts, ui/src/app/api/admin/teams/[id]/openfga/reconcile/route.ts, ui/src/components/admin/TeamDetailsDialog.tsx, ui/src/lib/rbac/__tests__/team-openfga-sync-status.test.ts, ui/src/app/api/admin/teams/__tests__/team-openfga-sync-status.test.ts |
| Helm installation and upgrade runbook for the RBAC/OpenFGA refactor, including optional in-chart Keycloak, AgentGateway, OpenFGA, and OpenFGA bridge runtime components | docs/docs/security/rbac/helm-install-upgrade.md |
Local and Helm standalone AgentGateway config and reconciler: JWT listener auth, OpenFGA ext_authz, one MCP route/backend per server, GitHub/GitLab target backendAuth that injects provider PATs upstream, chart extraEnv/extraEnvFrom Secret wiring for backend-auth placeholders, and config-bridge logic that preserves target-level policies during Mongo-backed MCP route reconciliation | deploy/agentgateway/config.yaml, deploy/agentgateway/config.caipe-rbac.yaml, deploy/agentgateway/config_bridge.py, deploy/agentgateway/tests/test_static_config.py, deploy/agentgateway/tests/test_config_bridge.py, charts/ai-platform-engineering/charts/agentgateway/templates/deployment.yaml, charts/ai-platform-engineering/charts/agentgateway/values.yaml, charts/ai-platform-engineering/values.yaml, deploy/openfga/bridge/tests/test_helm_values.py, docker-compose.dev.yaml |
| Keycloak Helm realm import: roles, clients, and optional demo users | charts/ai-platform-engineering/charts/keycloak/realm-config.json |
Keycloak Helm runtime: deployment args, realm-JWKS startup/readiness probes, management liveness probe, ingress, and mounted realm import; filters demo users unless keycloak.demoUsers.enabled=true | charts/ai-platform-engineering/charts/keycloak/templates/deployment.yaml, charts/ai-platform-engineering/charts/keycloak/templates/ingress.yaml, charts/ai-platform-engineering/charts/keycloak/templates/configmap-realm.yaml |
Keycloak Helm login theme: packaged caipe theme ConfigMap, conditional mount, value-driven branding (keycloak.theme.brandName, keycloak.theme.colors.*, keycloak.theme.files.*), and optional keycloak.theme.existingConfigMap override | charts/ai-platform-engineering/charts/keycloak/templates/configmap-theme.yaml, charts/ai-platform-engineering/charts/keycloak/templates/deployment.yaml, charts/ai-platform-engineering/charts/keycloak/themes/caipe/, charts/ai-platform-engineering/charts/keycloak/values.yaml |
Keycloak first-run bootstrap: shell-owned pre-BFF escape hatch for direct-admin app-realm bootstrap, IdP broker login bootstrap, caipe-ui client-secret reconciliation from KEYCLOAK_UI_CLIENT_SECRET, caipe-platform client-secret reconciliation from KEYCLOAK_PLATFORM_CLIENT_SECRET, bot client-secret reconciliation in init-token-exchange.sh, optional strict mode (keycloak.strictClientSecrets=true β KEYCLOAK_STRICT_CLIENT_SECRETS=true in all three init Jobs) that fails the Helm install if any of the four dev placeholder client_secrets are still accepted by Keycloak, optional demo personas (KEYCLOAK_SEED_DEMO_USERS=true), and master-realm admin-console settings that must not depend on BFF auth being healthy | charts/ai-platform-engineering/charts/keycloak/templates/job-auth-reconcile.yaml, charts/ai-platform-engineering/charts/keycloak/templates/job-init-idp.yaml, charts/ai-platform-engineering/charts/keycloak/templates/job-init-token-exchange.yaml, charts/ai-platform-engineering/charts/keycloak/templates/ui-client-external-secret.yaml, charts/ai-platform-engineering/charts/keycloak/templates/platform-client-external-secret.yaml, charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh, charts/ai-platform-engineering/charts/keycloak/scripts/init-token-exchange.sh, tests/test_keycloak_ui_client_secret_reconcile.py, tests/test_keycloak_platform_client_secret_reconcile.py, tests/test_keycloak_strict_client_secrets.py, tests/integration/test_keycloak_platform_client_reconcile.sh, tests/integration/test_keycloak_strict_client_secrets.sh |
Keycloak IdP-redirector / kc_idp_hint plumbing: init-idp.sh wires the identity-provider-redirector execution to IDP_ALIAS as the default broker, optionally enforces it (KEYCLOAK_FORCE_IDP_REDIRECT=true β requirement REQUIRED), and NextAuth conditionally forwards kc_idp_hint=${OIDC_IDP_HINT} so Keycloak skips its own login page and bounces straight to the upstream IdP (Okta / Duo SSO / Azure AD / β¦). The UI Jest test pins the conditional spread (must be present when OIDC_IDP_HINT is set, must be omitted when unset or empty). The integration test boots a throwaway Keycloak, runs init-idp.sh end-to-end, and validates that /realms/caipe/protocol/openid-connect/auth 302/303s to /broker/${IDP_ALIAS}/login with no hint, with an explicit hint, and that an unknown hint does not 5xx. | ui/src/lib/auth-config.ts (kc_idp_hint conditional spread on authorization.params), ui/src/lib/__tests__/auth-config.test.ts (OIDC kc_idp_hint forwarding describe), charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh (Setting '<alias>' as default IdP redirector + Enforcing '<alias>' as the only browser login path blocks), tests/integration/test_keycloak_idp_hint_redirect.sh, .github/workflows/ci-keycloak-init.yml (idp-hint-test job) |
| Export client secrets to env/dotenv/K8s Secret | deploy/keycloak/export-client-secrets.sh |
UI session & NextAuth OIDC config, deployment-configured Web UI admission group check, group claim extraction, login-time OpenFGA baseline/admin bootstrap (organization, system_config, user_profile, mcp_gateway:list caller, baseline admin_surface readers, admin admin_surface managers, and admin mcp_server:agentgateway manager), Mongo-backed baseline FGA profile overrides, login-time identity sync trigger, and AccessTokenMissing invalidation when the server-side token cache is lost | ui/src/lib/auth-config.ts, ui/src/lib/rbac/baseline-access.ts, ui/src/lib/rbac/login-openfga-bootstrap.ts, ui/src/lib/rbac/__tests__/login-openfga-bootstrap.test.ts, ui/src/components/token-expiry-guard.tsx, ui/src/lib/__tests__/auth-config.test.ts, ui/src/components/__tests__/token-expiry-guard.test.tsx |
Local no-SSO development auth provider β supplies the stable anonymous@local admin principal used by Web UI API middleware, admin tab gates, RAG proxy calls, and admin-surface checks whenever SSO_ENABLED=false, ALLOW_DEV_ADMIN_WHEN_SSO_DISABLED=true, and CAIPE_UNSAFE_RBAC_BYPASS=true outside production. New local-dev auth behavior should use this provider rather than checking the env vars directly. | ui/src/lib/auth/dev-auth-provider.ts, ui/src/lib/api-middleware.ts, ui/src/hooks/use-admin-role.ts, ui/src/hooks/useAdminTabGates.ts, ui/src/app/api/rag/[...path]/route.ts, ui/src/lib/rbac/require-openfga.ts, ui/src/components/layout/AppHeader.tsx |
UI RBAC middleware (dual bearer/session auth, server-side rejection of sessions that failed Web UI admission, per-route OpenFGA enforcement, split capability gates for legacy withAuth handlers such as self_profile, chat_supervisor, credential_vault, and best-effort persistence of users.keycloak_sub / users.metadata.keycloak_sub) | ui/src/lib/api-middleware.ts, ui/src/lib/__tests__/api-middleware.test.ts |
PDP coverage audit for every /api/* BFF route β route-by-route table grouped by gate type (resource-scoped PDP, capability-scoped PDP, legacy withAuth umbrella, admin bypass, hybrid Mongo+PDP), the full resolveLegacyWithAuthRbacPolicy prefixβcapability table, how to read an audit_event_id row, and the grep snippets used to (re-)produce the audit. Linked from architecture.md and cited by the 2026-05-27-fine-grained-rbac-for-withauth-routes plan. Re-run the audit and update this table whenever resolveLegacyWithAuthRbacPolicy changes or a new /api/* route lands. | docs/docs/security/rbac/pdp-coverage-audit.md, docs/docs/specs/2026-05-27-fine-grained-rbac-for-withauth-routes/plan.md |
R4 (NEXTAUTH_SECRET strict mode, May 2026): NEXTAUTH_SECRET HS256-signs both NextAuth session cookies and the internal skills-API tokens minted by signLocalSkillsToken. Sharing one across installs is a cross-install identity compromise. The guard assertNextAuthSecretSafe rejects known dev placeholders (caipe-dev-secret, caipe-dev-secret-change-in-production, changeme, your-secret-here, β¦) and secrets shorter than 32 chars in strict mode; an explicit ALLOW_NEXTAUTH_DEV_SECRET=true override exists for CI parity with R1. signLocalSkillsToken throws at mint time when the gate trips; validateLocalSkillsJWT returns null (caller falls through to OIDC) instead of 5xx-ing. The Makefile run-caipe-ui-docker target embeds the same placeholder list as a shell-level guard so an operator who copies that one-liner gets the same loud failure even before the BFF code runs. The compose-file docker-compose.yaml uses ${NEXTAUTH_SECRET:?β¦} to abort docker compose up if the env var is unset. | ui/src/lib/nextauth-secret-guard.ts, ui/src/lib/jwt-validation.ts, ui/src/lib/__tests__/nextauth-secret-guard.test.ts, ui/src/lib/__tests__/jwt-validation.test.ts, Makefile, docker-compose.yaml, docker-compose.dev.yaml, ui/env.example, docs/docs/ui/auth-flow.md |
R3 (MongoDB rootPassword strict mode, May 2026): the caipe-ui-mongodb subchart used to ship auth.rootPassword: "changeme" as the chart default. An operator who ran helm install without explicitly overriding auth.rootPassword (and without enabling externalSecrets.enabled=true) shipped admin/changeme to the cluster β the in-cluster Secret comes directly from this value. The new strictPasswords value is a chart-render gate that mirrors keycloak.strictClientSecrets: when strictPasswords=true AND externalSecrets.enabled=false, the _strict-passwords.tpl helper calls {{ fail }} if auth.rootPassword is in a known-placeholder set (changeme, admin, password, mongo, root, test, secret, your-password-here, β¦) or shorter than 8 chars. When externalSecrets.enabled=true the gate skips its check (the in-cluster Secret comes from Vault/AWS Secrets Manager/etc., not auth.rootPassword). Pinned by 14 chart-render tests in tests/test_mongodb_strict_passwords.py and a 4-step helm template integration test that mirrors tests/integration/test_keycloak_strict_client_secrets.sh. Default ships strictPasswords=false so the docker-compose dev flow and CI matrix runs continue to work; production GitOps installs should flip it to true. | charts/ai-platform-engineering/charts/caipe-ui-mongodb/values.yaml (strictPasswords flag + comment), charts/ai-platform-engineering/charts/caipe-ui-mongodb/templates/_strict-passwords.tpl, charts/ai-platform-engineering/charts/caipe-ui-mongodb/templates/secret.yaml (calls mongodb.assertStrictPasswords), charts/ai-platform-engineering/values.yaml (umbrella caipe-ui.mongodb.strictPasswords), tests/test_mongodb_strict_passwords.py, tests/integration/test_mongodb_strict_passwords.sh |
R2 (setup-caipe.sh MongoDB random password, May 2026): the workshop on-ramp script setup-caipe.sh used to bake the literal password changeme into four sites: the bitnami/mongodb helm upgrade (auth.rootPassword=changeme, auth.passwords[0]=changeme), the MONGODB_URI written into the dynamic-agents seed values file, and the MONGODB_URI patched into the caipe-dynamic-agents-config, caipe-supervisor-agent-env, caipe-single-node-agent-env ConfigMaps and the caipe-ui-secret Secret. Every workshop install inherited the same admin password and the same cluster-internal mongodb://admin:changeme@β¦ URI. The new _resolve_mongodb_password helper generates an openssl rand -hex 24 value (48 hex chars, URL-safe for the connection string) on first run and persists it in the caipe-mongodb-credentials Secret; subsequent runs read the existing value back out, so the install stays idempotent. The "Services Ready" banner now prints a kubectl get secret caipe-mongodb-credentials β¦ one-liner for recovery (same pattern the script already used for langfuse-credentials). Pinned by tests/integration/test_setup_caipe_mongodb_password.sh which: extracts the helper from setup-caipe.sh, mocks kubectl with a sandboxed Secret store on PATH, and asserts (1) a fresh run generates and persists a password, (2) a re-run reuses it, (3) the password is exactly 48 hex chars with no URL-special characters, (4) the password is never the literal changeme, and (5) the string changeme no longer appears in any non-comment line of setup-caipe.sh. | setup-caipe.sh (_resolve_mongodb_password helper + four changemeβ${MONGODB_ROOT_PASSWORD} substitutions + banner hint), tests/integration/test_setup_caipe_mongodb_password.sh |
Web UI backend Keycloak Admin REST credentials (KEYCLOAK_ADMIN_CLIENT_ID / KEYCLOAK_ADMIN_CLIENT_SECRET) for admin users, teams, roles, and per-team scope provisioning. fetchFreshAdminToken tries client_credentials first; on missing env vars or 401 it MAY fall back to a password grant against /realms/master using the dev-only admin/admin credentials. R1 (production-safety gate, May 2026): the fallback is now off by default in production builds β adminPasswordFallbackAllowed() only returns true when ALLOW_KEYCLOAK_ADMIN_PASSWORD_FALLBACK=true (or =1), or implicitly when NODE_ENV != "production". Anything else throws a configuration error so a misconfigured prod install fails loudly instead of silently calling /realms/master. The keycloak-admin-token.test.ts Jest suite has 8 tests: 4 pinning the legacy fallback chain (when the flag is on / NODE_ENV != "production") and 4 in the "production safety gate (R1)" describe block pinning the new prod-default-deny behavior, the per-call re-raise without a master fallback, and both directions of the explicit override flag. R1 upstream fix (May 2026): the umbrella values.yaml now defaults keycloak.platformClient.secretRef=caipe-platform-secret AND caipe-ui.keycloakAdminClient.secretName=caipe-platform-secret; the caipe-ui Deployment auto-injects KEYCLOAK_ADMIN_CLIENT_SECRET via valueFrom.secretKeyRef (key OIDC_CLIENT_SECRET) and the ConfigMap auto-injects KEYCLOAK_ADMIN_CLIENT_ID=caipe-platform whenever keycloakAdminClient.secretName is non-empty. The ConfigMap uses hasKey to skip auto-injection when the operator has set caipe-ui.config.KEYCLOAK_ADMIN_CLIENT_ID explicitly (no-clobber). The ESO target name now also resolves to caipe-platform-secret for the default Path 3 install (was <release>-keycloak-platform-client before the fix) β see the migration matrix in secrets-bootstrap.md Β§ "R1 upstream fix". The chart-render test tests/test_caipe_ui_keycloak_admin_client_env.py pins 9 things total: Pins 1β4 (default install + explicit secretRef + ESO + legacy existingSecret back-compat all wire KEYCLOAK_ADMIN_CLIENT_* correctly), Pins 5β6 (R1 BFF gate: NODE_ENV=production default + ALLOW_KEYCLOAK_ADMIN_PASSWORD_FALLBACK not default-true + dev opt-in flag propagation), and Pins 7β9 (operator override of keycloakAdminClient.secretName, explicit-empty-skips-wiring escape hatch for standalone caipe-ui installs, and operator-supplied config.KEYCLOAK_ADMIN_CLIENT_ID wins over auto-injection). | ui/src/lib/rbac/keycloak-admin.ts, ui/src/lib/rbac/__tests__/keycloak-admin.test.ts, ui/src/lib/rbac/__tests__/keycloak-admin-token.test.ts, charts/ai-platform-engineering/values.yaml (umbrella defaults for keycloak.platformClient.secretRef + caipe-ui.keycloakAdminClient), charts/ai-platform-engineering/charts/caipe-ui/values.yaml (keycloakAdminClient block), charts/ai-platform-engineering/charts/caipe-ui/templates/configmap.yaml (R1 KEYCLOAK_ADMIN_CLIENT_ID auto-injection with hasKey no-clobber guard), charts/ai-platform-engineering/charts/caipe-ui/templates/deployment.yaml (R1 KEYCLOAK_ADMIN_CLIENT_SECRET valueFrom.secretKeyRef), charts/ai-platform-engineering/charts/caipe-ui/templates/external-secret.yaml, charts/ai-platform-engineering/charts/keycloak/templates/platform-client-external-secret.yaml, docker-compose.dev.yaml, tests/test_caipe_ui_keycloak_admin_client_env.py |
| Supervisor middleware stack (auth + JWT context) | ai_platform_engineering/multi_agents/platform_engineer/protocol_bindings/a2a/main.py |
| Per-request user identity (contextvar) | ai_platform_engineering/utils/auth/jwt_context.py |
| JWT context middleware (Starlette) | ai_platform_engineering/utils/auth/jwt_user_context_middleware.py |
Supervisor agent executor (ENABLE_USER_INFO_TOOL) | ai_platform_engineering/multi_agents/platform_engineer/protocol_bindings/a2a/agent_executor.py |
| Dynamic agents JWT validation & userinfo | ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/auth.py |
Dynamic Agents Web UI backend execution proxy auth and UI shell: session/bearer authentication, stable subject and role propagation, X-User-Context browser-session fallback when the server-side Keycloak token cache is missing, backend bearer forwarding when available, W3C traceparent forwarding, no OIDC_REQUIRED_DYNAMIC_AGENTS_GROUP/admin-only UI gate for the Agents page, Conversations tab shown only when OpenFGA admin audit-log access is granted, the Models tab uses the LLM provider credential surface plus the OpenFGA-filtered model list, and conversation-scoped file proxies gated by dynamic_agent#invoke | ui/src/lib/da-proxy.ts, ui/src/lib/auth-config.ts, ui/src/components/layout/AppHeader.tsx, ui/src/app/(app)/dynamic-agents/page.tsx, ui/src/app/(app)/dynamic-agents/__tests__/page.test.tsx, ui/src/components/dynamic-agents/LLMProvidersTab.tsx, ui/src/components/dynamic-agents/__tests__/LLMProvidersTab.test.tsx, ui/src/components/layout/__tests__/AppHeader.test.tsx, ui/src/app/api/dynamic-agents/conversations/[id]/files/list/route.ts, ui/src/app/api/dynamic-agents/conversations/[id]/files/content/route.ts, ui/src/lib/__tests__/da-proxy-auth-result.test.ts, ui/src/lib/__tests__/auth-config.test.ts |
Dynamic Agents Web UI backend OpenFGA execution, picker, and conversation binding gates: subject-first, email-fallback can_use agent:<agent_id> checks for start/invoke/resume/cancel, chat agent selection, subagent selection, and new conversation agent bindings; implicit-owner-or-explicit conversation checks on chat routes; authz trace creation and openfga_rebac audit emission | ui/src/lib/rbac/openfga-agent-authz.ts, ui/src/lib/rbac/authz-tracing.ts, ui/src/lib/rbac/resource-authz.ts, ui/src/lib/rbac/conversation-implicit-authz.ts, ui/src/app/api/v1/chat/_conversation-authz.ts, ui/src/app/api/dynamic-agents/available/route.ts, ui/src/app/api/dynamic-agents/available-subagents/route.ts, ui/src/app/api/chat/conversations/route.ts, ui/src/lib/rbac/__tests__/resource-authz.test.ts, ui/src/lib/rbac/__tests__/openfga-agent-authz.test.ts, ui/src/lib/rbac/__tests__/authz-tracing.test.ts, ui/src/lib/rbac/__tests__/audit-tracing.test.ts, ui/src/app/api/v1/chat/stream/start/route.ts, ui/src/app/api/v1/chat/invoke/route.ts, ui/src/app/api/v1/chat/stream/resume/route.ts, ui/src/app/api/v1/chat/stream/cancel/route.ts, ui/src/app/api/v1/chat/__tests__/routes.test.ts, ui/src/app/api/dynamic-agents/__tests__/route-rbac.test.ts, ui/src/app/api/__tests__/chat-conversations-agent-auth.test.ts |
Connections & Secrets credential management: feature flag, signed-in-only and organization:<org>#can_use page gate, MongoDB envelope store, env/ESO OAuth connector startup bootstrap, browser guardrails for raw retrieval, service-to-service retrieve/exchange APIs with service JWT validation, JWT-sub provider-key exchange, future AgentGateway credential injector route, and secret_ref:provider_connection:<id>#can_use, browser-safe OAuth profile validators and automatic refresh endpoints that return only redacted provider/refresh metadata, deep-linked user My Secrets/My Connections tabs, user-visible enabled OAuth connector list with Connect/Relink and Check Profile actions, Dynamic Agents LLM provider credentials saved as OpenFGA-governed credential secrets, OpenFGA-backed global admin secret metadata management, deep-linked OAuth connector/admin secret/audit tabs, org-admin-only global credential audit/admin tab, Dynamic Agents MCP provider credential-ref resolution, and Jira MCP provider-token header consumption | ui/src/lib/feature-flags/credentials.ts, ui/src/lib/credentials/, ui/src/lib/seed-config.ts, ui/src/app/(app)/credentials/page.tsx, ui/src/app/(app)/credentials/__tests__/page.test.tsx, ui/src/app/api/credentials/, ui/src/app/api/credentials/inject/[provider]/route.ts, ui/src/app/api/credentials/inject/[provider]/__tests__/route.test.ts, ui/src/app/api/credentials/connections/[connection_id]/profile/route.ts, ui/src/app/api/credentials/connections/[connection_id]/profile/__tests__/route.test.ts, ui/src/app/api/credentials/connections/[connection_id]/refresh/route.ts, ui/src/app/api/credentials/connections/[connection_id]/refresh/__tests__/route.test.ts, ui/src/app/api/admin/credentials/secrets/route.ts, ui/src/app/api/admin/credentials/secrets/[secret_id]/route.ts, ui/src/app/api/admin/credentials/oauth-connectors/route.ts, ui/src/app/api/admin/credentials/oauth-connectors/[connector_id]/route.ts, ui/src/app/api/admin/credentials/audit/route.ts, ui/src/components/credentials/, ui/src/components/dynamic-agents/LLMProvidersTab.tsx, docker-compose.dev.yaml, docker-compose.yaml, charts/ai-platform-engineering/charts/caipe-ui/values.yaml, charts/ai-platform-engineering/values.yaml, ai_platform_engineering/dynamic_agents/src/dynamic_agents/services/credential_exchange.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/services/mcp_client.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/models.py, ai_platform_engineering/agents/jira/mcp/mcp_jira/api/client.py, ai_platform_engineering/agents/jira/mcp/tests/test_client.py |
Remaining OpenFGA cutovers for conversations, skills, workflow runs, MCP servers, and RAG tools: shared/search/trash conversation candidate filtering, conversation share/pin/archive/restore resource checks, skill nested route/import authorization, workflow-run parent-config checks, self-service mcp_server owner/team tuples, and concrete RAG tool checks | ui/src/app/api/chat/shared/route.ts, ui/src/app/api/chat/search/route.ts, ui/src/app/api/chat/conversations/trash/route.ts, ui/src/app/api/chat/conversations/[id]/share/route.ts, ui/src/app/api/chat/conversations/[id]/pin/route.ts, ui/src/app/api/chat/conversations/[id]/archive/route.ts, ui/src/app/api/chat/conversations/[id]/restore/route.ts, ui/src/app/api/__tests__/chat-conversations-remaining-rbac.test.ts, ui/src/lib/agent-skill-visibility.ts, ui/src/app/api/skills/configs/route.ts, ui/src/app/api/skills/configs/import-zip/route.ts, ui/src/app/api/skills/configs/[id]/revisions/route.ts, ui/src/app/api/skills/configs/[id]/revisions/[revisionId]/route.ts, ui/src/app/api/skills/configs/[id]/revisions/[revisionId]/restore/route.ts, ui/src/app/api/skills/configs/[id]/export/route.ts, ui/src/app/api/skills/configs/[id]/clone/route.ts, ui/src/app/api/__tests__/agent-skill-subroutes-rbac.test.ts, ui/src/app/api/skills/configs/__tests__/route-rbac.test.ts, ui/src/app/api/skills/configs/import-zip/__tests__/route.test.ts, ui/src/app/api/workflow-runs/route.ts, ui/src/app/api/workflow-runs/[id]/resume/route.ts, ui/src/app/api/workflow-runs/[id]/cancel/route.ts, ui/src/app/api/__tests__/workflow-runs-rbac.test.ts, ui/src/app/api/mcp-servers/route.ts, ui/src/lib/rbac/openfga-owned-resources.ts, ui/src/app/api/__tests__/mcp-servers-rbac.test.ts, ui/src/app/api/rag/tools/route.ts, ui/src/app/api/rag/tools/[toolId]/route.ts |
| Plain Web UI supervisor SSE proxy and legacy message/metadata persistence β forwards cookie-session or first-party bearer access tokens to the backend and uses implicit-owner-or-explicit conversation read/write checks, including Slack OBO thread metadata updates | ui/src/app/api/chat/stream/route.ts, ui/src/app/api/chat/conversations/[id]/messages/route.ts, ui/src/app/api/chat/conversations/[id]/metadata/route.ts, ui/src/app/api/__tests__/chat-stream-route.test.ts, ui/src/app/api/__tests__/chat-conversation-metadata-auth.test.ts, ui/src/app/api/__tests__/owner-id-denormalization.test.ts |
Dynamic Agents runtime OpenFGA execution gate, email fallback, durable MongoDB openfga_rebac runtime audit event, bearer forwarding for AgentGateway-backed MCP probe/tool calls, and traceparent propagation before agent lookup/runtime work | ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/openfga_authz.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/token_context.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/jwt_middleware.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/services/mcp_client.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/routes/chat.py, ai_platform_engineering/dynamic_agents/tests/test_openfga_authz.py, ai_platform_engineering/dynamic_agents/tests/test_chat_pdp_gate.py, ai_platform_engineering/dynamic_agents/tests/test_mcp_client_token_forwarding.py |
Per-agent MCP tool restriction and ownership enforcement: Web UI backend reconciles creator owner, organization-admin manager, owner-team user/manager, explicit shared-team user/manager (the Agent editor's "Share with Teams" multi-select β nextSharedTeamSlugs/previousSharedTeamSlugs on reconcileAgentRelationships emit additive writes and symmetric deletes when teams are unchecked), and agent:<id> caller tool:<server>/<tool> OpenFGA tuples. The route translates legacy Mongo _id entries to canonical slugs, drops the owner-team duplicate, and persists slugs so the form round-trips. The agent_shared_team_grants_backfill_v1 migration replays this for every existing agent so pre-2026-05-27 multi-selects retroactively write the missing tuples. Dynamic Agents signs agent_id context for AgentGateway, and the bridge checks derived can_call on tools/call | ui/src/lib/rbac/openfga-agent-tools.ts, ui/src/app/api/dynamic-agents/route.ts, ui/src/app/api/dynamic-agents/teams/route.ts, ui/src/components/dynamic-agents/DynamicAgentEditor.tsx, ui/src/lib/rbac/migrations/registry.ts, ui/src/lib/rbac/__tests__/openfga.test.ts, ui/src/lib/rbac/__tests__/openfga-agent-tools-shared-teams.test.ts, ui/src/lib/rbac/migrations/__tests__/agent-organization-inheritance.test.ts, ui/src/app/api/dynamic-agents/__tests__/route-rbac.test.ts, ui/src/components/dynamic-agents/__tests__/DynamicAgentEditor.unsaved.test.tsx, ai_platform_engineering/dynamic_agents/src/dynamic_agents/services/mcp_client.py, ai_platform_engineering/dynamic_agents/tests/test_mcp_client_token_forwarding.py, deploy/openfga/bridge/main.py, deploy/openfga/bridge/tests/test_grpc_bridge.py, scripts/backfill-agent-tool-openfga.ts, scripts/__tests__/backfill-agent-tool-openfga.test.ts, docker-compose.dev.yaml, charts/ai-platform-engineering/charts/openfga-authz-bridge/templates/deployment.yaml, charts/ai-platform-engineering/charts/openfga-authz-bridge/values.yaml |
| Dynamic agents legacy visibility helpers (not the authoritative execution PDP for start/invoke/resume) | ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/access.py |
AgentGateway static ext_authz configuration with one MCP route/backend per target plus the local standalone config bridge that reconciles AgentGateway-managed Mongo mcp_servers rows into the hot-reloaded config volume | deploy/agentgateway/config.yaml, deploy/agentgateway/config_bridge.py, deploy/agentgateway/Dockerfile.config-bridge, deploy/agentgateway/tests/test_config_bridge.py, docker-compose.dev.yaml |
Host-specific caipe.example.com AgentGateway + nginx assets used by an external Docker Compose overlay (the overlay itself lives outside this repo under ~/outshift/) | deploy/caipe-rbac-nginx.conf, deploy/agentgateway/config.caipe-rbac.yaml |
| AgentGateway ext_authz template (no CEL policy rendering) | deploy/agentgateway/config.yaml.j2 |
AgentGateway OpenFGA gRPC extAuthz route gate | deploy/agentgateway/config.yaml and deploy/agentgateway/config.yaml.j2 (extAuthz block) |
AgentGateway data-plane ingress (service.port, default 4000; admin port 15000 stays private) | charts/ai-platform-engineering/charts/agentgateway/templates/ingress.yaml, charts/ai-platform-engineering/charts/agentgateway/values.yaml |
AgentGateway MCP endpoint routing invariant β every AgentGateway-routed MCP server must persist a target-qualified endpoint {agentgateway_base}/mcp/<server_id>. A bare {agentgateway_base}/mcp returns HTTP 404 from AgentGateway (no matching path-prefix) and surfaces in the UI as Failed to connect to MCP server: HTTP 404 Not Found from http://agentgateway:4000/mcp. Defence in depth: (a) POST/PUT /api/mcp-servers normalises the endpoint on save via the shared TS helper, (b) MCPServerEditor offers a Pick AgentGateway target row populated by the discovery API so admins don't type endpoints by hand, (c) the dynamic-agents probe path self-heals stale Mongo rows at read time via the mirror Python helper, (d) scripts/fix-mcp-endpoint-routing.ts audits Mongo in dry-run and rewrites mis-shaped rows under --apply. Direct upstream URLs (http://mcp-confluence:8000/mcp) and config-driven rows are intentionally left untouched. | ui/src/lib/rbac/mcp-endpoint-normalizer.ts, ui/src/lib/rbac/__tests__/mcp-endpoint-normalizer.test.ts, ui/src/app/api/mcp-servers/__tests__/endpoint-normalization.test.ts, ui/src/components/dynamic-agents/MCPServerEditor.tsx, ai_platform_engineering/dynamic_agents/src/dynamic_agents/services/mcp_endpoint_normalizer.py, ai_platform_engineering/dynamic_agents/tests/test_mcp_endpoint_normalizer.py, ai_platform_engineering/dynamic_agents/tests/test_mcp_client_endpoint_self_heal.py, scripts/fix-mcp-endpoint-routing.ts, scripts/__tests__/fix-mcp-endpoint-routing.test.ts |
Web UI backend AgentGateway MCP discovery and one-click onboarding (mcp_server:agentgateway can_discover for discovery, can_manage for sync), concrete MCP server list/probe/update/delete checks for admins and non-admins, organization-member grant repair for existing/synced config-driven MCP servers, in-place migration of matching legacy direct MCP rows to AgentGateway routes, tuple cleanup before MCP server deletion, no session-role bypass, and migration warnings for true duplicate MCP server ID conflicts | ui/src/lib/rbac/agentgateway-mcp-discovery.ts, ui/src/app/api/mcp-servers/route.ts, ui/src/app/api/mcp-servers/probe/route.ts, ui/src/app/api/mcp-servers/agentgateway/discover/route.ts, ui/src/app/api/mcp-servers/agentgateway/sync/route.ts, ui/src/app/api/mcp-servers/agentgateway/_lib.ts, ui/src/components/dynamic-agents/MCPServersTab.tsx, ui/src/app/api/__tests__/mcp-servers-rbac.test.ts, ui/src/app/api/mcp-servers/agentgateway/__tests__/route.test.ts, ui/src/components/dynamic-agents/__tests__/MCPServersTab.test.tsx |
Helm-packaged RBAC runtime services for 0.5.0: AgentGateway standalone proxy and native Gateway API MCP routes (AgentgatewayBackend + HTTPRoute) for built-in Knowledge Base and configured extra targets, OpenFGA, OpenFGA authz bridge, Keycloak dependency gating, stable admin/IdP/UI-client/platform-client secret enforcement and reconciliation, setup script enablement, and release image workflows | charts/ai-platform-engineering/Chart.yaml, charts/ai-platform-engineering/values.yaml, charts/ai-platform-engineering/templates/agentgateway-mcp.yaml, charts/ai-platform-engineering/templates/agentgateway-static-config.yaml, charts/ai-platform-engineering/charts/keycloak/templates/secret.yaml, charts/ai-platform-engineering/charts/keycloak/templates/ui-client-external-secret.yaml, charts/ai-platform-engineering/charts/keycloak/templates/platform-client-external-secret.yaml, charts/ai-platform-engineering/charts/keycloak/templates/job-init-idp.yaml, charts/ai-platform-engineering/charts/keycloak/templates/job-auth-reconcile.yaml, charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh, charts/ai-platform-engineering/charts/agentgateway/, charts/ai-platform-engineering/charts/openfga/, charts/ai-platform-engineering/charts/openfga-authz-bridge/, deploy/openfga/bridge/tests/test_helm_values.py, tests/test_keycloak_ui_client_secret_reconcile.py, tests/test_keycloak_platform_client_secret_reconcile.py, tests/integration/test_keycloak_platform_client_reconcile.sh, setup-caipe.sh, .github/workflows/ci-keycloak-init.yml, .github/workflows/ci-openfga-authz-bridge.yml |
AgentGateway Helm routing mode selector (global.agentgateway.routingMode, default static): static renders the CRD-free standalone proxy config ConfigMap; gateway-api (opt-in) renders the Gateway API/AgentGateway custom resources (needs CRDs + controller) (one /mcp/<id> route/backend per enabled target, optional listener static.jwtAuth, per-route OpenFGA extAuth) and the subchart mounts it. The agentgatewayMcpTargets helper is the single source of truth for both paths; single-node agent env points MCP traffic at the standalone proxy in static mode. The caipe-ui deployment auto-wires AGENT_GATEWAY_ADMIN_URL (admin /config discovery on <release>-agentgateway:15000) and the mode-aware AGENT_GATEWAY_URL (MCP listener) consumed by ui/src/lib/rbac/agentgateway-mcp-discovery.ts. | charts/ai-platform-engineering/templates/agentgateway-static-config.yaml, charts/ai-platform-engineering/templates/agentgateway-mcp.yaml, charts/ai-platform-engineering/templates/_helpers.tpl (ai-platform-engineering.agentgatewayMcpTargets), charts/ai-platform-engineering/templates/single-node-agent-env.yaml, charts/ai-platform-engineering/charts/agentgateway/templates/deployment.yaml, charts/ai-platform-engineering/charts/agentgateway/templates/configmap.yaml, charts/ai-platform-engineering/charts/caipe-ui/templates/deployment.yaml, charts/ai-platform-engineering/charts/caipe-ui/values.yaml, charts/ai-platform-engineering/values.yaml |
OpenFGA dev PDP stack in Docker Compose (openfga, bridge, init, Postgres) with MongoDB-backed authz audit | docker-compose.dev.yaml (rbac profile for OpenFGA/bridge) |
OpenFGA authorization model + seed tuple writer, including typed wildcard support for default-agent user:* user agent:<id> grants, organization-member reader llm_model:<id> grants for seeded LLM models, organization-member read/use/invoke grants for seeded MCP servers, emergency tuple seeding via openfga.init.seedTuples, organization-admin manager agent:<id>, manager llm_model:<id>, and admin_surface inheritance, user_profile:<sub> self-read ownership, self-service owner relations for MCP servers/LLM models/Slack channels/Webex spaces, team can_use/can_manage, and agent:<id> caller tool:<server>/<tool> grants. docker-compose.dev.yaml mounts the chart JSON into the local init container, so charts/ai-platform-engineering/charts/openfga/authorization-model.json is the single JSON artifact. | deploy/openfga/model.fga, charts/ai-platform-engineering/charts/openfga/authorization-model.json, charts/ai-platform-engineering/charts/openfga/templates/configmap-model.yaml, charts/ai-platform-engineering/charts/openfga/templates/job-init.yaml, docker-compose.dev.yaml, deploy/openfga/init/seed.py, ui/src/lib/seed-config.ts |
| BFF bootstrap-admin resolver: reads bootstrap emails, resolves or creates Keycloak users, seeds durable OpenFGA organization/system_config/mcp_gateway baseline tuples, and reports status in Keycloak health diagnostics | ui/src/lib/rbac/keycloak-bootstrap-admins.ts, ui/src/lib/rbac/keycloak-admin.ts, ui/src/lib/rbac/keycloak-rbac-reconciliation.ts, ui/src/lib/rbac/keycloak-migration-health.ts, ui/src/components/admin/KeycloakMigrationHealthPanel.tsx, ui/src/lib/rbac/__tests__/keycloak-bootstrap-admins.test.ts, ui/src/lib/rbac/__tests__/keycloak-admin.test.ts, ui/src/lib/rbac/__tests__/keycloak-rbac-reconciliation.test.ts, ui/src/components/admin/__tests__/KeycloakMigrationHealthPanel.test.tsx |
AgentGateway Envoy gRPC ext_authz adapter to OpenFGA Check and durable MongoDB openfga_rebac audit writer | deploy/openfga/bridge/main.py, deploy/openfga/bridge/audit.py, deploy/openfga/bridge/requirements.txt, deploy/openfga/bridge/tests/test_grpc_bridge.py, deploy/openfga/bridge/tests/test_helm_values.py |
Admin UI OpenFGA tuple writer for Team Resources saves base relationships (team:<slug>#member user agent:<id>, caller tool:<prefix>) | ui/src/lib/rbac/openfga.ts, ui/src/lib/rbac/__tests__/openfga.test.ts, ui/src/app/api/admin/teams/[id]/resources/route.ts, ui/src/app/api/__tests__/admin-team-resources.test.ts |
Admin UI: top-level admin IA (Settings, Teams & Users, Integrations, Insights, Metrics & Health, Security & Policy), non-admin settings shell fallback when privileged stats APIs deny, Settings-hosted Credentials tab gated by CAIPE_CREDENTIALS_ENABLED and organization admin access, Settings-hosted Knowledge Bases tab for admin_surface:rag_datasources plus team Knowledge Base read/ingest/admin grants, OpenFGA ReBAC Access Manager with catalog-driven effective-access checks, searchable subject selection, Default FGA Grants editor with realtime all-user reconciliation, custom profile CRUD, team profile override assignment, OpenFGA Store: Catalog & Live Relationships read-only model/resource/tuple visibility, baseline diagnostics, staged grant/revoke remediation, policy graph, tuple inspector, and unified RBAC Audit entries from audit_events; the Audit type filter's All option is literal and returns auth, openfga_rebac, tool_action, and agent_delegation rows without hiding routine admin page-view checks | ui/src/app/(app)/admin/page.tsx, ui/src/app/(app)/admin/__tests__/admin-page.test.tsx, ui/src/app/api/rbac/admin-tab-gates/route.ts, ui/src/app/api/rbac/admin-tab-gates/__tests__/route.test.ts, ui/src/components/credentials/AdminCredentialManagementPanel.tsx, ui/src/components/credentials/AdminSecretsManager.tsx, ui/src/components/credentials/OAuthConnectorAdminPanel.tsx, ui/src/components/admin/rebac/RagTeamAccessPanel.tsx, ui/src/components/admin/rebac/__tests__/RagTeamAccessPanel.test.tsx, ui/src/components/admin/rebac/BaselineFgaProfilePanel.tsx, ui/src/components/admin/rebac/__tests__/BaselineFgaProfilePanel.test.tsx, ui/src/components/admin/rebac/UserBaselineDiagnosticsPanel.tsx, ui/src/app/api/admin/openfga/baseline-profile/route.ts, ui/src/app/api/admin/openfga/__tests__/baseline-profile-route.test.ts, ui/src/lib/rbac/baseline-access.ts, ui/src/lib/rbac/__tests__/baseline-access.test.ts, ui/src/app/api/admin/openfga/baseline-diagnostics/route.ts, ui/src/app/api/admin/openfga/__tests__/baseline-diagnostics-route.test.ts, ui/src/app/api/admin/teams/[id]/kb-assignments/route.ts, ui/src/app/api/admin/teams/[id]/kb-assignments/__tests__/route.test.ts, ui/src/components/admin/OpenFgaRebacTab.tsx, ui/src/components/admin/__tests__/OpenFgaRebacTab.test.tsx, ui/src/app/api/admin/openfga/*/route.ts, ui/src/lib/rbac/openfga.ts, ui/src/lib/rbac/audit.ts, ui/src/lib/rbac/types.ts, ui/src/app/api/admin/audit-events/route.ts, ui/src/app/api/admin/audit-events/__tests__/route.test.ts, ui/src/components/admin/UnifiedAuditTab.tsx, ui/src/components/admin/__tests__/UnifiedAuditTab.test.tsx |
OpenFGA tuple inspector query behavior β filter inputs are draft-only until Apply filters is clicked; complete user, relation, and object values are forwarded as exact OpenFGA read filters so the BFF does not broad-read the tuple store for precise lookups, while partial/free-text values remain post-read contains filters. | ui/src/components/admin/OpenFgaRebacTab.tsx, ui/src/components/admin/__tests__/OpenFgaRebacTab.test.tsx, ui/src/app/api/admin/openfga/tuples/route.ts, ui/src/app/api/admin/openfga/__tests__/tuples-route.test.ts |
Admin UI Default Agent setting and chat-available agent picker β reads system_config:platform_settings, saves platform_config.default_agent_id, and reconciles the OpenFGA typed-wildcard grant user:* user agent:<id> so authenticated users can see the configured default and enabled global Dynamic Agents through the OpenFGA-only picker. PATCH requires acknowledge_public_access: true whenever a non-null default changes, emits an [AUDIT] platform_default_agent_changed line, and the per-agent PUT/DELETE handlers block demoting visibility: global β team or deleting the current platform default via isPlatformDefaultAgent(id) with 409 / AGENT_IS_PLATFORM_DEFAULT | ui/src/app/api/admin/platform-config/route.ts, ui/src/app/api/admin/platform-config/__tests__/route.test.ts, ui/src/components/admin/PlatformSettingsTab.tsx, ui/src/components/admin/__tests__/PlatformSettingsTab.test.tsx, ui/src/lib/rbac/platform-default.ts, ui/src/app/api/dynamic-agents/route.ts, ui/src/components/dynamic-agents/DynamicAgentEditor.tsx, ui/src/app/api/dynamic-agents/available/route.ts, ui/src/app/api/dynamic-agents/__tests__/route-rbac.test.ts, ui/src/lib/rbac/login-openfga-bootstrap.ts, ui/src/lib/rbac/__tests__/login-openfga-bootstrap.test.ts |
Universal ReBAC resource/action vocabulary, including mcp_gateway:list, llm_model, relationship validation, OpenFGA tuple conversion, and Mongo collection helpers (spec 2026-05-11 identity group ReBAC foundation) | ui/src/types/rbac-universal.ts, ui/src/lib/rbac/resource-model.ts, ui/src/lib/rbac/relationship-validator.ts, ui/src/lib/rbac/tuple-builders.ts, ui/src/lib/rbac/mongo-collections.ts, ui/src/lib/rbac/__tests__/rebac/resource-model.test.ts, ui/src/lib/rbac/__tests__/rebac/tuple-builders.test.ts, ui/src/lib/rbac/__tests__/mongo-collections.test.ts |
| Universal ReBAC resource catalog and enforcement-status transition visibility | ui/src/lib/rbac/resource-catalog.ts, ui/src/lib/rbac/enforcement-status.ts, ui/src/app/api/admin/rebac/catalog/route.ts, ui/src/app/api/admin/rebac/enforcement-status/route.ts, ui/src/lib/rbac/__tests__/resource-catalog.test.ts, ui/src/app/api/admin/rebac/__tests__/catalog-route.test.ts, ui/src/app/api/admin/rebac/__tests__/enforcement-status-route.test.ts, tests/rbac/fixtures/rebac_resources.ts, tests/rbac/unit/ts/universal-rebac-matrix.test.ts |
| Universal ReBAC policy authoring, staged change-set validation/apply, relationship provenance, and guided Access Manager / policy graph UI | ui/src/lib/rbac/policy-rule-store.ts, ui/src/lib/rbac/policy-change-set-store.ts, ui/src/lib/rbac/policy-change-validator.ts, ui/src/app/api/admin/rebac/change-sets/route.ts, ui/src/app/api/admin/rebac/change-sets/[changeSetId]/validate/route.ts, ui/src/app/api/admin/rebac/change-sets/[changeSetId]/apply/route.ts, ui/src/app/api/admin/rebac/resources/[type]/[id]/relationships/route.ts, ui/src/components/admin/rebac/RebacPolicyBuilder.tsx, ui/src/components/admin/rebac/PolicyChangeSetDiff.tsx, ui/src/components/admin/OpenFgaRebacTab.tsx, ui/src/lib/rbac/__tests__/rebac/policy-change-validator.test.ts, ui/src/app/api/admin/rebac/__tests__/change-sets-route.test.ts, ui/src/app/api/admin/rebac/__tests__/resource-relationships-route.test.ts, tests/rbac/e2e/story-policy-authoring.spec.ts |
Universal ReBAC graph filtering, relationship provenance visualization, read-only Slack/Webex team-routing metadata overlays, catalog-driven resource visibility, effective can_* access edges, authorization-model topology layers, and access explanation. Team-scoped graph filters include both team:<slug>#member and team:<slug>#admin usersets, and the policy graph derives supported resource types/actions from the universal model/catalog instead of a graph-local resource list. | ui/src/lib/rbac/rebac-graph.ts, ui/src/lib/rbac/access-explainer.ts, ui/src/app/api/admin/rebac/graph/route.ts, ui/src/app/api/admin/rebac/check/route.ts, ui/src/app/api/admin/openfga/graph/route.ts, ui/src/components/admin/rebac/RebacGraphFilters.tsx, ui/src/components/admin/rebac/RebacAccessChecker.tsx, ui/src/components/admin/OpenFgaRebacTab.tsx, ui/src/app/api/admin/rebac/__tests__/graph-route.test.ts, ui/src/app/api/admin/rebac/__tests__/check-route.test.ts, tests/rbac/e2e/story-graph-and-access-checker.spec.ts |
| Identity Group Sync shared types, deterministic team slug helpers, stores, matcher/planner/reconciler, reviewed team creation, guarded member-only removal, OIDC claim login reconciliation and admin claim-suggestions preview, zero-change detected-group preview, backend-rule manual dry-run, Okta directory connector, dual session/bearer admin gates, manual team membership provenance, scoped team-admin guards, API routes, UI tab, and fixtures | ui/src/types/identity-group-sync.ts, ui/src/lib/rbac/team-slugs.ts, ui/src/lib/rbac/identity-provider-store.ts, ui/src/lib/rbac/identity-group-sync-rule-store.ts, ui/src/lib/rbac/external-group-store.ts, ui/src/lib/rbac/team-membership-source-store.ts, ui/src/lib/rbac/team-admin-guards.ts, ui/src/lib/rbac/identity-group-rule-matcher.ts, ui/src/lib/rbac/identity-group-sync-planner.ts, ui/src/lib/rbac/identity-group-sync-reconciler.ts, ui/src/lib/rbac/oidc-claim-reconciler.ts, ui/src/lib/rbac/okta-directory-connector.ts, ui/src/app/api/admin/teams/route.ts, ui/src/app/api/admin/teams/[id]/route.ts, ui/src/app/api/admin/teams/[id]/members/route.ts, ui/src/app/api/admin/identity-group-sync/_lib.ts, ui/src/app/api/admin/identity-group-sync/*/route.ts, ui/src/app/api/admin/identity-group-sync/claim-suggestions/route.ts, ui/src/app/api/admin/identity-group-sync/teams/[teamId]/membership-sources/route.ts, ui/src/components/admin/TeamDetailsDialog.tsx, ui/src/components/admin/identity-group-sync/IdentityGroupSyncTab.tsx, ui/src/components/admin/identity-group-sync/MappingClusterEditor.tsx, ui/src/components/admin/identity-group-sync/DryRunPreview.tsx, ui/src/app/(app)/admin/page.tsx, ui/src/app/api/rbac/admin-tab-gates/route.ts, ui/src/hooks/useAdminTabGates.ts, tests/rbac/fixtures/identity_groups.ts, tests/rbac/fixtures/identity_groups.py, tests/rbac/e2e/identity-group-rebac-fixture.ts, tests/rbac/e2e/story-identity-group-sync.spec.ts, tests/rbac/e2e/story-manual-team-management.spec.ts, ui/src/lib/rbac/__tests__/identity-group-sync/, ui/src/app/api/admin/identity-group-sync/__tests__/, ui/src/components/admin/identity-group-sync/__tests__/, ui/src/app/api/admin/teams/__tests__/, ui/src/app/api/admin/teams/[id]/members/__tests__/ |
Slack channel ReBAC shared types, workspace alias normalization (SLACK_WORKSPACE_ALIAS), team-owned channel management gates, resource-scoped non-admin channel list/self-service Integrations tab, shared Slack/Webex connector onboarding wizard, OpenFGA source-of-truth channel-agent associations, dependent Mongo listen/priority route metadata, Slack Channel Association Default read/apply/discovered-channel onboarding APIs with per-channel team/agent overrides and Keycloak OBO repair, Web UI backend checks, runtime bot access-check delegation, editable/deleteable admin UI, runtime diagnostics, runtime evaluator, Slack bot admin runtime status/reload/config-sync/config-defaults APIs for legacy Slackbot migration prefill, explicit local-dev Slack admin API token mode, and fixtures | ui/src/types/slack-rebac.ts, ui/src/lib/rbac/slack-channel-grant-store.ts, ui/src/lib/rbac/slack-channel-route-store.ts, ui/src/lib/rbac/slack-channel-rebac.ts, ui/src/lib/slack-bot-admin.ts, ui/src/lib/__tests__/slack-bot-admin.test.ts, ui/src/app/api/admin/slack/channels/_lib.ts, ui/src/app/api/admin/slack/channels/route.ts, ui/src/app/api/admin/slack/channels/defaults/route.ts, ui/src/app/api/admin/slack/channels/[workspaceId]/[channelId]/resources/route.ts, ui/src/app/api/admin/slack/channels/[workspaceId]/[channelId]/routes/route.ts, ui/src/app/api/admin/slack/channels/[workspaceId]/[channelId]/diagnostics/route.ts, ui/src/app/api/admin/slack/channels/[workspaceId]/[channelId]/access-check/route.ts, ui/src/app/api/integrations/slack/channels/[workspaceId]/[channelId]/access-check/route.ts, ui/src/app/api/admin/slack/runtime/*/route.ts, ui/src/app/api/admin/slack/runtime/__tests__/config-defaults-route.test.ts, ui/src/components/admin/rebac/ConnectorOnboardingWizard.tsx, ui/src/components/admin/rebac/SlackChannelRebacPanel.tsx, ui/src/components/admin/rebac/__tests__/SlackChannelRebacPanel.test.tsx, ui/src/app/(app)/admin/page.tsx, ui/src/app/api/admin/teams/[id]/slack-channels/route.ts, ui/src/components/admin/TeamDetailsDialog.tsx, ai_platform_engineering/integrations/slack_bot/utils/slack_rebac.py, ai_platform_engineering/integrations/slack_bot/utils/slack_agent_routes.py, ai_platform_engineering/integrations/slack_bot/utils/slack_admin_api.py, ai_platform_engineering/integrations/slack_bot/utils/channel_team_mapper.py, ai_platform_engineering/integrations/slack_bot/utils/rbac_middleware.py, ai_platform_engineering/integrations/slack_bot/app.py, ui/src/app/api/admin/slack/channels/__tests__/channel-resources-route.test.ts, ui/src/app/api/integrations/slack/channels/[workspaceId]/[channelId]/__tests__/access-check-route.test.ts, ai_platform_engineering/integrations/slack_bot/tests/test_slack_channel_rebac.py, ai_platform_engineering/integrations/slack_bot/tests/test_slack_agent_routes.py, ai_platform_engineering/integrations/slack_bot/tests/test_slack_admin_api.py, tests/rbac/e2e/story-slack-channel-rebac.spec.ts, tests/rbac/fixtures/slack_rebac.ts |
Slack/Webex bot team membership prechecks: Mongo mapping still resolves channel/space to the owning team, but team#member OpenFGA decisions now take precedence over legacy teams.members fallback | ai_platform_engineering/integrations/slack_bot/utils/channel_team_resolver.py, ai_platform_engineering/integrations/slack_bot/tests/test_channel_team_resolver.py, ai_platform_engineering/integrations/webex_bot/utils/space_team_resolver.py, ai_platform_engineering/integrations/webex_bot/tests/test_space_team_resolver.py |
Team β Slack channel / Webex space visibility tuples (so previously-onboarded channels/spaces show as "Setup completed" in the admin panels): the admin listing routes (GET /api/admin/slack/channels, GET /api/admin/webex/spaces) filter each row by can_read on the channel/space OpenFGA object. Until this fix the onboarding writers only emitted the outbound slack_channel|webex_space β user β agent:<id> grant tuples; they never wrote any inbound visibility tuple onto the channel/space object, so can_read resolved to false for everyone and the panel silently dropped every configured row, showing it again as "Needs setup". The fix: (1) onboarding writers (defaults/route.ts for Slack, onboardWebexSpace for Webex) now also emit team:<slug>#admin β manage (relation "manager") β slack_channel|webex_space:<workspace>--<id> and team:<slug>#member β use (relation "user") β slack_channel|webex_space:<workspace>--<id> β matching the relation pair already written by the team-channels / team-spaces PUT endpoints so admin-PUT and onboarding-defaults converge on identical tuples; (2) the new messaging_team_visibility_v1 migration walks channel_team_mappings + webex_space_team_mappings and backfills those same tuples for previously-onboarded rows. slackChannelTeamVisibilityRelationships / webexSpaceTeamVisibilityRelationships are the shared builders so the writer and the backfill produce identical tuples. The migration is idempotent (OpenFGA no-ops on identical writes). Both user and reader relations grant can_read through the model's union, so any previously-injected reader tuples from manual repair remain effective and do not need to be rewritten. | ui/src/lib/rbac/slack-channel-rebac.ts (slackChannelTeamVisibilityRelationships), ui/src/lib/rbac/webex-space-rebac.ts (webexSpaceTeamVisibilityRelationships), ui/src/lib/rbac/__tests__/slack/slack-channel-rebac.test.ts, ui/src/lib/rbac/__tests__/webex-space-rebac.test.ts, ui/src/app/api/admin/slack/channels/defaults/route.ts (emits visibility tuples per onboarded channel), ui/src/lib/rbac/webex-space-onboarding.ts (emits visibility tuples per onboarded space), ui/src/lib/rbac/migrations/registry.ts (MESSAGING_TEAM_VISIBILITY_MIGRATION_ID, deriveMessagingTeamVisibilityPlan), ui/src/lib/rbac/migrations/schema-area-classifications.ts (messaging_team_visibility), ui/src/lib/rbac/migrations/__tests__/messaging-migrations.test.ts |
| Keycloak-to-ReBAC transition helpers, curated-vs-raw user role display, engineer-facing enforcement comparison API, task/skill role filtering, Keycloak resource sync guardrails, and drift detection | ui/src/lib/rbac/keycloak-transition.ts, ui/src/components/admin/UserManagementTab.tsx, ui/src/lib/rbac/enforcement-comparison.ts, ui/src/app/api/rbac/enforcement-comparison/route.ts, ui/src/lib/rbac/task-skill-realm-access.ts, ui/src/lib/rbac/keycloak-resource-sync.ts, ui/src/lib/rbac/drift-detection.ts, ui/src/lib/rbac/__tests__/rebac/keycloak-transition.test.ts, ui/src/lib/rbac/__tests__/rebac/task-skill-realm-access-transition.test.ts, ui/src/lib/rbac/__tests__/rebac/drift-detection.test.ts, ui/src/app/api/rbac/__tests__/enforcement-comparison-route.test.ts, ui/src/lib/rbac/__tests__/keycloak-resource-sync.test.ts |
ReBAC admin API gates, audit emitters, RBAC matrix seed entries, Mongo index initialization, and universal ReBAC backfill/rollback scripts; the backfill prefers persisted users.keycloak_sub for OpenFGA subjects | ui/src/app/api/admin/rebac/_lib.ts, ui/src/lib/rbac/audit.ts, tests/rbac/rbac-matrix.yaml, scripts/init-rbac-mongo-indexes.ts, scripts/backfill-universal-rebac.ts, scripts/__tests__/backfill-universal-rebac.test.ts, scripts/rollback-universal-rebac-tuples.ts |
| OpenFGA relationship backfill status and provenance collections | MongoDB rbac_migrations, team_membership_sources, rebac_relationships |
| RBAC/ReBAC route drift validator, including OpenFGA Dynamic Agent execution route coverage | scripts/validate-rbac-matrix.py, tests/rbac/rbac-matrix.yaml |
Admin tab visibility gates (deterministic OpenFGA + feature flag checks, including admin_surface:credentials; repairs baseline member tuples, opens Slack/Webex for concrete resource managers, no CEL editor or admin_tab_policies) | ui/src/lib/rbac/types.ts, ui/src/app/api/rbac/admin-tab-gates/route.ts, ui/src/app/api/rbac/admin-tab-gates/__tests__/route.test.ts, ui/src/hooks/useAdminTabGates.ts, ui/src/app/(app)/admin/page.tsx |
DB-managed release migration control plane β Admin UI seeds/reads migration_manifest, hides completed migrations by default, exposes every MongoDB collection with its current data_schema_versions row plus runtime migration targets, offers metadata-only v1 initialization for unversioned schema areas, requires schema-area classification guardrails for new collections, records schema_migrations, surfaces admin-only migration-required/header version-metadata alerts through GET /api/rbac/migration-status, exposes Keycloak reconciliation diagnostics and one-click Reconcile now repair in the dedicated Security & Policy β Keycloak tab through GET /api/admin/keycloak/migration-health, and keeps the audited super-admin override API at POST /api/admin/rebac/migrations/override; migration handlers remain code-backed for conversation owner normalization, direct organization membership backfill for existing users, universal team-resource OpenFGA backfill, Dynamic Agent tool tuple reconciliation, Dynamic Agent organization-admin inheritance, Slack/Webex messaging ReBAC backfills, messaging team mapping reconciliation, RBAC/messaging index creation, and BFF-owned Keycloak RBAC mapping reconciliation (keycloak_rbac_mapping_reconciliation_v1) | ui/src/app/(app)/admin/page.tsx, ui/src/app/(app)/admin/__tests__/admin-page.test.tsx, ui/src/components/admin/MigrationTab.tsx, ui/src/components/admin/KeycloakMigrationHealthPanel.tsx, ui/src/components/admin/__tests__/KeycloakMigrationHealthPanel.test.tsx, ui/src/components/admin/__tests__/MigrationTab.test.tsx, ui/src/components/layout/AppHeader.tsx, ui/src/components/layout/__tests__/AppHeader.test.tsx, ui/src/hooks/use-migration-status.ts, ui/src/app/api/rbac/migration-status/route.ts, ui/src/app/api/rbac/migration-status/__tests__/route.test.ts, ui/src/app/api/admin/keycloak/migration-health/route.ts, ui/src/app/api/admin/rebac/migrations/route.ts, ui/src/app/api/admin/rebac/migrations/status/route.ts, ui/src/app/api/admin/rebac/migrations/override/route.ts, ui/src/app/api/admin/rebac/migrations/version-bootstrap/apply/route.ts, ui/src/app/api/admin/rebac/migrations/[migrationId]/plan/route.ts, ui/src/app/api/admin/rebac/migrations/[migrationId]/apply/route.ts, ui/src/lib/rbac/migrations/registry.ts, ui/src/lib/rbac/migrations/types.ts, ui/src/lib/rbac/migrations/schema-area-classifications.ts, ui/src/lib/rbac/migrations/__tests__/registry-guardrails.test.ts, ui/src/lib/rbac/keycloak-rbac-reconciliation.ts, ui/src/lib/rbac/keycloak-migration-health.ts, ui/src/lib/rbac/migrations/conversation-owner-identity.ts, ui/src/lib/rbac/migrations/__tests__/agent-organization-inheritance.test.ts, ui/src/lib/rbac/migrations/__tests__/messaging-migrations.test.ts, ui/src/lib/rbac/conversation-implicit-authz.ts, ui/src/app/api/admin/rebac/__tests__/migrations-route.test.ts, ui/src/lib/rbac/__tests__/keycloak-rbac-reconciliation.test.ts, ui/src/lib/rbac/migrations/__tests__/conversation-owner-identity.test.ts, ui/src/lib/rbac/__tests__/conversation-implicit-authz.test.ts |
| AgentGateway policy model | OpenFGA ext_authz only; CEL policy CRUD and Mongo sync bridge are retired |
| Slack OBO token exchange (RFC 8693) | ai_platform_engineering/integrations/slack_bot/utils/obo_exchange.py |
| Slack identity auto-bootstrap + JIT branch + manual link fallback (spec 103) | ai_platform_engineering/integrations/slack_bot/utils/identity_linker.py |
Slack bot Keycloak Admin REST client β user-by-slack_user_id lookup, set_user_attribute, and JIT create_user_from_slack (spec 103). Uses KEYCLOAK_SLACK_BOT_ADMIN_CLIENT_ID/_SECRET (surface-specific prefix leaves room for future Webex/Teams bots), distinct from UI Web UI backend's KEYCLOAK_ADMIN_* | ai_platform_engineering/integrations/slack_bot/utils/keycloak_admin.py |
| Slack-bot email masking helper (privacy-safe log redaction, spec 103 FR-010) | ai_platform_engineering/integrations/slack_bot/utils/email_masking.py |
Slack bot Helm wiring: Socket-mode Slack tokens, SLACK_RBAC_ENABLED, JIT flags, Keycloak bot OBO secret (KEYCLOAK_BOT_CLIENT_SECRET), Keycloak Admin lookup secret, and ExternalSecret-backed Vault keys such as projects/caipe/rbac/slackbot | charts/ai-platform-engineering/charts/slack-bot/values.yaml, charts/ai-platform-engineering/charts/slack-bot/templates/deployment.yaml, charts/ai-platform-engineering/charts/slack-bot/templates/external-secret.yaml |
| JIT feature flags β Docker Compose (dev + prod) | docker-compose.dev.yaml and docker-compose.yaml (slack-bot service env) |
Keycloak realm-management role pinning for service-account-caipe-platform (view-users, query-users, manage-users, query-clients, view-clients, manage-clients, view-authorization, manage-authorization) β bootstrap shell establishes the first-run baseline for user JIT and BFF Keycloak RBAC migration repair | charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh (_ensure_caipe_platform_user_roles block), ui/src/lib/rbac/keycloak-rbac-reconciliation.ts, ui/src/lib/rbac/keycloak-migration-health.ts |
| Keycloak realm-management role pinning β declarative source of truth | charts/ai-platform-engineering/charts/keycloak/realm-config.json and deploy/keycloak/realm-config.example.json (clientRoles.realm-management for service-account-caipe-platform) |
| Slack account linking callback and admin unlink action | ui/src/app/api/auth/slack-link/route.ts, ui/src/app/api/admin/slack/users/[id]/route.ts, ui/src/components/admin/UserDetailModal.tsx |
| Webex account linking callback, nonce store, and 1:1 Adaptive Card bootstrap | ui/src/app/api/auth/webex-link/route.ts, ui/src/lib/rbac/webex-link-nonce.ts, ai_platform_engineering/integrations/webex_bot/utils/identity_linker.py, ai_platform_engineering/integrations/webex_bot/webex_responder.py |
| Slack channel β team routing + OpenFGA-backed channel-agent dispatch, setup-mode Slack response suppression, and opt-in first-message auto-assignment for unmapped channels | ai_platform_engineering/integrations/slack_bot/utils/channel_team_mapper.py, ai_platform_engineering/integrations/slack_bot/utils/slack_channel_auto_assign.py, ai_platform_engineering/integrations/slack_bot/tests/test_slack_channel_auto_assign.py, ai_platform_engineering/integrations/slack_bot/utils/slack_agent_routes.py, ai_platform_engineering/integrations/slack_bot/utils/slack_rebac.py, ai_platform_engineering/integrations/slack_bot/utils/slack_runtime_policy.py, ai_platform_engineering/integrations/slack_bot/tests/test_slack_route_no_silent_failure.py, ai_platform_engineering/integrations/slack_bot/app.py |
Admin API: Keycloak identities and user admin actions (list/stats require admin_surface:users#can_read; user details require user_profile:<id>#can_read; product authorization is exposed through teams/OpenFGA, not role chips, a Roles tab, or a standalone Slack users tab; Users list exposes Slack linked / pending / unlinked filters) | ui/src/app/api/admin/users/route.ts, ui/src/app/api/admin/users/[id]/route.ts, ui/src/lib/rbac/require-openfga.ts, ui/src/app/api/admin/users/[id]/teams/route.ts, ui/src/app/api/admin/users/[id]/roles/route.ts, ui/src/app/api/admin/users/[id]/role/route.ts, ui/src/app/api/admin/users/stats/route.ts, ui/src/components/admin/UserManagementTab.tsx, ui/src/components/admin/UserDetailModal.tsx, ui/src/components/admin/__tests__/UserDetailModal.test.tsx, ui/src/app/api/__tests__/admin-users.test.ts, ui/src/app/api/admin/users/__tests__/dual-auth-user-admin-routes.test.ts |
Admin API: Prometheus metrics proxy (admin_surface:metrics#can_read instant and batch PromQL for the baseline Metrics & Health dashboard) | ui/src/app/api/admin/metrics/route.ts, ui/src/lib/rbac/require-openfga.ts, ui/src/app/api/admin/metrics/__tests__/route.test.ts |
Admin API: platform stats dashboards (admin_surface:stats#can_manage for the main stats dashboard; related skills/checkpoint stats routes still require privileged admin view) | ui/src/app/api/admin/stats/route.ts, ui/src/app/api/admin/stats/skills/route.ts, ui/src/app/api/admin/stats/checkpoints/route.ts, ui/src/app/api/admin/stats/__tests__/stats-rbac-routes.test.ts |
RBAC e2e port band + E2E_COMPOSE_ENV contract (spec 102) | Makefile (test-rbac-up target) + spec 102 quickstart βΊ E2E port band |
| RBAC e2e env-var substitutions inside the dev compose file | docker-compose.dev.yaml (search for MONGODB_HOST_PORT, SUPERVISOR_HOST_PORT, RBAC_FALLBACK_FILE, E2E_RUN) |
| Shared custom MCP auth middleware β JWT/shared-key validation and localhost dev bypass for embedded MCPs | ai_platform_engineering/agents/common/mcp-auth/mcp_agent_auth/middleware.py and ai_platform_engineering/agents/common/mcp-auth/mcp_agent_auth/pdp.py |
| Shared custom MCP auth package docs / operator knobs for local and embedded MCP servers | ai_platform_engineering/agents/common/mcp-auth/README.md |
| Spec 104 team/resource grants for gateway authorization | OpenFGA tuples written by Team Resources APIs and enforced through AgentGateway ext_authz |
| Keycloak seed (identity-only CAIPE realm; does not create CAIPE business/resource roles) and local cleanup helper for stale legacy roles | charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh, scripts/cleanup-local-keycloak-legacy-roles.py |
| Spec 104 spec / acceptance criteria | docs/docs/specs/104-team-scoped-rbac/spec.md |
Spec 104 Story 4 β Admin UI Team Resources API: GET/PUT /api/admin/teams/[id]/resources, persists team.resources = { agents, agent_admins, tools, tool_wildcard }, writes OpenFGA team-resource tuples, and does not mirror per-resource Keycloak roles | ui/src/app/api/admin/teams/[id]/resources/route.ts |
Skill Hub skill permission overlay β Team Resources exposes hub skills as skill:hub-<hub_id>-<hub_skill_id> grants; locally-created and .zip-imported team skills write skill#user tuples; Skill Hubs persist shared_with_teams, grant those teams on refresh, and backfill already-crawled hub skills via skill_hub_team_grants_backfill_v1. /api/skill-hubs lists hub metadata through admin_surface:skills#can_read, while hub mutations require admin_surface:skills#can_manage. /api/skills filters non-admin catalog/runtime responses through OpenFGA can_read/can_use, while built-in source=default catalog templates remain readable to signed-in users without per-template tuples | ui/src/lib/rbac/skill-team-grants.ts, ui/src/app/api/skills/configs/route.ts, ui/src/app/api/skills/configs/import-zip/route.ts, ui/src/components/skills/ImportSkillZipDialog.tsx, ui/src/app/api/skill-hubs/route.ts, ui/src/lib/rbac/require-openfga.ts, ui/src/app/api/skill-hubs/[id]/route.ts, ui/src/app/api/skill-hubs/[id]/refresh/route.ts, ui/src/components/admin/SkillHubsSection.tsx, ui/src/components/admin/__tests__/SkillHubsSection.autoswitch.test.tsx, ui/src/lib/rbac/migrations/registry.ts, ui/src/app/api/admin/teams/[id]/resources/route.ts, ui/src/app/api/skills/route.ts, ui/src/app/api/skills/__tests__/runnable-gate.test.ts, ui/src/lib/rbac/__tests__/skill-team-grants.test.ts, ui/src/app/api/skill-hubs/__tests__/route-team-grants.test.ts, ui/src/app/api/skill-hubs/[id]/refresh/__tests__/route.test.ts |
| Spec 104 β Admin UI Team management dialog: Resources tab (Use+Manage per agent and MCP-server tool prefixes) backed by OpenFGA team/resource relationships; there is no product Roles tab | ui/src/components/admin/TeamDetailsDialog.tsx |
| Spec 104 Story 4 β Keycloak Admin helpers retained for identity administration and legacy cleanup paths; product access is represented by OpenFGA relationships | ui/src/lib/rbac/keycloak-admin.ts |
Spec 104 Story 4 β Team.resources = { agents, tools } Mongo schema field | ui/src/types/teams.ts |
Spec 098 US9 β Team-owned Team Slack Channels API: GET/PUT /api/admin/teams/[id]/slack-channels, idempotent full-replace into channel_team_mappings, denormalises team.slack_channels, checks team#read/manage through OpenFGA, writes channel ownership tuples, and rejects channels already mapped to a different team (409). Agent grants live under Slack channel ReBAC. | ui/src/app/api/admin/teams/[id]/slack-channels/route.ts |
Spec 098 US9 β Admin UI Slack channel discovery (server-side users.conversations, bot-member channels only, in-process discovery cache, 503 if SLACK_BOT_TOKEN unset so UI falls back to manual ID entry) | ui/src/app/api/admin/slack/available-channels/route.ts |
| Spec 098 US9 β Slack Channels tab inside the team-management dialog (live discovery picker and manual-ID fallback for channelβteam binding only) | ui/src/components/admin/TeamDetailsDialog.tsx (SlackChannelsPanel) |
Spec 098 US9 β Team.slack_channels = [{ slack_channel_id, channel_name, slack_workspace_id }] denormalised count for team-card chip | ui/src/types/teams.ts |
| Spec 104 Story 4 β Admin UI dialog with the Resources tab (checkboxes for agents + per-MCP tool prefixes) | ui/src/components/admin/TeamDetailsDialog.tsx |
| Spec 104 Story 4 β Jest coverage for resources API (auth gates, OpenFGA tuple reconciliation, and no Keycloak resource-role mirroring) | ui/src/app/api/__tests__/admin-team-resources.test.ts, ui/src/lib/rbac/__tests__/openfga.test.ts |
Team Knowledge Bases/Data Sources API β writes model-valid OpenFGA tuples before persisting team_kb_ownership metadata: read and ingest grant team:<slug>#member reader/ingestor knowledge_base:<datasource_id>, while admin grants team:<slug>#admin manager knowledge_base:<datasource_id> | ui/src/app/api/admin/teams/[id]/kb-assignments/route.ts, ui/src/app/api/admin/teams/[id]/kb-assignments/__tests__/route.test.ts |
Strict UI object-level OpenFGA helper and BFF enforcement pass β conversation, skill, task, workflow-config-as-task, knowledge base, agent, LLM model, MCP server, tool, system config, self-service RAG datasource owner/team tuple reconciliation, RAG datasource list filtering, and search constraint injection before bearer-token proxying. The helper does not bypass checks for session role=admin and no longer has a test-only PDP bypass. Built-in tool catalog (GET /api/dynamic-agents/builtin-tools) is intentionally excluded from this pass β see the route comment for why (static catalog, not a permissioned object surface). | ui/src/lib/rbac/resource-authz.ts, ui/src/lib/rbac/openfga-owned-resources.ts, ui/src/lib/rbac/__tests__/resource-authz.test.ts, ui/src/app/api/chat/**/route.ts, ui/src/app/api/skills/**/route.ts, ui/src/app/api/task-configs/route.ts, ui/src/app/api/workflow-configs/route.ts, ui/src/app/api/__tests__/task-workflow-rbac.test.ts, ui/src/app/api/rag/[...path]/route.ts, ui/src/app/api/rag/kb/[...path]/route.ts, ui/src/app/api/dynamic-agents/route.ts, ui/src/app/api/dynamic-agents/agents/[id]/route.ts, ui/src/app/api/dynamic-agents/builtin-tools/route.ts, ui/src/app/api/dynamic-agents/models/route.ts, ui/src/app/api/llm-models/route.ts, ui/src/app/api/llm-models/__tests__/route.test.ts, ui/src/app/api/dynamic-agents/__tests__/route-rbac.test.ts, ui/src/app/api/admin/platform-config/route.ts, ui/src/app/api/admin/platform-config/__tests__/route.test.ts |
Org-admin super-grant for KB / Search / Data Sources / Graph / MCP Tools (PR 1, 2026-05-27 fine-grained KB ReBAC plan) β bypassForOrgAdmin: true option on requireResourcePermission / filterResourcesByPermission; matching org-admin short-circuit in constrainSearchBody so admins skip per-KB filter injection; rag_datasources added to PRIVILEGED_ADMIN_SURFACES so org admins get an explicit user:<sub> manager admin_surface:rag_datasources tuple at login (no longer inheritance-only); kill switch RAG_ADMIN_BYPASS_DISABLED=true reverts to pure per-resource checks. Backfill migration admin_surface_rag_datasources_admin_grant_v1 walks OpenFGA for existing user:<sub> admin organization:<key> tuples and writes the matching admin-surface manager tuple. | ui/src/lib/rbac/resource-authz.ts, ui/src/lib/rbac/__tests__/resource-authz-admin-bypass.test.ts, ui/src/app/api/rag/[...path]/route.ts, ui/src/app/api/rag/kb/[...path]/route.ts, ui/src/app/api/rag/__tests__/admin-kb-bypass.test.ts, ui/src/lib/rbac/baseline-access.ts, ui/src/lib/rbac/__tests__/keycloak-bootstrap-admins.test.ts, ui/src/lib/rbac/migrations/registry.ts (ADMIN_SURFACE_RAG_DATASOURCES_ADMIN_GRANT_MIGRATION_ID, deriveAdminSurfaceRagDatasourcesAdminGrantPlan, loadOrgAdminSubjects), ui/src/lib/rbac/migrations/__tests__/agent-organization-inheritance.test.ts |
Knowledge sidebar per-tab OpenFGA gates and empty states (PR 2, 2026-05-27 fine-grained KB ReBAC plan) β new BFF route GET /api/rbac/kb-tab-gates returns {search, data_sources, graph, mcp_tools, has_any_kb, kb_count}. Org admins (user:<sub> can_manage organization:<key> via OpenFGA or BOOTSTRAP_ADMIN_EMAILS) short-circuit to every tab true and kb_count=-1; non-admins get a count from the /v1/datasources list filtered by knowledge_base:<id>#can_read. useKbTabGates is the SWR-style hook the sidebar consumes. KnowledgeSidebar renders the disabled-with-tooltip variant for tabs the user can't see, suppresses the empty-state banner when the org-admin bypass fires, and respects graphRagEnabled AND RBAC on the Graph tab. NoKbAccessEmpty is the page-level fallback for Search / Data Sources / Graph / MCP Tools when no KB is readable. Fails closed β every tab is hidden until the BFF responds. | ui/src/app/api/rbac/kb-tab-gates/route.ts, ui/src/app/api/rbac/__tests__/kb-tab-gates.test.ts, ui/src/hooks/use-kb-tab-gates.ts, ui/src/components/rag/KnowledgeSidebar.tsx, ui/src/components/rag/__tests__/KnowledgeSidebar.test.tsx, ui/src/components/rag/NoKbAccessEmpty.tsx, ui/src/lib/rbac/types.ts (KbTabKey, KbTabGatesMap) |
Graph tab gate + info banner + per-KB ontology filtering follow-up (PR 5, 2026-05-27 fine-grained KB ReBAC plan) β useKbTabGates is wired into /knowledge-bases/graph so non-admins with zero readable KBs see the NoKbAccessEmpty empty state instead of the global graph. A new GraphInfoBanner shows whenever the tab is visible, reminding users (and org admins under the PR 1 super-grant) that the ontology graph is global today. The follow-up spec docs/docs/specs/2026-05-27-per-kb-ontology-graph-filtering/spec.md tracks the RAG-server work needed for true per-KB filtering. | ui/src/app/(app)/knowledge-bases/graph/page.tsx, ui/src/app/(app)/knowledge-bases/graph/__tests__/page.test.tsx, docs/docs/specs/2026-05-27-per-kb-ontology-graph-filtering/spec.md |
data_source and mcp_tool OpenFGA types + reconcilers + BFF list filter (PR 4, 2026-05-27 fine-grained KB ReBAC plan) β the OpenFGA model source and Helm-packaged JSON add two new types so per-KB ingest and per-tool invoke can be granted independently of knowledge_base:<id> reads. buildDataSourceRelationshipTupleDiff and buildMcpToolRelationshipTupleDiff mirror the shared-teams diff semantics from buildKnowledgeBaseRelationshipTupleDiff (mcp_tool also emits the user relation so member teams can can_call). The BFF route now reconciles mcp_tool:<tool_id> tuples on PUT /v1/mcp/custom-tools/<tool_id> and filters the list response by mcp_tool:<id>#can_read (org admins bypass via PR 1's super-grant). Two strictly-additive backfill migrations (data_source_grants_backfill_v1, mcp_tool_grants_backfill_v1) keep day-zero behaviour intact. | deploy/openfga/model.fga, charts/ai-platform-engineering/charts/openfga/authorization-model.json, ui/src/lib/rbac/openfga-owned-resources.ts, ui/src/lib/rbac/__tests__/openfga-data-source-mcp-tool.test.ts, ui/src/lib/rbac/__tests__/openfga.test.ts, ui/src/app/api/rag/[...path]/route.ts, ui/src/app/api/rag/__tests__/mcp-tool-list-filter.test.ts, ui/src/lib/rbac/migrations/registry.ts (DATA_SOURCE_GRANTS_BACKFILL_MIGRATION_ID, MCP_TOOL_GRANTS_BACKFILL_MIGRATION_ID), ui/src/lib/rbac/migrations/__tests__/agent-organization-inheritance.test.ts |
Per-KB Share-with-Teams panel + reconciler (PR 3, 2026-05-27 fine-grained KB ReBAC plan) β buildKnowledgeBaseRelationshipTupleDiff now accepts nextSharedTeamSlugs / previousSharedTeamSlugs so the reconciler emits explicit deletes when a team is removed from the picker (mirrors reconcileAgentRelationships). New BFF route PUT /api/rag/kbs/[id]/sharing reconciles the team list through OpenFGA. New /knowledge-bases/sharing/[id] page hosts the KbSharingPanel (TeamMultiPicker + Effective-Access callout). Backfill migration knowledge_base_shared_team_grants_backfill_v1 walks team_kb_ownership Mongo docs and writes the canonical team:<slug>#member reader/ingestor knowledge_base:<id> + team:<slug>#admin manager knowledge_base:<id> tuples for every existing (team, kb) row so admins can migrate without losing access. | ui/src/lib/rbac/openfga-owned-resources.ts, ui/src/lib/rbac/__tests__/openfga-kb-shared-teams.test.ts, ui/src/app/api/rag/kbs/[id]/sharing/route.ts, ui/src/app/api/rag/kbs/__tests__/sharing-route.test.ts, ui/src/app/(app)/knowledge-bases/sharing/[id]/page.tsx, ui/src/components/rag/KbSharingPanel.tsx, ui/src/lib/rbac/migrations/registry.ts (KNOWLEDGE_BASE_SHARED_TEAM_GRANTS_MIGRATION_ID, deriveKnowledgeBaseSharedTeamGrantsPlan, loadKnowledgeBaseSharedTeamGrantsInputs), ui/src/lib/rbac/migrations/__tests__/agent-organization-inheritance.test.ts |
RAG server team/OpenFGA Knowledge Base checks (check_kb_datasource_access, inject_kb_filter, OpenFGA check/list-objects) β datasource-level RBAC for direct API and MCP requests | ai_platform_engineering/knowledge_bases/rag/common/src/common/models/rbac.py, ai_platform_engineering/knowledge_bases/rag/server/src/server/rbac.py, ai_platform_engineering/knowledge_bases/rag/server/src/server/restapi.py, ai_platform_engineering/knowledge_bases/rag/server/src/server/tools.py, ai_platform_engineering/knowledge_bases/rag/server/tests/test_openfga_team_rebac.py |
RAG hybrid ACL β per-document acl_tags filter (opt-in: RBAC_DOC_ACL_TAGS_ENABLED) | ai_platform_engineering/knowledge_bases/rag/server/src/server/doc_acl.py |
RAG hybrid ACL backfill β assigns acl_tags=["__public__"] to existing Milvus rows before flipping the flag | scripts/rag-doc-acl-migration.py |
RAG datasource display name (DataSourceInfo.name) β human-friendly label only, NEVER an authorization key. datasource_id remains the immutable RBAC/storage key used by Milvus filters, OpenFGA tuples, document metadata, and ingestor jobs. | ai_platform_engineering/knowledge_bases/rag/common/src/common/models/rag.py |
RAG datasource name derivation helpers (derive_friendly_name, derive_friendly_name_from_url) β used by ingestors at creation and by the rag-server for lazy-backfill of legacy rows | ai_platform_engineering/knowledge_bases/rag/common/src/common/utils.py |
RAG datasource rename API (PATCH /v1/datasource/{datasource_id} β display-label only, requires Role.ADMIN and check_kb_datasource_access(scope="admin")) | ai_platform_engineering/knowledge_bases/rag/server/src/server/restapi.py |
RAG Data Sources card β show friendly name with monospace datasource_id as secondary line, inline pencil rename | ui/src/components/rag/IngestView.tsx |
Spec 104 (REMOVED by Phase 3 of spec 2026-05-24-derive-team-from-channel) β active_team JWT claim design / sequence diagrams / spike notes (historical) | docs/docs/specs/104-team-scoped-rbac/active-team-design.md |
Spec 104 (REMOVED) β Per-team Keycloak client scope provisioning (team-<slug> + hardcoded active_team mapper) was deleted from Helm/runtime init in Phase 3. The mechanism never shipped to production, so no realm has legacy scopes to clean up | charts/ai-platform-engineering/charts/keycloak/scripts/init-token-exchange.sh, deploy/keycloak/init-token-exchange.sh, charts/ai-platform-engineering/charts/keycloak/scripts/init-idp.sh |
| Spec 104 (REMOVED) β Web UI backend Keycloak Admin helpers for per-team client scopes were deleted; surviving file holds OBO-permission helpers only | ui/src/lib/rbac/keycloak-admin.ts |
Spec 104 (REMOVED) β Web UI backend startup auto-sync of Keycloak team-* scopes / team-personal scope / orphan-scope deletion / audience-default selection was deleted in Phase 3. The surviving reconciliation migration covers OBO permission strategy, bot service-account impersonation roles, and bootstrap admin tuples only | ui/src/lib/rbac/keycloak-rbac-reconciliation.ts, ui/src/lib/rbac/keycloak-migration-health.ts, ui/src/app/api/admin/keycloak/migration-health/route.ts, ui/src/components/admin/KeycloakMigrationHealthPanel.tsx (startup called from ui/src/instrumentation.ts) |
Spec 104 β Team slug field (immutable) + create/delete provisioning. (Phase 3 removed the Keycloak side of team CRUD; only Mongo + OpenFGA writes remain.) | ui/src/types/teams.ts, ui/src/app/api/admin/teams/route.ts, ui/src/app/api/admin/teams/[id]/route.ts |
Spec 104 (REMOVED) β Slack-bot OBO impersonate_user(active_team=...) and active_team claim verification were removed in Phase 3. Bot now mints a team-agnostic OBO token | ai_platform_engineering/integrations/slack_bot/utils/obo_exchange.py |
| Spec 104 β Slack channel β team-slug resolver + per-user team membership pre-check with friendly bot denial copy | ai_platform_engineering/integrations/slack_bot/utils/channel_team_resolver.py, ai_platform_engineering/integrations/slack_bot/utils/user_messages.py |
Spec 104 β Slack-bot _rbac_enrich_context (DM β personal chain, group β resolver, hard-deny on missing mapping or OBO failure, no SA fallback). Phase 3 dropped the active_team field from the enriched context | ai_platform_engineering/integrations/slack_bot/app.py, ai_platform_engineering/integrations/slack_bot/utils/user_messages.py |
Spec 104 (REMOVED) β RAG server UserContext.active_team claim wiring + extract_active_team_from_claims were deleted in Phase 3 | ai_platform_engineering/knowledge_bases/rag/common/src/common/models/rbac.py, ai_platform_engineering/knowledge_bases/rag/server/src/server/rbac.py, ai_platform_engineering/knowledge_bases/rag/server/src/server/restapi.py |
Spec 104 β Dynamic-agents JWT middleware: accept aud=agentgateway + aud=caipe-platform. (Phase 3 stopped reading or logging the legacy active_team claim.) | ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/jwks_validate.py, ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/jwt_middleware.py |
Dynamic-agents vendored JWKS validator env-var contract β pins the resolution order for KEYCLOAK_URL (defaults to http://localhost:7080 β the documented in-pod trap), OIDC_ISSUER (verbatim if set, derived from KEYCLOAK_URL otherwise β the Kevin failure mode when the in-cluster URL differs from the browser-facing issuer), and KEYCLOAK_JWKS_URL / OIDC_JWKS_URL (explicit overrides win). The vendored copy is the runtime path Bearer-token validation actually executes; the shared copy has its own tests under tests/rbac/unit/py/test_jwks_validate.py | ai_platform_engineering/dynamic_agents/src/dynamic_agents/auth/jwks_validate.py, ai_platform_engineering/dynamic_agents/tests/test_jwks_validate.py |
Dynamic-agents Helm chart Keycloak/OIDC wiring β subchart values.yaml exposes config.KEYCLOAK_URL and config.OIDC_ISSUER, templates/configmap.yaml skips empty values so the in-code defaults still apply when an operator hasn't set them, templates/NOTES.txt prints a non-blocking helm install warning whenever either is empty, and the umbrella values.yaml defaults KEYCLOAK_URL to the bundled ai-platform-engineering-keycloak:8080 service so the vanilla install works out of the box | charts/ai-platform-engineering/charts/dynamic-agents/values.yaml, charts/ai-platform-engineering/charts/dynamic-agents/templates/configmap.yaml, charts/ai-platform-engineering/charts/dynamic-agents/templates/NOTES.txt, charts/ai-platform-engineering/values.yaml, tests/test_dynamic_agents_chart_keycloak_env.py |
Spec 104 β AGW delegates gateway authorization to OpenFGA through ext_authz | deploy/agentgateway/config.yaml, deploy/openfga/bridge/main.py |
Spec 104 β Web UI backend writes OpenFGA user:<sub> member team:<slug> tuples when admins add/remove team members; no new team_member:<slug> realm-role assignments | ui/src/app/api/admin/teams/[id]/members/route.ts (writeTeamMembershipTuple) |
Spec 104 β Slack-bot membership pre-check uses Mongo-backed channel/team membership; it no longer requires team_member:<slug> in the JWT | ai_platform_engineering/integrations/slack_bot/utils/channel_team_resolver.py, ai_platform_engineering/integrations/slack_bot/utils/rbac_middleware.py |
| Operational recovery β remove stale CAIPE business/resource realm roles from local Keycloak after the identity-only migration | scripts/cleanup-local-keycloak-legacy-roles.py |
| Roadmap β feasibility analysis of remote PDP options (OpenFGA / OPA / Cedar / Cerbos / keep CEL) for AGW external authz | docs/docs/security/rbac/feasibility-pdp-options.md |