9e3c92ca95
Build on PR 1's tenant_members schema by enforcing the role matrix on
every authenticated route and at agent run time. Enforcement is gated
behind cfg.Tenant.EnableRBAC so the change ships dormant — the same
code paths log the would-be rejection during the rollout window and
start blocking once operators flip the flag.
Middleware (internal/middleware/rbac.go):
- RequireRole(min, cfg): role-only gate using TenantRole.HasPermission.
- RequireOwnershipOrRole(min, lookup, cfg): allows the original creator
through even when their role is below min, otherwise falls back to
the role check. Lookup errors fail closed; an empty creator_id is
treated as tenant-owned and requires the role.
- Both honour cfg.Tenant.EnableRBAC for the rollout window.
Creator lookups (internal/handler/rbac_lookups.go):
- KBCreatorLookup resolves :id -> KnowledgeBase.CreatorID.
- AgentCreatorLookup resolves :id -> CustomAgent.CreatedBy; built-in
agents (IsBuiltin=true) return ("", nil) so they remain Admin+ only.
- Compile-time guards keep the methods aligned with CreatorLookup.
Route guards (internal/router/rbac.go + router.go):
- rbacGuards bundles the matrix into Viewer/Contributor/Admin/Owner
helpers plus OwnedKBOrAdmin / OwnedAgentOrAdmin. Each Register*
function takes *rbacGuards so route lines remain readable.
- KB / Agent / Knowledge / Chunk / Tenant / Model / MCPService /
VectorStore / DataSource / IMChannel routes annotated per the PR 1
design doc matrix. Tenant-wide infra (models, vector stores, MCP,
data sources, IM channels) reads as Viewer+ and writes as Admin+;
per-resource mutations on KB / Agent route through the
ownership-or-Admin guard.
Resource ownership (services):
- CreateKnowledgeBase / CopyKnowledgeBase populate CreatorID from the
authenticated user, so the OwnedKBOrAdmin guard has a creator to
match.
- CreateAgent / CopyAgent populate CreatedBy similarly. Built-in
agents stay tenant-owned.
Agent run-time gate (internal/handler/session/qa.go):
- AgentQA refuses to launch the agent engine for Viewers when the
target agent has RunnableByViewer=false. Same EnableRBAC gating as
the middleware so it's a no-op until enforcement is turned on.
Tests:
- 12 unit tests cover RequireRole / RequireOwnershipOrRole, including
the fail-open and fail-closed branches and the lookup-error path.