build: the gate is scripts/check.sh, and CI goes

The Gitea Actions gate repeated, after the merge, what a release already runs
before it, and its runner shared the git host's container: one run there
starved Gitea of memory until it stopped answering. The lanes now live in
scripts/check.sh, run the same by the release dispatcher and by hand, and the
gate-contract test holds its lines (the ruff and pip-audit checks read it
instead of the workflow). The two audits only CI ran join the gate: pip-audit
over the lock (pinned in the test image, blocking at any severity) and npm
audit of the production dependencies. The engine contracts drop the CI
containers; the register records that pushes have no server-side gate.
This commit is contained in:
2026-10-04 06:20:58 +02:00
parent 0528b35f2d
commit cc414de952
14 changed files with 234 additions and 253 deletions
+13 -15
View File
@@ -105,7 +105,7 @@ function restrictedEntries(entry: Linter.RuleEntry | undefined): Set<string> {
// the lines that run the lanes. Any change fails until the snapshot is regenerated, so a narrowing
// arrives as a diff of `gates.snapshot.json` in review instead of slipping past a detector. (Three
// review rounds kept finding shapes the detector this replaced did not model — a `basePath` global
// ignore, a widened glob filed under a narrow row, a substring check on a CI line. A comparison
// ignore, a widened glob filed under a narrow row, a substring check on a gate line. A comparison
// against everything has no shape to miss.)
//
// Regenerate: GATE_SNAPSHOT=update npx vitest run src/lib/gates.contract.test.ts
@@ -285,7 +285,7 @@ function meaningful(path: string): string[] {
});
}
const lanes = () => ({
".gitea/workflows/check.yml": meaningful(".gitea/workflows/check.yml"),
"scripts/check.sh": meaningful("scripts/check.sh"),
"backend/docker-compose.test.yml": meaningful("backend/docker-compose.test.yml"),
"backend/Dockerfile.test": meaningful("backend/Dockerfile.test"),
"frontend/package.json scripts": (
@@ -485,19 +485,17 @@ describe("the gate register (docs/gates.md)", () => {
});
// Every ruff lane passes `--config backend/ruff.toml`, which makes ruff ignore any other config
// file it would discover (measured: a nested `ruff.toml` selecting the `D` rules changed nothing).
// So that flag is what makes this file the whole configuration. The snapshot holds every CI line;
// these two are spelled out here because the reason is (round 3: a substring check let an extra
// `--config "lint.extend-ignore=…"` override through).
it("runs every CI ruff step on backend/ruff.toml and nothing else", () => {
const steps = read(".gitea/workflows/check.yml")
// So that flag is what makes this file the whole configuration. The snapshot holds every line of
// the gate script; these two are spelled out because the reason is (round 3: a substring check let
// an extra `--config "lint.extend-ignore=…"` override through).
it("runs both ruff lanes on backend/ruff.toml and nothing else", () => {
const lanes = read("scripts/check.sh")
.split("\n")
.filter((l) => /\bruff\b/.test(l) && !l.trim().startsWith("#"))
.filter((l) => /python -m ruff (check|format)/.test(l))
.map((l) => l.trim());
expect(steps).toEqual([
"- name: ruff",
"run: python -m ruff check --config backend/ruff.toml backend scripts",
"- name: ruff format --check",
"run: python -m ruff format --check --config backend/ruff.toml backend scripts",
expect(lanes).toEqual([
"ruff_check() { ruff_pinned && python -m ruff check --config backend/ruff.toml backend scripts; }",
"ruff_format() { ruff_pinned && python -m ruff format --check --config backend/ruff.toml backend scripts; }",
]);
});
@@ -577,9 +575,9 @@ describe("the gate register (docs/gates.md)", () => {
expires: /\n\s*expired_at:\s*\d{4}-\d{2}-\d{2}/.test(chunk),
}));
expect(entries.filter((e) => !e.statement || !e.expires).map((e) => e.id)).toEqual([]);
const pipIgnores = read(".gitea/workflows/check.yml")
const pipIgnores = read("scripts/check.sh")
.split("\n")
.filter((l) => !l.trim().startsWith("#")) // the step's comment explains the flag; it is not one
.filter((l) => !l.trim().startsWith("#")) // the lane's comment explains the flag; it is not one
.flatMap((l) => [...l.matchAll(/--ignore-vuln[ =](\S+)/g)].map((m) => m[1]!));
const found = [...entries.map((e) => e.id), ...pipIgnores].map((id): [string, string] => [
"accepted",