Remove repo reports and add recurrence guard

This commit is contained in:
Code Queue Review
2026-05-24 02:23:27 +00:00
parent 0c53c94714
commit 4e302faee9
83 changed files with 1042 additions and 27077 deletions
@@ -7,14 +7,15 @@ import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT } from "../../internal/protocol/index.mjs";
import { inspectCloudWebDistFreshness } from "../../web/hwlab-cloud-web/scripts/dist-contract.mjs";
import { buildDesiredStatePlan } from "./deploy-desired-state-plan.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultArtifactReport = "reports/dev-gate/dev-artifacts.json";
const defaultArtifactReport = tempReportPath("dev-artifacts.json");
const defaultArtifactCatalog = "deploy/artifact-catalog.dev.json";
const defaultRuntimeReport = "reports/dev-gate/dev-cloud-workbench-live.json";
const defaultEdgeReport = "reports/dev-gate/dev-edge-health.json";
const defaultOutputReport = "reports/dev-gate/dev-artifact-runtime-readiness.json";
const defaultRuntimeReport = tempReportPath("dev-cloud-workbench-live.json");
const defaultEdgeReport = tempReportPath("dev-edge-health.json");
const defaultOutputReport = tempReportPath("dev-artifact-runtime-readiness.json");
const defaultTargetRef = "origin/main";
const guardCommand = `node scripts/artifact-runtime-readiness-guard.mjs --target-ref ${defaultTargetRef} --check --no-report`;
const expectedBlockedGuardCommand = `${guardCommand} --expect-blocked`;
@@ -104,7 +105,7 @@ export function evaluateArtifactRuntimeReadiness({
pass: commitsMatch(artifact.sourceCommitId, catalog.commitId),
type: "observability_blocker",
summary: `Artifact report source=${artifact.shortCommitId}; catalog=${catalog.shortCommitId}.`,
nextTask: "Keep reports/dev-gate/dev-artifacts.json and deploy/artifact-catalog.dev.json aligned to the same artifact source."
nextTask: `Keep ${defaultArtifactReport} and deploy/artifact-catalog.dev.json aligned to the same artifact source.`
});
addCheck(checks, {
id: "desired-state-internal-valid",
@@ -237,7 +238,7 @@ export function buildArtifactRuntimeReadinessReport({
});
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/dev-artifact-runtime-readiness.json",
$id: "https://hwlab.pikastech.local/dev-gate/dev-artifact-runtime-readiness.json",
reportVersion: "v1",
status,
issue: "pikasTech/HWLAB#164",
@@ -528,7 +529,7 @@ export async function runArtifactRuntimeReadinessGuardCli(argv = process.argv.sl
});
if (args.writeReport) {
const outputPath = path.join(repoRoot, args.report);
const outputPath = ensureNotRepoReportsPath(repoRoot, args.report, "--report");
await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
}
@@ -1180,7 +1181,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
return [
"node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty",
"node scripts/deploy-desired-state-plan.mjs --promotion-commit <origin-main-sha> --check",
"node scripts/dev-deploy-apply.mjs --dry-run --write-report",
`node scripts/dev-deploy-apply.mjs --dry-run --report ${tempReportPath("dev-deploy-report.json")}`,
guardCommand
];
}
@@ -1193,7 +1194,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
];
}
return [
"node scripts/dev-deploy-apply.mjs --dry-run --write-report",
`node scripts/dev-deploy-apply.mjs --dry-run --report ${tempReportPath("dev-deploy-report.json")}`,
"node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report"
];
}
@@ -1264,8 +1265,6 @@ function parseArgs(argv) {
} else if (arg === "--timeout-ms") {
args.timeoutMs = Number.parseInt(requireValue(argv, index, arg), 10);
index += 1;
} else if (arg === "--write-report") {
args.writeReport = true;
} else if (arg === "--no-report") {
args.writeReport = false;
} else if (arg === "--no-live") {
@@ -1301,7 +1300,7 @@ function requireValue(argv, index, arg) {
function usage() {
return [
`usage: ${guardCommand} [--write-report] [--live] [--expect-blocked]`,
`usage: ${guardCommand} [--report ${defaultOutputReportPath}] [--live] [--expect-blocked]`,
"",
"Read-only latest-main DEV artifact/runtime readiness guard.",
"",
@@ -8,11 +8,12 @@ import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { DEV_ENDPOINT, DEV_FRONTEND_ENDPOINT } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const namespace = "hwlab-dev";
const defaultReportPath = "reports/d601-k3s-readonly-observability.json";
const defaultReportPath = tempReportPath("d601-k3s-readonly-observability.json");
const issue = "pikasTech/HWLAB#46";
const supports = ["#7", "#33", "#36", "#38", "#39", "#42"].map((id) => `pikasTech/HWLAB${id}`);
const forbiddenActions = [
@@ -604,7 +605,7 @@ function runnerImageFollowUp(blockers) {
}
async function writeReport(report, reportPath) {
const absoluteReportPath = path.join(repoRoot, reportPath);
const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}
@@ -628,7 +629,7 @@ async function buildReport(args) {
});
return {
$schema: "https://hwlab.pikastech.local/schemas/d601-k3s-readonly-observability-report.schema.json",
$id: "https://hwlab.pikastech.local/reports/d601-k3s-readonly-observability.json",
$id: "https://hwlab.pikastech.local/dev-gate/d601-k3s-readonly-observability.json",
reportVersion: "v1",
reportKind: "d601-k3s-readonly-observability",
issue,
+3 -2
View File
@@ -9,6 +9,7 @@ import {
inspectCodeAgentProviderManifestRefs
} from "../../internal/cloud/code-agent-contract.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
import { tempReportPath } from "./report-paths.mjs";
const execFileAsync = promisify(execFile);
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
@@ -16,7 +17,7 @@ const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)
const deployPath = "deploy/deploy.json";
const catalogPath = "deploy/artifact-catalog.dev.json";
const workloadsPath = "deploy/k8s/base/workloads.yaml";
const artifactReportPath = "reports/dev-gate/dev-artifacts.json";
const artifactReportPath = tempReportPath("dev-artifacts.json");
const mutableTags = new Set(["latest", "dev", "main", "master", "prod", "production"]);
const mirrorEnvNames = [
"HWLAB_COMMIT_ID",
@@ -661,7 +662,7 @@ function reportHints(report, desiredCommitId) {
path: artifactReportPath,
present: true,
authoritative: false,
note: "report snapshots are contextual evidence only; deploy desired-state remains authoritative in deploy/, not reports/",
note: "temporary publish JSON is contextual evidence only; deploy desired-state remains authoritative in deploy/",
commitId: reportCommitId,
matchesDesiredState: reportCommitId ? commitEquivalent(reportCommitId, desiredCommitId) : null,
artifactPublish: {
+33 -30
View File
@@ -12,24 +12,25 @@ import {
redactSensitiveText,
resolveDevKubeconfigSelection
} from "./dev-deploy-apply.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultNamespace = "hwlab-dev";
const defaultLockName = "hwlab-dev-cd-lock";
const defaultTtlSeconds = 3600;
const defaultReportPath = "reports/dev-gate/dev-cd-apply.json";
const defaultReportPath = tempReportPath("dev-cd-apply.json");
const d601NativeKubeconfigPath = "/etc/rancher/k3s/k3s.yaml";
const artifactCatalogPath = "deploy/artifact-catalog.dev.json";
const artifactReportPath = "reports/dev-gate/dev-artifacts.json";
const artifactReportPath = tempReportPath("dev-artifacts.json");
const artifactDesiredStatePaths = [
"deploy/artifact-catalog.dev.json",
"deploy/deploy.json",
"deploy/k8s/base/workloads.yaml",
"reports/dev-gate/dev-artifacts.json"
"deploy/k8s/base/workloads.yaml"
];
const runtimeProvisioningReportPath = "reports/dev-gate/dev-runtime-provisioning-report.json";
const runtimeMigrationReportPath = "reports/dev-gate/dev-runtime-migration-report.json";
const runtimePostflightReportPath = "reports/dev-gate/dev-runtime-postflight-report.json";
const runtimeProvisioningReportPath = tempReportPath("dev-runtime-provisioning-report.json");
const runtimeMigrationReportPath = tempReportPath("dev-runtime-migration-report.json");
const runtimePostflightReportPath = tempReportPath("dev-runtime-postflight-report.json");
const deployApplyReportPath = tempReportPath("dev-deploy-report.json");
const runtimeJobReportPrefix = "/tmp/hwlab-dev-runtime-report";
const browserLiveUrl = "http://74.48.78.17:16666/health/live";
const apiLiveUrl = `${DEV_ENDPOINT}/health/live`;
@@ -103,9 +104,6 @@ export function parseArgs(argv) {
} else if (arg === "--confirmed-non-production") {
args.confirmedNonProduction = true;
args.flags.add(arg);
} else if (arg === "--write-report") {
args.writeReport = true;
args.flags.add(arg);
} else if (arg === "--break-stale-lock") {
args.breakStaleLock = true;
args.flags.add(arg);
@@ -124,6 +122,7 @@ export function parseArgs(argv) {
args.flags.add(arg);
} else if (arg === "--report") {
args.reportPath = readOption(argv, ++index, arg);
args.writeReport = true;
args.flags.add(arg);
} else if (arg === "--target-ref") {
args.targetRef = readOption(argv, ++index, arg);
@@ -182,7 +181,7 @@ export function usage() {
examples: [
"node scripts/dev-cd-apply.mjs --status",
"node scripts/dev-cd-apply.mjs --dry-run",
"node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report"
`node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath}`
],
options: {
"--status": "Read-only status: target ref, deploy.json, current Lease, and public live health.",
@@ -195,8 +194,7 @@ export function usage() {
"--ttl-seconds SECONDS": "lock TTL; default: 3600",
"--owner-task-id ID": "lock holder; default: Code Queue/env/user derived",
"--break-stale-lock": "May take over an expired lock only with --confirm-dev.",
"--report PATH": `default: ${defaultReportPath}`,
"--write-report": "Write the full report to --report.",
"--report PATH": `write the full transaction JSON outside the repo; packaged scripts use ${defaultReportPath}`,
"--full-output": "Print the full report to stdout instead of the concise summary.",
"--skip-runtime-postflight": "Skip the M3/durable runtime postflight while still applying DEV and verifying 16666/16667 health.",
"--skip-live-verify": "test-only escape hatch; do not use for DEV acceptance"
@@ -326,7 +324,8 @@ async function readDeployJson(repoRoot) {
async function readJsonFileSummary(repoRoot, relativePath, summarize) {
try {
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
const absolutePath = path.isAbsolute(relativePath) ? relativePath : path.join(repoRoot, relativePath);
const raw = await readFile(absolutePath, "utf8");
const json = JSON.parse(raw);
return {
path: relativePath,
@@ -1854,7 +1853,7 @@ function stepBlockerSafeNextAction(stepResult) {
if (stepResult.id === "runtime-db-provisioning") return "Repair repo-owned runtime provisioning inputs/SecretRefs without printing Secret values, then rerun DEV CD.";
if (stepResult.id === "runtime-db-migration") return "Repair repo-owned runtime migration/schema state, then rerun DEV CD.";
if (stepResult.id === "runtime-durable-postflight") return "Inspect the runtime postflight report, repair durable readiness or M3 evidence persistence, then rerun DEV CD after host-controlled rollout if needed.";
if (stepResult.id === "dev-deploy-apply") return "Inspect reports/dev-gate/dev-deploy-report.json and rerun the repo-owned DEV CD path after the apply blocker is fixed.";
if (stepResult.id === "dev-deploy-apply") return `Inspect ${deployApplyReportPath} and rerun the repo-owned DEV CD path after the apply blocker is fixed.`;
return "Fix the reported step blocker and rerun the repo-owned DEV CD transaction.";
}
@@ -2165,8 +2164,8 @@ function buildStatusNextActions({ target, deployBefore, lock, liveVerify }) {
}
if (actions.length === 0) {
actions.push(target.publishRequired === false
? "Run --apply --confirm-dev --confirmed-non-production --write-report when ready to apply the published deploy.json promotion without republishing HEAD."
: "Run --apply --confirm-dev --confirmed-non-production --write-report when ready to publish/apply DEV.");
? `Run --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} when ready to apply the published deploy.json promotion without republishing HEAD.`
: `Run --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} when ready to publish/apply DEV.`);
}
return actions;
}
@@ -2229,7 +2228,7 @@ async function runDevCdStatus(args, ctx, stdout) {
live: compactLiveReport(liveVerify),
liveDelta,
nextActions: buildStatusNextActions({ target, deployBefore, lock, liveVerify }),
fullOutputHint: "Use --apply --confirm-dev --confirmed-non-production --write-report for the full transaction report; use --full-output only when stdout needs the full JSON."
fullOutputHint: `Use --apply --confirm-dev --confirmed-non-production --report ${defaultReportPath} for the full transaction JSON; use --full-output only when stdout needs the full JSON.`
};
stdout.write(`${JSON.stringify(report, null, 2)}\n`);
return 0;
@@ -2284,7 +2283,7 @@ function summarizeDevCdApplyReport(report) {
} : null,
blockers: report.blockers ?? [],
reportPaths: apply.reportPaths ?? {},
fullOutputHint: "Use --full-output to print the full report to stdout. Use --write-report to persist the full report."
fullOutputHint: `Use --full-output to print the full report to stdout. Use --report ${defaultReportPath} to persist the full report outside the repo.`
};
}
@@ -2307,7 +2306,7 @@ function buildReport({
}) {
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-cd-apply.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/dev-cd-apply.json",
$id: "https://hwlab.pikastech.local/dev-cd/dev-cd-apply.json",
reportVersion: "v1",
issue: "pikasTech/HWLAB#274",
taskId: "dev-cd-apply",
@@ -2357,7 +2356,7 @@ function buildReport({
},
dryRun: {
status: "not_run",
commands: ["node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report"],
commands: [`node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report ${deployApplyReportPath}`],
evidence: ["The transaction path owns live side effects; dry-run remains a preflight support mode."],
summary: "DEV CD mutation requires --apply and the transaction Lease lock."
},
@@ -2422,7 +2421,7 @@ function buildReport({
reportPaths: {
transaction: args.writeReport ? args.reportPath : null,
artifacts: artifactReportPath,
deployApply: "reports/dev-gate/dev-deploy-report.json",
deployApply: deployApplyReportPath,
runtimeProvisioning: runtimeProvisioningReportPath,
runtimeMigration: runtimeMigrationReportPath,
runtimePostflight: runtimePostflightReportPath
@@ -2544,7 +2543,7 @@ export async function runDevCdApply(argv, io = {}) {
type: "contract_blocker",
scope: "artifact-boundary",
status: "open",
summary: "deploy/deploy.json, deploy/artifact-catalog.dev.json, reports/dev-gate/dev-artifacts.json, and the artifact merge parent must resolve to the same promotion commit before apply."
summary: "deploy/deploy.json, deploy/artifact-catalog.dev.json, deploy/k8s/base/workloads.yaml, and the artifact merge parent must resolve to the same promotion commit before apply."
});
throw new DevCdApplyError("artifact desired-state provenance does not match the deploy.json promotion", {
code: "artifact-boundary-provenance-mismatch",
@@ -2635,7 +2634,7 @@ export async function runDevCdApply(argv, io = {}) {
id: "artifact-publish",
phase: "publishing",
command: process.execPath,
args: ["scripts/dev-artifact-publish.mjs", "--publish"],
args: ["scripts/dev-artifact-publish.mjs", "--publish", "--report", artifactReportPath],
timeoutMs: 60 * 60 * 1000,
reportPath: artifactReportPath
},
@@ -2686,7 +2685,8 @@ export async function runDevCdApply(argv, io = {}) {
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--write-report",
"--report",
runtimeProvisioningReportPath,
"--fail-on-blocked"
],
timeoutMs: 5 * 60 * 1000,
@@ -2702,7 +2702,8 @@ export async function runDevCdApply(argv, io = {}) {
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--write-report",
"--report",
runtimeMigrationReportPath,
"--fail-on-blocked"
],
timeoutMs: 5 * 60 * 1000,
@@ -2717,11 +2718,12 @@ export async function runDevCdApply(argv, io = {}) {
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--write-report",
"--report",
deployApplyReportPath,
...(args.kubeconfig ? ["--kubeconfig", args.kubeconfig] : [])
],
timeoutMs: 20 * 60 * 1000,
reportPath: "reports/dev-gate/dev-deploy-report.json"
reportPath: deployApplyReportPath
},
...(
args.skipRuntimePostflight
@@ -2737,7 +2739,8 @@ export async function runDevCdApply(argv, io = {}) {
"--confirmed-non-production",
"--target",
"api",
"--write-report",
"--report",
runtimePostflightReportPath,
"--fail-on-blocked"
],
timeoutMs: 2 * 60 * 1000,
@@ -2871,7 +2874,7 @@ export async function runDevCdApply(argv, io = {}) {
});
if (args.writeReport) {
const absoluteReportPath = path.resolve(ctx.repoRoot, args.reportPath);
const absoluteReportPath = ensureNotRepoReportsPath(ctx.repoRoot, args.reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}
+34 -24
View File
@@ -17,6 +17,16 @@ import {
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../..");
const tempReportRoot = "/tmp/hwlab-dev-gate";
const artifactReportFixturePath = path.join(tempReportRoot, "dev-artifacts.json");
function tempReport(name) {
return path.join(tempReportRoot, name);
}
function stateReport(repoRoot, name) {
return path.join(repoRoot, ".state/test-reports", name);
}
function iso(offsetMs = 0) {
return new Date(Date.parse("2026-05-23T08:00:00.000Z") + offsetMs).toISOString();
@@ -51,7 +61,7 @@ async function makeRepo(options = {}) {
const commitId = options.commitId ?? "abc1234";
const repoRoot = await fsTempDir();
await mkdir(path.join(repoRoot, "deploy/k8s/base"), { recursive: true });
await mkdir(path.join(repoRoot, "reports/dev-gate"), { recursive: true });
await mkdir(tempReportRoot, { recursive: true });
await writeFile(
path.join(repoRoot, "deploy/deploy.json"),
`${JSON.stringify({
@@ -92,7 +102,7 @@ async function makeRepo(options = {}) {
publish: {
ciPublished: true,
registryVerified: true,
provenance: "reports/dev-gate/dev-artifacts.json"
provenance: "ci-artifact:dev-artifacts.json"
},
services: [
{
@@ -121,7 +131,7 @@ async function makeRepo(options = {}) {
}, null, 2)}\n`
);
await writeFile(
path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"),
artifactReportFixturePath,
`${JSON.stringify({
reportVersion: "v1",
taskId: "dev-artifact-publish",
@@ -211,7 +221,7 @@ function makeRunCommand({
"deploy/artifact-catalog.dev.json",
"deploy/deploy.json",
"deploy/k8s/base/workloads.yaml",
"reports/dev-gate/dev-artifacts.json"
"ci-artifact:dev-artifacts.json"
],
desiredStatePromotionStatus = "pass",
runtimeJobLogSuffix = "",
@@ -469,7 +479,7 @@ test("dev-cd status and dry-run are read-only compact JSON", async () => {
"--dry-run",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report"
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
@@ -490,7 +500,7 @@ test("dev-cd status and dry-run are read-only compact JSON", async () => {
assert.equal(status.live.summary.checked, 2);
assertNoReadOnlySideEffects(commandLog);
await assert.rejects(
readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"),
readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"),
{ code: "ENOENT" }
);
});
@@ -664,7 +674,7 @@ test("dev-cd status degrades artifact merge provenance when catalog/report do no
kind: "hwlab-artifact-catalog",
commitId: "9999999",
artifactState: "published",
publish: { ciPublished: true, registryVerified: true, provenance: "reports/dev-gate/dev-artifacts.json" },
publish: { ciPublished: true, registryVerified: true, provenance: "ci-artifact:dev-artifacts.json" },
services: []
}, null, 2)}\n`
);
@@ -705,7 +715,7 @@ test("dev-cd status degrades artifact merge provenance when catalog/report do no
test("apply stops before lock acquisition when artifact merge provenance mismatches deploy-json promotion", async () => {
const repoRoot = await makeRepo({ commitId: "abc1234" });
await writeFile(
path.join(repoRoot, "reports/dev-gate/dev-artifacts.json"),
artifactReportFixturePath,
`${JSON.stringify({
status: "published",
commitId: "9999999",
@@ -1036,7 +1046,7 @@ test("apply preflight passes with D601 native k3s and required SecretRef keys be
"task-preflight-pass",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output"
], {
repoRoot,
@@ -1088,7 +1098,7 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma
"task-missing-secret",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report"
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
@@ -1105,7 +1115,7 @@ test("apply preflight blocks missing SecretRef before Lease or side-effect comma
});
const summary = JSON.parse(output);
const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"));
const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
assert.equal(code, 2);
assert.equal(summary.status, "blocked");
assert.equal(summary.preflight.status, "blocked");
@@ -1230,7 +1240,7 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a
"task-b",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output"
], {
repoRoot,
@@ -1270,10 +1280,10 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a
assert.equal(report.devCdApply.liveVerify.summary.checked, 2);
assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-web-16666"), true);
assert.equal(report.devCdApply.liveVerify.endpoints.some((endpoint) => endpoint.id === "cloud-api-16667"), true);
assert.equal(report.devCdApply.reportPaths.transaction, "reports/dev-gate/dev-cd-apply.json");
assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, "reports/dev-gate/dev-runtime-provisioning-report.json");
assert.equal(report.devCdApply.reportPaths.runtimeMigration, "reports/dev-gate/dev-runtime-migration-report.json");
assert.equal(report.devCdApply.reportPaths.runtimePostflight, "reports/dev-gate/dev-runtime-postflight-report.json");
assert.equal(report.devCdApply.reportPaths.transaction, stateReport(repoRoot, "dev-cd-apply.json"));
assert.equal(report.devCdApply.reportPaths.runtimeProvisioning, tempReport("dev-runtime-provisioning-report.json"));
assert.equal(report.devCdApply.reportPaths.runtimeMigration, tempReport("dev-runtime-migration-report.json"));
assert.equal(report.devCdApply.reportPaths.runtimePostflight, tempReport("dev-runtime-postflight-report.json"));
const publishCall = commandLog.find((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs"));
const applyCall = commandLog.find((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs"));
@@ -1309,7 +1319,7 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a
commandLog.findIndex((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs"))
);
const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"));
const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
assert.equal(writtenReport.devCdApply.liveVerify.summary.checked, 2);
});
@@ -1325,7 +1335,7 @@ test("transaction can skip runtime postflight while preserving publish apply and
"task-skip-postflight",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output",
"--skip-runtime-postflight"
], {
@@ -1403,7 +1413,7 @@ test("transaction blocks with structured runtime postflight blocker when M3 evid
"task-postflight-blocked",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report"
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
@@ -1414,7 +1424,7 @@ test("transaction blocks with structured runtime postflight blocker when M3 evid
});
const summary = JSON.parse(output);
const report = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"));
const report = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
const blocker = report.blockers.find((item) => item.scope === "runtime-durable-postflight");
assert.equal(code, 2);
assert.equal(summary.status, "blocked");
@@ -1447,7 +1457,7 @@ test("runtime maintenance job reports are parsed after redacting secret-like log
"task-redaction",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output"
], {
repoRoot,
@@ -1484,7 +1494,7 @@ test("transaction can apply an already published deploy-json promotion without r
"task-promotion",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report",
"--report", stateReport(repoRoot, "dev-cd-apply.json"),
"--full-output"
], {
repoRoot,
@@ -1551,7 +1561,7 @@ test("apply stdout is concise by default while write-report keeps the full repor
"task-c",
"--kubeconfig",
"/etc/rancher/k3s/k3s.yaml",
"--write-report"
"--report", stateReport(repoRoot, "dev-cd-apply.json")
], {
repoRoot,
env: {},
@@ -1569,7 +1579,7 @@ test("apply stdout is concise by default while write-report keeps the full repor
assert.equal(summary.lock.acquired, true);
assert.equal(summary.steps.some((step) => step.id === "dev-deploy-apply"), true);
const writtenReport = JSON.parse(await readFile(path.join(repoRoot, "reports/dev-gate/dev-cd-apply.json"), "utf8"));
const writtenReport = JSON.parse(await readFile(stateReport(repoRoot, "dev-cd-apply.json"), "utf8"));
assert.equal(writtenReport.devCdApply.deployJson.before.manifest.environment, "dev");
});
+3 -1
View File
@@ -1,3 +1,5 @@
import { tempReportPath } from "./report-paths.mjs";
const transactionEnvNames = [
"HWLAB_CD_TRANSACTION_ID",
"HWLAB_CD_TRANSACTION_OWNER",
@@ -22,7 +24,7 @@ export function devCdTransactionGuardFailure({
mode,
requiredEnv,
acceptedEnv: transactionEnvNames,
entrypoint: "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report",
entrypoint: `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${tempReportPath("dev-cd-apply.json")}`,
summary: `${script} ${mode} is a DEV CD side-effect step and must run inside scripts/dev-cd-apply.mjs.`,
devOnly: true,
prodTouched: false,
+13 -10
View File
@@ -12,10 +12,13 @@ import {
classifyCodeAgentBrowserFailure,
summarizeCodeAgentPayload
} from "./code-agent-response-contract.mjs";
import { tempReportPath } from "./report-paths.mjs";
export { classifyCodeAgentBrowserJourney };
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const domOnlyReportPath = tempReportPath("dev-cloud-workbench-dom-only.json");
const liveReportPath = tempReportPath("dev-cloud-workbench-live.json");
const webRoot = path.join(repoRoot, "web/hwlab-cloud-web");
const defaultLiveUrl = "http://74.48.78.17:16666/";
const helpOwner = "codex_1779444232735_1";
@@ -1584,11 +1587,11 @@ function baseReport({
"node scripts/dev-cloud-workbench-smoke.mjs --quick-prompts-fixture",
"node scripts/dev-cloud-workbench-smoke.mjs --local-agent-timeout-fixture",
"node scripts/dev-cloud-workbench-smoke.mjs --layout --url http://74.48.78.17:16666/",
"node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json",
"node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json",
`node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report ${domOnlyReportPath}`,
`node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report ${liveReportPath}`,
mode === "dom-only"
? "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-dom-only.json"
: "node scripts/validate-dev-gate-report.mjs reports/dev-gate/dev-cloud-workbench-live.json",
? `node scripts/validate-dev-gate-report.mjs ${domOnlyReportPath}`
: `node scripts/validate-dev-gate-report.mjs ${liveReportPath}`,
"node scripts/validate-dev-gate-report.mjs"
],
localSmoke: {
@@ -1620,8 +1623,8 @@ function baseReport({
"No hardware write API, service restart, PROD access, or secret read is performed."
],
commands: [
"node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-dom-only.json",
"node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report reports/dev-gate/dev-cloud-workbench-live.json"
`node scripts/dev-cloud-workbench-smoke.mjs --dom-only --url http://74.48.78.17:16666/ --report ${domOnlyReportPath}`,
`node scripts/dev-cloud-workbench-smoke.mjs --live --confirm-dev-live --url http://74.48.78.17:16666/ --report ${liveReportPath}`
],
summary: liveJourneyPassed
? "Deployed 16666 browser journey and same-origin Code Agent chat completed."
@@ -1647,10 +1650,10 @@ function baseReport({
function reportModeId(mode) {
return mode === "layout"
? "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-layout.json"
? "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-layout.json"
: mode === "dom-only"
? "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-dom-only.json"
: "https://hwlab.pikastech.local/reports/dev-gate/dev-cloud-workbench-live.json";
? "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-dom-only.json"
: "https://hwlab.pikastech.local/dev-gate/dev-cloud-workbench-live.json";
}
function addCheck(checks, blockers, id, result, summary, options = {}) {
@@ -8441,7 +8444,7 @@ function escapeRegExp(value) {
export function printSmokeHelp() {
return {
status: "usage",
command: "node scripts/dev-cloud-workbench-smoke.mjs --source | --layout [--url http://74.48.78.17:16666/] | --mobile | --local-agent-fixture | --quick-prompts-fixture | --local-agent-timeout-fixture | --dom-only --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-dom-only.json] | --live --confirm-dev-live --url http://74.48.78.17:16666/ [--report reports/dev-gate/dev-cloud-workbench-live.json]",
command: `node scripts/dev-cloud-workbench-smoke.mjs --source | --layout [--url http://74.48.78.17:16666/] | --mobile | --local-agent-fixture | --quick-prompts-fixture | --local-agent-timeout-fixture | --dom-only --url http://74.48.78.17:16666/ [--report ${domOnlyReportPath}] | --live --confirm-dev-live --url http://74.48.78.17:16666/ [--report ${liveReportPath}]`,
notes: [
"Default/source mode reads repository files and emits SOURCE-level evidence only; it never calls the live provider and must not claim DEV-LIVE.",
"--layout runs desktop and mobile browser geometry/hit-target checks for sidebar collapse/expand; with --url it remains layout-only live evidence and does not claim M3 acceptance.",
+35 -8
View File
@@ -18,9 +18,10 @@ import {
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
import { requireDevCdTransactionForSideEffect } from "./dev-cd-transaction-guard.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const reportPath = "reports/dev-gate/dev-deploy-report.json";
const defaultReportPath = tempReportPath("dev-deploy-report.json");
const namespace = "hwlab-dev";
const healthPath = "/health/live";
const defaultD601KubeconfigPath = "/etc/rancher/k3s/k3s.yaml";
@@ -73,9 +74,9 @@ const requiredValidationCommands = [
"node --check scripts/src/dev-deploy-apply.mjs",
"node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked"
];
const applyCommand = "node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report";
const dryRunPlanCommand = "node scripts/dev-deploy-apply.mjs --dry-run --write-report";
const blockedDryRunCommand = "node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --write-report";
const applyCommand = `node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --report ${tempReportPath("dev-cd-apply.json")}`;
const dryRunPlanCommand = `node scripts/dev-deploy-apply.mjs --dry-run --report ${defaultReportPath}`;
const blockedDryRunCommand = `node scripts/dev-deploy-apply.mjs --dry-run --expect-blocked --report ${defaultReportPath}`;
const forbiddenActions = [
"prod-deploy",
"secret-read",
@@ -93,6 +94,7 @@ export function parseArgs(argv) {
const errors = [];
let kubeconfig = null;
let kubeconfigSpecified = false;
let reportPath = defaultReportPath;
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
@@ -118,6 +120,27 @@ export function parseArgs(argv) {
}
continue;
}
if (arg === "--report") {
flags.add("--report");
const next = argv[index + 1];
if (typeof next !== "string" || next.startsWith("--")) {
errors.push("--report requires a non-empty path value");
} else {
reportPath = next;
flags.add("--report-output");
index += 1;
}
continue;
}
if (arg.startsWith("--report=")) {
flags.add("--report");
reportPath = arg.slice("--report=".length);
flags.add("--report-output");
if (!reportPath.trim()) {
errors.push("--report requires a non-empty path value");
}
continue;
}
flags.add(arg);
}
@@ -126,7 +149,8 @@ export function parseArgs(argv) {
dryRun: !flags.has("--apply"),
expectBlocked: flags.has("--expect-blocked"),
skipLiveProbe: flags.has("--skip-live-probe"),
writeReport: flags.has("--write-report"),
writeReport: flags.has("--report-output"),
reportPath,
kubeconfig,
kubeconfigSpecified,
errors,
@@ -1052,7 +1076,8 @@ function buildPlanConclusion(status, blockers) {
function validateArgs(args, blockers) {
for (const error of args.errors ?? []) {
addBlocker(blockers, "safety_blocker", "kubeconfig-argument", error);
const scope = error.startsWith("--report") ? "report-argument" : "kubeconfig-argument";
addBlocker(blockers, "safety_blocker", scope, error);
}
for (const forbidden of ["--prod", "--production", "--read-secret", "--force-push", "--heavyweight-e2e"]) {
if (args.flags.has(forbidden)) {
@@ -1898,7 +1923,7 @@ export async function runDevDeployApply(argv, io = {}) {
const report = {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/dev-deploy-report.json",
$id: "https://hwlab.pikastech.local/dev-cd/dev-deploy-report.json",
reportVersion: "v1",
issue: "pikasTech/HWLAB#33",
supports: ["pikasTech/HWLAB#164", "pikasTech/HWLAB#143", "pikasTech/HWLAB#99", "pikasTech/HWLAB#63", "pikasTech/HWLAB#50", "pikasTech/HWLAB#7", "pikasTech/HWLAB#33", "pikasTech/HWLAB#17", "pikasTech/HWLAB#32", "pikasTech/HWLAB#30"],
@@ -2009,7 +2034,9 @@ export async function runDevDeployApply(argv, io = {}) {
};
if (args.writeReport) {
await writeFile(path.join(repoRoot, reportPath), `${JSON.stringify(report, null, 2)}\n`);
const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, args.reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}
stdout.write(`${JSON.stringify(report, null, 2)}\n`);
+9 -8
View File
@@ -15,9 +15,10 @@ import {
} from "../../internal/cloud/code-agent-contract.mjs";
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = path.join(repoRoot, "reports/dev-gate/dev-edge-health.json");
const defaultReportPath = tempReportPath("dev-edge-health.json");
const publicHost = "74.48.78.17";
const publicPort = 16667;
const namespace = "hwlab-dev";
@@ -124,12 +125,11 @@ function parseArgs(argv) {
const arg = argv[index];
if (arg === "--live") {
args.live = true;
} else if (arg === "--write-report") {
args.writeReport = true;
} else if (arg === "--report") {
index += 1;
assert.ok(argv[index], "--report requires a path");
args.reportPath = path.resolve(process.cwd(), argv[index]);
args.reportPath = argv[index];
args.writeReport = true;
} else {
throw new Error(`unknown argument ${arg}`);
}
@@ -149,7 +149,7 @@ async function createDevGateReport(edgeHealth) {
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/dev-edge-health.json",
$id: "https://hwlab.pikastech.local/dev-gate/dev-edge-health.json",
reportVersion: "v1",
issue: "pikasTech/HWLAB#36",
taskId: "dev-edge-health",
@@ -179,7 +179,7 @@ async function createDevGateReport(edgeHealth) {
"node --check scripts/dev-edge-health-smoke.mjs",
"node --check scripts/src/dev-edge-health-smoke-lib.mjs",
"node --test scripts/src/dev-edge-health-smoke-lib.test.mjs",
"node scripts/dev-edge-health-smoke.mjs --live --write-report"
`node scripts/dev-edge-health-smoke.mjs --live --report ${defaultReportPath}`
],
localSmoke: {
status: "pass",
@@ -1202,6 +1202,7 @@ async function maybeWriteReport(report, args) {
if (!args.writeReport) {
return;
}
await mkdir(path.dirname(args.reportPath), { recursive: true });
await writeFile(args.reportPath, `${JSON.stringify(report, null, 2)}\n`);
const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, args.reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}
File diff suppressed because it is too large Load Diff
@@ -3,7 +3,7 @@ import test from "node:test";
import { buildReport } from "./dev-evidence-blocker-aggregator.mjs";
test("aggregator reports M4 current blocker as durable runtime or skills injection, not stale DB live", async () => {
test("aggregator no longer treats deleted repository reports as live evidence", async () => {
const report = await buildReport();
const m4 = report.milestoneBlockerClassification.find((item) => item.milestone === "M4");
const m5 = report.milestoneBlockerClassification.find((item) => item.milestone === "M5");
@@ -12,22 +12,19 @@ test("aggregator reports M4 current blocker as durable runtime or skills injecti
assert.ok(m4, "M4 blocker classification is present");
assert.ok(m5, "M5 blocker classification is present");
assert.equal(dbLayer.status, "pass");
assert.equal(dbLayer.evidenceLevel, "DEV-LIVE");
assert.equal(dbCheck.status, "pass");
assert.equal(dbCheck.evidenceLevel, "DEV-LIVE");
assert.equal(report.overall.status, "blocked");
assert.equal(report.sourceReports.status, "removed");
assert.equal(dbLayer.status, "blocked");
assert.equal(dbLayer.evidenceLevel, "BLOCKED");
assert.equal(dbCheck.status, "blocked");
assert.equal(dbCheck.evidenceLevel, "BLOCKED");
assert.equal(
report.blockers.some((blocker) =>
["cloud-api-db", "cloud-api-db-health-gate", "cloud-api-db-live", "db-live"].includes(blocker.scope)
),
false,
"DB live blockers must be stale once current DB live evidence is pass"
report.blockers.some((blocker) => blocker.scope === "repo-report-directory-removed"),
true,
"governance blocker must remain explicit"
);
assert.notEqual(m4.blockerClass, "db-live-readiness");
assert.ok(
["runtime_durable_adapter_query_blocked", "skills-commit-version-injection"].includes(m4.blockerClass),
`unexpected M4 blocker class ${m4.blockerClass}`
);
assert.equal(m4.blockerClass, "runtime_durable_adapter_query_blocked");
assert.match(m4.dependency, /durable runtime adapter|skill commit and version/u);
assert.match(m4.nonPromotionReason, /durable agent runtime readiness|skill version injection/u);
assert.match(m5.dependency, /runtime durable adapter readiness, skills commit\/version injection/u);
+16 -13
View File
@@ -17,10 +17,13 @@ import {
import { activeReportLifecycle } from "../../internal/dev-report-lifecycle.mjs";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../../internal/protocol/index.mjs";
import { probeRegistryCapabilities } from "./registry-capabilities.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const execFileAsync = promisify(execFile);
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = "reports/dev-gate/dev-preflight-report.json";
const defaultReportPath = tempReportPath("dev-preflight-report.json");
const defaultArtifactReportPath = tempReportPath("dev-artifacts.json");
const defaultEdgeHealthReportPath = tempReportPath("dev-edge-health.json");
const issue = "pikasTech/HWLAB#34";
const supports = ["#7", "#12", "#14", "#22", "#23", "#29", "#30", "#31", "#33", "#35", "#39", "#43", "#48", "#49", "#57", "#66", "#143", "#164"].map(
(id) => `pikasTech/HWLAB${id}`
@@ -92,7 +95,7 @@ function parseArgs(argv) {
}
function usage() {
return "Usage: node scripts/dev-gate-preflight.mjs [--target-ref origin/main] [--report reports/dev-gate/dev-preflight-report.json] [--timeout-ms 5000] [--no-write] [--fail-on-blocked]";
return `Usage: node scripts/dev-gate-preflight.mjs [--target-ref origin/main] [--report ${defaultReportPath}] [--timeout-ms 5000] [--no-write] [--fail-on-blocked]`;
}
function commandLine(command, args) {
@@ -452,7 +455,7 @@ async function artifactIdentityFor({ deploy, catalog, artifactReport, targetComm
publishVerified,
refreshCommands: {
blocked: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --blocked`,
published: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --publish-report reports/dev-gate/dev-artifacts.json`
published: `node scripts/refresh-artifact-catalog.mjs --target-ref ${targetRef} --publish-report ${defaultArtifactReportPath}`
}
};
}
@@ -536,8 +539,8 @@ async function loadContracts() {
async function loadOptionalReports() {
return {
artifactPublish: await readOptionalJson("reports/dev-gate/dev-artifacts.json"),
edgeHealth: await readOptionalJson("reports/dev-gate/dev-edge-health.json")
artifactPublish: await readOptionalJson(defaultArtifactReportPath),
edgeHealth: await readOptionalJson(defaultEdgeHealthReportPath)
};
}
@@ -655,8 +658,8 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit
reporter.block({
type: "runtime_blocker",
scope: "dev-artifact-publish",
summary: "reports/dev-gate/dev-artifacts.json is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.",
nextTask: "Run the DEV artifact publish workflow and record reports/dev-gate/dev-artifacts.json before real deployment."
summary: `${defaultArtifactReportPath} is missing, so the preflight has no publish evidence for HWLAB runtime artifacts.`,
nextTask: `Run the DEV artifact publish workflow and keep its temporary JSON at ${defaultArtifactReportPath}; do not commit report output.`
});
return;
}
@@ -716,7 +719,7 @@ function validateArtifactPublishReport(reporter, artifactReport, artifactIdentit
reporter.block({
type: "runtime_blocker",
scope: "dev-artifact-publish",
summary: `reports/dev-gate/dev-artifacts.json does not prove all required HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published and ${sourceSummary}.`,
summary: `${defaultArtifactReportPath} does not prove all required HWLAB service artifacts for ${targetRef} ${targetShortCommit}; current status is ${artifactPublish?.status ?? "missing"} with ${publishedCount}/${requiredServices.length || serviceCount} required services published and ${sourceSummary}.`,
nextTask: "Complete DEV artifact publishing for every enabled required HWLAB service at the artifact source commit, or prove the target commit has no artifact build input changes; keep disabled services marked not_published with reasons."
});
}
@@ -775,8 +778,8 @@ function validateEdgeHealthReport(reporter, edgeReport) {
reporter.block({
type: "network_blocker",
scope: "dev-edge-health-report",
summary: "reports/dev-gate/dev-edge-health.json is missing, so the preflight has no committed edge/frp health evidence.",
nextTask: "Run the read-only DEV edge health smoke and commit reports/dev-gate/dev-edge-health.json before real deployment."
summary: `${defaultEdgeHealthReportPath} is missing, so the preflight has no temporary edge/frp health JSON.`,
nextTask: `Run the read-only DEV edge health smoke and keep its temporary JSON at ${defaultEdgeHealthReportPath}; do not commit report output.`
});
return;
}
@@ -785,7 +788,7 @@ function validateEdgeHealthReport(reporter, edgeReport) {
const pass = edgeReport.status === "pass" && edgeHealth?.status === "pass";
const evidence = [
{
report: "reports/dev-gate/dev-edge-health.json",
report: defaultEdgeHealthReportPath,
status: edgeReport.status,
classification: edgeHealth?.classification ?? "unknown",
publicTcp: edgeHealth?.publicTcp ?? [],
@@ -1133,7 +1136,7 @@ function validateRuntimeBoundary(reporter, deploy) {
}
async function writeReport(report, reportPath) {
const absoluteReportPath = path.join(repoRoot, reportPath);
const absoluteReportPath = ensureNotRepoReportsPath(repoRoot, reportPath, "--report");
await mkdir(path.dirname(absoluteReportPath), { recursive: true });
await writeFile(absoluteReportPath, `${JSON.stringify(report, null, 2)}\n`);
}
@@ -1145,7 +1148,7 @@ function makeReport(args, targetCommit, targetShortCommit, reporter, artifactIde
return {
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-preflight-report.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/dev-preflight-report.json",
$id: "https://hwlab.pikastech.local/dev-gate/dev-preflight-report.json",
reportVersion: "v1",
reportKind: "dev-gate-preflight",
issue,
+5 -3
View File
@@ -22,10 +22,11 @@ import {
} from "../../internal/db/runtime-store.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const migrationPath = "internal/db/migrations/0001_cloud_core_skeleton.sql";
const defaultReportPath = "reports/dev-gate/dev-runtime-migration-report.json";
const defaultReportPath = tempReportPath("dev-runtime-migration-report.json");
const issue = "pikasTech/HWLAB#164";
export async function runDevRuntimeMigrationCli(argv = process.argv.slice(2), options = {}) {
@@ -167,15 +168,16 @@ export function parseArgs(argv = []) {
else if (arg === "--allow-live-db-read") args.allowLiveDbRead = true;
else if (arg === "--confirm-dev") args.confirmDev = true;
else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true;
else if (arg === "--write-report") args.writeReport = true;
else if (arg === "--pretty") args.pretty = true;
else if (arg === "--fail-on-blocked") args.failOnBlocked = true;
else if (arg === "--help" || arg === "-h") args.help = true;
else if (arg === "--report") {
index += 1;
args.reportPath = requireArgValue(argv[index], "--report");
args.writeReport = true;
} else if (arg.startsWith("--report=")) {
args.reportPath = requireArgValue(arg.slice("--report=".length), "--report");
args.writeReport = true;
} else {
throw new Error(`unknown argument: ${arg}`);
}
@@ -651,7 +653,7 @@ function isMigrationSslError(error) {
}
async function writeReport(report, reportPath, root) {
const absolute = path.resolve(root, reportPath);
const absolute = ensureNotRepoReportsPath(root, reportPath, "--report");
await mkdir(path.dirname(absolute), { recursive: true });
await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`);
}
+5 -3
View File
@@ -9,9 +9,10 @@ import {
buildM3IoControlSourceReport,
runM3IoControlLiveReport
} from "./m3-io-control-e2e.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = "reports/dev-gate/dev-runtime-postflight-report.json";
const defaultReportPath = tempReportPath("dev-runtime-postflight-report.json");
const issue = "pikasTech/HWLAB#311";
const defaultHealthUrl = `${DEV_ENDPOINT}/health/live`;
const defaultV1Url = `${DEV_ENDPOINT}/v1`;
@@ -149,7 +150,6 @@ export function parseArgs(argv = []) {
else if (arg === "--live") args.live = true;
else if (arg === "--confirm-dev") args.confirmDev = true;
else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true;
else if (arg === "--write-report") args.writeReport = true;
else if (arg === "--pretty") args.pretty = true;
else if (arg === "--fail-on-blocked") args.failOnBlocked = true;
else if (arg === "--target") {
@@ -161,8 +161,10 @@ export function parseArgs(argv = []) {
} else if (arg === "--report") {
index += 1;
args.reportPath = requireArgValue(argv[index], "--report");
args.writeReport = true;
} else if (arg.startsWith("--report=")) {
args.reportPath = requireArgValue(arg.slice("--report=".length), "--report");
args.writeReport = true;
} else if (arg === "--help" || arg === "-h") {
args.help = true;
} else {
@@ -604,7 +606,7 @@ function sanitizeRedactedValue(value) {
}
async function writeReport(report, reportPath, root) {
const absolute = path.resolve(root, reportPath);
const absolute = ensureNotRepoReportsPath(root, reportPath, "--report");
await mkdir(path.dirname(absolute), { recursive: true });
await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`);
}
+5 -3
View File
@@ -15,9 +15,10 @@ import {
buildPostgresPoolConfig
} from "../../internal/db/runtime-store.mjs";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultReportPath = "reports/dev-gate/dev-runtime-provisioning-report.json";
const defaultReportPath = tempReportPath("dev-runtime-provisioning-report.json");
const issue = "pikasTech/HWLAB#311";
export const DEV_DB_ADMIN_SECRET_REF = Object.freeze({
@@ -189,15 +190,16 @@ export function parseArgs(argv = []) {
else if (arg === "--allow-live-db-read") args.allowLiveDbRead = true;
else if (arg === "--confirm-dev") args.confirmDev = true;
else if (arg === "--confirmed-non-production") args.confirmedNonProduction = true;
else if (arg === "--write-report") args.writeReport = true;
else if (arg === "--pretty") args.pretty = true;
else if (arg === "--fail-on-blocked") args.failOnBlocked = true;
else if (arg === "--help" || arg === "-h") args.help = true;
else if (arg === "--report") {
index += 1;
args.reportPath = requireArgValue(argv[index], "--report");
args.writeReport = true;
} else if (arg.startsWith("--report=")) {
args.reportPath = requireArgValue(arg.slice("--report=".length), "--report");
args.writeReport = true;
} else {
throw new Error(`unknown argument: ${arg}`);
}
@@ -878,7 +880,7 @@ function isSslError(error) {
}
async function writeReport(report, reportPath, root) {
const absolute = path.resolve(root, reportPath);
const absolute = ensureNotRepoReportsPath(root, reportPath, "--report");
await mkdir(path.dirname(absolute), { recursive: true });
await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`);
}
+22
View File
@@ -0,0 +1,22 @@
import path from "node:path";
export const TEMP_REPORT_ROOT = process.env.HWLAB_TEMP_REPORT_ROOT || "/tmp/hwlab-dev-gate";
const forbiddenRepoReportDir = "reports";
export function tempReportPath(...segments) {
return path.join(TEMP_REPORT_ROOT, ...segments);
}
export function resolveOutputPath(root, outputPath) {
return path.isAbsolute(outputPath) ? outputPath : path.resolve(root, outputPath);
}
export function ensureNotRepoReportsPath(root, outputPath, label = "report path") {
const absoluteRoot = path.resolve(root);
const absoluteOutput = resolveOutputPath(absoluteRoot, outputPath);
const relative = path.relative(absoluteRoot, absoluteOutput).split(path.sep).join("/");
if (relative === forbiddenRepoReportDir || relative.startsWith(`${forbiddenRepoReportDir}/`)) {
throw new Error(`${label} must not write under the forbidden repository report directory; use ${TEMP_REPORT_ROOT} or .state instead`);
}
return absoluteOutput;
}
+9 -9
View File
@@ -26,10 +26,12 @@ import {
HWLAB_M3_IO_API_ROUTE,
HWLAB_M3_IO_SKILL_NAME
} from "../../skills/hwlab-agent-runtime/scripts/src/m3-io-skill-client.mjs";
import { ensureNotRepoReportsPath, tempReportPath } from "./report-paths.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const defaultJsonReportPath = "reports/dev-gate/rpt-004-mvp-e2e.json";
const defaultMarkdownReportPath = "reports/dev-gate/rpt-004-mvp-e2e.md";
const defaultJsonReportPath = tempReportPath("rpt-004-mvp-e2e.json");
const defaultMarkdownReportPath = tempReportPath("rpt-004-mvp-e2e.md");
const defaultArtifactReportPath = tempReportPath("dev-artifacts.json");
const issue = "pikasTech/HWLAB#316";
const taskId = "rpt004-mvp-e2e-harness";
const reportKind = "rpt-004-mvp-e2e-harness";
@@ -214,8 +216,6 @@ export function parseArgs(argv = []) {
} else if (arg === "--markdown") {
args.markdownPath = readArg(argv, ++index, arg);
args.writeReport = true;
} else if (arg === "--write-report") {
args.writeReport = true;
} else if (arg === "--no-write") {
args.writeReport = false;
} else if (arg === "--pretty") {
@@ -612,7 +612,7 @@ function baseReport({ parsed, now, root }) {
const liveMutationUsed = parsed.live === true && parsed.allowM3Write === true;
return {
$schema: "https://hwlab.pikastech.local/schemas/rpt-004-mvp-e2e-harness.schema.json",
$id: "https://hwlab.pikastech.local/reports/dev-gate/rpt-004-mvp-e2e.json",
$id: "https://hwlab.pikastech.local/dev-gate/rpt-004-mvp-e2e.json",
reportVersion: "v1",
reportKind,
issue,
@@ -677,7 +677,7 @@ function baseReport({ parsed, now, root }) {
"node --test scripts/src/rpt004-mvp-e2e-harness.test.mjs",
"node scripts/rpt004-mvp-e2e-harness.mjs --check --no-write",
"node scripts/rpt004-mvp-e2e-harness.mjs --live --url http://74.48.78.17:16666/ --api-url http://74.48.78.17:16667/",
"node scripts/validate-dev-gate-report.mjs reports/dev-gate/rpt-004-mvp-e2e.json"
`node scripts/validate-dev-gate-report.mjs ${defaultJsonReportPath}`
],
localSmoke: {
status: "not_run",
@@ -743,7 +743,7 @@ async function buildExpectedArtifactState({ root, expectedCommit, fsJson }) {
: (relativePath) => readJsonOptional(root, relativePath);
const deploy = await readJson("deploy/deploy.json");
const catalog = await readJson("deploy/artifact-catalog.dev.json");
const artifactReport = await readJson("reports/dev-gate/dev-artifacts.json");
const artifactReport = await readJson(defaultArtifactReportPath);
const deployCommit = sanitizeCommit(deploy?.commitId);
const catalogCommit = sanitizeCommit(catalog?.commitId);
const reportCommit = sanitizeCommit(artifactReport?.artifactPublish?.sourceCommitId ?? artifactReport?.commitId);
@@ -1382,8 +1382,8 @@ function compactReport(report) {
}
async function writeReports(report, args, root) {
const jsonPath = path.resolve(root, args.reportPath);
const markdownPath = path.resolve(root, args.markdownPath);
const jsonPath = ensureNotRepoReportsPath(root, args.reportPath, "--report");
const markdownPath = ensureNotRepoReportsPath(root, args.markdownPath, "--markdown");
report.artifacts = {
jsonReportPath: relativeOrAbsolute(jsonPath, root),
markdownReportPath: relativeOrAbsolute(markdownPath, root),
+1 -1
View File
@@ -837,7 +837,7 @@ function fixtureArtifactJson() {
return async (relativePath) => {
if (relativePath === "deploy/deploy.json") return deploy;
if (relativePath === "deploy/artifact-catalog.dev.json") return catalog;
if (relativePath === "reports/dev-gate/dev-artifacts.json") return artifactReport;
if (relativePath === "/tmp/hwlab-dev-gate/dev-artifacts.json") return artifactReport;
return null;
};
}