diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5f1ef78 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,60 @@ +# Keep every action pin current — including the shared osv-scan lane's. +# +# DESTINATION: .github/dependabot.yml — NOT .github/workflows/. This is repo +# configuration, not a workflow. +# +# WHY DEPENDABOT AND NOT A BESPOKE RE-PIN BOT (ci-workflows#10) +# ------------------------------------------------------------- +# A caller's `uses: ...@` resolves the reusable workflow AT THAT COMMIT, so +# a stale pin means the repo runs an old scanner with old rules. The 2026-08-03 +# census (caller-pins.yml) measured 43 of 58 callers behind the template, and +# nothing makes a stale caller go red on its own. +# +# The fix decomposes the way OpenTofu does: the template is the DESIRED STATE, +# the caller-pins census is the PLAN (an ancestry diff of actual vs desired), +# and something has to be the APPLY. Dependabot's `github-actions` ecosystem is +# that applier, chosen over a broker-credentialed bot because: +# +# - the engine is GitHub-maintained — nothing bespoke to keep working; +# - no new credential: a custom actor would need org-wide contents:write PLUS +# the `workflows` permission (GitHub rejects workflow-file pushes without +# it), minted from a broker entry that would make ci-workflows' main able to +# reach every repo's contents — a posture change SHA pins exist to avoid; +# - it covers consumers the census cannot see: repos that call the lane from +# inside a combined workflow (infra's _infra-test.yml) rather than a +# byte-identical deps.yml, and every OTHER action pin (checkout, +# setup-node, setup-deno) going stale the same way. +# +# Dependabot converges callers onto the head of each action's default branch, +# which is what the template pin IS at bump time. The census stays on as the +# independent verifier that convergence actually happened — Dependabot proposes, +# a human merges, the Monday census proves. Once the fleet reads current, flip +# caller-pins' `fail-on-lag` to true and the loop is closed: plan must be empty. +# +# ADOPTING THIS FILE +# ------------------ +# Repo has no .github/dependabot.yml: copy this file byte-identical. +# Repo already has one (other ecosystems — npm, cargo, pip): APPEND the single +# `github-actions` entry below to its `updates:` list instead. Byte-identity is +# the norm, not a law; a merged config is correct, a clobbered one is not. +# +# The `ci` commit-message prefix is load-bearing: semantic-PR gates in this org +# reject `deps:` (claude-box did, during the rollout), and `ci:` is the accurate +# conventional-commit type for CI configuration. +# +# The single `actions` group collapses each week's bumps into ONE PR per repo — +# 58 repos on a weekly schedule is otherwise a noise machine nobody reads, +# which is the required-baseline.yml failure with extra steps. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + groups: + actions: + patterns: + - "*" + commit-message: + prefix: "ci" diff --git a/README.md b/README.md index 67e1368..6c6322c 100644 --- a/README.md +++ b/README.md @@ -200,19 +200,32 @@ The same shape, with one step that exists because skipping it is what 1. Edit `.github/workflows/osv-scan.yml` 2. Merge, and re-pin `templates/deps.yml` to the merge commit -3. **Re-pin every caller** — dispatch `caller-pins.yml` to get the list - -Step 3 is not bookkeeping. `uses: …@` resolves the reusable workflow *at that -commit*, so a caller's pin decides which scanner that repo actually runs: a lane -improvement that stops at step 2 is merged and deployed to nobody. And unlike the -canonical-script gate above, **nothing makes a stale caller go red on its own** — it -keeps scanning happily under the old rules — which is why this one has to be run rather -than waited for. - -`caller-pins.yml` compares by **ancestry** (`git merge-base --is-ancestor`), not -existence. `self-test`'s `template-pins` job already proves pins name real commits, and -that is a different question: a stale pin *is* a real commit. `62990dd` resolves -perfectly and is four commits behind. +3. **Let the callers converge, then verify.** Dependabot (`templates/dependabot.yml`, + vendored per repo as `.github/dependabot.yml`) opens each caller's re-pin PR on its + weekly cycle; a human merges. The Monday `caller-pins.yml` census proves the fleet + actually converged. + +Step 3 matters because `uses: …@` resolves the reusable workflow *at that commit*: +a caller's pin decides which scanner that repo actually runs, a lane improvement that +stops at step 2 is merged and deployed to nobody, and — unlike the canonical-script +gate above — **nothing makes a stale caller go red on its own.** The 2026-08-03 census +measured 43 of 58 callers behind for exactly this reason (ci-workflows#10). + +The division of labour is deliberate, and it is the OpenTofu shape: the template is the +**desired state**, the census is the **plan** (an ancestry diff — `merge-base +--is-ancestor` — because a stale pin *is* a real commit, so existence checks cannot see +it; `62990dd` resolves perfectly and is four behind), Dependabot is the **apply**, and +merging stays human. Once the fleet reads current, flip `caller-pins`' `fail-on-lag` +to `true`: from then on the plan must be empty, and a caller that lags a week reds the +Monday lane instead of drifting silently. + +Why Dependabot rather than a broker-credentialed re-pin bot: the engine is +GitHub-maintained (nothing bespoke to keep working); a custom actor would need +org-wide `contents:write` *plus* the `workflows` permission — GitHub rejects +workflow-file pushes without it — minted from an entry that would make this repo's +`main` a lever over every repo's contents; and Dependabot also covers what the census +cannot see: consumers that call the lane from inside a combined workflow (infra's +`_infra-test.yml`) and every other action pin going stale the same way. **Two copies, currently in step.** `infra` and `front-desk-scheduler` both carry `c530b86a…`, byte-identical to canonical as of adoption — so this gate was introduced diff --git a/templates/dependabot.yml b/templates/dependabot.yml new file mode 100644 index 0000000..5f1ef78 --- /dev/null +++ b/templates/dependabot.yml @@ -0,0 +1,60 @@ +# Keep every action pin current — including the shared osv-scan lane's. +# +# DESTINATION: .github/dependabot.yml — NOT .github/workflows/. This is repo +# configuration, not a workflow. +# +# WHY DEPENDABOT AND NOT A BESPOKE RE-PIN BOT (ci-workflows#10) +# ------------------------------------------------------------- +# A caller's `uses: ...@` resolves the reusable workflow AT THAT COMMIT, so +# a stale pin means the repo runs an old scanner with old rules. The 2026-08-03 +# census (caller-pins.yml) measured 43 of 58 callers behind the template, and +# nothing makes a stale caller go red on its own. +# +# The fix decomposes the way OpenTofu does: the template is the DESIRED STATE, +# the caller-pins census is the PLAN (an ancestry diff of actual vs desired), +# and something has to be the APPLY. Dependabot's `github-actions` ecosystem is +# that applier, chosen over a broker-credentialed bot because: +# +# - the engine is GitHub-maintained — nothing bespoke to keep working; +# - no new credential: a custom actor would need org-wide contents:write PLUS +# the `workflows` permission (GitHub rejects workflow-file pushes without +# it), minted from a broker entry that would make ci-workflows' main able to +# reach every repo's contents — a posture change SHA pins exist to avoid; +# - it covers consumers the census cannot see: repos that call the lane from +# inside a combined workflow (infra's _infra-test.yml) rather than a +# byte-identical deps.yml, and every OTHER action pin (checkout, +# setup-node, setup-deno) going stale the same way. +# +# Dependabot converges callers onto the head of each action's default branch, +# which is what the template pin IS at bump time. The census stays on as the +# independent verifier that convergence actually happened — Dependabot proposes, +# a human merges, the Monday census proves. Once the fleet reads current, flip +# caller-pins' `fail-on-lag` to true and the loop is closed: plan must be empty. +# +# ADOPTING THIS FILE +# ------------------ +# Repo has no .github/dependabot.yml: copy this file byte-identical. +# Repo already has one (other ecosystems — npm, cargo, pip): APPEND the single +# `github-actions` entry below to its `updates:` list instead. Byte-identity is +# the norm, not a law; a merged config is correct, a clobbered one is not. +# +# The `ci` commit-message prefix is load-bearing: semantic-PR gates in this org +# reject `deps:` (claude-box did, during the rollout), and `ci:` is the accurate +# conventional-commit type for CI configuration. +# +# The single `actions` group collapses each week's bumps into ONE PR per repo — +# 58 repos on a weekly schedule is otherwise a noise machine nobody reads, +# which is the required-baseline.yml failure with extra steps. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + groups: + actions: + patterns: + - "*" + commit-message: + prefix: "ci"