docs(master): add Phase 8 (access control + unified permission-aware routing); route naming map

This commit is contained in:
Satyam Rastogi
2026-05-29 05:23:37 +05:30
parent 16389dbca7
commit bb3a6a42e1
+30 -3
View File
@@ -14,7 +14,7 @@
5. Data architecture — current state
6. Data architecture — target unified model
7. Status: what's already done (Phase 1)
8. The phases (27) in depth
8. The phases (28) in depth
9. The project lifecycle state machine
10. The subcontractor task state machine
11. The estimate & inspection models
@@ -127,7 +127,7 @@ Field normalization map (kanban → project): `estimatedAmount`→`budget`/`appr
- **Login** — per-role person picker (expand role → pick person → one-click in); all role paths verified.
- **Earlier fixes** — Vite `/api/*` dev stub (no more `@vercel/kv` crash); owners can create map entries; animated leaderboard podium (count-up + staggered bottom-up bars); the `_Demo_Old` feature branch merged in (subcontractor, org-settings, commission features) with conflicts resolved.
## 8. The phases (27) in depth
## 8. The phases (28) in depth
### Phase 2 — Unify lead↔project + routing/naming *(planned; plan written)*
- **Current:** two data models, two detail pages, two route families, no join key (see §5).
@@ -161,6 +161,14 @@ Field normalization map (kanban → project): `estimatedAmount`→`budget`/`appr
- **Target:** a two-layer profile per person + a self-service User Details page + an owner Employee/Subcontractor Details view + a shared org skill catalog, all feeding LynkDispatch (see §13).
- **Why:** richer structured context yields more believable AI dispatch recommendations (investor wow), and the profile depth is what a prospect expects from a mature CRM.
### Phase 8 — Access control + unified permission-aware routing *(plan)*
- **Current:** a real permission framework already exists — `orgPermissions` (role × module → actions), `userPermissionOverrides` (per-person grant/deny), the `usePermissions().can(module, action)` hook (Owner→all; person-override wins; else role default), and `PermissionGate` for component-level gating — all editable in Org Settings. But the **routing layer ignores it**, and routing is **triplicated**: the same feature components are mounted under `/owner/…`, `/admin/…`, and `/emp/fa/…` with only `<ProtectedRoute allowedRoles={[...]}>` (a coarse role check), plus a `basePath` helper recomputing the role prefix everywhere to build links. `PERMISSION_MODULES` covers only 7 features; denied users bounce to `/`; feature segments are terse (`maps`, `kanban`).
- **Target (Option B — unified, permission-guarded):** **one route per feature, no role prefix**, with human-readable names: `/territory-map`, `/pipeline`, `/projects/:id`, `/leads/:id`, `/dispatch`, `/storm-intel`, `/estimates`, `/leaderboard`, `/pro-canvas`, `/org-settings`, `/subcontractor-tasks`, etc. A **permission-aware route guard** decides access: `can(module, 'view')` honoring per-person overrides (role is just one input via the role default). Role-specific **dashboards/landing stay distinct** (`/owner`, `/admin`, `/agent`, `/contractor`, `/vendor`, `/subcontractor` homes, or a `/dashboard` that resolves per role). This **removes the 3× triplication and the `basePath` logic** (links become absolute feature paths). **Expand `PERMISSION_MODULES`** to map every gated route to a module. **Hide nav items** a person can't `view` (in `Layout`); show a **styled "Access Restricted" page** on direct-URL hits to denied routes. Edit/delete stay gated **inside** pages via `PermissionGate` (read-vs-edit). **No seeded override examples** — capability built; exceptions configured live in Org Settings. `/emp/fa` is retired in favor of `/agent` for the landing.
- **Why:** the demo's two example asks — "all admins except one get feature X," "all agents except one are read-only" — already map to the data model (role default + a per-person grant/deny override; view-only = read-only). This phase makes that granularity **enforced at the route and reflected in the UI**, and simultaneously cleans the routes to one obvious, human-readable URL per feature — a credible enterprise-RBAC story plus a far tidier app.
- **Decisions:** Option B unified routes (no role prefix) + human-readable names; route refactor **folded into this phase** (reverses the earlier "no consolidation"); permission-aware guard (`can(view)` + overrides); hide-nav **and** restricted page; role-specific dashboards kept; no seeded overrides.
- **Acceptance:** every feature has exactly one route; the guard blocks a `userPermissionOverride`-denied user at the route (Access Restricted) and hides it in nav; allowed users see the view while edit controls respect `can(module,'edit')`; owner sees everything; no `basePath`/role-prefix duplication remains; every gated route maps to a `PERMISSION_MODULES` entry.
- **Note on Plan 2:** Plan 2 (unify) adds interim role-prefixed project routes (`/owner|/admin|/emp-fa/projects/:id`) so won-leads are viewable before Phase 8; Phase 8 then **consolidates all routes** (including projects) to the unified scheme. Accept the minor interim churn to keep each plan shippable.
## 9. The project lifecycle state machine
A `lifecycleStage` field per project drives a state machine; the coarse kanban stage (In-Progress/Complete) derives from it. Each stage maps to a project progress %:
@@ -236,7 +244,26 @@ These hold across all phases and are the acceptance backbone:
**Current (relevant routes):** `/owner/projects` + `/owner/projects/:projectId` (OwnerProjectDetail, owner-only); `/owner|admin|emp-fa/kanban` (KanbanPage); `/owner|admin|emp-fa/leads/:leadId` (LeadProjectPage); `/emp/fa/leads`, `/emp/fa/leads/new`. Kanban cards open a side **drawer**; the drawer's "More Details" is the only kanban→detail navigation, and it always goes to the lead page.
**Target:** add `/admin/projects/:projectId` and `/emp/fa/projects/:projectId` (and optionally the list routes) → the same rich detail page, viewable by owner/admin/field-agent. Won leads' "More Details" routes to `/…/projects/PRJ-2026-###`; pre-sale leads keep `/…/leads/:id`. Project ids become `PRJ-2026-###` throughout.
**Plan-2 interim target:** add `/admin/projects/:projectId` and `/emp/fa/projects/:projectId` → the same rich detail page, viewable by owner/admin/field-agent. Won leads' "More Details" routes to `/…/projects/PRJ-2026-###`; pre-sale leads keep `/…/leads/:id`. Project ids become `PRJ-2026-###` throughout. (These role-prefixed routes are interim; Phase 8 consolidates them.)
**Final target (Phase 8 — Option B, unified):** **one human-readable route per feature, no role prefix**, access decided by the permission guard:
| Feature | Final route | Old (triplicated) |
|---------|------------|--------------------|
| Territory map | `/territory-map` | `/owner/maps`, `/admin/maps`, `/emp/fa/maps` |
| Pipeline (kanban) | `/pipeline` | `/…/kanban` |
| Project detail | `/projects/:id` | `/owner/projects/:id` (+ interim admin/fa) |
| Lead detail (pre-sale) | `/leads/:id` | `/…/leads/:id` |
| LynkDispatch | `/dispatch` | `/…/dispatch` |
| Storm Intel | `/storm-intel` | `/…/storm-intel` |
| Estimates | `/estimates` | `/…/estimates` |
| Leaderboard | `/leaderboard` | `/admin/leaderboard` |
| ProCanvas | `/pro-canvas` | `/…/pro-canvas` |
| Org settings | `/org-settings` | `/owner/settings`, `/admin/settings` |
| Subcontractor tasks | `/subcontractor-tasks` | `/owner|/admin/subcontractor-tasks` |
| User details / profile | `/profile` | (new, Phase 7) |
Role-specific **dashboards/landing stay distinct**: `/owner`, `/admin`, `/agent` (replacing `/emp/fa`), `/contractor`, `/vendor`, `/subcontractor`. The permission-aware guard (`can(module,'view')` + per-person overrides) replaces `allowedRoles`-only checks; nav hides what a person can't view; denied direct-URL hits show a styled **Access Restricted** page; edit/delete stay gated inside pages via `PermissionGate`. The `basePath` triplication helper is removed (links become absolute feature paths).
## 16. Execution model (subagent-driven, parallel-safe)