Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/construction/plans/u5-ci-workflow-code-generation-plan.md
T
Sluijsens 9f4ae475e7 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.
2026-07-28 16:07:03 +02:00

9.7 KiB
Raw Blame History

Code Generation Plan — U5 CI Workflow & Quality Gates

Unit Context

  • Unit: U5 CI Workflow & Quality Gates (Pipeline-type — YAML + prerequisite frontend/package fixes, no new C# business logic)
  • Component: C-12 CI workflow (.gitea/workflows/continuous_integration.yaml)
  • Requirements: FR-01, FR-04, FR-05, FR-21, FR-22
  • Carried-in item: REF-U3-01 — a blocking gate comparing VITE_UMAMI_SCRIPT_URL against that environment's SecurityHeaders:AllowedScriptOrigins, since the backend cannot see the frontend's build-time Umami variable itself
  • Depends on: U3, U4 (both committed — gates must run against finished application code, per unit-of-work-dependency.md Ordering Constraint 4)
  • Depended on by: U6 (invoked by this workflow's deploy-test / deploy-production jobs)
  • No test project applies: same category as U6 — .gitea/workflows/ is pipeline configuration. The lint fixes and package pins ARE ordinary application/frontend code changes and are covered by the existing backend test suite and frontend lint/type-check, not a new test project.

Note on sequencing (transparency)

Two of this plan's steps — the lint fixes (FR-21) and the package pins (FR-22) — were investigated and completed during initial analysis, before this plan document existed, because they required running the actual toolchain (pnpm run lint, dotnet list package --vulnerable, live NuGet advisory lookups) to discover the current facts (exact files/lines, exact patched versions) rather than anything design-level. They are recorded here as completed steps for full traceability, and are already build/test-verified (see Step 13.5 record below). Step 3 — the CI workflow YAML itself — is the step awaiting your review before being written.

Steps

  • Step 1: Fix the 5 lint errors + 1 warning (FR-21)

    • AddCmsInstanceDialog.tsx:55 and InviteUserDialog.tsx:50 (react-hooks/set-state-in-effect): replaced the close-triggered useEffect with a handleOpenChange wrapper around the Dialog's onOpenChange, since resetting state on close is an event response, not a render synchronization
    • InviteUserDialog.tsx:54 (react-hooks/immutability, reset used before its declaration): resolved as part of the same change — useForm() now runs before the effect it used to feed
    • SettingsPage.tsx:40 (react-hooks/set-state-in-effect): replaced with the "adjust state during render" pattern (compare fetched availability against a tracked previous value, call setState directly in the render body) — the React-documented alternative when state is derived from changing data rather than a user action
    • SetStatusDialog.tsx:72 (warning, react-hooks/incompatible-library on watch()): replaced watch('status') with useWatch({ control, name: 'status' }), react-hook-form's own compiler-compatible alternative
    • Drift found during analysis, fixed as part of the same requirement, not filed: current pnpm run lint no longer matched FR-21's original 6-item list exactly — SetStatusDialog.tsx:32 from the requirement was already clean, but two problems FR-21 did not list were present: badge.tsx:32 (react-refresh/only-export-components, fixed identically to the existing precedent in button.tsx — an inline eslint-disable, not a file split, to match established convention) and a second, previously-hidden effect in SetStatusDialog.tsx (lines 7486) that the React Compiler's lint plugin had not been able to analyze until the watch() incompatibility above was resolved. Fixed with the same render-time-sync pattern as SettingsPage.tsx, split so the close-triggered reset lives in handleOpenChange and the instance-driven reset lives in the render-time sync
    • Result: pnpm run lint — 0 problems
  • Step 2: Pin vulnerable packages (FR-22, closing OPEN-03)

    • Microsoft.OpenApi 2.0.0 (transitive via Microsoft.AspNetCore.OpenApi 10.0.9, high severity, GHSA-v5pm-xwqc-g5wc) → pinned to 2.11.0 in SlpModularCms.Core.csproj (latest 2.x; patched at 2.7.5+, deliberately not jumping to the 3.x major that Microsoft.AspNetCore.OpenApi 10.0.9 does not target)
    • System.Security.Cryptography.Xml 10.0.9 (transitive via Microsoft.AspNetCore.DataProtection, five high-severity advisories, all patched at 10.0.10) → pinned to 10.0.10 in SlpModularCms.Core.csproj, plus independently in SlpModularCms.Modules.Master.Tests.csproj and SlpModularCms.Modules.Availability.Tests.csproj, which each carry their own direct Microsoft.AspNetCore.DataProtection reference for testing and therefore have their own unpinned path to the vulnerable transitive version that Core's pin alone does not reach
    • Result: dotnet list SlpModularCms.sln package --vulnerable --include-transitive — clean across all 10 projects
  • Step 3: Generate .gitea/workflows/continuous_integration.yaml

    • Triggers: pull_request (opened/synchronize/reopened, validation only), push to master (validation + automatic test deploy), workflow_dispatch with deploy_production boolean (default false)
    • config job: turns environment-specific env: values into job outputs (same reason as the reference project — the env context is unavailable inside a called reusable workflow's with: block)
    • Six blocking gates (FR-01, D-10): backend build (dotnet build -c Release), backend tests (dotnet test -c Release), vulnerability scan (dotnet list package --vulnerable, now clean per Step 2), frontend build, frontend tests, frontend lint/format-check (now clean per Step 1)
    • REF-U3-01 gate — a mechanism decision this unit must make, not just implement: the pattern doc says the CI job compares "both values" (frontend's VITE_UMAMI_SCRIPT_URL vs. backend's SecurityHeaders:AllowedScriptOrigins), but per D-16 the live backend value is a host environment variable the Gitea runner cannot read — no appsettings.{Environment}.json exists; there's only the single, environment-agnostic appsettings.json. Reading the real runtime value is therefore not possible from CI, the exact problem this gate exists to solve, one level up. Resolution (new, flagged as REF-U5-01): add a second pair of Gitea Actions variables, SECURITY_ALLOWED_SCRIPT_ORIGINS_TEST / _PRODUCTION, that mirror the intended host env var value and are compared against VITE_UMAMI_SCRIPT_URL_TEST / _PRODUCTION at CI time. This makes the gate implementable today, at the cost of a second place that must be kept in sync with the host's actual SecurityHeaders__AllowedScriptOrigins__0 env var — carried to Operations host-setup documentation as an explicit "these two must match" note, not silently assumed
    • Refinement during generation: VITE_UMAMI_SCRIPT_URL is one shared Gitea variable for both environments (D-24 — one Umami instance), not a _TEST/_PRODUCTION pair; only VITE_UMAMI_WEBSITE_ID differs per environment, matching the reference project's exact pattern. The REF-U5-01 gate therefore compares the same origin against each environment's own SECURITY_ALLOWED_SCRIPT_ORIGINS_* variable — two gate runs, one shared input
    • Artifact clarification during generation: the "frontend build" gate (pnpm run build) is a standalone PR-time validation, independent of environment. The actual per-environment deployable artifact is a full dotnet publish of SlpModularCms.Api (publish-test / publish-production jobs), which triggers the admin SPA's build a second time internally via the existing BuildAndCopyAdminFrontend MSBuild target — the environment's VITE_* variables are set on that step's process environment, not passed as build arguments, since that target invokes pnpm build directly
    • Two environment-specific publish artifacts (FR-05): publish-test (VITE_APP_ENV=test) and publish-production (gated on workflow_dispatch with deploy_production: true), each with its own VITE_UMAMI_WEBSITE_ID, shared VITE_SENTRY_DSN and VITE_UMAMI_SCRIPT_URL
    • Toolchain pinned explicitly (actions/setup-dotnet, pnpm/action-setup), no latest tags (D-04)
    • deploy-test / deploy-production jobs call ./.gitea/workflows/deploy-scp.yaml (U6) with secrets: inherit, supplying artifact_name, environment, deploy_path, service_name, health_check_url from Gitea variables per infrastructure-design.md § 6, plus run_db_backup: true only for deploy-production
    • Production reachable only via workflow_dispatch with the flag set (FR-04) — deploy-test never triggers a production deploy under any push condition
  • Step 4: Validate YAML

    • Parsed successfully: 12 jobs, deploy-production's condition requires workflow_dispatch AND deploy_production == 'true' — confirmed unreachable from a bare push event (FR-04). Job graph: config/backend-build/vulnerability-scan/frontend-prepare run independently → backend-test needs backend-buildfrontend-build/frontend-test/ frontend-lint need frontend-preparepublish-test/publish-production need all six gates → deploy-test/deploy-production need their publish job plus config
  • Step 5: Documentation — construction/u5-ci-workflow/code/generation-summary.md

Story / Requirement Traceability

Step Covers
1 FR-21
2 FR-22, OPEN-03
3 FR-01, FR-04, FR-05, REF-U3-01
4 Definition of Done: "workflow YAML is syntactically valid"
5 Definition of Done: "all six gates pass locally against the current tree" — recorded from Steps 12's verification plus this unit's own build/test check (Step 13.5)