## Summary
Drafts [ADR-0028](docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md) as `proposed`: migrate CI/CD + git hosting from Gitea (`git.unespace.com`) to GitLab CE self-hosted on `vm-gitlab` (`10.100.201.10`). The migration was anticipated by [ADR-0015](docs/decisions/0015-cicd-gitea-actions.md) ("level-2 implementation; will be superseded by a GitLab migration ADR within 6-18 months") — that window opens now. **Decision-only PR** — the actual 4-phase migration ships across follow-up PRs after acceptance.
ADR-0028's number was previously a placeholder reference in ADR-0026 and ADR-0027 for the Pléiades + Acteurs+ sync ADR. **Renumbering**: that future sync ADR shifts to `ADR-0029`, and the placeholder links in ADR-0026, ADR-0027 and `CLAUDE.md` update to match — included in the same PR so the chain stays consistent.
## What lands
| File | Change |
| --- | --- |
| `docs/decisions/0028-migrate-cicd-and-git-hosting-to-gitlab.md` | **New.** MADR 4.0.0 ADR, `proposed`. Decision = Option B (GitLab CE on `vm-gitlab`). Considered options A (status quo Gitea), C (Forgejo), D (cloud SaaS). Documents what carries over from ADR-0015 (architectural principles unchanged — thin YAML, trunk-based, all-gates-blocking, on-prem runners), what changes (host, pipeline file, runner type, scan tooling), the 4-phase migration sequence, and the signed-commits revisit. |
| `docs/decisions/0026-person-user-portal-data-model.md` | All 11 `ADR-0028` references → `ADR-0029` (sync + facets shifts to 0029). |
| `docs/decisions/0027-portal-side-organisational-hierarchy.md` | All 14 `ADR-0028` references → `ADR-0029`. |
| `docs/decisions/README.md` | New row for ADR-0028 (`proposed`, tags `infrastructure`, `process`, 2026-05-26). |
| `CLAUDE.md` | Roll-up updated: `ADRs 0001 → 0027 accepted; ADR-0028 + ADR-0029 proposed`. ADR-0028's relationship to ADR-0015 spelled out inline ("supersedes ADR-0015's Gitea Actions platform choice — the rest of ADR-0015's architectural principles carry over unchanged"). ADR-0026 + ADR-0027 architecture bullets renumbered 0028 → 0029 to track. |
## Key choices in the ADR
- **What carries over from ADR-0015 vs what changes** — explicit table so future readers see immediately that the migration is **platform-only**, not a re-litigation of CI principles. Trunk-based + squash, all-gates-blocking, thin YAML over portable scripts (`pnpm ci:check` etc. — unchanged), on-prem runners, Conventional Commits in CI + hook (defense in depth) — all carried over. Host, pipeline-file grammar, runner type, and scan tooling are the only things that move.
- **Native security scanning replaces the manual Trivy + gitleaks setup.** GitLab CE's built-in `Dependency-Scanning.gitlab-ci.yml` + `Secret-Detection.gitlab-ci.yml` includes consolidate the ~30 lines of inline `curl + tar` install dance currently in `.gitea/workflows/ci.yml`. Same blocking thresholds (CRITICAL+HIGH dependency vulns, any secret).
- **`vm-gitlab` is already provisioned.** No infra wait — the only sequencing constraint is operator-driven, not infrastructure-driven.
- **4-phase migration, parallel pipelines for ~1 week before cutover.** Phase 1 mirrors repos and bootstraps GitLab side (no `.gitlab-ci.yml` yet — Gitea still gates). Phase 2 lands `.gitlab-ci.yml` alongside `.gitea/workflows/ci.yml` so both pipelines run per PR until parity is confirmed. Phase 3 flips the remote URLs and deletes `.gitea/workflows/` + `infra/ci-runners.compose.yml`. Phase 4 sweeps stale references.
- **Gitea moves to read-only / archive, not decommissioned** at cutover. Existing references to Gitea PRs (`#213`, `#217`, `#219`, …) in commit messages and ADR bodies stay resolvable as historical artefacts. One VM at idle is a low long-term cost.
- **Signed commits revisit.** ADR-0015 noted "signed commits recommended, revisited at GitLab migration". ADR-0028 makes the recommendation: enable required signed commits on `main` once GitLab is live, paired with GnuPG agent forwarding (already documented in `docs/setup/01-dev-debian-vm-setup.md` §8.5). `apf-portal-bot` (Renovate) gets a dedicated signing key at PR 1.
## Renumbering — what moved and why
Before this PR, ADR-0026 and ADR-0027 used `ADR-0028` as a placeholder link for the Pléiades + Acteurs+ sync ADR. That sync ADR hasn't been drafted yet — the number was reserved.
This PR claims `ADR-0028` for the GitLab migration (the immediately-actionable decision), and shifts the sync placeholder to **ADR-0029**. All 25 link references across ADR-0026 (11) and ADR-0027 (14) update in lockstep — replace_all is safe here because in those files `ADR-0028` consistently meant "the sync ADR".
Content of the sync ADR is unchanged — only the number. When that ADR is eventually drafted as `0029-…md`, it gets the existing content reserved for it in the placeholder text.
## Test plan
- [x] `pnpm exec prettier --check` clean on the touched files.
- [x] All `ADR-0028` references in `0026-…md` / `0027-…md` / `CLAUDE.md` now read `ADR-0029` (grep confirms zero remaining references to the old number in those files).
- [x] The new `0028-…md` self-references (status frontmatter, title, internal anchors) are consistent — no leftover `0029`.
- [ ] **Review focus** — drivers / consequences / migration sequence in the ADR; the "what carries over from ADR-0015" table; the renumbering rationale.
## What's next
Per ADR-0028 §"Migration sequence", post-acceptance:
1. **ADR-0028 acceptance PR** — small status-flip, same pattern as #219 (ADR-0026 + ADR-0027 acceptance).
2. **`mirror-and-bootstrap` PR** — `git push --mirror` Gitea → GitLab; GitLab side groups / projects / branch protection / MR templates / deploy keys / Renovate reconfig. No `.gitlab-ci.yml` yet, Gitea pipelines still gate.
3. **`gitlab-ci-pipeline` PR** — `.gitlab-ci.yml` alongside the existing `.gitea/workflows/ci.yml`. Parallel runs ~1 week for parity. GitLab Runner registered on `vm-gitlab`.
4. **`cutover` PR** — remotes flip across docs, `.gitea/workflows/` + `infra/ci-runners.compose.yml` deleted, Gitea read-only.
5. **`cleanup` PR** — stale references sweep, signed-commit policy finalised on `main`.
In parallel — once the dev VM (#220 / #221 / #222 / #223 / #224) is fully bootstrapped — the paused **ADR-0027 PR 1** (Region / Delegation / Structure Prisma schema + inline seed) and **ADR-0026 PR 1** (Person / User / UserScope schema + provisioner) can ship on Gitea; they have no dependency on the GitLab migration and don't need to wait.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #226
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:
proposed→accepted→ optionallydeprecatedorsuperseded 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 |
| 0022 | Documentation site — VitePress + Mermaid plugin, separate static deployment | accepted | process, infrastructure |
2026-05-15 |
| 0023 | Charts + dashboards — D3 + Observable Plot wrapped in libs/shared/charts |
accepted | frontend, accessibility, performance |
2026-05-16 |
| 0024 | 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 | Authorization model — three orthogonal axes (privileges × functional roles × scopes), Entra-backed | accepted | security, backend, data |
2026-05-20 |
| 0026 | Person golden record + User portal-account — portal-side identity model |
accepted | data, backend, security |
2026-05-24 |
| 0027 | Portal-side organisational hierarchy — Structure with kind discriminator and nullable FINESS / SIRET |
accepted | data, backend |
2026-05-24 |
| 0028 | Migrate CI/CD + git hosting from Gitea to GitLab self-hosted | proposed | infrastructure, process |
2026-05-26 |