Files
CapaKraken/docs/route-access-matrix.md

14 KiB

Route Access Matrix

Date: 2026-03-30 Purpose: Make high-sensitivity API audiences explicit and reduce ambiguous protectedProcedure usage on broad read routes.

Audience Classes

  • self-service: authenticated users can only read or mutate data that belongs to their linked resource or account
  • authenticated-safe-lookup: authenticated users can access a deliberately narrow, identity-safe lookup surface
  • resource-overview: users with viewAllResources or manageResources
  • planning-read: users with viewPlanning
  • controller-finance: controller, manager, or admin through controllerProcedure
  • manager-write: manager or admin through managerProcedure
  • admin-only: admin through adminProcedure

Current Classification

packages/api/src/router/resource.ts

  • getMyResource: self-service
  • getById, getByEid, getHoverCard, getByIdentifier, getByIdentifierDetail, resolveByIdentifier, getChargeabilitySummary: self-service unless the caller also has resource-overview
  • directory: authenticated-safe-lookup
  • chapters: authenticated-safe-lookup
  • listSummaries, listSummariesDetail, listStaff, resolveResponsiblePersonName: resource-overview
  • getSkillsAnalytics, searchBySkills, listWithUtilization, getChargeabilityStats, getSkillMarketplace: controller-finance
  • importSkillMatrix: self-service
  • create, update, deactivate, batch update, imports for other users: manager-write or admin-only

packages/api/src/router/project.ts

  • resolveByIdentifier, searchSummaries, getByIdentifier: planning-read
  • searchSummariesDetail, list, getById, getByIdentifierDetail, getShoringRatio, listWithCosts: controller-finance
  • create, update, status changes, cover mutations: manager-write
  • delete and batch delete: admin-only
  • isImageGenConfigured, isDalleConfigured: authenticated low-risk configuration checks

packages/api/src/router/timeline.ts

  • getMyEntriesView, getMyHolidayOverlays: self-service
  • timeline-wide planning reads and shift previews: controller-finance
  • allocation updates, quick-assign, project shifts: manager-write

packages/api/src/router/allocation.ts

  • list, listView, listDemands, listAssignments, getAssignmentById, resolveAssignment, getDemandRequirementById, checkResourceAvailability, getResourceAvailabilityView, getResourceAvailabilitySummary: planning-read
  • mutations already sit behind manager-write

packages/api/src/router/dashboard.ts

  • all current routes are controller-finance

packages/api/src/router/role.ts

  • resolveByIdentifier: authenticated-safe-lookup
  • list, getByIdentifier, getById: planning-read
  • create, update, delete: manager-write

Reasoning:

  • resolveByIdentifier returns a narrow lookup shape without planning counts
  • list, getByIdentifier, and getById attach planning-linked usage counts, so they must not remain broad protectedProcedure reads

packages/api/src/router/scenario.ts

  • getProjectBaseline: planning-read plus explicit viewCosts

Reasoning:

  • the route combines staffing baseline data with commercial totals, so both planning and cost audiences are required

packages/api/src/router/estimate.ts

  • list: controller-finance
  • drafting, versioning, export generation, and approval writes: manager-write

packages/api/src/router/comment.ts

  • list, listMentionCandidates, count, create, resolve, delete: entity-scoped

Reasoning:

  • comments must inherit the audience of the backing entity, not the comment row itself
  • supported entity types are currently estimate and resource
  • estimate comments inherit the estimate workspace audience: controller, manager, or admin
  • resource comments inherit the resource detail audience: self-service for the caller's own linked resource, plus broad access for users who already have resource overview visibility
  • mention autocomplete uses the same entity-scoped access check instead of reusing assignment-oriented user directory routes
  • the registry keeps router policy, assistant metadata, and web comment targets on the same supported-entity definition
  • future entity types must be added through an explicit registry with per-entity access checks, assistant parity, and router tests in the same slice

packages/api/src/router/system-role-config.ts

  • all reads and writes: admin-only

Reasoning:

  • system role defaults define the effective permission model and therefore belong to the smallest operational audience

