c9a1e195fe
## Summary ADR-0025 was merged as `proposed` in #201. The test tenant (`apfrd.onmicrosoft.com`) has since been provisioned with the full role / user matrix — 4 privileges + 24 security groups + 19 users with all assignments per the persona table. The ADR is now the implementation reference, so this PR: 1. Promotes the ADR from `proposed` to `accepted`. 2. Syncs the document with what is actually in place — the privilege catalogue grows from 1 to 4 entries (the previously "anticipated future" privileges that the test tenant already has), and the persona matrix grows from 10 to 19 entries (so every one of the 24 functional-role groups has at least one member, closing the gap that prompted `notes/test-tenant-role-assignments.md` and `notes/entra-group-members.md`). 3. Records the Entra app-role GUIDs in a new "Provisioned in the test tenant" subsection for traceability — the GUIDs are stable IDs the implementation will need. 4. Updates the index + the `CLAUDE.md` roll-up. No code changes. No implementation skeleton — that lands in the next PR (proposed: `feat(libs/feature/auth): authorization types + Principal builder skeleton`). ## What lands | File | Change | |---|---| | `docs/decisions/0025-authorization-model-privileges-roles-scopes.md` | Frontmatter `status: proposed → accepted`; privilege catalogue extended from 1 to 4 entries; persona matrix rewritten from 10 to 19 entries; new `Provisioned in the test tenant (2026-05-20)` subsection capturing the four app-role GUIDs. | | `docs/decisions/README.md` | 0025 row: `proposed → accepted`. | | `CLAUDE.md` | Roll-up `0001 → 0024 accepted` → `0001 → 0025 accepted`; Architecture list grows an "Authorization model" bullet. | The two operator-facing notes files (`notes/test-tenant-role-assignments.md`, `notes/entra-group-members.md`) are gitignored and unchanged — they served their purpose during tenant provisioning and remain as runbooks. ## Notes for the reviewer - **Why promote now rather than couple with the first implementation PR (the pattern from #194 → #195 → #196).** The Entra-side provisioning *is* the implementation for this ADR — the next PR is a portal-side reflection of decisions that are already concrete in the tenant. Promoting now keeps the document honest about what the test tenant runs against. - **Why all 4 privileges enter the v1 catalogue.** The ADR originally shipped `Portal.Admin` as the sole v1 entry and listed the other three under "anticipated near-future entries". The test tenant has all four; the catalogue should match. The three new entries are explicitly marked "provisioned; consumer surface deferred" so a reader does not look for non-existent surfaces. - **Why 19 personas, not the cleaner 24 (one per role).** Several APF jobs genuinely combine multiple roles (RH siège often handles paie + compta; DPO often wears the quality officer hat; local delegates often grow out of volunteer roles). Densifying these existing personas is more faithful to real APF org structure than inventing 14 single-role test users. Distinct personas were created where the *scenario* is distinct (scope variations, governance positions) — see the matrix in the ADR for the breakdown. - **Why `apf-role-partenaire` stays empty in v1.** Placeholder per the original ADR; no consuming surface to test against. The group exists in Entra so the schema is locked, but a user assignment without a guard to exercise would be theatre. The first partner-facing feature adds the user. - **GUIDs in the ADR.** The four app-role GUIDs are repo-stable identifiers; recording them in the ADR keeps the document self-sufficient when a future contributor opens it without access to the Entra portal. The 24 functional-role group GUIDs are tenant-specific and stay in a gitignored `infra/test-tenant.entra.json` once the implementation PR creates it — referenced by name only in `libs/feature/auth/src/lib/entra-group-to-role.ts`. - **No `prettier --write` damage.** The persona matrix is a wide table; Prettier sometimes reflows wide markdown tables. The diff is clean — Prettier left the table intact on this run. ## Test plan - [x] `prettier --check docs/decisions/0025-authorization-model-privileges-roles-scopes.md` — passes (hook ran on commit). - [x] Markdown links inside the ADR still resolve (`0020`, references to other ADRs unchanged). - [x] Status row in `docs/decisions/README.md` reflects `accepted`. - [x] `CLAUDE.md` roll-up line + Architecture list updated; no other instances of "0024" needed bumping. - [ ] **Review focus** — the expanded privilege catalogue (4 entries, one of them already had a guard, three new ones documented), the 19-entry persona matrix, the "Provisioned in the test tenant" subsection (especially the GUIDs — make sure none was mistyped from `notes/role-user.txt`). ## What's next With ADR-0025 accepted, the implementation phasing recorded in its `§More Information` opens: 1. **PR — `libs/feature/auth` extension** : `authorization.types.ts` (catalogue constants for the 4 privileges + 24 functional roles + 6 scope kinds), `entra-group-to-role.ts` (slug map skeleton with placeholder GUIDs ; the operator drops real GUIDs into `infra/test-tenant.entra.json` separately), `Principal` builder hook on the OIDC callback, no new guards yet. 2. **PR — `@RequireRole` + `@RequireScope` decorators + guard tests** : real composition tests against the 19 personas. 3. **PR — drift CI gate** : ESLint custom rule asserting every `@RequireRole('...')` literal in code is in the catalogue. 4. **PR — `prisma/seed.ts` for `user_scopes`** : depends on the `Person` + `User` schema (proposed ADR-0026). --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #205
72 lines
10 KiB
Markdown
72 lines
10 KiB
Markdown
# Architectural Decision Records
|
||
|
||
This project records architecturally-significant decisions as **ADRs** in the [MADR 4.0.0](https://github.com/adr/madr) format. References: [adr.github.io](https://adr.github.io/).
|
||
|
||
## Why ADRs
|
||
|
||
ADRs capture the _why_ behind a decision — context, drivers, options considered, trade-offs accepted — at the moment the decision is made. They make architecture reviewable, onboarding faster, and prevent the same debate from being re-litigated later.
|
||
|
||
## Conventions
|
||
|
||
- **Format:** MADR 4.0.0. Start from [template.md](template.md).
|
||
- **Filename:** `NNNN-kebab-case-title.md`, e.g. `0007-adopt-tailwind-for-design-tokens.md`.
|
||
- **Numbering:** globally sequential 4-digit prefix. Numbers never reset, never get reused — even when an ADR is superseded or deprecated.
|
||
- **Layout:** flat folder. ADRs are not nested into category subfolders; topical organization happens via tags.
|
||
- **Tags:** every ADR carries a `tags:` array in the MADR frontmatter, drawn from the [tag vocabulary](#tag-vocabulary) below. An ADR may carry several tags. Propose new tags (or renames) in the same PR that needs them; never invent ad-hoc tags inline.
|
||
- **Status lifecycle:** `proposed` → `accepted` → optionally `deprecated` or `superseded by [ADR-NNNN](NNNN-other.md)`. Update the YAML frontmatter; never delete an ADR.
|
||
- **Index maintenance:** every ADR addition or status change must update the [Index](#index) below in the same commit.
|
||
|
||
## When to write an ADR
|
||
|
||
Write one whenever a development decision is non-trivial: tool or library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change, or any choice that future contributors would benefit from understanding the _why_ of.
|
||
|
||
## Tag vocabulary
|
||
|
||
The vocabulary below is the source of truth. It is intentionally coarse — propose extensions only when an existing tag genuinely doesn't fit, and avoid overly narrow tags.
|
||
|
||
| Tag | Scope |
|
||
| ---------------- | ------------------------------------------------------------------- |
|
||
| `frontend` | UI, Angular, components, design system, client-side state |
|
||
| `backend` | API, BFF, server-side services |
|
||
| `security` | AuthN, AuthZ, sessions, CSP, dependency scanning, secret management |
|
||
| `performance` | Perf budgets, caching, bundle size, Lighthouse |
|
||
| `accessibility` | WCAG, a11y testing, keyboard, ARIA, contrast |
|
||
| `infrastructure` | CI/CD, hosting, deployment, runtime |
|
||
| `observability` | Logs, metrics, traces, correlation IDs, monitoring |
|
||
| `data` | Persistence, schemas, migrations, data flow |
|
||
| `process` | Team conventions, workflows, repo policy |
|
||
|
||
> _Status: starter vocabulary, to be refined as ADRs accumulate. Update this table whenever a tag is added, renamed, or retired._
|
||
|
||
## Index
|
||
|
||
ADRs are listed in numerical order. To slice by topic, filter on the `Tags` column.
|
||
|
||
| # | Title | Status | Tags | Date |
|
||
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------- | ---------- |
|
||
| [0001](0001-use-adrs-to-record-architectural-decisions.md) | Use ADRs to record architectural decisions | accepted | `process` | 2026-04-29 |
|
||
| [0002](0002-adopt-nx-monorepo-apps-preset.md) | Adopt Nx monorepo with the `apps` preset | accepted | `infrastructure`, `frontend`, `backend` | 2026-04-29 |
|
||
| [0003](0003-workspace-and-app-naming-convention.md) | Workspace and app naming convention | accepted | `process` | 2026-04-29 |
|
||
| [0004](0004-frontend-stack-angular-csr-zoneless-signals.md) | Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest | accepted | `frontend` | 2026-04-29 |
|
||
| [0005](0005-backend-stack-nestjs.md) | Backend stack — NestJS over Express, Fastify, Hono | accepted | `backend` | 2026-04-29 |
|
||
| [0006](0006-persistence-postgresql-prisma.md) | Persistence — PostgreSQL with Prisma | accepted | `data`, `backend` | 2026-04-29 |
|
||
| [0007](0007-pre-commit-hooks-and-conventional-commits.md) | Pre-commit hooks and Conventional Commits | accepted | `process` | 2026-04-29 |
|
||
| [0008](0008-identity-model-entra-workforce-dual-audience.md) | Identity model — multi-tenant Entra ID for workforce, dual-audience design for future External ID | accepted | `security`, `data` | 2026-04-29 |
|
||
| [0009](0009-auth-flow-oidc-pkce-msal-node.md) | Authentication flow — OIDC Authorization Code + PKCE via MSAL Node, BFF session pattern | accepted | `security`, `backend` | 2026-04-29 |
|
||
| [0010](0010-session-management-redis.md) | Session management — opaque session IDs in cookies, payload in self-hosted Redis with AES-GCM at rest | accepted | `security`, `backend`, `infrastructure` | 2026-04-29 |
|
||
| [0011](0011-mfa-enforcement-entra-conditional-access.md) | MFA enforcement — Entra ID Conditional Access baseline, BFF claim sanity-check, step-up hooks designed-in | accepted | `security` | 2026-04-29 |
|
||
| [0012](0012-observability-pino-opentelemetry.md) | Observability — Pino structured logs + OpenTelemetry tracing, W3C Trace Context propagation, stdout + collector | accepted | `observability`, `backend`, `frontend` | 2026-04-29 |
|
||
| [0013](0013-audit-trail-separated-postgres-append-only.md) | Audit trail — separated append-only Postgres schema, decoupled from app logs | accepted | `security`, `observability`, `data` | 2026-04-29 |
|
||
| [0014](0014-downstream-api-access-obo-pattern.md) | Downstream API access — On-Behalf-Of pattern, unified `DownstreamApiClient`, audience-aware authorization | accepted | `security`, `backend` | 2026-04-29 |
|
||
| [0015](0015-cicd-gitea-actions.md) | CI/CD pipeline — Gitea Actions, trunk-based + squash-merge, thin YAML over portable scripts | accepted | `infrastructure`, `process` | 2026-04-30 |
|
||
| [0016](0016-accessibility-baseline-wcag-aa-targeted-aaa.md) | Accessibility baseline — WCAG 2.2 AA + targeted AAA, Angular CDK + spartan-ng + Tailwind, APF panel testing | accepted | `accessibility`, `frontend`, `process` | 2026-04-30 |
|
||
| [0017](0017-performance-budgets-lighthouse-ci.md) | Performance budgets — Core Web Vitals + Lighthouse CI gates, bundle budgets, BFF p95/p99 SLOs | accepted | `performance`, `frontend`, `backend`, `process` | 2026-04-30 |
|
||
| [0018](0018-environment-configuration-strategy.md) | Environment configuration — Angular `environment.ts`, BFF env vars, audit pool split | accepted | `frontend`, `backend`, `infrastructure`, `process` | 2026-05-10 |
|
||
| [0019](0019-internationalisation-angular-localize.md) | Internationalisation — `@angular/localize`, build-time per-locale bundles, `/fr` + `/en` path-based routing | accepted | `frontend`, `accessibility`, `performance`, `process` | 2026-05-11 |
|
||
| [0020](0020-portal-admin-app.md) | `portal-admin` — dedicated SPA for portal administration, sharing the existing BFF | accepted | `frontend`, `backend`, `security`, `infrastructure`, `process` | 2026-05-11 |
|
||
| [0021](0021-phase-2-security-baseline.md) | Phase-2 security baseline — helmet, CORS allowlist, double-submit CSRF, rate limiting, structured error envelope | accepted | `security`, `backend` | 2026-05-13 |
|
||
| [0022](0022-docs-site-vitepress.md) | Documentation site — VitePress + Mermaid plugin, separate static deployment | accepted | `process`, `infrastructure` | 2026-05-15 |
|
||
| [0023](0023-charts-d3-observable-plot.md) | Charts + dashboards — D3 + Observable Plot wrapped in `libs/shared/charts` | accepted | `frontend`, `accessibility`, `performance` | 2026-05-16 |
|
||
| [0024](0024-ai-service-relay-grpc-sse-bridge.md) | AI service relay — vendored gRPC protos, NestJS gRPC client, SSE bridge to the SPA, POC unsigned principal | accepted | `backend`, `security`, `observability` | 2026-05-19 |
|
||
| [0025](0025-authorization-model-privileges-roles-scopes.md) | Authorization model — three orthogonal axes (privileges × functional roles × scopes), Entra-backed | accepted | `security`, `backend`, `data` | 2026-05-20 |
|