Skip to content

Add CI pipeline, NuGet lock files and Dependabot (R-04) - #14

Merged
guillerlp merged 2 commits into
mainfrom
chore/r-04-ci-pipeline
Aug 8, 2026
Merged

Add CI pipeline, NuGet lock files and Dependabot (R-04)#14
guillerlp merged 2 commits into
mainfrom
chore/r-04-ci-pipeline

Conversation

@guillerlp

Copy link
Copy Markdown
Owner

What

The CI pipeline. .github/workflows/ci.yml runs the full pre-merge checklist on every PR to main, plus NuGet lock files, Dependabot for three ecosystems, and a pinned Node version.

Closes R-04, INFRA-01, INFRA-06, BUILD-07Phase 1 of the roadmap is now complete.

Stacked on #13. This branch sits on fix/sec-03-npm-vulnerabilities, so until that merges this PR shows both commits. It targets main rather than that branch on purpose: the workflow triggers on PRs to main, and "both jobs actually run" is an acceptance criterion that cannot be checked any other way. Merge #13 first and this narrows to one commit.

Why

Spec: docs/specs/002-ci-pipeline.md · ADR-013.

ADR-010 made backend warnings build failures. ADR-012 made the frontend type-check and lint. Both were built explicitly so this item would have something to call — and until now nothing called either. BUILD-03 is the standing proof that this matters: npm run lint could not start for eleven months and nobody noticed.

Layers touched

Docs / build configuration / CI. No file under Domain/, Application/, Infrastructure/, Api/, or recipe-manager-frontend/src/ is modified.

Migration

None.

Contract impact

None.

What lands

Area Change
Workflow Two parallel jobs on ubuntu-latest. Backend: locked restore → build → test → vulnerable-package check. Frontend: npm ci → typecheck → lint → build → npm audit --audit-level=high
Lock files RestorePackagesWithLockFile in Directory.Build.props; six committed packages.lock.json; --locked-mode in CI
Dependabot npm, nuget, github-actions, grouped, weekly
Node .nvmrc (24) + engines (>=20), workflow reads node-version-file

Three findings that changed the implementation

1. CI must build Debug, not Release — and this is load-bearing.

Choosing --configuration Release was reflex. ADR-005 makes Program.Main throw "IntegrationTest environment is not allowed in RELEASE builds" — a guard added in 2025 so that setting that environment on a real deployment cannot start the app with no database. WebApplicationFactory uses exactly that environment name, so a Release build fails all 14 integration tests by design. Measured: 84 passing → 70. The workflow carries a comment so nobody "improves" it back. Nothing is lost: TreatWarningsAsErrors is unconditional (ADR-010), so the warning gate is identical in Debug.

A security guard written about deployment turned out to dictate a CI flag three roadmap items later, with nothing linking the two.

2. dotnet list package --vulnerable exits 0 even when it finds vulnerabilities.

Verified locally. Written the obvious way, that step would have been a green tick that could never go red — the exact BUILD-03 failure mode, inside the code meant to prevent it. The step parses the output instead, and the comment says why so it does not get "simplified" later.

3. Actions are pinned by commit SHA, not tag.

A tag is mutable and can be repointed at new code — the mechanism behind the 2025 tj-actions/changed-files compromise. SHAs were resolved from the GitHub API, not written from memory. The github-actions Dependabot ecosystem is what keeps them from rotting; SHA-pinning without it is a decision you regret in six months.

Verification

Every command below was run locally, and the numbers are observed rather than inherited.

dotnet restore --locked-mode      succeeds
dotnet build (Debug, --no-restore) 0 warnings, 0 errors
dotnet test  (Debug, --no-build)   84/84 (70 unit + 14 integration)
dotnet list package --vulnerable   6 of 6 projects clean
npm ci / typecheck / lint / build  all clean from a fresh tree
npm audit --audit-level=high       0 vulnerabilities

Negative tests — each gate observed rejecting

Per the 2026-08-04 decisions-log entry: a gate never seen rejecting anything is indistinguishable from no gate.

Gate Broken deliberately Result
Lock-file drift bumped FluentResults 3.16→3.15 NU1004, restore refuses
Vulnerable NuGet added Newtonsoft.Json@12.0.1 NU1903 at restore (ADR-010 fires first)
Vulnerable parser synthetic output, both directions detects when present, no false positive when clean
Compiler warning unused local in Recipe.cs CS0219 as error
Failing test inverted an EntityTests assertion dotnet test exits 1
TypeScript error const x: number = "..." TS2322, build exits 1 before Vite
console.log added one to HomePage.tsx no-console error

