ci: give test:tech-log its own gate command and junit evidence

The TechLog suite already ran in CI, but only inside `test:coverage`'s combined
`vitest run tests/runtime-schema tests/unit … tests/features/tech-log`
invocation on FE-GATE-005. A TechLog regression therefore surfaced as a
coverage-gate failure, and the only junit that carried it was coverage.xml,
which reports every other suite at the same time.

`test-tech-log` now sits on FE-GATE-007 beside `test-reference-feature`, the
sibling it mirrors — both drive a suite under `tests/features/` — and declares
`artifacts/tests/tech-log.xml`, which `test:tech-log` already wrote, as its own
command-generated junit evidence. The gate log now names
`$ corepack pnpm test:tech-log` as its own step and the failure lands on the
suite that produced it.

Adding a command and an artifact moves the exact-count authority to 84
definitions / 96 references / 130 artifacts (109 evidence references), and
changes the canonical gate shape digest because FE-GATE-007's commandIds and
evidenceArtifactIds are part of it. There is no tooling to regenerate that
digest, so it was recomputed by hand under the standing procedure: a fresh
transcription of `canonicalGateShapeSha256` first reproduced the committed
f3cc9075… from the unedited config/ci/gates.json — proving the transcription,
not just agreeing with whatever the check compares against — and only then
hashed the edited file to 98d19911….

