docs(development): refresh after phase-3a + add topology / trace diagrams #160
+145
-30
@@ -12,6 +12,8 @@ For decision rationale, see the [ADRs](decisions/). For onboarding the local env
|
|||||||
apf_portal/
|
apf_portal/
|
||||||
├── .gitea/workflows/ # CI pipelines (ADR-0015)
|
├── .gitea/workflows/ # CI pipelines (ADR-0015)
|
||||||
│ ├── ci.yml # per-PR + push to main: check / scan / commits / perf / a11y
|
│ ├── ci.yml # per-PR + push to main: check / scan / commits / perf / a11y
|
||||||
|
│ ├── docs-site.yml # build docs site on PR + push (ADR-0022)
|
||||||
|
│ ├── renovate.yml # daily Renovate run (cron 03:00 UTC)
|
||||||
│ └── security-scheduled.yml # weekly full-tree scan + prod Lighthouse
|
│ └── security-scheduled.yml # weekly full-tree scan + prod Lighthouse
|
||||||
├── .github/ # Nx AI-tooling skills, prompts, agents (Nx-managed)
|
├── .github/ # Nx AI-tooling skills, prompts, agents (Nx-managed)
|
||||||
├── .husky/ # local git hooks (ADR-0007)
|
├── .husky/ # local git hooks (ADR-0007)
|
||||||
@@ -19,34 +21,42 @@ apf_portal/
|
|||||||
│ └── commit-msg # → pnpm exec commitlint
|
│ └── commit-msg # → pnpm exec commitlint
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── portal-shell/ # Angular 21 SPA (zoneless, standalone, Signals, Vitest, SCSS)
|
│ ├── portal-shell/ # Angular 21 SPA (zoneless, standalone, Signals, Vitest, SCSS)
|
||||||
│ │ ├── public/ # static assets
|
│ │ ├── public/ # static assets (incl. /logos)
|
||||||
│ │ ├── src/ # entry, app config, routes, styles
|
│ │ ├── src/ # entry, app config, routes, styles, locales
|
||||||
│ │ ├── .postcssrc.json # Tailwind PostCSS plugin (required by @angular/build esbuild — postcss.config.js is ignored)
|
│ │ ├── .postcssrc.json # Tailwind PostCSS plugin (required by @angular/build esbuild — postcss.config.js is ignored)
|
||||||
│ │ └── project.json # Nx project config (build, serve, test, lint targets)
|
│ │ └── project.json # Nx project config (build, serve, test, lint targets)
|
||||||
│ ├── portal-shell-e2e/ # Playwright e2e for portal-shell
|
│ ├── portal-shell-e2e/ # Playwright e2e for portal-shell
|
||||||
|
│ ├── portal-admin/ # Angular 21 SPA (admin surface, distinct origin + session per ADR-0020)
|
||||||
|
│ │ ├── public/ # static assets (incl. /logos)
|
||||||
|
│ │ ├── src/ # entry, app config, routes, audit + users + profile pages
|
||||||
|
│ │ └── project.json
|
||||||
|
│ ├── portal-admin-e2e/ # Playwright e2e for portal-admin
|
||||||
│ ├── portal-bff/ # NestJS 11 BFF (Express adapter, ValidationPipe, Jest)
|
│ ├── portal-bff/ # NestJS 11 BFF (Express adapter, ValidationPipe, Jest)
|
||||||
│ │ ├── src/ # main, app module, controllers, services
|
│ │ ├── src/ # main, app module, controllers, services
|
||||||
│ │ ├── prisma/schema.prisma # Prisma 7 schema (postgresql)
|
│ │ ├── prisma/schema.prisma # Prisma 6.x schema (PostgreSQL) — pin documented in ADR-0006
|
||||||
│ │ ├── prisma.config.ts # Prisma 7 TS config (loads DATABASE_URL from .env)
|
|
||||||
│ │ ├── .env.example # env-vars catalog (committed); .env stays gitignored
|
│ │ ├── .env.example # env-vars catalog (committed); .env stays gitignored
|
||||||
│ │ └── project.json
|
│ │ └── project.json
|
||||||
│ └── portal-bff-e2e/ # Jest e2e for portal-bff
|
│ └── portal-bff-e2e/ # Jest e2e for portal-bff
|
||||||
├── libs/
|
├── libs/
|
||||||
│ ├── feature/<name>/ # vertical feature libs (e.g. feature-auth)
|
│ ├── feature/<name>/ # vertical feature libs (e.g. feature-auth)
|
||||||
│ └── shared/<scope>/ # cross-cutting libs (tokens, ui, util)
|
│ └── shared/<scope>/ # cross-cutting libs (state, tokens, ui, util)
|
||||||
├── docs/
|
├── docs/
|
||||||
│ ├── README.md # doc index
|
│ ├── README.md # doc index (git/IDE side — excluded from the docs site)
|
||||||
|
│ ├── index.md # VitePress home (Hero layout, see ADR-0022)
|
||||||
|
│ ├── architecture.md # cross-cutting Mermaid diagrams (C4, module boundaries, CI)
|
||||||
|
│ ├── development.md # this file
|
||||||
│ ├── decisions/ # ADRs (MADR 4.0.0)
|
│ ├── decisions/ # ADRs (MADR 4.0.0)
|
||||||
│ ├── setup/ # local-environment onboarding (Zsh, pnpm, Nx workspace)
|
│ ├── setup/ # local-environment onboarding (Zsh, pnpm, Nx workspace)
|
||||||
│ └── development.md # this file
|
│ └── .vitepress/ # VitePress config + build cache (gitignored)
|
||||||
├── notes/ # personal scratchpad (gitignored)
|
├── notes/ # personal scratchpad (gitignored)
|
||||||
├── CLAUDE.md # project rules + architecture summary
|
├── CLAUDE.md # project rules + architecture summary
|
||||||
├── commitlint.config.cjs # Conventional Commits config
|
├── commitlint.config.cjs # Conventional Commits config
|
||||||
├── eslint.config.mjs # workspace ESLint with module boundaries
|
├── eslint.config.mjs # workspace ESLint with module boundaries
|
||||||
├── lighthouserc.js # Lighthouse CI thresholds (ADR-0017)
|
├── lighthouserc.js # Lighthouse CI thresholds (ADR-0017)
|
||||||
├── nx.json # Nx workspace config
|
├── nx.json # Nx workspace config
|
||||||
├── package.json # workspace deps + scripts
|
├── package.json # workspace deps + scripts (incl. pnpm.overrides for transitive remediation)
|
||||||
├── pnpm-workspace.yaml # apps/* + libs/**
|
├── pnpm-workspace.yaml # apps/* + libs/**
|
||||||
|
├── renovate.json # Renovate config (groupings, dashboard, vuln alerts)
|
||||||
├── tsconfig.base.json # shared TS strict config
|
├── tsconfig.base.json # shared TS strict config
|
||||||
└── vitest.workspace.ts # Vitest workspace projects
|
└── vitest.workspace.ts # Vitest workspace projects
|
||||||
```
|
```
|
||||||
@@ -123,6 +133,46 @@ cp apps/portal-bff/.env.example apps/portal-bff/.env
|
|||||||
|
|
||||||
`./infra/local/dev.sh help` lists the rest of the verbs (`down`, `status`, `logs`, `stop`, `restart`, `exec`). Full reference — service inventory, port table, persistence, bootstrap re-run procedure — lives in [`infra/README.md`](../infra/README.md) → "Local-dev stack".
|
`./infra/local/dev.sh help` lists the rest of the verbs (`down`, `status`, `logs`, `stop`, `restart`, `exec`). Full reference — service inventory, port table, persistence, bootstrap re-run procedure — lives in [`infra/README.md`](../infra/README.md) → "Local-dev stack".
|
||||||
|
|
||||||
|
### Topology at a glance
|
||||||
|
|
||||||
|
How the pieces fit together once the stack is up — three Node dev servers on the host, the infrastructure containers behind Docker Compose, optional viewer profiles for inspection:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph host["Dev host (WSL2 / Linux / macOS)"]
|
||||||
|
direction TB
|
||||||
|
shell["portal-shell<br/><code>:4200</code>"]
|
||||||
|
admin["portal-admin<br/><code>:4300</code>"]
|
||||||
|
bff["portal-bff<br/><code>:3000</code>"]
|
||||||
|
docs["docs site<br/><code>:5173</code><br/>(VitePress)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph compose["infra/local/ (Docker Compose)"]
|
||||||
|
direction TB
|
||||||
|
pg[("Postgres<br/><code>:5432</code>")]
|
||||||
|
redis[("Redis<br/><code>:6379</code>")]
|
||||||
|
otel["OTel Collector<br/><code>:4318</code>"]
|
||||||
|
subgraph obs["observability profile"]
|
||||||
|
jaeger["Jaeger UI<br/><code>:16686</code>"]
|
||||||
|
end
|
||||||
|
subgraph dbt["dbtools profile"]
|
||||||
|
pgweb["pgweb<br/><code>:8081</code>"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shell -. "<code>/api/*</code>" .-> bff
|
||||||
|
admin -. "<code>/api/admin/*</code>" .-> bff
|
||||||
|
bff --> pg
|
||||||
|
bff --> redis
|
||||||
|
bff -- "OTLP" --> otel
|
||||||
|
shell -- "OTLP/HTTP<br/>spans" --> otel
|
||||||
|
admin -- "OTLP/HTTP<br/>spans" --> otel
|
||||||
|
otel --> jaeger
|
||||||
|
pgweb --- pg
|
||||||
|
```
|
||||||
|
|
||||||
|
The docs site lives off to the side — it has no runtime dependency on the BFF or the infra stack and only reads files under `docs/`. Same goes for the SPAs from `portal-shell` to `portal-admin`: each runs its own dev server with its own port, talks to the same BFF, but holds a **distinct session cookie** (per [ADR-0020](decisions/0020-portal-admin-app.md) §"Sessions — distinct from `portal-shell`").
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Daily commands
|
## 4. Daily commands
|
||||||
@@ -131,26 +181,27 @@ cp apps/portal-bff/.env.example apps/portal-bff/.env
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm nx serve portal-shell # http://localhost:4200 (Angular dev server)
|
pnpm nx serve portal-shell # http://localhost:4200 (Angular dev server)
|
||||||
|
pnpm nx serve portal-admin # http://localhost:4300 (Angular dev server)
|
||||||
pnpm nx serve portal-bff # http://localhost:3000/api (NestJS)
|
pnpm nx serve portal-bff # http://localhost:3000/api (NestJS)
|
||||||
```
|
```
|
||||||
|
|
||||||
Both can run in parallel in two terminals; the SPA proxies API calls to the BFF in dev.
|
All three can run in parallel in separate terminals; the SPAs proxy API calls to the BFF in dev. Each SPA holds its own cookie / session — signing in to one does not authenticate the other (per [ADR-0020](decisions/0020-portal-admin-app.md)).
|
||||||
|
|
||||||
### Test
|
### Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm nx test portal-shell # Vitest (single run; --configuration=watch for watch mode)
|
pnpm nx test portal-shell # Vitest (single run; --configuration=watch for watch mode)
|
||||||
|
pnpm nx test portal-admin # Vitest
|
||||||
pnpm nx test portal-bff # Jest
|
pnpm nx test portal-bff # Jest
|
||||||
|
|
||||||
pnpm nx run-many -t test # all projects
|
pnpm nx run-many -t test # all projects
|
||||||
pnpm nx affected -t test # only projects affected since main
|
pnpm nx affected -t test # only projects affected since main
|
||||||
```
|
```
|
||||||
|
|
||||||
Run a single Vitest file:
|
Run a single test file:
|
||||||
|
|
||||||
```bash
|
- **Vitest projects** (`portal-shell`, `portal-admin`, `feature-auth`, `shared-*`): use a positional path filter — `pnpm nx test portal-shell --skip-nx-cache src/app/app.spec.ts`. `--testFile` is not accepted by the Nx vitest executor (`'testFile' is not found in schema`).
|
||||||
pnpm nx test portal-shell --testFile=src/app/app.spec.ts
|
- **Jest projects** (`portal-bff`): `pnpm nx test portal-bff --testPathPattern=src/auth/auth.service`.
|
||||||
```
|
|
||||||
|
|
||||||
### Lint, type-check, format
|
### Lint, type-check, format
|
||||||
|
|
||||||
@@ -178,8 +229,9 @@ pnpm nx affected -t build
|
|||||||
### Generate
|
### Generate
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Component in portal-shell
|
# Component (specify the SPA — `portal-shell` or `portal-admin`)
|
||||||
pnpm nx g @nx/angular:component <name> --project=portal-shell --standalone
|
pnpm nx g @nx/angular:component <name> --project=portal-shell --standalone
|
||||||
|
pnpm nx g @nx/angular:component <name> --project=portal-admin --standalone
|
||||||
|
|
||||||
# Service / controller / module in portal-bff
|
# Service / controller / module in portal-bff
|
||||||
pnpm nx g @nx/nest:service <name> --project=portal-bff
|
pnpm nx g @nx/nest:service <name> --project=portal-bff
|
||||||
@@ -190,7 +242,7 @@ pnpm nx g @nx/js:library --name=shared-<scope> --directory=libs/shared/<scope> \
|
|||||||
--bundler=tsc --unitTestRunner=vitest \
|
--bundler=tsc --unitTestRunner=vitest \
|
||||||
--tags="scope:shared,type:shared" --no-interactive
|
--tags="scope:shared,type:shared" --no-interactive
|
||||||
|
|
||||||
# New Angular feature lib (front-only)
|
# New Angular feature lib (front-only — usable by either SPA)
|
||||||
pnpm nx g @nx/angular:library --name=feature-<name> --directory=libs/feature/<name> \
|
pnpm nx g @nx/angular:library --name=feature-<name> --directory=libs/feature/<name> \
|
||||||
--standalone=true --unitTestRunner=vitest-analog \
|
--standalone=true --unitTestRunner=vitest-analog \
|
||||||
--tags="scope:portal-shell,type:feature" --no-interactive
|
--tags="scope:portal-shell,type:feature" --no-interactive
|
||||||
@@ -198,6 +250,18 @@ pnpm nx g @nx/angular:library --name=feature-<name> --directory=libs/feature/<na
|
|||||||
|
|
||||||
> Sweep generated files for `process.env.X` (dot notation) → `process.env['X']` (bracket notation), required by the strict-TS option `noPropertyAccessFromIndexSignature: true`. The Nx generators don't emit bracket form.
|
> Sweep generated files for `process.env.X` (dot notation) → `process.env['X']` (bracket notation), required by the strict-TS option `noPropertyAccessFromIndexSignature: true`. The Nx generators don't emit bracket form.
|
||||||
|
|
||||||
|
### Documentation site
|
||||||
|
|
||||||
|
`docs/**/*.md` renders as a VitePress site per [ADR-0022](decisions/0022-docs-site-vitepress.md). No Nx project for it — pure pnpm scripts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm docs:dev # http://localhost:5173 (HMR — edit a .md, see it live)
|
||||||
|
pnpm docs:build # static site → docs/.vitepress/dist/
|
||||||
|
pnpm docs:preview # serve the built site locally to smoke-test
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding a new ADR is a single-file change: drop `docs/decisions/NNNN-kebab-title.md` (MADR 4.0.0 frontmatter, see [`template.md`](decisions/template.md)) and the sidebar auto-lists it. Update [`decisions/README.md`](decisions/README.md) in the same PR so the curated tag-grouped index stays in sync.
|
||||||
|
|
||||||
### Prisma
|
### Prisma
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -233,6 +297,32 @@ pnpm ci:perf # production build + Lighthouse CI against the static-served
|
|||||||
|
|
||||||
The BFF and the SPA are both wired with OpenTelemetry tracing and structured Pino logging (per [ADR-0012](decisions/0012-observability-pino-opentelemetry.md)). This section is the practical guide to using them while debugging — finding a trace, reading the logs that go with it, untangling a slow request.
|
The BFF and the SPA are both wired with OpenTelemetry tracing and structured Pino logging (per [ADR-0012](decisions/0012-observability-pino-opentelemetry.md)). This section is the practical guide to using them while debugging — finding a trace, reading the logs that go with it, untangling a slow request.
|
||||||
|
|
||||||
|
### How a click becomes a trace
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor U as User
|
||||||
|
participant SPA as portal-shell
|
||||||
|
participant BFF as portal-bff
|
||||||
|
participant Pino as BFF stdout (Pino)
|
||||||
|
participant OC as OTel Collector
|
||||||
|
participant J as Jaeger UI
|
||||||
|
|
||||||
|
U->>SPA: click
|
||||||
|
Note over SPA: span `user_interaction`<br/>(root, generates trace_id)
|
||||||
|
SPA->>BFF: fetch /api/foo<br/>traceparent: 00-<trace_id>-<span_id>-01
|
||||||
|
Note over SPA: span `fetch` (child)
|
||||||
|
Note over BFF: span `HTTP GET /api/foo`<br/>(continues same trace)
|
||||||
|
BFF->>Pino: log line<br/>`{ ...payload, trace_id, span_id, request_id }`
|
||||||
|
BFF-->>SPA: 200 OK
|
||||||
|
SPA-)OC: OTLP/HTTP batch (every ~5 s)
|
||||||
|
BFF-)OC: OTLP batch
|
||||||
|
OC-)J: forward spans
|
||||||
|
Note over J: trace_id matches the one in Pino<br/>full nested timeline visible
|
||||||
|
```
|
||||||
|
|
||||||
|
The single point of correlation between the two pillars is **`trace_id`**: Jaeger's URL bar carries it (`/trace/<hex>`), and `@opentelemetry/instrumentation-pino` injects the same value on every BFF log line emitted inside that request's scope. Grep one, navigate the other.
|
||||||
|
|
||||||
### Bring up the observability stack
|
### Bring up the observability stack
|
||||||
|
|
||||||
Traces land in the Jaeger UI bundled in `infra/local/dev.compose.yml` under the `observability` profile. The OpenTelemetry Collector runs in the core profile; without Jaeger active, traces still go to the Collector but nothing visualises them — they appear as warnings in `dev.sh logs otel-collector` and are dropped after the buffer fills.
|
Traces land in the Jaeger UI bundled in `infra/local/dev.compose.yml` under the `observability` profile. The OpenTelemetry Collector runs in the core profile; without Jaeger active, traces still go to the Collector but nothing visualises them — they appear as warnings in `dev.sh logs otel-collector` and are dropped after the buffer fills.
|
||||||
@@ -346,6 +436,22 @@ Repo → Actions → "Renovate" workflow → Run workflow. Useful when you've ju
|
|||||||
- For a major bump that introduces breaking changes, **don't reflexively merge**: read the changelog, then either accept the work or close the PR with a "rejected" label. Renovate respects that label and won't keep re-opening the same major.
|
- For a major bump that introduces breaking changes, **don't reflexively merge**: read the changelog, then either accept the work or close the PR with a "rejected" label. Renovate respects that label and won't keep re-opening the same major.
|
||||||
- **Adding or removing** a dependency belongs in a feature PR, not in Renovate's scope. Renovate only updates _versions_ of existing deps.
|
- **Adding or removing** a dependency belongs in a feature PR, not in Renovate's scope. Renovate only updates _versions_ of existing deps.
|
||||||
|
|
||||||
|
### Transitive vulnerabilities — `pnpm.overrides`
|
||||||
|
|
||||||
|
Renovate's vulnerability remediation handles **direct** `package.json` deps. For **transitive** vulnerabilities (a vulnerable package pulled in by a healthy direct dep), Renovate stays silent in the dashboard — the v40 image doesn't write `pnpm.overrides` automatically. The manual pattern lives in [`package.json`](../package.json) → `pnpm.overrides`, version-selector form:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
"esbuild@<0.25.0": ">=0.25.0",
|
||||||
|
"vite@<6.4.2": ">=6.4.2",
|
||||||
|
```
|
||||||
|
|
||||||
|
Two properties of this form worth knowing:
|
||||||
|
|
||||||
|
1. **The override is gated on the vulnerable range** — it only kicks in when the resolved version is < the threshold, becoming a no-op the day the underlying dep's own bump ships a patched version.
|
||||||
|
2. **pnpm overrides bypass peer constraints** — if the parent dep declares `vite ^5.0.0` but no patched 5.x exists, pnpm will pick a patched 6.x/7.x. Verify the chantier still builds before merging the override.
|
||||||
|
|
||||||
|
The first instance of this pattern is preserved in [#159](https://git.unespace.com/julien/apf_portal/pulls/159) (vite + esbuild via VitePress 1.6.4). Next time `pnpm audit` fails with a transitive vuln and Renovate is silent, this is the playbook.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Conventional commit cycle
|
## 7. Conventional commit cycle
|
||||||
@@ -441,21 +547,30 @@ The template guides without enforcing — sections can be left blank when irrele
|
|||||||
|
|
||||||
## 9. Sections to come — roadmap by phase
|
## 9. Sections to come — roadmap by phase
|
||||||
|
|
||||||
This doc starts as a phase-1 + cross-cutting reference. As features for later phases land, the corresponding sections below are filled in directly. Each entry is mapped to the ADR / implementation work that unlocks it, so a contributor can see when each section becomes real and what triggers it.
|
As features for later phases land, the corresponding sections below are filled in directly. Entries are split between **code shipped — doc to write** (the work is on `main`, only the prose is missing) and **not yet** (neither code nor doc).
|
||||||
|
|
||||||
When a section grows beyond a short subsection, it is extracted to its own file under `docs/development/`. Per the documentation convention (see [README.md](README.md)), we group into a folder once we have at least three related files; this doc is then re-organised into an index pointing at the extracted files. Until then, all sections live here.
|
When a section grows beyond a short subsection, it is extracted to its own file under `docs/development/`. Per the documentation convention (see [README.md](README.md)), we group into a folder once we have at least three related files; this doc is then re-organised into an index pointing at the extracted files. Until then, all sections live here.
|
||||||
|
|
||||||
| Future section | Phase | Triggered by |
|
### Code shipped — doc to write
|
||||||
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| **Auth dev-loop** — Microsoft 365 Developer tenant configuration, MSAL Node connection, OIDC code-flow walkthrough, switching between dev and prod-like tenants. | 2 | Auth flow code lands ([ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md)) once the dev tenant is provisioned by IT. |
|
| Future section | Code shipped in | What the section will cover |
|
||||||
| **Session inspection** — reading the Redis session store in dev, decrypting the AES-GCM `tokens` blob with the dev key, force-logout patterns. | 2 | Sessions module lands ([ADR-0010](decisions/0010-session-management-redis.md)). |
|
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| **MFA step-up debugging** — triggering claims-challenge flows, verifying `mfaVerifiedAt` freshness, testing the SPA HTTP interceptor that handles 401 + claims challenge. | 2 | First `@RequireMfa()` route lands ([ADR-0011](decisions/0011-mfa-enforcement-entra-conditional-access.md)). |
|
| **Auth dev-loop** | [ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md) implementation series (PRs #100s) | Microsoft 365 Developer tenant configuration, MSAL Node connection, OIDC code-flow walkthrough, switching between dev and prod-like tenants. |
|
||||||
| **Audit-log inspection workflow** — querying `audit.events` as `audit_reader`, joining with app logs by `trace_id`, validating the append-only role grants in dev. | 2 | Audit module lands ([ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md)). |
|
| **Session inspection** | [ADR-0010](decisions/0010-session-management-redis.md) implementation | Reading the Redis session store in dev (`redis-cli`), decrypting the AES-GCM `tokens` blob with the dev `SESSION_ENCRYPTION_KEY`, force-logout patterns. |
|
||||||
| **Downstream API integration recipe** — adding a new `DownstreamApiConfig`, choosing the auth strategy (OBO vs service+assertion), wiring resilience policies, testing with a mocked downstream. | 2 | First downstream client lands ([ADR-0014](decisions/0014-downstream-api-access-obo-pattern.md)). |
|
| **MFA step-up debugging** | `@RequireMfa()` decorator + freshness guard (#128) | Triggering claims-challenge flows when the first consumer route lands, verifying `amrAt` freshness, testing the SPA interceptor on 401 + claims challenge. _(Decorator exists; no consumer route yet — section lands together with that.)_ |
|
||||||
| **Component patterns library** — the in-house, spartan-style components (Angular CDK + Tailwind) as they ship, with a11y notes per component (keyboard model, ARIA, screen-reader expectations). | 5b suite | First non-placeholder component in `libs/shared/ui/`. |
|
| **Admin surface workflows** | [ADR-0020](decisions/0020-portal-admin-app.md) skeleton (#127) + audit viewer (#136) + user directory (#140–#142) | Walking through `/audit` and `/users` viewers, role assignment in Entra (`Portal.Admin`), cross-app navigation. |
|
||||||
| **a11y testing workflow** — running axe-core via Playwright locally, screen-reader testing notes (NVDA / VoiceOver / TalkBack), the APF user-panel cadence and how to triage findings. | 3a | First Playwright e2e suite touching real screens ([ADR-0016](decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md)). |
|
| **Audit-log inspection workflow** | [ADR-0013](decisions/0013-audit-trail-separated-postgres-append-only.md) + admin viewer | Querying `audit.events` as `audit_reader`, joining with Pino logs by `trace_id` / `actor_id_hash`, validating the append-only role grants in dev. |
|
||||||
| **Performance debugging** — running Lighthouse CI locally with full config, reading the HTML reports, using `source-map-explorer` to investigate bundle bloat, interpreting BFF p95/p99 from OTel. | 3a | Lighthouse already wired in CI ([ADR-0017](decisions/0017-performance-budgets-lighthouse-ci.md)); section grows when first real route is added to the critical-routes list. |
|
| **Downstream API integration recipe** | [ADR-0014](decisions/0014-downstream-api-access-obo-pattern.md) strategies (OBO #137, signed-assertion + JWKS #138/#139) | Adding a new `DownstreamApiConfig`, choosing the auth strategy, wiring resilience policies, testing with a mocked downstream. _(Strategy layer ready; no v1 consumer yet — section grows with the first.)_ |
|
||||||
| **Debugging tips** — Angular DevTools, NestJS inspector, Prisma query log, OTel trace navigation, common gotchas. | cross | Accumulates organically as the team encounters them. |
|
| **OpenAPI / Scalar dev workflow** | [#143](https://git.unespace.com/julien/apf_portal/pulls/143) | Browsing `/api/docs`, annotating new controllers with `@ApiTags` / `@ApiOperation`, exporting the spec for Bruno / Postman, the CSRF caveat when hitting mutating routes from Scalar. |
|
||||||
| **Release workflow** — tag-driven release, what `release.yml` does, version bumping, changelog generation from Conventional Commits. | 3b | On-prem infrastructure ADR + populated `release.yml`. |
|
| **Capabilities-driven SPA UX** | `/api/me/capabilities` + cross-app menu links (#151) | When to add a new key to `CapabilitiesView`, how the SPA's `CapabilitiesService` consumes it, the "binary boolean, never the raw role" rule. |
|
||||||
| **GitLab migration runbook** — when the org migrates Gitea → GitLab, how the workflows are ported, which level-2 sections of [ADR-0015](decisions/0015-cicd-gitea-actions.md) get superseded. | future | GitLab migration ADR (6–18 months horizon). |
|
|
||||||
| **Architecture overview diagrams** — high-level component diagrams, data-flow diagrams, trust boundaries (for security review). | cross | First major architecture review or onboarding cohort ≥ 3 contributors. |
|
### Not yet
|
||||||
|
|
||||||
|
| Future section | Triggered by |
|
||||||
|
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **Component patterns library** — in-house spartan-style components (Angular CDK + Tailwind), a11y notes per component (keyboard model, ARIA, screen-reader expectations). | First non-placeholder component in `libs/shared/ui/` beyond `Icon` and `UserMenu`. |
|
||||||
|
| **a11y testing workflow** — running axe-core via Playwright locally, screen-reader testing notes (NVDA / VoiceOver / TalkBack), the APF user-panel cadence and how to triage findings. | First Playwright e2e suite touching real screens ([ADR-0016](decisions/0016-accessibility-baseline-wcag-aa-targeted-aaa.md)). |
|
||||||
|
| **Performance debugging** — running Lighthouse CI locally with full config, reading the HTML reports, using `source-map-explorer` to investigate bundle bloat, interpreting BFF p95/p99 from OTel. | First real route added to the critical-routes list ([ADR-0017](decisions/0017-performance-budgets-lighthouse-ci.md)). |
|
||||||
|
| **Debugging tips** — Angular DevTools, NestJS inspector, Prisma query log, OTel trace navigation, common gotchas. | Accumulates organically as the team encounters them. |
|
||||||
|
| **Release workflow** — tag-driven release, what `release.yml` does, version bumping, changelog generation from Conventional Commits. | On-prem infrastructure ADR + populated `release.yml`. |
|
||||||
|
| **GitLab migration runbook** — when the org migrates Gitea → GitLab, how the workflows are ported, which level-2 sections of [ADR-0015](decisions/0015-cicd-gitea-actions.md) get superseded. | GitLab migration ADR (6–18 months horizon). |
|
||||||
|
|||||||
Reference in New Issue
Block a user