Skip to content

feat(ci): auto-regenerate downstream packages.lock.json on Dependabot NuGet PRs (#203)#230

Merged
mforce merged 6 commits into
mainfrom
chore/203-dependabot-lockfix
Jul 26, 2026
Merged

feat(ci): auto-regenerate downstream packages.lock.json on Dependabot NuGet PRs (#203)#230
mforce merged 6 commits into
mainfrom
chore/203-dependabot-lockfix

Conversation

@mforce

@mforce mforce commented Jul 26, 2026

Copy link
Copy Markdown
Owner

What & why

Dependabot bumps a NuGet package in one project and regenerates only that project's packages.lock.json. Every downstream project in the reference chain (Domain ← Application ← Infrastructure ← Api ← tests) still pins the old transitive graph, so CI's dotnet restore --locked-mode fails with NU1004 (upstream limitation: dependabot-core#5461). This has recurred repeatedly — it broke #192/#194/#195/#196 at once on 2026-07-24, and main HEAD c104d88 is itself a manual fix of the same class.

This adds a workflow that heals the downstream locks automatically so the PR goes green with no manual restore --force-evaluate + commit.

Closes #203 · Epic #15.

How

New .github/workflows/dependabot-lockfix.yml, triggered on: workflow_run off CI (types: [completed]) — so it runs from main's trusted definition, never PR-author-controlled code.

  • compute job (permissions: {}, no credentials, persist-credentials: false): checks out the immutable head_sha, runs dotnet restore Cluckwork.sln --force-evaluate against a pinned --configfile (nuget.org-only), uploads the 7 regenerated locks. Runs the untrusted MSBuild restore but has no token to steal and no write access.
  • commit job (permissions: contents: write): runs zero project code — mints a short-lived GitHub App token, validates the artifact against a trusted 7-path allowlist, classifies the diff with .github/scripts/lockfix.mjs (run from the trusted default-branch checkout), and on a lock-only diff commits + non-force pushes (compare-and-swap). The App-token push re-triggers CI (a GITHUB_TOKEN push would not), which goes green.

Security model + the reasoning that shaped it: docs/superpowers/specs/2026-07-25-dependabot-lockfix-design.md.

Key invariants: provenance gate (event==pull_request, both actor and triggering_actor==dependabot[bot], run_attempt==1, dependabot/nuget/ prefix, numeric repo-id match, no conclusion==success); head_sha checkout; non-force CAS push; create-github-app-token SHA-pinned; classifier fail-closed with an exact 7-path allowlist + NUL-safe parse.

⚠️ Required one-time setup (workflow is inert / fails-closed until done)

Create a GitHub App (Repository → Contents: Read and write), install it on this repo, and add repo Actions secrets LOCKFIX_APP_ID and LOCKFIX_APP_PRIVATE_KEY. Until both exist, the token-mint step fails closed (no push) — nothing else breaks.

Notes

  • Inert until merged: workflow_run always runs the default-branch copy, so this does nothing until it lands on main. The first real smoke test is the next Dependabot NuGet PR after merge.
  • Once, confirm empirically that a non-Dependabot push to a dependabot/nuget/*-named branch yields a non-dependabot[bot] actor.login (the copy-cat-branch guard).

Verification

  • .github/scripts/lockfix.mjs classifier: 9/9 node:test self-tests, wired into CI as a gate step.
  • actionlint clean on the new workflow and ci.yml.
  • Restore fan-out proven locally: staling FluentValidation across 5 locks, a single dotnet restore Cluckwork.sln --force-evaluate re-syncs all of them and dirties only lock files.
  • Design was reviewed 4-way (codex + 2 agents + pi) before implementation; each task + the whole branch were reviewed and the findings fixed.

@mforce
mforce merged commit df5494b into main Jul 26, 2026
7 checks passed
@mforce
mforce deleted the chore/203-dependabot-lockfix branch July 26, 2026 06:00
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.

CI: auto-regenerate dependent packages.lock.json on Dependabot NuGet PRs

1 participant