Skip to content

fix(ci): repair broken gates and add a self-verifying CI contract #1588

Description

@FelixTJDietrich

A deep CI audit (2026-08-29, line-cited evidence in the PRs that will reference this issue) found gates that are broken or silently not running. Part of #1582's hardening goal; this is the repair layer, and it lands first in the CI track — everything here is toolchain-neutral and must not wait for the Node/pnpm migration (#1596#1598).

Confirmed broken:

  • ci-server-clean-reference.yml:80 pins a 39-character SHA (actions/upload-artifact@043fb46d…fc6a0 — the valid pin elsewhere ends …fc6a0a) — unresolvable, so the weekly server phase-reference workflow has been dead since perf(server): reuse generated client build outputs #1529.
  • openapi-autocommit.yml:28 passes cache-type: openapi-validation, which setup-caches does not recognise; every step in the composite is if:-gated on known types, so the composite succeeds while doing nothing — the job runs Maven with no pinned JDK and no cache.
  • 13 of 21 scripts/*.test.ts files never run in CI (only 8 are reachable, via check:changesets, check:preview-stack, check:env, check:contracts, check:instructions, check:java-nullness): ci:agents drops the test:tooling leg that check:agents runs. The release supply-chain suites (check-release-sbom, check-release-vulnerabilities, release-image-lock, release-deployment-policy) and the preview-controller suites are local-only. check:agent-runtime-pins and check-release-image-inventory.ts also appear in no workflow.
  • 20 runnable jobs have no timeout-minutes (default 360 min), including both reusable-docker-build jobs, deploy-locked-compose/deploy, and the four release.yml jobs that can carry the key (release, tag-images, publish-release, deploy-productiondeploy-staging is a reusable-workflow caller, where the key is invalid; caller jobs get their bounds inside the called workflows' jobs instead).
  • concurrency.queue: max (preview lifecycle: deploy-preview.yml:36, cleanup-preview.yml:13, reconcile-previews.yml:69) is not a documented GitHub key, and .github/actionlint.yaml exists solely to suppress the resulting error. If GitHub ignores it, the group falls back to "one pending run, newer cancels older" — queued preview teardowns get dropped, the exact opposite of the in-file comment's intent.
  • test:server:* scripts run the package phase (since fix(ci): package reactor dependencies before server tests #1585), so both server test legs repackage the Spring Boot fat JAR for nothing — add -Dspring-boot.repackage.skip=true. (perf(ci): cut PR feedback time from ~12 to ~6 minutes #1590 measures the effect; this issue owns the fix.)

Migration consistency: write scripts/ci-contract.test.ts in node:test style so the #1597 runtime port is a no-op. The interim wiring of test:tooling into CI may touch npm scripts that #1595 later reshapes into the vp task graph — #1595 re-points the contract test's "every gate referenced by CI" assertion at the graph; the assertions themselves are designed to survive that.

Done when

  • The 39-char SHA is fixed and the weekly workflow runs again
  • setup-caches fails closed on an unknown cache-type (explicit validation step, not if:-gating) and openapi-autocommit passes a recognised one
  • test:tooling, check:agent-runtime-pins, and the release-inventory check run in CI (fold ci:agents into check:agents with an env-driven -f github flag)
  • Every job that can carry timeout-minutes has one; reusable-workflow callers are bounded inside the called workflow
  • queue: max is empirically verified against a real burst or replaced with documented semantics — and the actionlint suppression in .github/actionlint.yaml is removed either way
  • Both server test legs pass -Dspring-boot.repackage.skip=true
  • A new scripts/ci-contract.test.ts (node:test) asserts: every leg of package.json#check is referenced by ≥1 workflow; every cache-type passed in workflows is recognised by setup-caches; every external uses: matches @[0-9a-f]{40} # v — so this whole class of drift cannot recur

Non-deferrable core

Deferral protocol (epic #1608): removing any item below from the implementing PR requires a linked follow-up issue filed before merge, naming the concrete blocker. Silent scope-shedding fails review.

  • The ci-contract.test.ts with all three assertions (check-leg wiring, cache-type recognition, SHA-pin format) — it is the point of the issue
  • Fail-closed setup-caches and timeouts on every capable job
  • -Dspring-boot.repackage.skip=true on both server test legs
  • queue: max may resolve either way, but it must be resolved, not deferred — the actionlint suppression falls either way

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciGitHub Actions, workflows, build pipeline changespriority:criticalDrop everything - Loss of functionality or datasize:MThis PR changes 30-99 lines, ignoring generated files.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions