Files
pikasTech-HWLAB/scripts/validate-dev-gate-report.mjs
T
Lyon 357a68bb95 fix: layer dev DB readiness evidence
Refs #49

Merged by commander after reviewing Code Queue task codex_1779422762669_1. Adds redacted DB readiness layering and refreshes DEV endpoint/report evidence. Follow-up: deploy/verify the new readiness probe in DEV via standard image CI/CD.
2026-05-22 12:27:01 +08:00

1406 lines
62 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import { access, readdir, readFile } from "node:fs/promises";
import { constants as fsConstants } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const reportsDir = path.join(repoRoot, "reports/dev-gate");
const contractIssue = "pikasTech/HWLAB#31";
const issueFamily = Object.freeze({
DEV_DEPLOY_APPLY: "pikasTech/HWLAB#33",
DEV_GATE_PREFLIGHT: "pikasTech/HWLAB#34",
DEV_ARTIFACT_PUBLISH: "pikasTech/HWLAB#35",
DEV_EDGE_HEALTH: "pikasTech/HWLAB#36",
DEV_M4_AGENT_LOOP: "pikasTech/HWLAB#37",
DEV_M3_HARDWARE_LOOP: "pikasTech/HWLAB#38",
DEV_M5_GATE_REPORT: "pikasTech/HWLAB#39",
DEV_BASE_IMAGE_PREFLIGHT: "pikasTech/HWLAB#40",
DEV_EVIDENCE_BLOCKER_AGGREGATOR: "pikasTech/HWLAB#41",
DEV_M5_GATE_AGGREGATOR_V2: "pikasTech/HWLAB#58"
});
const allowedIssues = new Set([
contractIssue,
...Object.values(issueFamily)
]);
const requiredPreflightIssue = issueFamily.DEV_GATE_PREFLIGHT;
const requiredMilestoneIds = ["M0", "M1", "M2", "M3", "M4", "M5"];
const evidenceLevels = new Set(["SOURCE", "LOCAL", "DRY-RUN", "DEV-LIVE", "BLOCKED"]);
const requiredValidationCommands = [
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs"
];
const requiredAggregatorV2Commands = [
"node --check scripts/dev-evidence-blocker-aggregator.mjs",
"node --check scripts/src/dev-evidence-blocker-aggregator.mjs",
"node scripts/dev-evidence-blocker-aggregator.mjs --check",
"node scripts/dev-evidence-blocker-aggregator.mjs --markdown",
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs"
];
const requiredDevDeployApplySupports = [
"pikasTech/HWLAB#50",
"pikasTech/HWLAB#7",
"pikasTech/HWLAB#33",
"pikasTech/HWLAB#17",
"pikasTech/HWLAB#32",
"pikasTech/HWLAB#30"
];
const requiredDevDeployApplyValidationCommands = [
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs",
"node --check scripts/dev-deploy-apply.mjs",
"node --check scripts/src/dev-deploy-apply.mjs",
"node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked"
];
const requiredPreflightValidationCommands = [
"node --check scripts/dev-gate-preflight.mjs",
"node --check scripts/src/dev-gate-preflight.mjs",
"node --check scripts/refresh-artifact-catalog.mjs",
"node scripts/dev-gate-preflight.mjs"
];
const requiredSmokeCommand = "node scripts/m1-contract-smoke.mjs";
const requiredDryRunCommand =
"node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run";
const requiredDocs = [
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md"
];
const requiredDevM3Issue = issueFamily.DEV_M3_HARDWARE_LOOP;
const requiredDevM3ValidationCommands = [
"node --check scripts/dev-m3-hardware-loop-smoke.mjs",
"node --check scripts/validate-dev-m3-cardinality.mjs",
"node scripts/validate-dev-m3-cardinality.mjs",
"node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"
];
const requiredDevM3Docs = [
"docs/dev-acceptance-matrix.md",
"docs/m3-hardware-loop.md"
];
const requiredDevEdgeIssue = issueFamily.DEV_EDGE_HEALTH;
const requiredDevEdgeValidationCommands = [
"node --check scripts/dev-edge-health-smoke.mjs",
"node --check scripts/src/dev-edge-health-smoke-lib.mjs",
"node scripts/dev-edge-health-smoke.mjs --live --write-report"
];
const requiredDevEdgeDocs = [
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md",
"docs/dev-edge-health.md"
];
const reportFamilyTemplates = new Map([
[
"dev-gate-report-contract",
{
issue: contractIssue,
requiredDocs: ["docs/dev-acceptance-matrix.md", "docs/m0-contract-audit.md"],
requiredValidationCommands,
requiredSmokeCommand,
requiredDryRunCommand
}
],
[
"dev-mvp-gate-report",
{
issue: issueFamily.DEV_M5_GATE_REPORT,
requiredDocs: [
"docs/dev-gate-report.md",
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md",
"docs/m1-local-smoke.md",
"docs/m2-dev-deploy-smoke.md",
"docs/m3-hardware-loop.md",
"docs/m4-agent-loop.md",
"docs/m5-mvp-e2e.md",
"docs/runtime-boundary-guard.md",
"docs/topology-constraints.md",
"docs/artifact-catalog.md",
"docs/schema-drift-map.md",
"protocol/README.md",
"protocol/audit.md",
"protocol/evidence-chain.md",
"protocol/mvp-e2e-contract.md"
],
requiredValidationCommands: [
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs"
],
requiredSmokeCommand: "node scripts/m1-contract-smoke.mjs",
requiredDryRunCommand:
"node tools/hwlab-cli/bin/hwlab-cli.mjs test e2e --env dev --mvp --dry-run"
}
],
[
"dev-deploy-apply",
{
issue: issueFamily.DEV_DEPLOY_APPLY,
requiredDocs: [
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md",
"docs/dev-deploy-apply.md",
"docs/artifact-catalog.md",
"deploy/README.md"
],
requiredValidationCommands: [
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs",
"node --check scripts/dev-deploy-apply.mjs",
"node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked"
],
requiredSmokeCommand,
requiredDryRunCommand
}
],
[
"dev-artifact-publish",
{
issue: issueFamily.DEV_ARTIFACT_PUBLISH,
requiredDocs: [
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md",
"docs/artifact-catalog.md",
"docs/dev-artifact-publish.md",
"docs/dev-base-image-preflight.md"
],
requiredValidationCommands: [
"node --check scripts/dev-artifact-publish.mjs",
"node --check scripts/preflight-dev-base-image.mjs",
"node --check scripts/src/dev-base-image-preflight.mjs",
"node scripts/preflight-dev-base-image.mjs",
"node scripts/dev-artifact-publish.mjs --preflight --no-report",
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs"
],
requiredSmokeCommand,
requiredDryRunCommand
}
],
[
"dev-m4-agent-loop",
{
issue: issueFamily.DEV_M4_AGENT_LOOP,
requiredDocs: ["docs/dev-acceptance-matrix.md", "docs/dev-m4-agent-loop.md"],
requiredValidationCommands: [
"node --check scripts/validate-dev-gate-report.mjs",
"node scripts/validate-dev-gate-report.mjs",
"node --check scripts/dev-m4-agent-loop-smoke.mjs",
"node scripts/dev-m4-agent-loop-smoke.mjs --dry-run",
"node scripts/dev-m4-agent-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"
],
requiredSmokeCommand: "node scripts/m4-agent-loop-smoke.mjs",
requiredDryRunCommand: "node scripts/dev-m4-agent-loop-smoke.mjs --dry-run"
}
]
]);
const statusValues = new Set(["pass", "blocked", "not_run", "not_applicable", "failed"]);
const blockerTypes = new Set([
"contract_blocker",
"environment_blocker",
"network_blocker",
"runtime_blocker",
"agent_blocker",
"observability_blocker",
"safety_blocker"
]);
const blockerStates = new Set(["open", "acknowledged", "closed"]);
const artifactSourceStates = new Set(["source-present", "intentionally-disabled"]);
const preflightConclusions = new Set(["ready", "blocked"]);
const requiredPreflightSupports = [
"pikasTech/HWLAB#7",
"pikasTech/HWLAB#12",
"pikasTech/HWLAB#22",
"pikasTech/HWLAB#23",
"pikasTech/HWLAB#29",
"pikasTech/HWLAB#30",
"pikasTech/HWLAB#31"
];
function assertObject(value, label) {
assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`);
}
function assertString(value, label) {
assert.equal(typeof value, "string", `${label} must be a string`);
assert.ok(value.trim().length > 0, `${label} must not be empty`);
assert.ok(!value.includes("\n"), `${label} must be a single line`);
}
function assertArray(value, label) {
assert.ok(Array.isArray(value), `${label} must be an array`);
}
function assertStatus(value, label) {
assertString(value, label);
assert.ok(statusValues.has(value), `${label} must be one of ${Array.from(statusValues).join(", ")}`);
}
function assertTimestamp(value, label) {
assertString(value, label);
assert.ok(!Number.isNaN(Date.parse(value)), `${label} must be an RFC 3339 timestamp`);
}
function assertDbLayerBooleans(value, label) {
assertObject(value, label);
for (const field of ["configReady", "liveConnected", "connectionAttempted"]) {
assertBoolean(value[field], `${label}.${field}`);
}
assertString(value.connectionResult, `${label}.connectionResult`);
assertNoDbSecretMaterial(value, label);
}
function assertDbRuntimeReadiness(value, label) {
assertDbLayerBooleans(value, label);
for (const field of ["envInjected", "valuesRedacted", "secretMaterialRead", "liveDbEvidence"]) {
assertBoolean(value[field], `${label}.${field}`);
}
assert.equal(value.valuesRedacted, true, `${label}.valuesRedacted`);
assert.equal(value.secretMaterialRead, false, `${label}.secretMaterialRead`);
assertObject(value.secret, `${label}.secret`);
assertString(value.secret.secretName, `${label}.secret.secretName`);
assertString(value.secret.secretKey, `${label}.secret.secretKey`);
assert.equal(value.secret.redacted, true, `${label}.secret.redacted`);
assert.equal(value.secret.secretValueRead, false, `${label}.secret.secretValueRead`);
assertArray(value.evidence, `${label}.evidence`);
if (value.status === "blocked") {
assertString(value.blocker, `${label}.blocker`);
}
}
function assertNoDbSecretMaterial(value, label) {
const serialized = JSON.stringify(value);
for (const forbidden of ["postgres://", "postgresql://", "password=", "://user:"]) {
assert.equal(serialized.includes(forbidden), false, `${label} must not include ${forbidden}`);
}
}
function assertRepoRelativePath(value, label) {
assertString(value, label);
assert.ok(!path.isAbsolute(value), `${label} must be repo-relative`);
assert.ok(!value.startsWith(".."), `${label} must stay inside the repository`);
const absolutePath = path.resolve(repoRoot, value);
assert.ok(
absolutePath.startsWith(`${repoRoot}${path.sep}`),
`${label} must stay inside the repository`
);
return absolutePath;
}
function assertStringArray(value, label, { minLength = 0 } = {}) {
assertArray(value, label);
assert.ok(value.length >= minLength, `${label} must contain at least ${minLength} item(s)`);
for (const [index, item] of value.entries()) {
assertString(item, `${label}[${index}]`);
}
}
function assertUnique(values, label) {
assert.equal(new Set(values).size, values.length, `${label} must be unique`);
}
async function readJsonFile(relativePath) {
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
return JSON.parse(raw);
}
async function collectReportFiles() {
if (process.argv.length > 2) {
return process.argv.slice(2).map((entry) => path.relative(repoRoot, path.resolve(process.cwd(), entry)));
}
const entries = await readdir(reportsDir, { withFileTypes: true });
return entries
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
.map((entry) => path.join("reports/dev-gate", entry.name))
.sort();
}
async function validateReport(relativePath) {
const report = await readJsonFile(relativePath);
if (report.reportKind === "dev-gate-preflight") {
await validatePreflightReport(relativePath, report);
return;
}
if (report.reportKind === "dev-m5-gate-aggregator") {
await validateAggregatorV2Report(relativePath, report);
return;
}
const label = relativePath;
assertObject(report, label);
if (report.issue === requiredDevM3Issue || report.taskId === "dev-m3-hardware-loop") {
await validateDevM3Report(report, label);
return;
}
if (report.issue === requiredDevEdgeIssue || report.taskId === "dev-edge-health") {
await validateDevEdgeReport(report, label);
return;
}
const template = reportFamilyTemplates.get(report.taskId);
assert.ok(
template,
`${label}.taskId must be one of ${Array.from(reportFamilyTemplates.keys()).join(", ")}`
);
for (const field of [
"$schema",
"$id",
"reportVersion",
"issue",
"taskId",
"commitId",
"acceptanceLevel",
"devOnly",
"prodDisabled",
"sourceContract",
"validationCommands",
"localSmoke",
"dryRun",
"devPreconditions",
"blockers"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v1", `${label}.reportVersion`);
assert.equal(report.issue, template.issue, `${label}.issue`);
assert.match(report.taskId, /^[a-z][a-z0-9-]*$/, `${label}.taskId`);
assert.match(report.commitId, /^[a-f0-9]{7,40}$/, `${label}.commitId`);
assert.match(report.acceptanceLevel, /^[a-z][a-z0-9_-]*$/, `${label}.acceptanceLevel`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assertObject(report.sourceContract, `${label}.sourceContract`);
assertStatus(report.sourceContract.status, `${label}.sourceContract.status`);
assertString(report.sourceContract.summary, `${label}.sourceContract.summary`);
assertStringArray(report.sourceContract.documents, `${label}.sourceContract.documents`, {
minLength: template.requiredDocs.length
});
const documents = new Set(report.sourceContract.documents);
assertUnique(report.sourceContract.documents, `${label}.sourceContract.documents`);
for (const requiredDoc of template.requiredDocs) {
assert.ok(documents.has(requiredDoc), `${label}.sourceContract.documents missing ${requiredDoc}`);
}
for (const [index, documentPath] of report.sourceContract.documents.entries()) {
const absolutePath = assertRepoRelativePath(
documentPath,
`${label}.sourceContract.documents[${index}]`
);
await access(absolutePath, fsConstants.R_OK);
}
assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 });
assertUnique(report.validationCommands, `${label}.validationCommands`);
for (const requiredCommand of template.requiredValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
assertObject(report.localSmoke, `${label}.localSmoke`);
assertStatus(report.localSmoke.status, `${label}.localSmoke.status`);
assertStringArray(report.localSmoke.commands, `${label}.localSmoke.commands`, { minLength: 1 });
assertUnique(report.localSmoke.commands, `${label}.localSmoke.commands`);
assert.ok(
report.localSmoke.commands.includes(template.requiredSmokeCommand),
`${label}.localSmoke.commands missing ${template.requiredSmokeCommand}`
);
assertStringArray(report.localSmoke.evidence, `${label}.localSmoke.evidence`, { minLength: 1 });
assertString(report.localSmoke.summary, `${label}.localSmoke.summary`);
assertObject(report.dryRun, `${label}.dryRun`);
assertStatus(report.dryRun.status, `${label}.dryRun.status`);
assertStringArray(report.dryRun.commands, `${label}.dryRun.commands`, { minLength: 1 });
assertUnique(report.dryRun.commands, `${label}.dryRun.commands`);
assert.ok(
report.dryRun.commands.includes(template.requiredDryRunCommand),
`${label}.dryRun.commands missing ${template.requiredDryRunCommand}`
);
assertStringArray(report.dryRun.evidence, `${label}.dryRun.evidence`, { minLength: 1 });
assertString(report.dryRun.summary, `${label}.dryRun.summary`);
assertObject(report.devPreconditions, `${label}.devPreconditions`);
assertStatus(report.devPreconditions.status, `${label}.devPreconditions.status`);
assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, {
minLength: 1
});
assertString(report.devPreconditions.summary, `${label}.devPreconditions.summary`);
assertArray(report.blockers, `${label}.blockers`);
assertUnique(
report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`),
`${label}.blockers`
);
for (const [index, blocker] of report.blockers.entries()) {
const blockerLabel = `${label}.blockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`);
assertString(blocker.summary, `${blockerLabel}.summary`);
}
if (Object.hasOwn(report, "notes")) {
assertString(report.notes, `${label}.notes`);
}
if (Object.hasOwn(report, "gateStatus")) {
assertStatus(report.gateStatus, `${label}.gateStatus`);
}
if (Object.hasOwn(report, "artifactPublish")) {
assertObject(report.artifactPublish, `${label}.artifactPublish`);
assertArray(report.artifactPublish.services ?? [], `${label}.artifactPublish.services`);
for (const [index, service] of report.artifactPublish.services.entries()) {
const serviceLabel = `${label}.artifactPublish.services[${index}]`;
assertObject(service, serviceLabel);
assertString(service.serviceId, `${serviceLabel}.serviceId`);
assertString(service.status, `${serviceLabel}.status`);
assertString(service.implementationState, `${serviceLabel}.implementationState`);
assertString(service.sourceState, `${serviceLabel}.sourceState`);
assert.ok(
artifactSourceStates.has(service.sourceState),
`${serviceLabel}.sourceState must be source-present or intentionally-disabled`
);
if (service.sourceState === "intentionally-disabled") {
assert.equal(service.entrypoint, null, `${serviceLabel}.entrypoint must be null when intentionally disabled`);
}
}
}
if (Object.hasOwn(report, "milestones")) {
assertArray(report.milestones, `${label}.milestones`);
assert.deepEqual(
report.milestones.map((milestone) => milestone.id),
requiredMilestoneIds,
`${label}.milestones must cover M0-M5 in order`
);
assertUnique(
report.milestones.map((milestone) => milestone.id),
`${label}.milestones`
);
for (const [index, milestone] of report.milestones.entries()) {
const milestoneLabel = `${label}.milestones[${index}]`;
assertObject(milestone, milestoneLabel);
for (const field of ["id", "status", "commands", "evidence", "summary"]) {
assert.ok(Object.hasOwn(milestone, field), `${milestoneLabel} missing ${field}`);
}
assert.match(milestone.id, /^M[0-5]$/, `${milestoneLabel}.id`);
assertStatus(milestone.status, `${milestoneLabel}.status`);
assertStringArray(milestone.commands, `${milestoneLabel}.commands`, { minLength: 1 });
assertUnique(milestone.commands, `${milestoneLabel}.commands`);
assertStringArray(milestone.evidence, `${milestoneLabel}.evidence`, { minLength: 1 });
assertUnique(milestone.evidence, `${milestoneLabel}.evidence`);
assertString(milestone.summary, `${milestoneLabel}.summary`);
if (Object.hasOwn(milestone, "blocker")) {
assertString(milestone.blocker, `${milestoneLabel}.blocker`);
}
}
}
if (Object.hasOwn(report.devPreconditions, "commands")) {
assertStringArray(report.devPreconditions.commands, `${label}.devPreconditions.commands`, {
minLength: 1
});
assertUnique(report.devPreconditions.commands, `${label}.devPreconditions.commands`);
}
if (Object.hasOwn(report.devPreconditions, "evidence")) {
assertStringArray(report.devPreconditions.evidence, `${label}.devPreconditions.evidence`, {
minLength: 1
});
assertUnique(report.devPreconditions.evidence, `${label}.devPreconditions.evidence`);
}
if (report.taskId === "dev-deploy-apply") {
assertDevDeployApplyReport(report, label);
}
}
async function assertDocumentSet(documentsValue, label, requiredDocs) {
assertStringArray(documentsValue, label, { minLength: requiredDocs.length });
const documents = new Set(documentsValue);
assertUnique(documentsValue, label);
for (const requiredDoc of requiredDocs) {
assert.ok(documents.has(requiredDoc), `${label} missing ${requiredDoc}`);
}
for (const [index, documentPath] of documentsValue.entries()) {
const absolutePath = assertRepoRelativePath(documentPath, `${label}[${index}]`);
await access(absolutePath, fsConstants.R_OK);
}
}
function assertBlockers(blockers, label) {
assertArray(blockers, label);
assertUnique(
blockers.map((blocker) => `${blocker.type}::${blocker.scope}`),
label
);
for (const [index, blocker] of blockers.entries()) {
const blockerLabel = `${label}[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`);
assertString(blocker.summary, `${blockerLabel}.summary`);
}
}
function assertCommandList(value, label, { minLength = 1 } = {}) {
assertStringArray(value, label, { minLength });
assertUnique(value, label);
for (const command of value) {
assert.ok(!command.includes("--prod"), `${label} must not include PROD command`);
assert.ok(!command.includes("--production"), `${label} must not include production command`);
}
}
function assertDevDeployApplyReport(report, label) {
assert.equal(report.issue, issueFamily.DEV_DEPLOY_APPLY, `${label}.issue`);
assertStringArray(report.supports, `${label}.supports`, {
minLength: requiredDevDeployApplySupports.length
});
for (const supportedIssue of requiredDevDeployApplySupports) {
assert.ok(report.supports.includes(supportedIssue), `${label}.supports missing ${supportedIssue}`);
}
for (const requiredCommand of requiredDevDeployApplyValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
assertObject(report.devDeployApply, `${label}.devDeployApply`);
const plan = report.devDeployApply;
for (const field of [
"conclusion",
"applyBoundary",
"target",
"artifactPlan",
"workloadPlan",
"servicePlan",
"manualCommands",
"rollbackHint",
"remainingBlockers"
]) {
assert.ok(Object.hasOwn(plan, field), `${label}.devDeployApply missing ${field}`);
}
assertObject(plan.conclusion, `${label}.devDeployApply.conclusion`);
assert.ok(["blocked", "ready"].includes(plan.conclusion.status), `${label}.devDeployApply.conclusion.status`);
assertString(plan.conclusion.reason, `${label}.devDeployApply.conclusion.reason`);
assert.equal(plan.conclusion.blockerCount, report.blockers.length, `${label}.devDeployApply.conclusion.blockerCount`);
assertObject(plan.applyBoundary, `${label}.devDeployApply.applyBoundary`);
assert.equal(plan.applyBoundary.defaultNoWrite, plan.mode !== "apply", `${label}.devDeployApply.applyBoundary.defaultNoWrite`);
assert.equal(plan.applyBoundary.mutationAttempted, report.devDeployApply.mutationAttempted, `${label}.devDeployApply.applyBoundary.mutationAttempted`);
assertStringArray(plan.applyBoundary.applyRequiresFlags, `${label}.devDeployApply.applyBoundary.applyRequiresFlags`, {
minLength: 3
});
for (const flag of ["--apply", "--confirm-dev", "--confirmed-non-production"]) {
assert.ok(plan.applyBoundary.applyRequiresFlags.includes(flag), `${label}.devDeployApply.applyBoundary.applyRequiresFlags missing ${flag}`);
}
assertStringArray(plan.applyBoundary.forbiddenActions, `${label}.devDeployApply.applyBoundary.forbiddenActions`, {
minLength: 6
});
for (const forbiddenAction of ["prod-deploy", "secret-read", "runtime-substitute", "heavy-master-e2e"]) {
assert.ok(plan.applyBoundary.forbiddenActions.includes(forbiddenAction), `${label}.devDeployApply.applyBoundary.forbiddenActions missing ${forbiddenAction}`);
}
assertString(plan.applyBoundary.writeScope, `${label}.devDeployApply.applyBoundary.writeScope`);
assertString(plan.applyBoundary.noWriteScope, `${label}.devDeployApply.applyBoundary.noWriteScope`);
assertObject(plan.target, `${label}.devDeployApply.target`);
assert.equal(plan.target.environment, "dev", `${label}.devDeployApply.target.environment`);
assert.equal(plan.target.namespace, "hwlab-dev", `${label}.devDeployApply.target.namespace`);
assert.equal(plan.target.endpoint, "http://74.48.78.17:16667", `${label}.devDeployApply.target.endpoint`);
assert.equal(plan.target.prodDisabled, true, `${label}.devDeployApply.target.prodDisabled`);
assertObject(plan.artifactPlan, `${label}.devDeployApply.artifactPlan`);
for (const field of ["expectedArtifactCommit", "deployCommitId", "catalogCommitId", "sourceCommitId"]) {
assertString(plan.artifactPlan[field], `${label}.devDeployApply.artifactPlan.${field}`);
}
assertStringArray(plan.artifactPlan.serviceCommitIds, `${label}.devDeployApply.artifactPlan.serviceCommitIds`, {
minLength: 1
});
assert.equal(typeof plan.artifactPlan.matchesSourceCommit, "boolean", `${label}.devDeployApply.artifactPlan.matchesSourceCommit`);
assert.equal(typeof plan.artifactPlan.published, "boolean", `${label}.devDeployApply.artifactPlan.published`);
assert.equal(typeof plan.artifactPlan.registryVerified, "boolean", `${label}.devDeployApply.artifactPlan.registryVerified`);
assert.ok(Number.isInteger(plan.artifactPlan.imageCount), `${label}.devDeployApply.artifactPlan.imageCount`);
assertStringArray(plan.artifactPlan.unpublishedServices, `${label}.devDeployApply.artifactPlan.unpublishedServices`);
assertArray(plan.workloadPlan, `${label}.devDeployApply.workloadPlan`);
assert.ok(plan.workloadPlan.length >= 1, `${label}.devDeployApply.workloadPlan must not be empty`);
assertArray(plan.servicePlan, `${label}.devDeployApply.servicePlan`);
assert.ok(plan.servicePlan.length >= 1, `${label}.devDeployApply.servicePlan must not be empty`);
for (const [index, workload] of plan.workloadPlan.entries()) {
const workloadLabel = `${label}.devDeployApply.workloadPlan[${index}]`;
assertObject(workload, workloadLabel);
for (const field of ["kind", "name", "namespace", "serviceIds", "containers"]) {
assert.ok(Object.hasOwn(workload, field), `${workloadLabel} missing ${field}`);
}
assert.equal(workload.namespace, "hwlab-dev", `${workloadLabel}.namespace`);
assertStringArray(workload.serviceIds, `${workloadLabel}.serviceIds`, { minLength: 1 });
assertArray(workload.containers, `${workloadLabel}.containers`);
}
assertObject(plan.manualCommands, `${label}.devDeployApply.manualCommands`);
assert.ok(["blocked", "ready"].includes(plan.manualCommands.status), `${label}.devDeployApply.manualCommands.status`);
assertCommandList(plan.manualCommands.beforeHumanApproval, `${label}.devDeployApply.manualCommands.beforeHumanApproval`);
assertCommandList(plan.manualCommands.afterHumanApproval, `${label}.devDeployApply.manualCommands.afterHumanApproval`, {
minLength: 0
});
assertString(plan.manualCommands.summary, `${label}.devDeployApply.manualCommands.summary`);
if (plan.conclusion.status === "blocked") {
assert.equal(plan.manualCommands.status, "blocked", `${label}.devDeployApply.manualCommands.status`);
assert.equal(plan.manualCommands.afterHumanApproval.length, 0, `${label}.devDeployApply.manualCommands.afterHumanApproval`);
}
if (plan.conclusion.status === "ready") {
assert.equal(plan.manualCommands.status, "ready", `${label}.devDeployApply.manualCommands.status`);
assert.ok(
plan.manualCommands.afterHumanApproval.includes("node scripts/dev-deploy-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report"),
`${label}.devDeployApply.manualCommands.afterHumanApproval missing apply command`
);
}
assertObject(plan.rollbackHint, `${label}.devDeployApply.rollbackHint`);
assert.equal(plan.rollbackHint.namespace, "hwlab-dev", `${label}.devDeployApply.rollbackHint.namespace`);
assertString(plan.rollbackHint.strategy, `${label}.devDeployApply.rollbackHint.strategy`);
assertCommandList(plan.rollbackHint.captureBeforeApply, `${label}.devDeployApply.rollbackHint.captureBeforeApply`);
assertCommandList(plan.rollbackHint.deploymentRollbackCommands, `${label}.devDeployApply.rollbackHint.deploymentRollbackCommands`);
assertCommandList(plan.rollbackHint.jobCleanupCommands, `${label}.devDeployApply.rollbackHint.jobCleanupCommands`);
assertCommandList(plan.rollbackHint.postRollbackChecks, `${label}.devDeployApply.rollbackHint.postRollbackChecks`);
assertArray(plan.remainingBlockers, `${label}.devDeployApply.remainingBlockers`);
assert.equal(plan.remainingBlockers.length, report.blockers.length, `${label}.devDeployApply.remainingBlockers.length`);
for (const [index, blocker] of plan.remainingBlockers.entries()) {
const blockerLabel = `${label}.devDeployApply.remainingBlockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary", "unblockHint"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be known`);
assertString(blocker.unblockHint, `${blockerLabel}.unblockHint`);
}
}
function assertShaOrNotPublished(value, label) {
assertString(value, label);
assert.ok(
value === "not_published" || /^sha256:[a-f0-9]{64}$/.test(value),
`${label} must be not_published or a sha256 digest`
);
}
function assertArtifactIdentity(identity, label, target) {
assertObject(identity, label);
for (const field of [
"source",
"deployManifest",
"artifactCatalog",
"services",
"serviceCommitIds",
"matchesSource",
"publishVerified",
"refreshCommands"
]) {
assert.ok(Object.hasOwn(identity, field), `${label} missing ${field}`);
}
assertObject(identity.source, `${label}.source`);
assert.equal(identity.source.ref, target.ref, `${label}.source.ref`);
assert.equal(identity.source.commitId, target.commitId, `${label}.source.commitId`);
assert.equal(identity.source.shortCommitId, target.shortCommitId, `${label}.source.shortCommitId`);
assertObject(identity.deployManifest, `${label}.deployManifest`);
assert.equal(identity.deployManifest.path, "deploy/deploy.json", `${label}.deployManifest.path`);
assert.match(identity.deployManifest.commitId, /^[a-f0-9]{7,40}$/, `${label}.deployManifest.commitId`);
assert.equal(typeof identity.deployManifest.matchesSource, "boolean", `${label}.deployManifest.matchesSource`);
assertObject(identity.artifactCatalog, `${label}.artifactCatalog`);
assert.equal(identity.artifactCatalog.path, "deploy/artifact-catalog.dev.json", `${label}.artifactCatalog.path`);
assert.match(identity.artifactCatalog.commitId, /^[a-f0-9]{7,40}$/, `${label}.artifactCatalog.commitId`);
assert.ok(["contract-skeleton", "published"].includes(identity.artifactCatalog.artifactState), `${label}.artifactCatalog.artifactState`);
assert.equal(typeof identity.artifactCatalog.ciPublished, "boolean", `${label}.artifactCatalog.ciPublished`);
assert.equal(typeof identity.artifactCatalog.registryVerified, "boolean", `${label}.artifactCatalog.registryVerified`);
assertString(identity.artifactCatalog.provenance, `${label}.artifactCatalog.provenance`);
assert.equal(typeof identity.artifactCatalog.matchesSource, "boolean", `${label}.artifactCatalog.matchesSource`);
assertObject(identity.artifactCatalog.digestCounts, `${label}.artifactCatalog.digestCounts`);
for (const field of ["sha256", "notPublished", "invalid"]) {
assert.equal(typeof identity.artifactCatalog.digestCounts[field], "number", `${label}.artifactCatalog.digestCounts.${field}`);
}
assertArray(identity.services, `${label}.services`);
assert.ok(identity.services.length >= 1, `${label}.services must not be empty`);
assertUnique(identity.services.map((service) => service.serviceId), `${label}.services`);
for (const [index, service] of identity.services.entries()) {
const serviceLabel = `${label}.services[${index}]`;
assertObject(service, serviceLabel);
for (const field of ["serviceId", "commitId", "matchesSource", "image", "imageTag", "digest", "publishState"]) {
assert.ok(Object.hasOwn(service, field), `${serviceLabel} missing ${field}`);
}
assertString(service.serviceId, `${serviceLabel}.serviceId`);
assert.match(service.commitId, /^[a-f0-9]{7,40}$/, `${serviceLabel}.commitId`);
assert.equal(typeof service.matchesSource, "boolean", `${serviceLabel}.matchesSource`);
assertString(service.image, `${serviceLabel}.image`);
assertString(service.imageTag, `${serviceLabel}.imageTag`);
assertShaOrNotPublished(service.digest, `${serviceLabel}.digest`);
assert.ok(["skeleton-only", "published"].includes(service.publishState), `${serviceLabel}.publishState`);
}
assertStringArray(identity.serviceCommitIds, `${label}.serviceCommitIds`, { minLength: 1 });
assert.equal(typeof identity.matchesSource, "boolean", `${label}.matchesSource`);
assert.equal(typeof identity.publishVerified, "boolean", `${label}.publishVerified`);
assertObject(identity.refreshCommands, `${label}.refreshCommands`);
assert.equal(
identity.refreshCommands.blocked,
`node scripts/refresh-artifact-catalog.mjs --target-ref ${target.ref} --blocked`,
`${label}.refreshCommands.blocked`
);
assert.equal(
identity.refreshCommands.published,
`node scripts/refresh-artifact-catalog.mjs --target-ref ${target.ref} --publish-report reports/dev-gate/dev-artifacts.json`,
`${label}.refreshCommands.published`
);
if (identity.publishVerified) {
assert.equal(identity.artifactCatalog.ciPublished, true, `${label}.artifactCatalog.ciPublished`);
assert.equal(identity.artifactCatalog.registryVerified, true, `${label}.artifactCatalog.registryVerified`);
assert.equal(identity.artifactCatalog.digestCounts.sha256, identity.services.length, `${label}.artifactCatalog.digestCounts.sha256`);
assert.equal(identity.artifactCatalog.digestCounts.notPublished, 0, `${label}.artifactCatalog.digestCounts.notPublished`);
assert.equal(identity.artifactCatalog.digestCounts.invalid, 0, `${label}.artifactCatalog.digestCounts.invalid`);
}
}
async function validateDevEdgeReport(report, label) {
for (const field of [
"$schema",
"$id",
"reportVersion",
"issue",
"taskId",
"commitId",
"acceptanceLevel",
"devOnly",
"prodDisabled",
"sourceContract",
"validationCommands",
"localSmoke",
"dryRun",
"devPreconditions",
"blockers",
"edgeHealth"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v1", `${label}.reportVersion`);
assert.equal(report.issue, requiredDevEdgeIssue, `${label}.issue`);
assert.equal(report.taskId, "dev-edge-health", `${label}.taskId`);
assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`);
assert.equal(report.acceptanceLevel, "dev_edge_health", `${label}.acceptanceLevel`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assertObject(report.sourceContract, `${label}.sourceContract`);
assertStatus(report.sourceContract.status, `${label}.sourceContract.status`);
await assertDocumentSet(
report.sourceContract.documents,
`${label}.sourceContract.documents`,
requiredDevEdgeDocs
);
assertString(report.sourceContract.summary, `${label}.sourceContract.summary`);
assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 3 });
assertUnique(report.validationCommands, `${label}.validationCommands`);
for (const requiredCommand of requiredDevEdgeValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
for (const section of ["localSmoke", "dryRun", "devPreconditions"]) {
assertObject(report[section], `${label}.${section}`);
assertStatus(report[section].status, `${label}.${section}.status`);
assertString(report[section].summary, `${label}.${section}.summary`);
}
assertStringArray(report.localSmoke.commands, `${label}.localSmoke.commands`, { minLength: 1 });
assertStringArray(report.localSmoke.evidence, `${label}.localSmoke.evidence`, { minLength: 1 });
assertStringArray(report.dryRun.commands, `${label}.dryRun.commands`, { minLength: 1 });
assertStringArray(report.dryRun.evidence, `${label}.dryRun.evidence`, { minLength: 1 });
assertStringArray(report.devPreconditions.requirements, `${label}.devPreconditions.requirements`, {
minLength: 1
});
assertBlockers(report.blockers, `${label}.blockers`);
assertObject(report.edgeHealth, `${label}.edgeHealth`);
assertTimestamp(report.edgeHealth.generatedAt, `${label}.edgeHealth.generatedAt`);
assert.equal(report.edgeHealth.endpoint, "http://74.48.78.17:16667", `${label}.edgeHealth.endpoint`);
assert.ok(
["pass", "blocker", "not_run"].includes(report.edgeHealth.status),
`${label}.edgeHealth.status must be pass, blocker, or not_run`
);
assertString(report.edgeHealth.classification, `${label}.edgeHealth.classification`);
assertObject(report.edgeHealth.contracts, `${label}.edgeHealth.contracts`);
assertDbLayerBooleans(
report.edgeHealth.contracts?.deploy?.cloudApiDb,
`${label}.edgeHealth.contracts.deploy.cloudApiDb`
);
assertDbRuntimeReadiness(
report.edgeHealth.runtimeDbReadiness,
`${label}.edgeHealth.runtimeDbReadiness`
);
assertArray(report.edgeHealth.publicTcp ?? [], `${label}.edgeHealth.publicTcp`);
assertArray(report.edgeHealth.publicHttp ?? [], `${label}.edgeHealth.publicHttp`);
assertObject(report.edgeHealth.kubernetes ?? {}, `${label}.edgeHealth.kubernetes`);
}
async function validateDevM3Report(report, label) {
for (const field of [
"$schema",
"$id",
"reportVersion",
"issue",
"taskId",
"commitId",
"acceptanceLevel",
"devOnly",
"prodDisabled",
"sourceContract",
"validationCommands",
"runtimeTarget",
"safetyGates",
"liveChecks",
"liveOperation",
"blockers",
"summary"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v1", `${label}.reportVersion`);
assert.equal(report.issue, requiredDevM3Issue, `${label}.issue`);
assert.equal(report.taskId, "dev-m3-hardware-loop", `${label}.taskId`);
assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`);
assert.equal(report.acceptanceLevel, "dev_m3_hardware_loop", `${label}.acceptanceLevel`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assertObject(report.sourceContract, `${label}.sourceContract`);
assertStatus(report.sourceContract.status, `${label}.sourceContract.status`);
assertStringArray(report.sourceContract.documents, `${label}.sourceContract.documents`, {
minLength: requiredDevM3Docs.length
});
assertString(report.sourceContract.summary, `${label}.sourceContract.summary`);
const documents = new Set(report.sourceContract.documents);
assertUnique(report.sourceContract.documents, `${label}.sourceContract.documents`);
for (const requiredDoc of requiredDevM3Docs) {
assert.ok(documents.has(requiredDoc), `${label}.sourceContract.documents missing ${requiredDoc}`);
}
for (const [index, documentPath] of report.sourceContract.documents.entries()) {
const absolutePath = assertRepoRelativePath(
documentPath,
`${label}.sourceContract.documents[${index}]`
);
await access(absolutePath, fsConstants.R_OK);
}
assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 });
assertUnique(report.validationCommands, `${label}.validationCommands`);
for (const requiredCommand of requiredDevM3ValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
assertObject(report.runtimeTarget, `${label}.runtimeTarget`);
assert.equal(report.runtimeTarget.endpoint, "http://74.48.78.17:16667", `${label}.runtimeTarget.endpoint`);
assert.equal(report.runtimeTarget.namespace, "hwlab-dev", `${label}.runtimeTarget.namespace`);
assert.equal(report.runtimeTarget.environment, "dev", `${label}.runtimeTarget.environment`);
assert.equal(report.runtimeTarget.requiredBoxSimulators, 2, `${label}.runtimeTarget.requiredBoxSimulators`);
assert.equal(report.runtimeTarget.requiredGatewaySimulators, 2, `${label}.runtimeTarget.requiredGatewaySimulators`);
assert.equal(report.runtimeTarget.realHardwareAllowed, false, `${label}.runtimeTarget.realHardwareAllowed`);
assert.equal(report.runtimeTarget.prodAllowed, false, `${label}.runtimeTarget.prodAllowed`);
assertObject(report.safetyGates, `${label}.safetyGates`);
for (const field of [
"liveFlagRequired",
"confirmDevRequired",
"confirmedNonProductionRequired",
"prodForbidden",
"realHardwareForbidden",
"secretReadForbidden",
"forcePushForbidden",
"unideskRuntimeSubstitutionForbidden"
]) {
assert.equal(report.safetyGates[field], true, `${label}.safetyGates.${field}`);
}
assertArray(report.liveChecks, `${label}.liveChecks`);
assert.ok(report.liveChecks.length >= 8, `${label}.liveChecks must include DEV M3 checks`);
assertUnique(report.liveChecks.map((check) => check.id), `${label}.liveChecks`);
for (const [index, check] of report.liveChecks.entries()) {
const checkLabel = `${label}.liveChecks[${index}]`;
assertObject(check, checkLabel);
for (const field of ["id", "status", "summary", "evidence"]) {
assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`);
}
assertString(check.id, `${checkLabel}.id`);
assertStatus(check.status, `${checkLabel}.status`);
assertString(check.summary, `${checkLabel}.summary`);
assertStringArray(check.evidence, `${checkLabel}.evidence`, { minLength: 1 });
if (Object.hasOwn(check, "blockerClass")) {
assert.ok(blockerTypes.has(check.blockerClass), `${checkLabel}.blockerClass must be known`);
}
}
if (Object.hasOwn(report, "localRuntimeObservations")) {
assertArray(report.localRuntimeObservations, `${label}.localRuntimeObservations`);
for (const [index, observation] of report.localRuntimeObservations.entries()) {
const observationLabel = `${label}.localRuntimeObservations[${index}]`;
assertObject(observation, observationLabel);
for (const field of ["id", "status", "command", "summary"]) {
assert.ok(Object.hasOwn(observation, field), `${observationLabel} missing ${field}`);
assertString(observation[field], `${observationLabel}.${field}`);
}
}
}
if (Object.hasOwn(report, "readOnlySupplementalEvidence")) {
assertArray(report.readOnlySupplementalEvidence, `${label}.readOnlySupplementalEvidence`);
for (const [index, evidence] of report.readOnlySupplementalEvidence.entries()) {
const evidenceLabel = `${label}.readOnlySupplementalEvidence[${index}]`;
assertObject(evidence, evidenceLabel);
for (const field of ["id", "status", "source", "summary", "evidence", "requiredFollowUp"]) {
assert.ok(Object.hasOwn(evidence, field), `${evidenceLabel} missing ${field}`);
}
assertString(evidence.id, `${evidenceLabel}.id`);
assert.ok(
["pass", "gap", "source-ready", "manifest-ready"].includes(evidence.status),
`${evidenceLabel}.status must be pass, gap, source-ready, or manifest-ready`
);
assertRepoRelativePath(evidence.source, `${evidenceLabel}.source`);
assertString(evidence.summary, `${evidenceLabel}.summary`);
assertObject(evidence.evidence, `${evidenceLabel}.evidence`);
assertString(evidence.requiredFollowUp, `${evidenceLabel}.requiredFollowUp`);
if (Object.hasOwn(evidence, "validationCommand")) {
assertString(evidence.validationCommand, `${evidenceLabel}.validationCommand`);
}
if (Object.hasOwn(evidence, "blockerClass") && evidence.blockerClass !== null) {
assert.ok(blockerTypes.has(evidence.blockerClass), `${evidenceLabel}.blockerClass must be known`);
}
}
}
assertObject(report.liveOperation, `${label}.liveOperation`);
assertStatus(report.liveOperation.status, `${label}.liveOperation.status`);
for (const field of ["operationId", "traceId", "auditId", "evidenceId", "summary"]) {
assertString(report.liveOperation[field], `${label}.liveOperation.${field}`);
}
assertArray(report.blockers, `${label}.blockers`);
assertUnique(
report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`),
`${label}.blockers`
);
for (const [index, blocker] of report.blockers.entries()) {
const blockerLabel = `${label}.blockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`);
assertString(blocker.summary, `${blockerLabel}.summary`);
}
assertObject(report.summary, `${label}.summary`);
assertStatus(report.summary.status, `${label}.summary.status`);
assertTimestamp(report.summary.observedAt, `${label}.summary.observedAt`);
assertString(report.summary.result, `${label}.summary.result`);
}
async function validatePreflightReport(relativePath, report) {
const label = relativePath;
assertObject(report, label);
for (const field of [
"$schema",
"$id",
"reportVersion",
"reportKind",
"issue",
"supports",
"target",
"generatedAt",
"mode",
"devOnly",
"prodDisabled",
"forbiddenActions",
"validationCommands",
"artifactIdentity",
"conclusion",
"checks",
"blockers"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v1", `${label}.reportVersion`);
assert.equal(report.reportKind, "dev-gate-preflight", `${label}.reportKind`);
assert.equal(report.issue, requiredPreflightIssue, `${label}.issue`);
assert.equal(report.mode, "read-only", `${label}.mode`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assert.ok(preflightConclusions.has(report.conclusion), `${label}.conclusion must be ready or blocked`);
assert.match(report.generatedAt, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, `${label}.generatedAt`);
assertStringArray(report.supports, `${label}.supports`, { minLength: requiredPreflightSupports.length });
for (const supportedIssue of requiredPreflightSupports) {
assert.ok(report.supports.includes(supportedIssue), `${label}.supports missing ${supportedIssue}`);
}
assertObject(report.target, `${label}.target`);
assert.equal(report.target.ref, "origin/main", `${label}.target.ref`);
assert.match(report.target.commitId, /^[a-f0-9]{40}$/, `${label}.target.commitId`);
assert.equal(report.target.shortCommitId, report.target.commitId.slice(0, 7), `${label}.target.shortCommitId`);
assertStringArray(report.forbiddenActions, `${label}.forbiddenActions`, { minLength: 1 });
for (const forbiddenAction of ["prod-deploy", "secret-material-read", "unidesk-runtime-substitute", "force-push"]) {
assert.ok(report.forbiddenActions.includes(forbiddenAction), `${label}.forbiddenActions missing ${forbiddenAction}`);
}
assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 });
for (const requiredCommand of requiredPreflightValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
assertArtifactIdentity(report.artifactIdentity, `${label}.artifactIdentity`, report.target);
assertArray(report.checks, `${label}.checks`);
assert.ok(report.checks.length >= 1, `${label}.checks must not be empty`);
assertUnique(report.checks.map((check) => check.id), `${label}.checks`);
for (const [index, check] of report.checks.entries()) {
const checkLabel = `${label}.checks[${index}]`;
assertObject(check, checkLabel);
for (const field of ["id", "category", "status", "summary", "evidence"]) {
assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`);
}
assert.match(check.id, /^[a-z][a-z0-9-]*$/, `${checkLabel}.id`);
assertString(check.category, `${checkLabel}.category`);
assertStatus(check.status, `${checkLabel}.status`);
assertString(check.summary, `${checkLabel}.summary`);
assertArray(check.evidence, `${checkLabel}.evidence`);
}
assertArray(report.blockers, `${label}.blockers`);
if (report.conclusion === "blocked") {
assert.ok(report.blockers.length >= 1, `${label}.blockers required when conclusion is blocked`);
}
assertUnique(
report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`),
`${label}.blockers`
);
for (const [index, blocker] of report.blockers.entries()) {
const blockerLabel = `${label}.blockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary", "nextTask"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`);
assertString(blocker.summary, `${blockerLabel}.summary`);
assertString(blocker.nextTask, `${blockerLabel}.nextTask`);
}
if (report.conclusion === "ready") {
assert.equal(report.blockers.length, 0, `${label}.blockers must be empty when ready`);
}
if (Object.hasOwn(report, "notes")) {
assertString(report.notes, `${label}.notes`);
}
}
function assertEvidenceLevel(value, label) {
assertString(value, label);
assert.ok(evidenceLevels.has(value), `${label} must be one of ${Array.from(evidenceLevels).join(", ")}`);
}
function assertBoolean(value, label) {
assert.equal(typeof value, "boolean", `${label} must be a boolean`);
}
async function validateAggregatorV2Report(relativePath, report) {
const label = relativePath;
assertObject(report, label);
for (const field of [
"$schema",
"$id",
"reportVersion",
"reportKind",
"issue",
"supports",
"generatedAt",
"generatedFromCommit",
"environment",
"endpoint",
"devOnly",
"prodDisabled",
"safety",
"sourceReports",
"overall",
"dod",
"milestones",
"evidence",
"levels",
"blockers",
"nextSteps",
"validationCommands"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v2", `${label}.reportVersion`);
assert.equal(report.reportKind, "dev-m5-gate-aggregator", `${label}.reportKind`);
assert.equal(report.issue, issueFamily.DEV_M5_GATE_AGGREGATOR_V2, `${label}.issue`);
assertTimestamp(report.generatedAt, `${label}.generatedAt`);
assert.match(report.generatedFromCommit, /^([a-f0-9]{7,40}|unknown)$/, `${label}.generatedFromCommit`);
assert.equal(report.environment, "dev", `${label}.environment`);
assert.equal(report.endpoint, "http://74.48.78.17:16667", `${label}.endpoint`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assertStringArray(report.supports, `${label}.supports`, { minLength: 10 });
for (const requiredIssue of [
"pikasTech/HWLAB#7",
"pikasTech/HWLAB#9",
"pikasTech/HWLAB#33",
"pikasTech/HWLAB#34",
"pikasTech/HWLAB#35",
"pikasTech/HWLAB#36",
"pikasTech/HWLAB#37",
"pikasTech/HWLAB#38",
"pikasTech/HWLAB#39",
"pikasTech/HWLAB#46"
]) {
assert.ok(report.supports.includes(requiredIssue), `${label}.supports missing ${requiredIssue}`);
}
assertObject(report.safety, `${label}.safety`);
for (const field of [
"reportOnly",
"noDeploy",
"noProd",
"noSecretRead",
"noRuntimeRestart",
"noLiveProbe",
"noHeavyE2E",
"noUniDeskRuntimeSubstitute"
]) {
assert.equal(report.safety[field], true, `${label}.safety.${field}`);
}
assertObject(report.sourceReports, `${label}.sourceReports`);
for (const requiredReport of [
"devPreflight",
"devDeploy",
"devArtifacts",
"devEdgeHealth",
"devM3Hardware",
"devM4Agent",
"devM5Gate",
"d601Observability"
]) {
assert.ok(Object.hasOwn(report.sourceReports, requiredReport), `${label}.sourceReports missing ${requiredReport}`);
const sourceReport = report.sourceReports[requiredReport];
assertObject(sourceReport, `${label}.sourceReports.${requiredReport}`);
for (const field of ["path", "issue", "taskId", "status", "commitId"]) {
assert.ok(Object.hasOwn(sourceReport, field), `${label}.sourceReports.${requiredReport} missing ${field}`);
assertString(sourceReport[field], `${label}.sourceReports.${requiredReport}.${field}`);
}
assertRepoRelativePath(sourceReport.path, `${label}.sourceReports.${requiredReport}.path`);
}
assertObject(report.overall, `${label}.overall`);
assert.ok(["green", "blocked"].includes(report.overall.status), `${label}.overall.status`);
assertBoolean(report.overall.green, `${label}.overall.green`);
assertString(report.overall.reason, `${label}.overall.reason`);
assert.equal(report.overall.green, report.overall.status === "green", `${label}.overall.green must match status`);
assertObject(report.dod, `${label}.dod`);
assert.ok(["green", "blocked"].includes(report.dod.status), `${label}.dod.status`);
assertBoolean(report.dod.green, `${label}.dod.green`);
assertArray(report.dod.checks, `${label}.dod.checks`);
assert.ok(report.dod.checks.length >= 7, `${label}.dod.checks must cover #9 DoD gates`);
assertUnique(report.dod.checks.map((check) => check.id), `${label}.dod.checks`);
for (const [index, check] of report.dod.checks.entries()) {
const checkLabel = `${label}.dod.checks[${index}]`;
assertObject(check, checkLabel);
for (const field of ["id", "status", "evidenceLevel", "summary"]) {
assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`);
}
assert.match(check.id, /^[a-z][a-z0-9-]*$/, `${checkLabel}.id`);
assert.ok(["pass", "blocked"].includes(check.status), `${checkLabel}.status`);
assertEvidenceLevel(check.evidenceLevel, `${checkLabel}.evidenceLevel`);
assertString(check.summary, `${checkLabel}.summary`);
}
assertArray(report.milestones, `${label}.milestones`);
assert.deepEqual(
report.milestones.map((milestone) => milestone.id),
requiredMilestoneIds,
`${label}.milestones must cover M0-M5 in order`
);
for (const [index, milestone] of report.milestones.entries()) {
const milestoneLabel = `${label}.milestones[${index}]`;
assertObject(milestone, milestoneLabel);
for (const field of ["id", "status", "highestVisibleLevel", "liveEvidence", "evidenceCount", "blockerCount", "summary"]) {
assert.ok(Object.hasOwn(milestone, field), `${milestoneLabel} missing ${field}`);
}
assertStatus(milestone.status, `${milestoneLabel}.status`);
assertEvidenceLevel(milestone.highestVisibleLevel, `${milestoneLabel}.highestVisibleLevel`);
assert.ok(["pass", "missing_or_blocked"].includes(milestone.liveEvidence), `${milestoneLabel}.liveEvidence`);
assert.ok(Number.isInteger(milestone.evidenceCount), `${milestoneLabel}.evidenceCount`);
assert.ok(Number.isInteger(milestone.blockerCount), `${milestoneLabel}.blockerCount`);
assertString(milestone.summary, `${milestoneLabel}.summary`);
}
assertArray(report.evidence, `${label}.evidence`);
assert.ok(report.evidence.length >= 10, `${label}.evidence must include M0-M5 entries`);
for (const [index, evidence] of report.evidence.entries()) {
const evidenceLabel = `${label}.evidence[${index}]`;
assertObject(evidence, evidenceLabel);
for (const field of ["milestone", "issue", "level", "status", "category", "summary"]) {
assert.ok(Object.hasOwn(evidence, field), `${evidenceLabel} missing ${field}`);
}
assert.match(evidence.milestone, /^M[0-5]$/, `${evidenceLabel}.milestone`);
assertString(evidence.issue, `${evidenceLabel}.issue`);
assertEvidenceLevel(evidence.level, `${evidenceLabel}.level`);
assertString(evidence.status, `${evidenceLabel}.status`);
assertString(evidence.category, `${evidenceLabel}.category`);
assertString(evidence.summary, `${evidenceLabel}.summary`);
if (Object.hasOwn(evidence, "reportPath")) {
assertRepoRelativePath(evidence.reportPath, `${evidenceLabel}.reportPath`);
}
if (Object.hasOwn(evidence, "commands")) {
assertStringArray(evidence.commands, `${evidenceLabel}.commands`);
}
if (Object.hasOwn(evidence, "evidence")) {
assertStringArray(evidence.evidence, `${evidenceLabel}.evidence`);
}
if (Object.hasOwn(evidence, "sources")) {
assertStringArray(evidence.sources, `${evidenceLabel}.sources`);
}
}
assertObject(report.levels, `${label}.levels`);
assert.deepEqual(Object.keys(report.levels), Array.from(evidenceLevels), `${label}.levels keys`);
for (const level of evidenceLevels) {
assertArray(report.levels[level], `${label}.levels.${level}`);
}
assertArray(report.blockers, `${label}.blockers`);
assert.ok(report.blockers.length >= 1, `${label}.blockers must not be empty while blocked`);
for (const [index, blocker] of report.blockers.entries()) {
const blockerLabel = `${label}.blockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["id", "priority", "type", "scope", "status", "source", "sourceIssue", "summary", "unblockOrder", "unblocks", "rationale"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assertString(blocker.id, `${blockerLabel}.id`);
assert.match(blocker.priority, /^P[0-3]$/, `${blockerLabel}.priority`);
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.equal(blocker.status, "open", `${blockerLabel}.status`);
assertRepoRelativePath(blocker.source, `${blockerLabel}.source`);
assertString(blocker.sourceIssue, `${blockerLabel}.sourceIssue`);
assertString(blocker.summary, `${blockerLabel}.summary`);
assert.ok(Number.isInteger(blocker.unblockOrder), `${blockerLabel}.unblockOrder`);
assertStringArray(blocker.unblocks, `${blockerLabel}.unblocks`, { minLength: 1 });
assertString(blocker.rationale, `${blockerLabel}.rationale`);
if (Object.hasOwn(blocker, "nextTask")) {
assertString(blocker.nextTask, `${blockerLabel}.nextTask`);
}
}
assertArray(report.nextSteps, `${label}.nextSteps`);
assert.ok(report.nextSteps.length >= 5, `${label}.nextSteps must contain blocker order`);
assert.deepEqual(
report.nextSteps.map((step) => step.order),
[...report.nextSteps].map((step) => step.order).sort((a, b) => a - b),
`${label}.nextSteps must be sorted by order`
);
for (const [index, step] of report.nextSteps.entries()) {
const stepLabel = `${label}.nextSteps[${index}]`;
assertObject(step, stepLabel);
for (const field of ["order", "priority", "scopes", "sourceIssues", "rationale", "action", "evidenceRequired"]) {
assert.ok(Object.hasOwn(step, field), `${stepLabel} missing ${field}`);
}
assert.ok(Number.isInteger(step.order), `${stepLabel}.order`);
if (Object.hasOwn(step, "blockerOrder")) {
assert.ok(Number.isInteger(step.blockerOrder), `${stepLabel}.blockerOrder`);
}
assert.match(step.priority, /^P[0-3]$/, `${stepLabel}.priority`);
assertStringArray(step.scopes, `${stepLabel}.scopes`, { minLength: 1 });
assertStringArray(step.sourceIssues, `${stepLabel}.sourceIssues`, { minLength: 1 });
assertString(step.rationale, `${stepLabel}.rationale`);
assertString(step.action, `${stepLabel}.action`);
assertString(step.evidenceRequired, `${stepLabel}.evidenceRequired`);
}
assertStringArray(report.validationCommands, `${label}.validationCommands`, {
minLength: requiredAggregatorV2Commands.length
});
for (const requiredCommand of requiredAggregatorV2Commands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
}
async function main() {
const reportFiles = await collectReportFiles();
assert.ok(reportFiles.length >= 1, "expected at least one dev-gate report JSON file");
for (const reportFile of reportFiles) {
await validateReport(reportFile);
}
console.log(`validated ${reportFiles.length} dev-gate report JSON file(s)`);
}
await main();