packages/api/src/router/settings.ts

  • getSystemSettings, updateSystemSettings, connection tests, getAiConfigured: admin-only

Reasoning:

  • even the boolean AI readiness check leaks whether admin-managed infrastructure is wired and available
  • the route has no current web consumer outside admin operations, so narrowing it does not block normal user workflows

packages/api/src/router/country.ts

  • list, resolveByIdentifier, getCityById: authenticated-safe-lookup
  • getByIdentifier, getById: resource-overview
  • create, update, metro-city writes: admin-only

Reasoning:

  • minimal country lookups are needed broadly for forms, filters, and location resolution
  • detailed country reads include metro-city detail plus _count.resources, so they should align with broad people-directory visibility

packages/api/src/router/org-unit.ts

  • resolveByIdentifier: authenticated-safe-lookup
  • list, getTree, getByIdentifier, getById: resource-overview
  • create, update, deactivate: admin-only

Reasoning:

  • resolveByIdentifier stays narrow enough for low-risk lookup flows
  • list and especially getTree expose the internal org hierarchy, parent links, sort order, and structure metadata, so they should not remain broad authenticated reads
  • detailed org-unit reads also expose _count.resources and parent/child context that maps the staffing structure

packages/api/src/router/client.ts

  • resolveByIdentifier: authenticated-safe-lookup
  • list, getTree, getByIdentifier, getById: planning-read
  • create and update: manager-write
  • delete: admin-only

Reasoning:

  • resolveByIdentifier returns a deliberately narrow lookup shape for code/name resolution
  • list already exposes _count.children and _count.projects, and getTree reveals the full client hierarchy used in planning and reporting flows
  • detailed client reads add parent/child structure plus project counts, so they should align with the explicit planning audience instead of broad authenticated access

packages/api/src/router/utilization-category.ts

  • list, getById: planning-read
  • create and update: admin-only

Reasoning:

  • the categories feed project configuration and planning/reporting workflows instead of broad self-service screens
  • getById includes _count.projects, so the detailed read should not remain a generic authenticated route

packages/api/src/router/management-level.ts

  • listGroups, getGroupById: planning-read
  • create, update, delete: admin-only

Reasoning:

  • management-level groups carry chargeability targets and resource-linked counts that feed planning and reporting workflows, so they should not stay on broad authenticated reads
  • the list is consumed by resource editing, reporting filters, and admin configuration, which all fit the explicit planning audience better than generic protectedProcedure

packages/api/src/router/blueprint.ts

  • listSummaries, list, getById, getByIdentifier, getGlobalFieldDefs: planning-read
  • resolveByIdentifier: authenticated-safe-lookup
  • create, update, delete, global-flag writes: admin-only

Reasoning:

  • listSummaries exposes _count.projects, so the assistant-facing summary list should not remain a broad authenticated read
  • resolveByIdentifier already returns a narrow lookup shape suitable for low-risk name/id resolution
  • the broader blueprint reads expose full template configuration such as field definitions, defaults, and validation rules that belong to planning workflows rather than generic authenticated access
  • getGlobalFieldDefs aggregates active global field definitions across blueprints, so it belongs with the same planning configuration audience rather than a broad authenticated read

packages/api/src/router/holiday-calendar.ts

  • listCalendars, listCalendarsDetail, getCalendarByIdentifier, getCalendarByIdentifierDetail, getCalendarById: admin-only
  • create, update, delete calendar and entry mutations: admin-only
  • previewResolvedHolidays, previewResolvedHolidaysDetail, resolveHolidays, resolveHolidaysDetail: authenticated-safe-lookup
  • resolveResourceHolidays, resolveResourceHolidaysDetail: self-service for the caller's own resource, with elevated cross-resource reads for manager and admin roles

Reasoning:

  • the calendar catalog is currently consumed in the web app only by the admin vacation editor, so broad authenticated reads expose internal configuration without a product need
  • region-based resolution helpers remain a narrow lookup surface because callers provide the location context directly instead of enumerating internal resource data
  • resource-based holiday resolution derives sensitive location context from a specific employee record, so it must follow the same self-service ownership model as other resource-scoped absence reads

