U5 — the gate everything else has to pass

continuous_integration.yaml: six blocking checks, then a separate
publish per environment so a Vite build never gets tagged for the
wrong one, then a call into last commit's deploy workflow. Along the
way: the lint list had drifted (two problems not in the requirement,
one already fixed), and the Umami-origin gate needed a variable pair
of its own since the backend's side of that comparison lives on the
host, not in CI. Pinned the two vulnerable packages while at it.
This commit is contained in:
2026-07-28 16:07:03 +02:00
parent bd2a963498
commit 9f4ae475e7
12 changed files with 691 additions and 36 deletions
@@ -0,0 +1,101 @@
# Code Generation Summary — U5 CI Workflow & Quality Gates
**Date**: 2026-07-28
## Files Created
- `.gitea/workflows/continuous_integration.yaml` — 12-job CI workflow (C-12)
## Files Modified (FR-21 — lint fixes)
- `frontend/src/components/cms/AddCmsInstanceDialog.tsx` — close-triggered reset moved from a
`useEffect` into a `handleOpenChange` wrapper (event response, not a render synchronization)
- `frontend/src/components/users/InviteUserDialog.tsx` — same pattern, plus reordered `useForm()`
before its use
- `frontend/src/pages/SettingsPage.tsx` — replaced the prop-sync effect with the "adjust state
during render" pattern (comparing fetched `availability` against a tracked previous value)
- `frontend/src/components/cms/SetStatusDialog.tsx``watch('status')` replaced with
`useWatch({ control, name: 'status' })`; the instance-driven reset effect (which the React
Compiler plugin could not previously analyze past the `watch()` incompatibility) split into a
render-time sync (on `instance` change) and a `handleOpenChange` wrapper (on close)
- `frontend/src/components/ui/badge.tsx` — added an inline `eslint-disable-next-line
react-refresh/only-export-components`, matching the existing precedent in `button.tsx` rather than
introducing a new file-splitting convention
## Files Modified (FR-22 — package pins, closing OPEN-03)
- `src/SlpModularCms.Core/SlpModularCms.Core.csproj` — added `Microsoft.OpenApi` `2.11.0` and
`System.Security.Cryptography.Xml` `10.0.10`
- `src/SlpModularCms.Modules.Master.Tests/SlpModularCms.Modules.Master.Tests.csproj` and
`src/SlpModularCms.Modules.Availability.Tests/SlpModularCms.Modules.Availability.Tests.csproj` —
added `System.Security.Cryptography.Xml` `10.0.10` (both projects carry their own direct
`Microsoft.AspNetCore.DataProtection` reference for testing, an independent path to the vulnerable
transitive version that Core's pin alone does not reach)
## What the CI Workflow Implements
Twelve jobs: `config` (env→output passthrough), the six blocking gates (`backend-build`,
`backend-test`, `vulnerability-scan`, `frontend-build`, `frontend-test`, `frontend-lint`),
`publish-test`/`publish-production` (environment-specific `dotnet publish`, each running the
REF-U5-01 Umami-origin drift check first), and `deploy-test`/`deploy-production` (call
`deploy-scp.yaml` from U6 with `secrets: inherit`).
Triggers: `pull_request` (validation only — the six gates, no publish/deploy jobs run since none of
them are reachable without `push`-to-`master` or `workflow_dispatch`), `push` to `master`
(validation + automatic test deploy), `workflow_dispatch` with `deploy_production` (default `false`).
## New Decisions Raised During This Unit
**REF-U5-01** — the design record said the REF-U3-01 gate compares "both values" without saying
how the CI job would read the backend side. It can't: per D-16, the live
`SecurityHeaders:AllowedScriptOrigins` is a **host** environment variable, invisible to the Gitea
runner, exactly the class of problem REF-U3-01 already exists to solve for the frontend's Umami ID.
Resolved by adding `SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST` / `_PRODUCTION` Gitea variables that must
be **manually kept in sync** with the corresponding host env var — carried to Operations host-setup
documentation as an explicit two-places-must-match note, not silently assumed to always agree.
**Artifact model clarified**: unlike the reference project (where the frontend `dist/` folder *is*
the deployable artifact), this repository's admin SPA is embedded into the .NET publish output by
the existing `BuildAndCopyAdminFrontend` MSBuild target. The CI workflow's `frontend-build` gate
therefore validates the frontend independently (PR-time signal only); the actual per-environment
deployable artifacts are produced by `publish-test` / `publish-production`, which set the
environment's `VITE_*` variables on the `dotnet publish` process so the embedded frontend build
picks them up exactly as it would during a local `dotnet publish`.
## Gitea Variables and Secrets Introduced by This Unit
Beyond what `infrastructure-design.md` § 6 already defined for U6:
| Variable | Scope | Purpose |
|---|---|---|
| `VITE_SENTRY_DSN` | shared | Frontend Sentry DSN, same value both environments (D-19 — one Sentry project) |
| `VITE_UMAMI_SCRIPT_URL` | shared | Umami script host, same value both environments (D-24 — one Umami instance) |
| `VITE_UMAMI_WEBSITE_ID_TEST` / `_PRODUCTION` | per-environment | Umami website entry ID |
| `SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST` / `_PRODUCTION` | per-environment | REF-U5-01 gate input — must mirror the host's `SecurityHeaders__AllowedScriptOrigins__0` |
## Verification (Step 13.5 — this unit's own check)
- **Lint (FR-21)**: `pnpm run lint` — 0 problems (was 6; 2 more found by drift, all fixed)
- **Vulnerability scan (FR-22)**: `dotnet list SlpModularCms.sln package --vulnerable
--include-transitive` — clean across all 10 projects (was 2 packages, multiple advisories each)
- **Backend build**: `dotnet build SlpModularCms.sln -c Release` — 0 errors
- **Backend tests**: 372 passed, 0 failed (196 Core, 82 Availability, 57 Master, 37 Identity) —
unchanged from the Round 2 baseline, confirming the package pins introduced no regressions
- **Frontend type-check**: `npx tsc -b` — clean
- **Frontend tests**: 237 passed, 0 failed — unchanged from the Round 2 baseline, confirming the
dialog refactors preserved behaviour
- **Frontend build**: `pnpm run build` — succeeds
- **CI workflow YAML**: parsed successfully — 12 jobs, `deploy-production` confirmed unreachable
without `workflow_dispatch` + the flag
No C# test project applies to `.gitea/workflows/` itself (pipeline configuration, same exception as
U6 and the Slave API). Real execution — an actual Gitea instance running the workflow against the
Pi — is out of scope until Operations, matching U6's boundary.
## Deferred to Operations (not built here, by design)
- Actual values for all Gitea variables and secrets listed above and in
`infrastructure-design.md` § 6
- Keeping `SECURITY_ALLOWED_SCRIPT_ORIGINS_*` in sync with the host's real CSP configuration
(REF-U5-01)
- Real pipeline run against the actual Gitea instance and self-hosted runner