fix(ci): enforce env budget before merge (#125189)

This commit is contained in:
Peter Steinberger
2026-08-17 02:32:57 -07:00
committed by GitHub
parent 1487b45eb8
commit fbd910d417
3 changed files with 32 additions and 5 deletions
+2 -2
View File
@@ -37,7 +37,7 @@ dispatch.
| `pnpm-store-warmup` | Warm the lockfile-pinned Actions cache for fork PRs, manual runs, and same-repo docs-only PRs | Node or docs-check lanes without an exact-cache writer |
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, startup memory, and embedded built-artifact checks | Node-relevant changes |
| `control-ui-i18n` | Verify generated Control UI locale bundles, metadata, and translation memory; advisory on automatic runs, blocking on manual release CI | Control UI i18n-relevant changes and manual CI |
| `checks-fast-core` | Fast Linux correctness lanes: max-lines and assertion-safety baseline ratchets, bundled + protocol, Bun launcher, and the CI-routing fast task | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes: environment-variable, max-lines, and assertion-safety baseline ratchets, bundled + protocol, Bun launcher, and the CI-routing fast task | Node-relevant changes |
| `qa-smoke-ci-profile` | Self-contained balanced parts of the automatic QA Smoke coverage set; one private-overlay build per part (the smoke set has no docker-lane or Control UI scenarios; the run step fails closed if one returns) | Pushes and manual runs; PRs only on QA-owned surfaces |
| `checks-fast-contracts-plugins-*` | Two weighted plugin contract shards | Node-relevant changes |
| `checks-fast-contracts-channels-*` | Two weighted channel contract shards | Node-relevant changes |
@@ -173,7 +173,7 @@ Barnacle treats bug-labeled issues as verification candidates rather than inacti
Ordinary manual CI dispatches run the same job graph as normal CI but force every non-Android scoped lane on: Linux Node shards, bundled-plugin shards, plugin and channel contract shards, Node 22 compatibility, `check-*`, `check-additional-*`, built-artifact smoke checks, docs checks, Python skills, Windows, macOS, iOS build, and Control UI/native app i18n. Node 22 compatibility runs in Full Release Validation and manual dispatches only; push and pull request CI skip it. The exact-head `release_gate` fallback instead keeps the pull request's macOS and iOS scope, including conservative release screenshot capture for screenshot-pipeline owners. Automatic source PRs verify native extraction inventory and Android/Apple localization safety without requiring translated or platform-generated output in the same PR. The serialized Native App Locale Refresh workflow rebuilds those artifacts in one isolated PR and enables exact-head auto-merge after required checks pass. Full native parity remains blocking for generated-artifact PRs, manual CI, Full Release Validation, and release prep. Control UI locale parity remains advisory on automatic PR and `main` runs and blocking on manual/release CI. Standalone manual CI dispatches run Android only with `include_android=true` (the `release_gate` input also forces Android); the full release umbrella enables Android by passing `include_android=true`. Plugin prerelease static checks, the release-only `agentic-plugins` shard, the full extension batch sweep, and plugin prerelease Docker lanes are excluded from CI. The Docker prerelease suite runs only when `Full Release Validation` dispatches the separate `Plugin Prerelease` workflow with the release-validation gate enabled.
PR baseline ratchets derive their comparison state from the checked-out synthetic merge tree and verify its head parent against the event head. Manual runs use a unique concurrency group so a release-candidate full suite is not cancelled by another push or PR run on the same ref. The optional `target_ref` input lets a trusted caller run that graph against a branch, tag, or full commit SHA while using the workflow file from the selected dispatch ref; ratchet baselines are compared with the target's merge base against the default-branch head resolved for that run. The `release_gate` input is an exact-SHA maintainer fallback for capacity-stalled PR CI: it requires `target_ref` to be a full commit SHA that matches the dispatched branch head and `pull_request_number` to identify the open PR whose merge tree is validated.
PR baseline ratchets derive their comparison state from the checked-out synthetic merge tree and verify its head parent against the event head. The max-lines entry chains the environment-variable budget with the same fork-point ref before the assertion-safety check, so production source growth cannot first surface on `main`. Manual runs use a unique concurrency group so a release-candidate full suite is not cancelled by another push or PR run on the same ref. The optional `target_ref` input lets a trusted caller run that graph against a branch, tag, or full commit SHA while using the workflow file from the selected dispatch ref; ratchet baselines are compared with the target's merge base against the default-branch head resolved for that run. The `release_gate` input is an exact-SHA maintainer fallback for capacity-stalled PR CI: it requires `target_ref` to be a full commit SHA that matches the dispatched branch head and `pull_request_number` to identify the open PR whose merge tree is validated.
```bash
gh workflow run ci.yml --ref release/YYYY.M.PATCH
+24 -2
View File
@@ -3,6 +3,7 @@ import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import ts from "typescript";
import { main as checkEnvVarCount } from "./check-env-var-count.mts";
import { resolveRatchetBase } from "./lib/ratchet-base.mts";
const BASELINE_PATH = "config/max-lines-baseline.txt";
@@ -314,6 +315,11 @@ function parseArgs(argv: string[]) {
return args;
}
function envVarCountArgs(argv: string[]) {
const args = parseArgs(argv);
return [...(args.staged ? ["--staged"] : []), ...(args.base ? ["--base", args.base] : [])];
}
function printEntries(title: string, entries: string[]) {
console.error(title);
for (const entry of entries) {
@@ -381,6 +387,22 @@ export function main(root = process.cwd(), argv: string[] = process.argv.slice(2
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exitCode = main();
function runBaselineRatchets(root = process.cwd(), argv: string[] = process.argv.slice(2)) {
const maxLinesStatus = main(root, argv);
if (maxLinesStatus !== 0) {
return maxLinesStatus;
}
try {
// CI invokes this entry with its frozen fork-point ref. Carry the same snapshot
// into the env budget so every baseline ratchet judges one tested tree.
checkEnvVarCount(envVarCountArgs(argv), root);
return 0;
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
return 1;
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
process.exitCode = runBaselineRatchets();
}
+6 -1
View File
@@ -5725,8 +5725,9 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
).toContain("--only=core --split-core --core-stripe=${{ matrix.stripe }}/5 --threads=1");
});
it("runs both baseline ratchets against the exact tested tree", () => {
it("runs all baseline ratchets against the exact tested tree", () => {
const workflow = readCiWorkflow();
const maxLinesRatchet = readFileSync("scripts/check-max-lines-ratchet.mts", "utf8");
const checksFastJob = workflow.jobs["checks-fast-core"];
const checksFastSteps = checksFastJob.steps;
const checkout = checksFastSteps.find((step: WorkflowStep) => step.name === "Checkout");
@@ -5827,6 +5828,10 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
expect(checksFastRun.run).not.toContain("+${merge_base}:refs/remotes/origin/ci-ratchet-base");
expect(checksFastRun.run).toContain('pnpm check:max-lines-ratchet --base "$base_ref"');
expect(checksFastRun.run).toContain('pnpm check:assertion-safety --base "$base_ref"');
expect(maxLinesRatchet).toContain(
'import { main as checkEnvVarCount } from "./check-env-var-count.mts";',
);
expect(maxLinesRatchet).toContain("checkEnvVarCount(envVarCountArgs(argv), root);");
expect(checksFastRun.run).toContain(
'if [[ "${RATCHET_RELEASE_MERGE_TREE:-}" == "true" ]]; then',
);