docs(master): add Phase 8 (access control + unified permission-aware routing); route naming map
This commit is contained in:
@@ -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 (2–7) in depth
|
||||
8. The phases (2–8) 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 (2–7) in depth
|
||||
## 8. The phases (2–8) 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user