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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions