docs(adr-0026): promote to accepted + resolve open questions
CI / commits (pull_request) Successful in 2m49s
CI / check (pull_request) Successful in 3m25s
CI / scan (pull_request) Successful in 3m37s
CI / a11y (pull_request) Successful in 3m25s
Docs site / build (pull_request) Successful in 3m32s
CI / perf (pull_request) Successful in 5m33s

ADR-0026 was reviewed; status flips proposed -> accepted with the
two open questions closed:

1. Person dedup at first sign-in. v1 looks up by entraOid only;
   no email-based merging. Two distinct people genuinely can
   share an email, and silent auto-merge would corrupt the
   golden record. ADR-0027 introduces operator-confirmed
   reconciliation; v1 takes the safe-by-default posture.

2. Org-hierarchy seed placement. Region / Delegation /
   Etablissement codes ship inline in the Prisma migration as
   reference data — slow-moving, environment-agnostic, single
   atomic deploy. ADR-0027's Pléiades sync supersedes the
   inline seed when it ships. user_scopes for the 19 test
   personas stay in prisma/seed.ts (implementation PR 2) since
   those are test-tenant-specific data.

Consequences section reworked to reflect the new dedup choice:
the email-fragility Bad-because is removed; a new Bad-because
documents the v1 cost (a self-signed-in Person + a later
Pléiades-imported Person look like duplicates until ADR-0027's
operator merge flow resolves them — observable, not silent).

CLAUDE.md updated:
- Roll-up bumped 0001 -> 0025 -> 0001 -> 0026.
- New Architecture bullet for the portal-side data model.
- Roadmap entry on @RequireScope Prisma resolver updated to
  reflect ADR-0026 (accepted) and the two-PR implementation
  phasing.

