163 lines
7.5 KiB
Markdown
163 lines
7.5 KiB
Markdown
# 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`
|
|
- `listSummaries`, `listSummariesDetail`, `listStaff`, `resolveResponsiblePersonName`: `resource-overview`
|
|
- `getSkillsAnalytics`, `searchBySkills`, `listWithUtilization`, `getChargeabilityStats`, `getSkillMarketplace`: `controller-finance`
|
|
- 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/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/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/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
|
|
|
|
## 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
|