Files
apf_portal/docs/decisions
julien aea395ae65
CI / check (push) Successful in 1m59s
CI / commits (push) Has been skipped
CI / scan (push) Successful in 2m34s
CI / a11y (push) Successful in 1m8s
CI / perf (push) Successful in 4m8s
feat(ci): assert gzip transfer sizes against ADR-0017 budgets (#133)
## Summary

Closes the ADR-0017 follow-up that was sitting on an unpushed local branch (`feat/ci/gzip-transfer-size-budgets`) for ~5 days, while ADRs 0018–0021 and the auth/admin/security tracks landed. Cherry-picked onto current `main`, with a small follow-up fix to make the script work against the locale-split build layout introduced by ADR-0019 between the two commits.

## What lands

### Commit 1 — `feat(ci): assert gzip transfer sizes against ADR-0017 budgets`

[`scripts/check-gzip-budgets.mjs`](scripts/check-gzip-budgets.mjs) — plain-Node, no deps, ~120 LOC at landing. Closes the ADR-0017 §Confirmation gap (Angular CLI's `budgets` only compare RAW sizes):

- Parses Angular's emitted `index.html` to separate **initial** assets (anything referenced via `src=` / `href=`) from **lazy** chunks (the rest).
- Gzips every JS / CSS file at level 9 — what most HTTP servers serve for static assets — and reports a per-file + per-bucket table.
- Asserts the [ADR-0017](docs/decisions/0017-performance-budgets-lighthouse-ci.md) thresholds (`initial JS total ≤ 300 KB`, `any lazy chunk ≤ 100 KB`, `total CSS ≤ 150 KB`) and exits non-zero on any breach.
- Wired through:
  - `pnpm ci:gzip-budgets` invokes the script with the default dist path.
  - `ci:perf` chains build → gzip check → Lighthouse, so a budget breach short-circuits before Lighthouse even runs.

ADR-0017 §Confirmation also updated to point at the script instead of describing it as a "future follow-up".

### Commit 2 — `fix(ci): adapt gzip-budget check for the @angular/localize multi-bundle layout`

The original commit predated PR #91 / [ADR-0019](docs/decisions/0019-internationalisation-angular-localize.md), which made `@angular/localize` emit one self-contained bundle per locale under `dist/apps/portal-shell/browser/<locale>/`. The script looked for `index.html` directly under the dist root and failed with ENOENT on every build since.

Fix:

- Auto-detects layout. If `dist/index.html` exists → flat mode (unchanged behaviour, kept for single-locale apps like `portal-admin`). Otherwise enumerates immediate subdirectories with their own `index.html` and runs the check **per locale**.
- Budget thresholds apply per locale — each bundle is what the user's browser actually downloads. Aggregating across locales would understate the worst case.
- Violations across locales are collected + reported together with the locale tag prefixed so a single CI run surfaces every breach.
- ADR-0017 §Confirmation amendment expanded to spell out the per-locale check + cite ADR-0019.

## Test plan

- [x] `node --check scripts/check-gzip-budgets.mjs` — syntax OK.
- [x] `pnpm ci:gzip-budgets` against the current production build:
  - 2 locale bundles detected (`en`, `fr`).
  - Initial JS total: ~141 KB / 300 KB budget ✓ (each locale).
  - CSS total: 4.62 KB / 150 KB budget ✓ (each locale).
  - Largest lazy chunk: 1.55 KB / 100 KB per-chunk budget ✓.
- [x] No conflict on `package.json` despite 11 PRs touching it since the branch base — cherry-pick auto-merged cleanly.
- [ ] CI: `pnpm ci:perf` end-to-end (build → gzip-budgets → Lighthouse). Validates on the runner.

## Notes for the reviewer

- The script lives at the repo root under `scripts/` rather than as an Nx target — it's a pure CI helper, not project-scoped. Matches the existing `ci:audit` / `ci:commits` pattern in `package.json`.
- No dependencies added. Uses `node:fs/promises` and `node:zlib`.
- The original branch (`feat/ci/gzip-transfer-size-budgets`) is preserved in local-only state for paranoia; once this PR merges, it can be `git branch -D`'d.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #133
2026-05-14 16:22:54 +02:00
..

Architectural Decision Records

This project records architecturally-significant decisions as ADRs in the MADR 4.0.0 format. References: 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.
  • 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 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: proposedaccepted → 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 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 Use ADRs to record architectural decisions accepted process 2026-04-29
0002 Adopt Nx monorepo with the apps preset accepted infrastructure, frontend, backend 2026-04-29
0003 Workspace and app naming convention accepted process 2026-04-29
0004 Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest accepted frontend 2026-04-29
0005 Backend stack — NestJS over Express, Fastify, Hono accepted backend 2026-04-29
0006 Persistence — PostgreSQL with Prisma accepted data, backend 2026-04-29
0007 Pre-commit hooks and Conventional Commits accepted process 2026-04-29
0008 Identity model — multi-tenant Entra ID for workforce, dual-audience design for future External ID accepted security, data 2026-04-29
0009 Authentication flow — OIDC Authorization Code + PKCE via MSAL Node, BFF session pattern accepted security, backend 2026-04-29
0010 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 MFA enforcement — Entra ID Conditional Access baseline, BFF claim sanity-check, step-up hooks designed-in accepted security 2026-04-29
0012 Observability — Pino structured logs + OpenTelemetry tracing, W3C Trace Context propagation, stdout + collector accepted observability, backend, frontend 2026-04-29
0013 Audit trail — separated append-only Postgres schema, decoupled from app logs accepted security, observability, data 2026-04-29
0014 Downstream API access — On-Behalf-Of pattern, unified DownstreamApiClient, audience-aware authorization accepted security, backend 2026-04-29
0015 CI/CD pipeline — Gitea Actions, trunk-based + squash-merge, thin YAML over portable scripts accepted infrastructure, process 2026-04-30
0016 Accessibility baseline — WCAG 2.2 AA + targeted AAA, Angular CDK + spartan-ng + Tailwind, APF panel testing accepted accessibility, frontend, process 2026-04-30
0017 Performance budgets — Core Web Vitals + Lighthouse CI gates, bundle budgets, BFF p95/p99 SLOs accepted performance, frontend, backend, process 2026-04-30
0018 Environment configuration — Angular environment.ts, BFF env vars, audit pool split accepted frontend, backend, infrastructure, process 2026-05-10
0019 Internationalisation — @angular/localize, build-time per-locale bundles, /fr + /en path-based routing accepted frontend, accessibility, performance, process 2026-05-11
0020 portal-admin — dedicated SPA for portal administration, sharing the existing BFF accepted frontend, backend, security, infrastructure, process 2026-05-11
0021 Phase-2 security baseline — helmet, CORS allowlist, double-submit CSRF, rate limiting, structured error envelope accepted security, backend 2026-05-13