Index row flipped to accepted.
This commit is contained in:
Julien Gautier
2026-05-24 05:46:48 +02:00
parent abd1f91809
commit 07cb466d51
3 changed files with 33 additions and 16 deletions
+3 -2
View File
@@ -54,13 +54,14 @@ The structural, security, observability, and quality choices are recorded as ADR
- **Charts + dashboards:** `D3 + Observable Plot` wrapped in `libs/shared/charts/`, one Angular component per chart type (bar, donut, line, stacked-bar, …). A11y baked in by the lib (SVG `<title>`/`<desc>`, `<details>` tabular fallback, colour-blind-safe palettes, AA-contrast text, `prefers-reduced-motion` gate). Bundle stays under [ADR-0017](docs/decisions/0017-performance-budgets-lighthouse-ci.md)'s lazy-chunk cap via per-`d3-*` module tree-shaking. Future bespoke visualisations land in raw D3 inside the same lib — see [ADR-0023](docs/decisions/0023-charts-d3-observable-plot.md).
- **AI service relay:** dedicated `apf-ai-service` repo (ASP.NET Core, Microsoft Agent Framework) consumed via native gRPC HTTP/2 only — proto contract vendored under `apps/portal-bff/src/grpc/proto/apf-ai/` with `ts-proto` codegen committed alongside. BFF dials with `@grpc/grpc-js` (h2c in dev, h2 + TLS in prod), bridges `ChatService.Chat` to `text/event-stream` for the SPA, exposes `RagService.Search` and `ModelsService.ListModels` as plain JSON endpoints. Identity travels as an unsigned `Principal` (subject, roles, attributes) in the proto body for the POC, hashed via the audit module's `HashUserIdService` so portal and AI service audit trails join on the same `actor_id_hash`. Production hardening (signed envelope vs mTLS) deferred — see [ADR-0024](docs/decisions/0024-ai-service-relay-grpc-sse-bridge.md).
- **Authorization model:** three orthogonal axes — **privileges** (Entra app roles, `Portal.*`), **functional roles** (Entra security groups → curated `apf-role-*` slug catalogue, 24 entries v1), **scopes** (portal-side `user_scopes` table, future Pléiades feed; kinds = `self / etablissement:<finess> / delegation:<dept> / region:<insee> / siege / unrestricted`). Composed at sign-in into a session-resident `Principal`; portal guards consume the structured shape, a deterministic `PrincipalProjector` flattens it to the AI-service `roles[]` contract. Replaces stargate's linear hierarchy. Catalogues are closed-set, drift gated by CI — see [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md).
- **Portal-side data model:** `Person` golden record (stable identity, can exist without a portal account — workforce pre-provisioning, dossier bénéficiaires, alumni) + `User` overlay (one-to-zero-or-one with Person, lazy-created on first OIDC callback in v1; carries portal-only state like `lastSignInAt`). `UserScope` table backs the ADR-0025 scope axis. Organisational hierarchy `Region` / `Delegation` / `Etablissement` keyed on INSEE / dept / FINESS codes (externally meaningful, matches scope literals on the wire). Facets (Salarié / Élu / Adhérent / Bénéficiaire) + Pléiades / Acteurs+ sync deferred to a follow-up ADR-0027 — see [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md).
- **Runtime:** Node.js latest LTS major.
## Repository status
The Nx workspace is **scaffolded and operational**. The three apps (`portal-shell`, `portal-admin`, `portal-bff`) and the seven lib roots (`libs/feature/auth`, `libs/shared/auth`, `libs/shared/charts`, `libs/shared/state`, `libs/shared/tokens`, `libs/shared/ui`, `libs/shared/util`) are in place; CI runs `format:check / lint / test / build` plus the ADR-0025 catalogue-drift gate on every PR.
ADRs 0001 → 0025 are accepted and cover the structural, security, observability, quality, i18n, admin-app, docs-site, charts, AI-relay, and authorization choices. **Shipped on `main`:**
ADRs 0001 → 0026 are accepted and cover the structural, security, observability, quality, i18n, admin-app, docs-site, charts, AI-relay, authorization, and portal-side data model choices. **Shipped on `main`:**
- **Phase-1 foundation** — Nx workspace, Angular `portal-shell`, NestJS `portal-bff`, Prisma + Postgres, Pino + OpenTelemetry, Husky/lint-staged/commitlint, Gitea Actions CI.
- **Phase-2 auth + audit + security** — OIDC Auth Code + PKCE via MSAL Node, Redis sessions with AES-256-GCM at rest, idle 30 min sliding + absolute 12 h hard ceiling, RP-initiated logout, double-submit CSRF, `audit.events` append-only schema with role-based grants, helmet + env-driven CORS allowlist + rate limiting + structured error envelope (see [ADR-0021](docs/decisions/0021-phase-2-security-baseline.md)).
@@ -74,7 +75,7 @@ ADRs 0001 → 0025 are accepted and cover the structural, security, observabilit
- `DownstreamApiClient` + OBO ([ADR-0014](docs/decisions/0014-downstream-api-access-obo-pattern.md)) — module scaffolded (`obo.strategy`, `signed-assertion.strategy`, JWKS publisher, encrypted token cache); no v1 consumer yet. Wires in when the first business route needs an Entra-protected API.
- `@RequireMfa()` step-up consumer routes ([ADR-0011](docs/decisions/0011-mfa-enforcement-entra-conditional-access.md)) — guard + decorator shipped; awaiting first sensitive route that needs explicit freshness enforcement beyond the Conditional Access baseline.
- **`@RequireScope` Prisma-backed resolver + first consumer surface** — `StubScopeResolver` returns `unrestricted` for everyone in v1 per [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) §331. The real implementation lands with ADR-0026 (proposed: `Person` golden record + portal-side `User` + `user_scopes` table); the first scope-aware route surface follows in the same PR family.
- **`@RequireScope` Prisma-backed resolver + first consumer surface** — `StubScopeResolver` returns `unrestricted` for everyone in v1 per [ADR-0025](docs/decisions/0025-authorization-model-privileges-roles-scopes.md) §331. [ADR-0026](docs/decisions/0026-person-user-portal-data-model.md) (accepted) specifies the `Person` + `User` + `user_scopes` schema; implementation lands in two PRs — the Prisma migration + lazy provisioner, then `PrismaScopeResolver` + admin-UI scope-seeding screen + test-tenant seed.
- **Proto-drift CI gate for the AI relay** ([ADR-0024](docs/decisions/0024-ai-service-relay-grpc-sse-bridge.md)) — asserts the vendored `apf-ai-service` proto files stay in lockstep with the upstream contract.
- **Admin app — CMS & menu management** ([ADR-0020](docs/decisions/0020-portal-admin-app.md)) — multilingual static-page editor + navigation menu builder. The user-list + audit-log-viewer modules already exist; the CMS/menu pair is the remaining v1 module scope.
- **Strategic security baseline ADR** — separate from the implementation-level [ADR-0021](docs/decisions/0021-phase-2-security-baseline.md). Remains **paused** awaiting RSSI input on the OWASP ASVS reference level and adjacent frameworks (HDS, GDPR, possibly NIS 2). When it lands it will either confirm 0021 or supersede pieces of it.