packages/api/src/router/entitlement.ts

  • getBalance, getBalanceDetail: self-service for the caller's own resource, with elevated cross-resource reads for controller, manager, and admin roles
  • get, set, getYearSummary, getYearSummaryDetail: manager-write
  • bulkSet: admin-only

Reasoning:

  • regular users can inspect only their own holiday-aware balance, and the route enforces that by checking resource ownership before loading entitlement data
  • cross-resource balance reads and year summaries are operational planning and approval workflows, so they stay with controller/manager/admin audiences rather than broad authenticated access
  • bulk entitlement changes affect many users at once and should remain restricted to the smallest administrative audience

packages/api/src/router/vacation.ts

  • previewRequest, list, getById, getForResource, getTeamOverlap, getTeamOverlapDetail, cancel: self-service for the caller's own resource, with elevated cross-resource reads for manager and admin roles
  • create: self-service for the caller's own resource, with elevated creation for manager and admin roles
  • approve, reject, getPendingApprovals, updateStatus approval paths: manager-write
  • batchCreatePublicHolidays: admin-only

Reasoning:

  • the employee-facing vacation flows are valid self-service features, but they must not reveal holiday context, overlap data, or request details for arbitrary resources
  • manager and admin roles already handle approval and operational cross-resource workflows, so they retain broader access where the route logic explicitly allows it
  • bulk public-holiday generation changes organization-wide absence data and therefore belongs to the smallest administrative audience

packages/api/src/router/notification.ts

  • list, unreadCount, markRead, task detail/status routes, reminder routes, and delete: self-service
  • create, createBroadcast, listBroadcasts, getBroadcastById, createTask, assignTask: manager-write

Reasoning:

  • the self-service surface is already constrained to the caller's own notifications, reminders, tasks, or assignee visibility
  • broadcast and task-assignment flows can affect other users and organization-wide messaging, so they must stay on explicit manager-or-admin procedures

packages/api/src/router/user.ts

  • me, dashboard layout/preferences, favorites, MFA setup/status: self-service
  • listAssignable: manager-write
  • list, activeCount, create/update role and permissions, resource linking, getEffectivePermissions, disableTotp: admin-only
  • verifyTotp: public for the login flow

Reasoning:

  • self-service user routes only expose or mutate the authenticated account's own preferences and MFA state
  • listAssignable is an operational lookup for delegation and assignment flows, which fits manager and admin audiences
  • user administration and effective-permission inspection expose high-sensitivity identity and authorization state and therefore should remain admin-only

packages/api/src/router/assistant.ts

  • listPendingApprovals: self-service
  • chat: authenticated shell with tool-level audience enforcement

Reasoning:

  • listPendingApprovals reads pending approvals by ctx.dbUser.id, so it is a self-service view of the caller's own approval queue
  • chat requires authentication, but the effective data audience is enforced by assistant tool selection and backing router permissions rather than by a single broad router audience on the chat endpoint itself

Assistant Parity Rule

  • assistant tool visibility must never widen the audience of the backing router
  • router audience is the source of truth; assistant gating mirrors it
  • when a route becomes narrower, update assistant visibility in the same hardening slice
  • search_resources must follow resourceOverviewProcedure, not broad authenticated access
  • search_by_skill must follow controllerProcedure, not broad authenticated or planning-only access
  • if assistant-tools.ts already has unrelated local edits, prefer updating packages/api/src/router/assistant.ts and parity tests first instead of mixing concerns into the tool implementation file

Rollout Discipline

For audience-scoping changes, use this order:

  1. narrow the backing router procedure first
  2. add or tighten authorization tests on the router
  3. align assistant visibility in packages/api/src/router/assistant.ts
  4. update assistant parity tests
  5. ship in small isolated commits so regressions can be reverted without undoing unrelated hardening

Immediate Follow-Ups

  • monitor whether viewPlanning should later split into narrower project-read vs allocation-read audiences
  • split allocation further into narrower future audiences where resource-capacity and staffing-demand reads diverge
  • add authorization tests for every route listed above so the matrix is CI-enforced, not just documented