Domain migration. Depends on PAR-413. Backend for the settings pages (PAR-384) and onboarding (PAR-383).
api/WorkspaceAdminService.java (~580 lines, 19 @Transactionals) and WorkspaceSettingsService become a workspaces domain with these sub-areas, all reached through WorkspacesFacade:
WorkspaceApiController to workspaces/api. Move permission checks into the facade, using RequestContext.domains/workspaces/data. Replace the Kysely reads for the settings, teams and workspace switcher.ActorContext and KeyContext ThreadLocal usage in these paths.Tests pass (including Testcontainers), and the ArchUnit store and ESLint suppressions shrink.
Plan (branch par-417-workspaces, PR to rebuild).
Backend workspaces/{model,persistence,usecase,api,mcp} behind one WorkspacesFacade, copying the labels domain:
workspace.invite.email, handler logs/no-ops with a TODO when no mail transport) and invite links (create/revoke/preview/accept)RequestContext gets a credentialScope; a verified user acting through an API key gets role permissions INTERSECT key scope. WorkspaceAccessService is the single copy of the rule; legacy PermissionService delegates to it, and the workspaces paths no longer touch ActorContext/KeyContext. Tests both ways (read-only key + admin user cannot write; full key + read-only member cannot admin).WorkspaceAdminService (not in this ticket's list; they belong to their own domains).
Web domains/workspaces/data/{server,hooks}: settings, teams page, switcher, sidebar teams, members, pins from the API; prune suppressions.PR open: https://github.com/ParadauxIO/taskl.ie/pull/48 (against rebuild). Workspaces domain behind WorkspacesFacade, permission-intersection fix with tests, V38/V39 migrations, web domain data layer, sidebar teams and server-side pins. ArchUnit store 1928 -> 1716, ESLint suppressions 89 -> 74. Waiting on CI.
Orchestrator review of #48: approved apart from the points below, so I've sent it back.
Must fix: rebase onto rebuild (PAR-414 merged as #47). Expect conflicts in RequestContext (keep both privateTeams and credentialScope), WorkspaceAccessService and AccessStore, and in the issue pages.
Email invites: any signed-in user holding the token can currently accept one.
Outbox: write a note in the code that delivered workspace.invite.email outbox payloads must be purged when the mail sender lands, because they contain the raw invite token.
Looks good:
Member.mayGrant.Merged into rebuild as #48 (bd60ed4) after the rebase onto PAR-414.
InviteEmailHandler notes that outbox payloads must be purged once the mail sender lands, because they contain the raw invite token.Ratchet compared with rebuild before this merge: ArchUnit store 1865 → 1653, ESLint suppressions 76 → 61. Handoffs to PAR-384 (settings UI) and PAR-383 (onboarding) are in the report above.
Added requirement (from the PAR-412 review): when the workspaces facade starts authorising from
RequestContext, the effective permissions for a verified user acting through an API key must be the user's role permissions ∩ the key's scope. TodayTenantBearerFilterassigns the key's permissions outright (platform/security).PermissionServiceread fromRequestContextinstead ofActorContext/KeyContext.