import { mkdir, readFile, writeFile } from "node:fs/promises"; import { evaluatePromotionReadiness, PROMOTION_FORMULA, } from "../src/application/policies/promotion-readiness.js"; const document = JSON.parse(await readFile("config/ci/gates.json", "utf8")); const workflow = await readFile(document.providerAdapter, "utf8"); const failures = []; const stageFormula = { merge: PROMOTION_FORMULA.MERGE_READY, release: PROMOTION_FORMULA.RELEASE_READY, production: PROMOTION_FORMULA.PROD_PROMOTION_READY, field: PROMOTION_FORMULA.FIELD_SLO_READY, documentation: PROMOTION_FORMULA.DOCUMENTATION_READY, }; for (const [stage, expectedGates] of Object.entries(stageFormula)) { const actual = document.stages[stage]?.gates; if (JSON.stringify(actual) !== JSON.stringify(expectedGates)) { failures.push(`${stage} gate formula drift`); } } const configuredGateIds = Object.keys(document.gates).sort(); const expectedGateIds = Array.from( { length: 26 }, (_, index) => `FE-GATE-${String(index + 1).padStart(3, "0")}`, ); if (JSON.stringify(configuredGateIds) !== JSON.stringify(expectedGateIds)) { failures.push("gate registry must contain FE-GATE-001..026 exactly once"); } for (const [gateId, gate] of Object.entries(document.gates)) { if (!gate.steps?.length || !gate.evidence?.length || !gate.retentionClass) { failures.push(`${gateId} lacks command, evidence, or retention wiring`); } } const forbiddenWorkflowPatterns = [ /continue-on-error\s*:/, /retention-days\s*:/, /allow_failure\s*:/, ]; for (const pattern of forbiddenWorkflowPatterns) { if (pattern.test(workflow)) { failures.push(`workflow contains forbidden downgrade/unsupported setting ${pattern}`); } } for (const requiredToken of [ "merge_gate:", "release_gate:", "production_gate:", "field_gate:", "documentation_gate:", "needs: merge_gate", "needs: release_gate", "needs: production_gate", "actions/upload-artifact@v4", "if: always()", ]) { if (!workflow.includes(requiredToken)) { failures.push(`workflow missing ${requiredToken}`); } } const passingResults = Object.fromEntries( expectedGateIds.map((gateId) => [gateId, /** @type {const} */ ("PASS")]), ); const allPass = evaluatePromotionReadiness(passingResults); const negativeFixtures = []; for (const [readiness, gateIds] of Object.entries(PROMOTION_FORMULA)) { const failedGate = gateIds[0]; const result = evaluatePromotionReadiness({ ...passingResults, [failedGate]: "FAIL", }); const passed = /** @type {Readonly>} */ (result)[readiness] === false; negativeFixtures.push({ readiness, failedGate, passed }); if (!passed) failures.push(`${readiness} did not fail closed`); } if (!Object.values(allPass).every(Boolean)) { failures.push("all-PASS formula did not produce every readiness state"); } const report = { schemaVersion: 1, generatedAt: new Date().toISOString(), providerAdapter: document.providerAdapter, gateCount: configuredGateIds.length, noDowngrade: failures.every( (failure) => !failure.includes("downgrade"), ), durationStatus: document.retention.durationStatus, negativeFixtures, failures, passed: failures.length === 0, }; await mkdir("artifacts/quality", { recursive: true }); await writeFile( "artifacts/quality/ci-contract.json", `${JSON.stringify(report, null, 2)}\n`, ); if (failures.length > 0) { process.stderr.write(`CI contract failed:\n${failures.join("\n")}\n`); process.exit(1); } process.stdout.write("CI contract: 26 blocking gates and 4-tier graph PASS\n");