Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.4 KiB
Switchboard Core — Session Turnover
Date: 2026-03-26 Author: Jeffrey Smith (jasafpro@gmail.com) Session: v0.2.0 PR 1 — Full RBAC migration
What Was Done
PR 1: Admin → RBAC group migration — COMPLETE
Branch feat/admin-rbac-migration (PR #1), 3 commits:
-
Admin → RBAC group migration —
surface.admin.accesspermission + Admins system group replaces hardcodedrole == "admin"checks. Admin bypass removed fromRequirePermission. Bootstrap/seed/OIDC/admin handlers sync group membership. -
Remove token budgets + allowed models from groups — Provider-era cruft:
token_budget_daily,token_budget_monthly,allowed_modelscolumns,ResolveTokenBudget(),ResolveModelAllowlist(), and all store/handler/UI code deleted. -283 lines. -
Drop
users.rolecolumn — Full RBAC. Zero magic roles. Therolecolumn,UserRoleAdmin/UserRoleUserconstants,CountByRole(),DefaultRoleconfig,rolein JWT claims, and all role-based shortcuts removed. Everyone group membership is now explicit (all users added on create). 28 files changed.
Net result: -364 lines across 3 commits.
Architecture after this PR:
- Everyone group (
00000000...0001): all users explicitly added on creation. Carriesextension.use,workflow.submit. - Admins group (
00000000...0002): carries all 7 kernel permissions includingsurface.admin.access. Not special-cased — just a group with permissions. - Permission resolution: union of all group memberships. No implicit groups, no role shortcuts, no admin bypass.
- Admin middleware: checks
surface.admin.accessgrant viaresolveAndCachePerms. - JWT claims:
user_id+emailonly. No role. - OIDC:
isIdPAdmin()maps IdP role claim → Admins group membership. Norolecolumn writes. - User creation paths: builtin register, OIDC auto-provision, mTLS auto-provision,
admin create, bootstrap, seed — all call
EnsureEveryoneGroup().
Known Issues
- Frontend test job: FE test suite may have remaining stale references from Phase 0 gut (pre-existing)
- Traefik middleware:
rbac-traefik.yamlneeds one-time manual apply by cluster admin (pre-existing, non-blocking) SEED_USERS: Not set in K8s secrets (pre-existing)- Editor modules:
src/editor/*.mjsstill reference "Chat Switchboard" (pre-existing, low priority) - K8s deploy: PR not yet merged to main — CI/CD will need a deploy after merge
What's Next
Merge PR #1
Review and merge feat/admin-rbac-migration to main.
v0.2.0 — Remaining PRs
PR 2: Settings cascade
- Add
user_overridableflag to settings schema - RBAC controls scope auth (admin → global, team-admin → team, user → personal)
- Resolution: user → team → global (first non-null wins)
PR 3: ICD (API contract)
- Generate full OpenAPI spec from registered routes
- Document kernel-only endpoints
PR 4: Trigger system
- Time triggers (cron), webhook (inbound HTTP), event (bus subscription)
- Extensions register match expressions at install
PR 5: SDK stabilization
sb.slots(),sb.actions,sb.api.ext(),sb.storage- Theme tokens exposed to extensions
Architecture Decisions (this session)
| Decision | Detail |
|---|---|
| Full RBAC — no magic roles | users.role column dropped entirely. All authorization through group membership + permission grants. |
| Explicit Everyone membership | No implicit "all users get Everyone perms". Every user added to Everyone group on create. member_count reflects reality. |
| Admins group not special-cased | Code never checks group identity — only checks for surface.admin.access permission. Any group can grant it. |
| No new migrations pre-MVP | Edit existing SQL files in place. Schema isn't in production. |
| JWT simplified | Claims carry user_id + email only. Permissions resolved server-side from groups on every request. |
| OIDC role → group sync | isIdPAdmin() replaces resolveRole(). IdP admin claim maps to Admins group membership, not a DB column. |
| Token budgets/allowed models removed | Provider-era cruft. Belongs in a future provider extension, not the kernel. |
File Locations
| What | Where |
|---|---|
| Roadmap | ROADMAP.md |
| Changelog | CHANGELOG.md |
| CI workflow | .gitea/workflows/ci.yaml |
| K8s manifests | k8s/ |
| Docker compose (local dev) | docker-compose.yml (SQLite, port 3000) |
| Nginx template | nginx.conf.template |
| Migrations | server/database/migrations/{postgres,sqlite}/ |
| Permission constants | server/auth/permissions.go |
| Admin middleware | server/middleware/admin.go |
| Group store | server/store/{postgres,sqlite}/groups.go |
| Bootstrap/seed | server/handlers/auth.go |
| Admin handlers | server/handlers/admin.go |
| Groups UI | src/js/sw/surfaces/admin/groups.js |
| Users UI | src/js/sw/surfaces/admin/users.js |
| SDK API | src/js/sw/sdk/api-domains.js |
| Memory (Claude) | .claude/projects/-config-Projects-core/memory/ |
Gitea Secrets/Vars Needed
Secrets: POSTGRES_USER, POSTGRES_USER_PASSWORD, POSTGRES_ADMIN_USER,
POSTGRES_ADMIN_PASSWORD, ENCRYPTION_KEY
Vars: DOMAIN, NAMESPACE, S3_BUCKET, S3_ENDPOINT, STORAGE_BACKEND,
STORAGE_CLASS, STORAGE_SIZE
Not yet configured: S3_ACCESS_KEY, S3_SECRET_KEY (using PVC for now),
SEED_USERS (needed for initial admin bootstrap)