ci: run the TechLog contract drift gate in CI and test:all
`check:tech-log-contract` existed and worked but nothing ran it. No gate in `config/ci/gates.json` referenced it and `test:all` did not chain it, so a hand-edit of the vendored canonical yaml or of `generated.ts` passed every gate the repository actually executes -- the exact regression the digest pin exists to prevent. FE-GATE-010 (architecture/contract governance) now owns the command, beside `check-registries` and `check-ci`, and `test:all` runs it in front of `test:tech-log`. The canonical authority baseline moves to 83 command definitions / 95 references and the gate-shape SHA-256 is recomputed with `canonicalGateShapeSha256`; the recomputation was first verified by reproducing the previous constant from the previous gates.json. `tests/features/tech-log/contract-generation.test.ts` now fails if either wiring is removed again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5c997f3a7e
commit
f19be639a3
@@ -4,8 +4,11 @@ import { createHash } from "node:crypto";
|
||||
import { test } from "vitest";
|
||||
|
||||
import canonicalSource from "../../../src/features/tech-log/contracts/studio/canonical-source.json" with { type: "json" };
|
||||
import ciGates from "../../../config/ci/gates.json" with { type: "json" };
|
||||
import packageDocument from "../../../package.json" with { type: "json" };
|
||||
|
||||
const YAML_PATH = "src/features/tech-log/contracts/studio/studio-api.openapi.yaml";
|
||||
const DRIFT_GATE_SCRIPT = "check:tech-log-contract";
|
||||
|
||||
test("vendored contract matches the recorded canonical digest", () => {
|
||||
const bytes = readFileSync(YAML_PATH);
|
||||
@@ -32,3 +35,32 @@ test("vendored contract declares the CSRF header", () => {
|
||||
const yaml = readFileSync(YAML_PATH, "utf8");
|
||||
assert.ok(yaml.includes("X-CSRF-TOKEN"));
|
||||
});
|
||||
|
||||
// 이 브랜치의 중심 산출물은 "canonical 계약이 다시 갈라지지 못하게 빌드로 막는다"
|
||||
// (§선택한 접근 A)이다. 스크립트가 존재하는 것만으로는 그 약속이 지켜지지 않는다.
|
||||
// 누군가 손으로 vendor yaml이나 generated.ts를 고쳐도, 실제로 실행되는 게이트가
|
||||
// 하나도 그것을 보지 않으면 digest 고정은 의미를 잃는다. 아래 두 테스트가
|
||||
// "실행 경로에 실제로 연결돼 있는가"를 검증한다.
|
||||
test("the contract drift check is a real CI gate command, not just a package script", () => {
|
||||
const command = ciGates.commands.find((entry) => entry.script === DRIFT_GATE_SCRIPT);
|
||||
assert.ok(command, `config/ci/gates.json declares no ${DRIFT_GATE_SCRIPT} command`);
|
||||
assert.equal(command.expect, "pass");
|
||||
|
||||
const owningGates = ciGates.gates.filter((gate) =>
|
||||
(gate.commandIds as readonly string[]).includes(command.id),
|
||||
);
|
||||
assert.equal(
|
||||
owningGates.length,
|
||||
1,
|
||||
`${command.id} must be referenced by exactly one gate; found ${owningGates.length}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("test:all runs the contract drift check alongside the TechLog suite", () => {
|
||||
const segments = packageDocument.scripts["test:all"].split(" && ").map((value) => value.trim());
|
||||
assert.ok(
|
||||
segments.includes(`corepack pnpm ${DRIFT_GATE_SCRIPT}`),
|
||||
`test:all does not run ${DRIFT_GATE_SCRIPT}: ${packageDocument.scripts["test:all"]}`,
|
||||
);
|
||||
assert.ok(segments.includes("corepack pnpm test:tech-log"), packageDocument.scripts["test:all"]);
|
||||
});
|
||||
|
||||
@@ -189,8 +189,9 @@ describe("CI gate contract", () => {
|
||||
),
|
||||
);
|
||||
expect(contract.jobs).toHaveLength(9);
|
||||
expect(contract.commands).toHaveLength(82);
|
||||
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(94);
|
||||
// Final fix wave item 1: FE-GATE-010 gained `check-tech-log-contract`.
|
||||
expect(contract.commands).toHaveLength(83);
|
||||
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(95);
|
||||
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23);
|
||||
// Template merge. Recounted from the merged config/ci/gates.json rather
|
||||
// than taking either side's number.
|
||||
|
||||
@@ -185,9 +185,10 @@ describe("selective Task 3 contract closure", () => {
|
||||
// product's 26/81; the artifact set is the product's 126 plus the
|
||||
// template's 2. Task 11 added TECH_LOG_STUDIO_ASSETS's manual
|
||||
// accessibility evidence, bringing the total to 129.
|
||||
// Final fix wave item 1 added `check-tech-log-contract` to FE-GATE-010.
|
||||
expect(canonical.gates).toHaveLength(27);
|
||||
expect(canonical.commands).toHaveLength(82);
|
||||
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(94);
|
||||
expect(canonical.commands).toHaveLength(83);
|
||||
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(95);
|
||||
expect(canonical.artifacts).toHaveLength(129);
|
||||
expect(canonical.stages).toHaveLength(5);
|
||||
expect(canonical.retention.classes).toHaveLength(5);
|
||||
|
||||
Reference in New Issue
Block a user