10 KiB
10 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 accountauthenticated-safe-lookup: authenticated users can access a deliberately narrow, identity-safe lookup surfaceresource-overview: users withviewAllResourcesormanageResourcesplanning-read: users withviewPlanningcontroller-finance: controller, manager, or admin throughcontrollerProceduremanager-write: manager or admin throughmanagerProcedureadmin-only: admin throughadminProcedure
Current Classification
packages/api/src/router/resource.ts
getMyResource:self-servicegetById,getByEid,getHoverCard,getByIdentifier,getByIdentifierDetail,resolveByIdentifier,getChargeabilitySummary:self-serviceunless the caller also hasresource-overviewdirectory:authenticated-safe-lookuplistSummaries,listSummariesDetail,listStaff,resolveResponsiblePersonName:resource-overviewgetSkillsAnalytics,searchBySkills,listWithUtilization,getChargeabilityStats,getSkillMarketplace:controller-finance- create, update, deactivate, batch update, imports for other users:
manager-writeoradmin-only
packages/api/src/router/project.ts
resolveByIdentifier,searchSummaries,getByIdentifier:planning-readsearchSummariesDetail,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-lookuplist,getByIdentifier,getById:planning-read- create, update, delete:
manager-write
Reasoning:
resolveByIdentifierreturns a narrow lookup shape without planning countslist,getByIdentifier, andgetByIdattach planning-linked usage counts, so they must not remain broadprotectedProcedurereads
packages/api/src/router/scenario.ts
getProjectBaseline:planning-readplus explicitviewCosts
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/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-lookupgetByIdentifier,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-lookuplist,getTree,getByIdentifier,getById:resource-overview- create, update, deactivate:
admin-only
Reasoning:
resolveByIdentifierstays narrow enough for low-risk lookup flowslistand especiallygetTreeexpose 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.resourcesand parent/child context that maps the staffing structure
packages/api/src/router/client.ts
resolveByIdentifier:authenticated-safe-lookuplist,getTree,getByIdentifier,getById:planning-read- create and update:
manager-write - delete:
admin-only
Reasoning:
resolveByIdentifierreturns a deliberately narrow lookup shape for code/name resolutionlistalready exposes_count.childrenand_count.projects, andgetTreereveals 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
getByIdincludes_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:planning-readresolveByIdentifier:authenticated-safe-lookup- create, update, delete, global-flag writes:
admin-only
Reasoning:
listSummariesexposes_count.projects, so the assistant-facing summary list should not remain a broad authenticated readresolveByIdentifieralready 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
packages/api/src/router/holiday-calendar.ts
listCalendars,listCalendarsDetail,getCalendarByIdentifier,getCalendarByIdentifierDetail,getCalendarById:admin-only- create, update, delete calendar and entry mutations:
admin-only - holiday resolution and preview helpers remain unchanged in this rollout
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
- narrowing just the catalog reads keeps the hardening slice small while avoiding regressions in shared holiday-resolution helpers used by vacation, timeline, and assistant flows
packages/api/src/router/notification.ts
list,unreadCount,markRead, task detail/status routes, reminder routes, anddelete:self-servicecreate,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-servicelistAssignable:manager-writelist,activeCount, create/update role and permissions, resource linking,getEffectivePermissions,disableTotp:admin-onlyverifyTotp:publicfor the login flow
Reasoning:
- self-service user routes only expose or mutate the authenticated account's own preferences and MFA state
listAssignableis 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
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_resourcesmust followresourceOverviewProcedure, not broad authenticated accesssearch_by_skillmust followcontrollerProcedure, not broad authenticated or planning-only access- if
assistant-tools.tsalready has unrelated local edits, prefer updatingpackages/api/src/router/assistant.tsand parity tests first instead of mixing concerns into the tool implementation file
Rollout Discipline
For audience-scoping changes, use this order:
- narrow the backing router procedure first
- add or tighten authorization tests on the router
- align assistant visibility in
packages/api/src/router/assistant.ts - update assistant parity tests
- ship in small isolated commits so regressions can be reverted without undoing unrelated hardening
Immediate Follow-Ups
- monitor whether
viewPlanningshould later split into narrower project-read vs allocation-read audiences - split
allocationfurther 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