Every change was reverted; git status confirms no test artifact survived.

Not verified locally, by nature: that both jobs actually start on a PR. That is what this PR's own checks demonstrate — please confirm they appear and go green before merging.

Architecture impact

ADR-013 appended to docs/architecture.md. An ADR is warranted not because CI is structural in the layering sense, but because RestorePackagesWithLockFile in Directory.Build.props changes restore behaviour for every project — the same blast radius as ADR-010 and ADR-011.

No new dependency in either ecosystem. Dependency direction untouched.

Security review

  • Improves the dependency posture materially. A regression in either ecosystem is now merge-visible: npm audit --audit-level=high fails the frontend job, and NU1903 fails restore. Dependabot raises PRs weekly.
  • permissions: contents: read at the top level; no secrets are referenced, so a fork PR gains nothing by running the workflow, and nothing here can read the database password.
  • Trigger is pull_request, deliberately not pull_request_target — the latter runs a writable token in the base repo's context against untrusted PR code, which is the standard way CI becomes an exfiltration path.
  • Actions SHA-pinned (see finding 3).
  • Unchanged: SEC-01 and SEC-02 remain the two Critical deploy-gate items. Every endpoint is still anonymous and world-writable; CI does not alter that.

Known issues / roadmap

Fixes:       R-04, INFRA-01, INFRA-06, BUILD-07  (deleted from the docs in this PR)
Adds:        INFRA-07 — CI runs but is not yet required to merge
Deploy gate: "CI green on every PR" — workflow shipped; row now tracks INFRA-07
Unblocks:    R-06 (Docker on the runner), R-07 (somewhere to run frontend tests)

Docs updated: README.md, CLAUDE.md, known-issues.md, roadmap.md, tech-stack.md, conventions.md, architecture.md (ADR-013), decisions-log.md, workflows/release-workflow.md, and three agent files.

Action required from you — this is not fully delivered without it

Enable branch protection on main requiring the Backend and Frontend checks. The workflow makes the checks exist; only a repository setting makes them required, and no PR can contain that. Until then a red run can be merged past. Tracked as INFRA-07.

While there: confirm Dependabot pull requests are enabled — dependabot.yml configures them, but the repository toggle governs.

Follow-ups

  • R-06 Testcontainers, now unblocked.
  • R-07 frontend tests; the workflow gains a npm test step when a test script exists.
  • TEST-05 / BUILD-06 — agree a coverage number, fix the coverage script, then add a coverage step.
  • INFRA-02 / INFRA-03 — versioning and rollback. CI publishes nothing.

🤖 Generated with Claude Code

guillerlp and others added 2 commits August 8, 2026 20:48
Every gate this repo has was enforced by nothing. ADR-010 made backend
warnings build failures and ADR-012 made the frontend type-check and
lint, both explicitly so this item would have something to call.

Adds .github/workflows/ci.yml running two parallel jobs on every PR to
main: backend restores in locked mode, builds, tests and checks for
vulnerable packages; frontend runs npm ci, typecheck, lint, build and a
blocking npm audit. Adds .github/dependabot.yml for npm, nuget and
github-actions, and pins Node in .nvmrc plus engines.

NuGet now restores from six committed packages.lock.json files, so the
transitive closure is reproducible and an unexpected change fails with
NU1004 instead of resolving silently.

CI builds Debug, not Release, and this is load-bearing. ADR-005 makes
the IntegrationTest environment throw in RELEASE builds, so a Release
build fails all 14 integration tests by design: 84 passing becomes 70.
A comment in the workflow records this so it is not "fixed" back.

The vulnerable-package step parses output rather than trusting the exit
code, because dotnet list package --vulnerable exits 0 even when it
finds something. A step that cannot fail is the exact failure this item
exists to remove.

Every gate verified by negative test: NU1004 on a lock mismatch, NU1903
on a vulnerable package, CS0219 on an unused local, a non-zero exit on
an inverted assertion, TS2322 on a bad annotation, no-console on a
console.log.

Closes INFRA-01, INFRA-06, BUILD-07 and R-04, completing Phase 1. Adds
INFRA-07: the checks run but are not yet required to merge, which is a
branch-protection setting rather than anything a PR can contain.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ticks the one acceptance criterion that could not be verified locally:
both jobs run on a PR. Run 31275373275 on PR #14, 84/84 on Linux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@guillerlp
guillerlp merged commit d9b7a77 into main Aug 8, 2026
2 checks passed
@guillerlp
guillerlp deleted the chore/r-04-ci-pipeline branch August 8, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant