0077 — Automated Dependabot upgrades: comprehensive gate, then auto-merge on green

Superseded. The auto-merge-everything-on-green bet failed in production: a 41-package prod batch auto-merged into an incident (#2975) and routine bumps piled into a 28-PR red wall. Dependabot is now security-only with auto-merge disarmed — see ADR-0079. The comprehensive CI gate this ADR introduced is retained.

Context

Dependabot opens a large weekly batch of dependency PRs (20+ open at the time of writing) across six lanes: the root npm app, four nested npm manifests (agents/clara, testing-harness, mock-pms-service, tools/graphify), Python/uv (tools/agent-smith), and GitHub Actions. Every one required a human to merge — there was no Dependabot auto-merge — so they piled up.

The goal: no manual review of dependency PRs, no wall of noisy open PRs, and no regressions — all three at once. Those only co-exist if the CI gate itself becomes the reviewer, so a human doesn't have to be.

Two findings (verified) made a naive "just turn on auto-merge" unsafe:

  1. Green CI was a false signal for nested-package bumps. Root ci.yml runs npm ci/tsc/vitest against the root lockfile only. A PR bumping a nested manifest was never installed/built/tested against the bumped version — root CI went green without exercising it. Only tools/agent-smith (via ci-smith.yml) had real scoped CI.
  2. PR test selection was vacuous for a lockfile diff. The PR test job is vitest --changed <base> --passWithNoTests; a lockfile-only diff imports into no test file, so it selects ~0 tests. The real next build is deferred on PRs, and the full sharded suite runs only nightly/merge_group. So a bad root bump could merge on a green-but-meaningless signal.

Decision

Make the gate comprehensive first, then auto-merge everything that passes it.

  1. Scoped nested CI (ci-nested.yml). Validate each nested manifest against its own deps, scoped to what's meaningful in isolation (verified on PR #2961): mock-pms-service → install + typecheck + next build; tools/graphify → install + import-smoke + a graphology/vendored-bundle version guard (its build needs a gitignored input); agents/clara → install + esbuild bundle build (its typecheck needs root @/… context, already covered by root Type Check); testing-harness → install-only resolution check (tsconfig spans root src, no build/test). Triggers on every PR with internal dorny/paths-filter gating, and a single always-run Nested Gate aggregator that fails closed if the detector or any lane fails — so a skipped required check can never silently open the gate.
  2. Real gate on Dependabot PRs (ci.yml). Run the full sharded suite + real next build when the PR author is dependabot[bot], reusing the existing required Unit Tests / Build check names. Auth env vars get a non-empty CI placeholder fallback so Dependabot-triggered runs (which read the Dependabot secret store, not Actions secrets) stay deterministic without mirroring secrets.
  3. Auto-merge on green (dependabot-auto-merge.yml). Arm GitHub-native squash auto-merge on every Dependabot PR except a short blocklist (graphology). Armed, not forced — the now-comprehensive required checks must be green for it to fire, so a breaking bump never merges. Security bumps auto-merge on green too: speed-to-patch is the point and the gate protects against regression. This supersedes the earlier "human is the merge gate" stance (Smith/axios PR #1728) for dependency PRs specifically, justified by the gate now being real.
  4. Risk-matched grouping (dependabot.yml). Per-directory, dev/prod-split groups so batches are homogeneous and independently mergeable (replaces the 50-update cross-directory mega-PR). graphology is ignored — it's exact-pinned and coupled to a hand-vendored viewer bundle.
  5. Janitor (separate agent-smith track). A weekly Smith activity keeps the PR list empty: rebase stale PRs, auto-defer (close) bumps stuck red past a window, and roll them into a Slack/Trello digest. Smith already has an authed gh CLI, Temporal crons, and Slack/Trello surfaces; the janitor needs only write-scope (list/comment/close), never gh pr merge, so it stays inside Smith's existing "stop at mergeable" policy.

Consequences