Development
Department Scope Pattern
Every new authenticated route must apply department isolation via forUser() from src/lib/department-scope.ts. forDepartment() still works as a compatibility alias, but forUser() exposes the permission and multi-department scope helpers.
Use the smallest scope helper that matches the data shape:
const scope = forUser(request.user);
// Model has departmentId directly.
await prisma.knowledgeBase.findMany({
where: { ...scope.directWhere() },
});
// Model is scoped through Namespace.
await prisma.agentTask.findMany({
where: { ...scope.nestedWhere('namespace') },
});
Global-scope users see all departments. Everyone else is restricted to scope.deptIds. Don't let a departmentId query parameter override scope.canSeeDept() or scope.directWhere().
For raw SQL that only supports a single department filter, derive filters from scope.departmentId and pass values as Prisma SQL parameters:
const departmentFilter = scope.departmentId
? Prisma.sql`AND kb."departmentId" = ${scope.departmentId}`
: Prisma.empty;
For multi-department scopes, use scope.deptIds and an IN (...) filter instead of assuming scope.departmentId is present.
Routes can also read request.departmentScope — src/plugins/department-scope.ts decorates the request after auth.
Route Checklist
- Add
fastify.authenticateto every non-public route. - Add RBAC middleware like
requirePermission()on routes that mutate privileged data. - Apply
forDepartment()orrequest.departmentScopebefore querying department-scoped records. - Use
sendError()for explicit failures so responses use{ error, message, statusCode, details? }. - Add route schemas so
/docsstays aligned with runtime behavior. - Extend department-isolation tests when a new endpoint exposes department-scoped data.
Agent-Tasks Pipeline
Inbound channel handlers should not own RAG, trust, or HITL decisions. For a new channel:
- parse the transport payload;
- find or create the user, conversation, and inbound message;
- create an
AgentTask; - enqueue the
agent-tasksqueue; - provide an outbound dependency so the agent runner can send approved or bypassed replies.
The agent runner worker owns inject_profile, rag_search, generate, and confidence_check. See ADR-001.