`corepack pnpm ci:gate -- FE-GATE-007` passes end to end; the generated
workflow bytes are unchanged, since the yml dispatches gates rather than
commands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-18 12:58:41 +09:00
co-authored by Claude Opus 5
parent 74281b0277
commit 419d9d006d
4 changed files with 41 additions and 12 deletions
+16
View File
@@ -174,6 +174,11 @@
"script": "test:reference-feature", "script": "test:reference-feature",
"expect": "pass" "expect": "pass"
}, },
{
"id": "test-tech-log",
"script": "test:tech-log",
"expect": "pass"
},
{ {
"id": "test-recipes", "id": "test-recipes",
"script": "test:recipes", "script": "test:recipes",
@@ -901,6 +906,15 @@
"test-reference-feature" "test-reference-feature"
] ]
}, },
{
"id": "artifact-artifacts-tests-tech-log-xml",
"path": "artifacts/tests/tech-log.xml",
"schemaId": "junit",
"production": "command-generated",
"producerCommandIds": [
"test-tech-log"
]
},
{ {
"id": "artifact-artifacts-tests-optional-recipes-xml", "id": "artifact-artifacts-tests-optional-recipes-xml",
"path": "artifacts/tests/optional-recipes.xml", "path": "artifacts/tests/optional-recipes.xml",
@@ -1852,6 +1866,7 @@
"test-integration", "test-integration",
"test-http-scenario-evidence", "test-http-scenario-evidence",
"test-reference-feature", "test-reference-feature",
"test-tech-log",
"test-recipes" "test-recipes"
], ],
"logArtifactId": "artifact-artifacts-quality-gates-FE-GATE-007-txt", "logArtifactId": "artifact-artifacts-quality-gates-FE-GATE-007-txt",
@@ -1861,6 +1876,7 @@
"artifact-artifacts-quality-http-scenario-evidence-json", "artifact-artifacts-quality-http-scenario-evidence-json",
"artifact-artifacts-quality-http-scenario-evidence-fixture-json", "artifact-artifacts-quality-http-scenario-evidence-fixture-json",
"artifact-artifacts-tests-reference-feature-xml", "artifact-artifacts-tests-reference-feature-xml",
"artifact-artifacts-tests-tech-log-xml",
"artifact-artifacts-tests-optional-recipes-xml" "artifact-artifacts-tests-optional-recipes-xml"
], ],
"retentionClassId": "merge-cycle" "retentionClassId": "merge-cycle"
+14 -5
View File
@@ -452,7 +452,11 @@ const CANONICAL_GATE_SHAPE_SHA256 =
// `check-tech-log-contract`. Recomputed with `canonicalGateShapeSha256` // `check-tech-log-contract`. Recomputed with `canonicalGateShapeSha256`
// below, verified by first reproducing the previous constant from the // below, verified by first reproducing the previous constant from the
// previous `config/ci/gates.json` before hashing the new one. // previous `config/ci/gates.json` before hashing the new one.
"f3cc90758084e16757e71a8a3fe772d8e408f0c51b044294f329309027ce6563"; // Alignment follow-up item 2: recomputed again after FE-GATE-007 gained
// `test-tech-log` and its junit evidence, by the same method — the previous
// constant f3cc9075… was reproduced from the previous gates.json first, so
// the transcription that produced this value is known to be the real one.
"98d19911c37a18f579fe51b3e3b8164ff0515579557512bc4040b407d4399be9";
function canonicalGateShapeSha256(gates: CiGateContract["gates"]): string { function canonicalGateShapeSha256(gates: CiGateContract["gates"]): string {
const normalized = gates.map( const normalized = gates.map(
@@ -489,15 +493,20 @@ function canonicalAuthorityBaselineFailures(contract: CiGateContract): string[]
// Final fix wave, item 1: FE-GATE-010 gained `check-tech-log-contract`, the // Final fix wave, item 1: FE-GATE-010 gained `check-tech-log-contract`, the
// drift gate that pins the vendored canonical Studio contract to its digest. // drift gate that pins the vendored canonical Studio contract to its digest.
// Until it was referenced by a gate it ran only when typed by hand. // Until it was referenced by a gate it ran only when typed by hand.
if (contract.commands.length !== 83 || commandReferenceCount !== 95) { // Alignment follow-up, item 2: FE-GATE-007 gained `test-tech-log`. The suite
// already ran inside `test:coverage`'s combined vitest invocation, so a
// TechLog failure was reported as a coverage-gate failure with no junit of
// its own to name it.
if (contract.commands.length !== 84 || commandReferenceCount !== 96) {
failures.push( failures.push(
`command authority baseline must contain exactly 83 definitions and 95 references; received ${contract.commands.length} definitions and ${commandReferenceCount} references`, `command authority baseline must contain exactly 84 definitions and 96 references; received ${contract.commands.length} definitions and ${commandReferenceCount} references`,
); );
} }
// Template merge. 126 product artifacts plus the two the template added. // Template merge. 126 product artifacts plus the two the template added.
// Task 11 added one more: the TECH_LOG_STUDIO_ASSETS manual a11y evidence file. // Task 11 added one more: the TECH_LOG_STUDIO_ASSETS manual a11y evidence file.
if (contract.artifacts.length !== 129) { // Alignment follow-up, item 2 added the TechLog junit report.
failures.push(`artifact authority baseline must contain exactly 129 artifacts; received ${contract.artifacts.length}`); if (contract.artifacts.length !== 130) {
failures.push(`artifact authority baseline must contain exactly 130 artifacts; received ${contract.artifacts.length}`);
} }
if (contract.stages.length !== 5) { if (contract.stages.length !== 5) {
failures.push(`stage authority baseline must contain exactly 5 stages; received ${contract.stages.length}`); failures.push(`stage authority baseline must contain exactly 5 stages; received ${contract.stages.length}`);
+6 -4
View File
@@ -190,14 +190,16 @@ describe("CI gate contract", () => {
); );
expect(contract.jobs).toHaveLength(9); expect(contract.jobs).toHaveLength(9);
// Final fix wave item 1: FE-GATE-010 gained `check-tech-log-contract`. // Final fix wave item 1: FE-GATE-010 gained `check-tech-log-contract`.
expect(contract.commands).toHaveLength(83); // Alignment follow-up item 2: FE-GATE-007 gained `test-tech-log`.
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(95); expect(contract.commands).toHaveLength(84);
expect(contract.gates.reduce((total, gate) => total + gate.commandIds.length, 0)).toBe(96);
expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23); expect(contract.commands.filter(({ expect }) => expect === "fail")).toHaveLength(23);
// Template merge. Recounted from the merged config/ci/gates.json rather // Template merge. Recounted from the merged config/ci/gates.json rather
// than taking either side's number. // than taking either side's number.
// Task 11 added TECH_LOG_STUDIO_ASSETS's manual accessibility evidence. // Task 11 added TECH_LOG_STUDIO_ASSETS's manual accessibility evidence.
expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(108); // Alignment follow-up item 2 added the TechLog junit report.
expect(contract.artifacts).toHaveLength(129); expect(contract.gates.reduce((total, gate) => total + gate.evidenceArtifactIds.length, 0)).toBe(109);
expect(contract.artifacts).toHaveLength(130);
expect(contract.stages).toHaveLength(5); expect(contract.stages).toHaveLength(5);
expect(contract.retention.classes).toHaveLength(5); expect(contract.retention.classes).toHaveLength(5);
expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2); expect(index.gates.get("FE-GATE-015")?.commandIds).toHaveLength(2);
@@ -186,10 +186,12 @@ describe("selective Task 3 contract closure", () => {
// template's 2. Task 11 added TECH_LOG_STUDIO_ASSETS's manual // template's 2. Task 11 added TECH_LOG_STUDIO_ASSETS's manual
// accessibility evidence, bringing the total to 129. // accessibility evidence, bringing the total to 129.
// Final fix wave item 1 added `check-tech-log-contract` to FE-GATE-010. // Final fix wave item 1 added `check-tech-log-contract` to FE-GATE-010.
// Alignment follow-up item 2 added `test-tech-log` and its junit report to
// FE-GATE-007, bringing the totals to 84/96/130.
expect(canonical.gates).toHaveLength(27); expect(canonical.gates).toHaveLength(27);
expect(canonical.commands).toHaveLength(83); expect(canonical.commands).toHaveLength(84);
expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(95); expect(canonical.gates.reduce((sum, gate) => sum + gate.commandIds.length, 0)).toBe(96);
expect(canonical.artifacts).toHaveLength(129); expect(canonical.artifacts).toHaveLength(130);
expect(canonical.stages).toHaveLength(5); expect(canonical.stages).toHaveLength(5);
expect(canonical.retention.classes).toHaveLength(5); expect(canonical.retention.classes).toHaveLength(5);