Implements FED-010: Agent Spawn via Federation feature that enables spawning and managing Claude agents on remote federated Mosaic Stack instances via COMMAND message type. Features: - Federation agent command types (spawn, status, kill) - FederationAgentService for handling agent operations - Integration with orchestrator's agent spawner/lifecycle services - API endpoints for spawning, querying status, and killing agents - Full command routing through federation COMMAND infrastructure - Comprehensive test coverage (12/12 tests passing) Architecture: - Hub → Spoke: Spawn agents on remote instances - Command flow: FederationController → FederationAgentService → CommandService → Remote Orchestrator - Response handling: Remote orchestrator returns agent status/results - Security: Connection validation, signature verification Files created: - apps/api/src/federation/types/federation-agent.types.ts - apps/api/src/federation/federation-agent.service.ts - apps/api/src/federation/federation-agent.service.spec.ts Files modified: - apps/api/src/federation/command.service.ts (agent command routing) - apps/api/src/federation/federation.controller.ts (agent endpoints) - apps/api/src/federation/federation.module.ts (service registration) - apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint) - apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration) Testing: - 12/12 tests passing for FederationAgentService - All command service tests passing - TypeScript compilation successful - Linting passed Refs #93 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Common Guards and Decorators
This directory contains shared guards and decorators for workspace-based permission management in the Mosaic Stack API.
Overview
The permission system provides:
- Workspace isolation via Row-Level Security (RLS)
- Role-based access control (RBAC) using workspace member roles
- Declarative permission requirements using decorators
Guards
AuthGuard
Located in ../auth/guards/auth.guard.ts
Verifies user authentication and attaches user data to the request.
Sets on request:
request.user- Authenticated user objectrequest.session- User session data
WorkspaceGuard
Validates workspace access and sets up RLS context.
Responsibilities:
- Extracts workspace ID from request (header, param, or body)
- Verifies user is a member of the workspace
- Sets the current user context for RLS policies
- Attaches workspace context to the request
Sets on request:
request.workspace.id- Validated workspace IDrequest.user.workspaceId- Workspace ID (for backward compatibility)
Workspace ID Sources (in priority order):
X-Workspace-Idheader:workspaceIdURL parameterworkspaceIdin request body
Example:
@Controller("tasks")
@UseGuards(AuthGuard, WorkspaceGuard)
export class TasksController {
@Get()
async getTasks(@Workspace() workspaceId: string) {
// workspaceId is validated and RLS context is set
}
}
PermissionGuard
Enforces role-based access control using workspace member roles.
Responsibilities:
- Reads required permission from
@RequirePermission()decorator - Fetches user's role in the workspace
- Checks if role satisfies the required permission
- Attaches role to request for convenience
Sets on request:
request.user.workspaceRole- User's role in the workspace
Must be used after AuthGuard and WorkspaceGuard.
Example:
@Controller("admin")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class AdminController {
@RequirePermission(Permission.WORKSPACE_ADMIN)
@Delete("data")
async deleteData() {
// Only ADMIN or OWNER can execute
}
}
Decorators
@RequirePermission(permission: Permission)
Specifies the minimum permission level required for a route.
Permission Levels:
| Permission | Allowed Roles | Use Case |
|---|---|---|
WORKSPACE_OWNER |
OWNER | Critical operations (delete workspace, transfer ownership) |
WORKSPACE_ADMIN |
OWNER, ADMIN | Administrative functions (manage members, settings) |
WORKSPACE_MEMBER |
OWNER, ADMIN, MEMBER | Standard operations (create/edit content) |
WORKSPACE_ANY |
All roles including GUEST | Read-only or basic access |
Example:
@RequirePermission(Permission.WORKSPACE_ADMIN)
@Post('invite')
async inviteMember(@Body() inviteDto: InviteDto) {
// Only admins can invite members
}
@Workspace()
Parameter decorator to extract the validated workspace ID.
Example:
@Get()
async getTasks(@Workspace() workspaceId: string) {
// workspaceId is guaranteed to be valid
}
@WorkspaceContext()
Parameter decorator to extract the full workspace context.
Example:
@Get()
async getTasks(@WorkspaceContext() workspace: { id: string }) {
console.log(workspace.id);
}
@CurrentUser()
Located in ../auth/decorators/current-user.decorator.ts
Extracts the authenticated user from the request.
Example:
@Post()
async create(@CurrentUser() user: any, @Body() dto: CreateDto) {
// user contains authenticated user data
}
Usage Patterns
Basic Controller Setup
import { Controller, Get, Post, UseGuards } from "@nestjs/common";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
import { Workspace, Permission, RequirePermission } from "../common/decorators";
import { CurrentUser } from "../auth/decorators/current-user.decorator";
@Controller("resources")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class ResourcesController {
@Get()
@RequirePermission(Permission.WORKSPACE_ANY)
async list(@Workspace() workspaceId: string) {
// All members can list
}
@Post()
@RequirePermission(Permission.WORKSPACE_MEMBER)
async create(@Workspace() workspaceId: string, @CurrentUser() user: any, @Body() dto: CreateDto) {
// Members and above can create
}
@Delete(":id")
@RequirePermission(Permission.WORKSPACE_ADMIN)
async delete(@Param("id") id: string) {
// Only admins can delete
}
}
Mixed Permissions
Different endpoints can have different permission requirements:
@Controller("projects")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class ProjectsController {
@Get()
@RequirePermission(Permission.WORKSPACE_ANY)
async list() {
/* Anyone can view */
}
@Post()
@RequirePermission(Permission.WORKSPACE_MEMBER)
async create() {
/* Members can create */
}
@Patch("settings")
@RequirePermission(Permission.WORKSPACE_ADMIN)
async updateSettings() {
/* Only admins */
}
@Delete()
@RequirePermission(Permission.WORKSPACE_OWNER)
async deleteProject() {
/* Only owner */
}
}
Workspace ID in Request
The workspace ID can be provided in multiple ways:
Via Header (Recommended for SPAs):
// Frontend
fetch("/api/tasks", {
headers: {
Authorization: "Bearer <token>",
"X-Workspace-Id": "workspace-uuid",
},
});
Via URL Parameter:
@Get(':workspaceId/tasks')
async getTasks(@Param('workspaceId') workspaceId: string) {
// workspaceId extracted from URL
}
Via Request Body:
@Post()
async create(@Body() dto: { workspaceId: string; name: string }) {
// workspaceId extracted from body
}
Row-Level Security (RLS)
When WorkspaceGuard is applied, it automatically:
- Calls
setCurrentUser(userId)to set the RLS context - All subsequent database queries are automatically filtered by RLS policies
- Users can only access data in workspaces they're members of
See: docs/design/multi-tenant-rls.md for full RLS documentation.
Testing
Tests are provided for both guards:
workspace.guard.spec.ts- WorkspaceGuard testspermission.guard.spec.ts- PermissionGuard tests
Run tests:
npm test -- workspace.guard.spec
npm test -- permission.guard.spec
Error Handling
WorkspaceGuard Errors
ForbiddenException("User not authenticated")- No authenticated userBadRequestException("Workspace ID is required...")- No workspace ID providedForbiddenException("You do not have access to this workspace")- User is not a workspace member
PermissionGuard Errors
ForbiddenException("Authentication and workspace context required")- Missing user or workspace contextForbiddenException("You are not a member of this workspace")- User not found in workspaceForbiddenException("Insufficient permissions. Required: ...")- User role doesn't meet requirements
Migration Guide
Before (Manual Checks):
@Get()
async getTasks(@Request() req: any) {
const workspaceId = req.user?.workspaceId;
if (!workspaceId) {
throw new UnauthorizedException("Authentication required");
}
return this.tasksService.findAll(workspaceId);
}
After (Guard-Based):
@Get()
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
@RequirePermission(Permission.WORKSPACE_ANY)
async getTasks(@Workspace() workspaceId: string) {
return this.tasksService.findAll(workspaceId);
}
Benefits
✅ Declarative - Permission requirements are clear from decorators
✅ DRY - No repetitive auth/workspace checks in every handler
✅ Type-safe - Workspace ID is guaranteed to exist when using @Workspace()
✅ Secure - RLS context automatically set, defense in depth
✅ Testable - Guards are independently testable
✅ Maintainable - Permission changes in one place
Related Files
apps/api/src/lib/db-context.ts- RLS utility functionsdocs/design/multi-tenant-rls.md- RLS architecture documentationapps/api/prisma/schema.prisma- Database schema with role definitions