Files
cnc_wams/wasm-port/runtime/sdk/src/project-release-readiness.js

2074 lines
68 KiB
JavaScript

const REQUIRED_RELEASE_GATES = [
{
id: "diff-check",
label: "Git diff check",
command: "git diff --check",
},
{
id: "vendor-sync",
label: "Vendor sync guard",
command: "wasm-port/tools/verify_vendor_sync.sh",
expectedOutput: "vendor sync up to date",
},
{
id: "standalone-cnc-semantics",
label: "Standalone CNC semantics guard",
command: "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
expectedOutput: "standalone CNC semantics guard complete",
},
{
id: "interp-wasm",
label: "Interpreter WASM Node smoke",
command: "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
expectedOutput: "interp_wasm_node_smoke=ok",
},
{
id: "sim-config-inventory",
label: "Sim config inventory WASM Node smoke",
command: "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
expectedOutput: "sim_configs_wasm_node_inventory_unexpected_fail=0",
},
{
id: "ini-panel-browser",
label: "INI panel browser smoke",
command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
expectedOutput: "browser_ini_shell_integration_workflow_smoke=ok",
},
{
id: "opfs-session-browser",
label: "OPFS session browser workflow smoke",
command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
expectedOutput: "browser_opfs_session_workflow_smoke=ok",
},
{
id: "release-artifact-url-browser",
label: "Release artifact URL browser workflow smoke",
command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
expectedOutput: "browser_release_artifact_url_workflow_smoke=ok",
},
{
id: "project-batch-acceptance",
label: "Project batch acceptance workflow smoke",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
expectedOutput: "project_batch_acceptance_workflow_node_smoke=ok",
},
{
id: "ui-node-smokes",
label: "UI Node smokes",
command: "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
expectedOutput: "ui_node_smokes=ok",
},
{
id: "host-smokes",
label: "Host aggregate smoke",
command: "wasm-port/tests/host/verify_host_smokes.sh",
expectedOutput: "host_wasm_opfs_browser_smokes=ok",
},
{
id: "project-release-gate",
label: "Project release gate",
command: "wasm-port/tests/host/verify_project_release_gate.sh",
expectedOutput: "project_release_gate=ok",
},
];
const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = {
executed: 28,
passed: 28,
skipped: 131,
unexpectedFail: 0,
};
const BLOCKED_RUNTIME_FAMILIES = [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
];
const PROJECT_RELEASE_READINESS_ARTIFACT_API = "project-release-readiness-report";
const PROJECT_RELEASE_READINESS_ARTIFACT_VERSION = 1;
function outputContains(observedOutputs, expectedOutput) {
return observedOutputs.some((output) => String(output).includes(expectedOutput));
}
function readGatePassed({ gate, gateResults, observedOutputs }) {
if (gateResults?.[gate.id] === true) {
return true;
}
if (gate.expectedOutput && outputContains(observedOutputs, gate.expectedOutput)) {
return true;
}
return false;
}
function objectOrEmpty(value) {
return value && typeof value === "object" ? value : {};
}
function arrayOrEmpty(value) {
return Array.isArray(value) ? value : [];
}
function normalizeObservedOutput(value) {
if (Array.isArray(value)) {
return value.map((output) => String(output));
}
if (typeof value === "string") {
return [value];
}
return [];
}
function normalizeAxisScreenshotArtifacts(value) {
return arrayOrEmpty(value)
.map((artifact) => objectOrEmpty(artifact))
.filter(({ apiName }) => apiName === "real-browser-simulation-axis-screenshot-artifact")
.map((artifact) => ({
apiName: artifact.apiName,
artifactVersion: artifact.artifactVersion ?? 1,
viewportName: artifact.viewportName ?? "unknown",
windowSize: artifact.windowSize ?? "unknown",
query: artifact.query ?? "",
screenshotBytes: Number.isFinite(artifact.screenshotBytes) ? artifact.screenshotBytes : 0,
validatedBy: artifact.validatedBy ?? "unknown",
fullDiagnosticsApiName: artifact.fullDiagnosticsApiName ?? null,
fullDiagnosticsPath: artifact.fullDiagnosticsPath ?? null,
previewRenderer: artifact.previewRenderer ?? null,
previewViewMode: artifact.previewViewMode ?? null,
threeReady: artifact.threeReady === true,
threePathPoints: Number.isFinite(artifact.threePathPoints) ? artifact.threePathPoints : 0,
screenshotPath: artifact.screenshotPath ?? null,
diagnosticsPath: artifact.diagnosticsPath ?? null,
}));
}
function createAxisScreenshotArtifactSummary(axisScreenshotArtifacts = []) {
const artifacts = normalizeAxisScreenshotArtifacts(axisScreenshotArtifacts);
const expectedViewports = ["desktop-preview", "desktop-dro", "mobile-preview", "mobile-mdi"];
const viewportNames = artifacts.map(({ viewportName }) => viewportName);
const missingViewports = expectedViewports.filter((name) => !viewportNames.includes(name));
const undersizedViewports = artifacts
.filter(({ screenshotBytes }) => screenshotBytes < 10000)
.map(({ viewportName }) => viewportName);
const ready = artifacts.length === 0 || (missingViewports.length === 0 && undersizedViewports.length === 0);
return {
apiName: "project-release-axis-screenshot-artifact-summary",
summaryVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
expectedViewports,
artifactCount: artifacts.length,
viewportNames,
missingViewports,
undersizedViewports,
totalScreenshotBytes: artifacts.reduce((sum, { screenshotBytes }) => sum + screenshotBytes, 0),
artifacts,
};
}
export function createProjectReleaseGateManifest() {
return {
apiName: "project-release-gate-manifest",
manifestVersion: 1,
gateCount: REQUIRED_RELEASE_GATES.length,
gateIds: REQUIRED_RELEASE_GATES.map(({ id }) => id),
gates: REQUIRED_RELEASE_GATES.map((gate) => ({
id: gate.id,
label: gate.label,
command: gate.command,
expectedOutput: gate.expectedOutput ?? null,
})),
};
}
export function createProjectReleaseGateExecutionManifest({
gateResults = {},
observedOutputs = [],
gateObservedOutputs = {},
manifest = createProjectReleaseGateManifest(),
} = {}) {
const globalOutputs = normalizeObservedOutput(observedOutputs);
const gates = arrayOrEmpty(manifest?.gates);
const rows = gates.map((gate) => {
const scopedOutputs = [
...normalizeObservedOutput(gateObservedOutputs?.[gate.id]),
...globalOutputs,
];
const matchedOutput = gate.expectedOutput
? scopedOutputs.find((output) => output.includes(gate.expectedOutput)) ?? null
: null;
const explicitPassed = gateResults?.[gate.id] === true;
const passed = explicitPassed || matchedOutput !== null;
return {
id: gate.id,
label: gate.label,
command: gate.command,
expectedOutput: gate.expectedOutput ?? null,
observedOutput: matchedOutput,
observedOutputCount: scopedOutputs.length,
status: passed ? "passed" : "unknown",
passed,
evidence: matchedOutput !== null
? "expected-output"
: (explicitPassed ? "explicit-result" : "missing"),
};
});
return {
apiName: "project-release-gate-execution-manifest",
manifestVersion: 1,
phase: rows.every(({ passed }) => passed) ? "ready" : "waiting",
ready: rows.every(({ passed }) => passed),
gateCount: rows.length,
passedCount: rows.filter(({ passed }) => passed).length,
unknownCount: rows.filter(({ passed }) => !passed).length,
missingGateIds: rows.filter(({ passed }) => !passed).map(({ id }) => id),
manifest,
rows,
};
}
export function createProjectReleaseGateExecutionSummaryViewModel(
executionManifest = createProjectReleaseGateExecutionManifest(),
) {
const rows = arrayOrEmpty(executionManifest?.rows);
const ready = executionManifest?.ready === true;
const missingGateIds = arrayOrEmpty(executionManifest?.missingGateIds);
const statusText = ready ? "Ready" : "Waiting";
const detailText = ready
? "All release gate evidence present"
: (missingGateIds.length > 0 ? missingGateIds.join(", ") : "release gate evidence missing");
const evidenceCounts = rows.reduce((counts, row) => ({
...counts,
[row.evidence ?? "missing"]: (counts[row.evidence ?? "missing"] ?? 0) + 1,
}), {});
return {
apiName: "project-release-gate-execution-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "waiting",
ready,
title: "Project release gate execution",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
gateCount: executionManifest?.gateCount ?? rows.length,
passedCount: executionManifest?.passedCount ?? rows.filter(({ passed }) => passed === true).length,
unknownCount: executionManifest?.unknownCount ?? rows.filter(({ passed }) => passed !== true).length,
missingGateIds,
nextGateId: missingGateIds[0] ?? null,
evidenceCounts,
rows: [
{
id: "gate-results",
label: "Release gate evidence",
value: `${executionManifest?.passedCount ?? 0}/${executionManifest?.gateCount ?? rows.length} passed`,
},
{
id: "unknown-gates",
label: "Unknown gates",
value: `${executionManifest?.unknownCount ?? 0}`,
},
{
id: "next-gate",
label: "Next gate",
value: missingGateIds[0] ?? "none",
},
{
id: "expected-output-evidence",
label: "Expected-output evidence",
value: `${evidenceCounts["expected-output"] ?? 0}`,
},
{
id: "explicit-result-evidence",
label: "Explicit-result evidence",
value: `${evidenceCounts["explicit-result"] ?? 0}`,
},
{
id: "missing",
label: "Missing gate evidence",
value: missingGateIds.length > 0 ? missingGateIds.join(", ") : "none",
},
],
gateRows: rows.map((row) => ({
id: row.id,
label: row.label,
command: row.command,
expectedOutput: row.expectedOutput ?? null,
observedOutput: row.observedOutput ?? null,
evidence: row.evidence ?? "missing",
status: row.status ?? (row.passed === true ? "passed" : "unknown"),
passed: row.passed === true,
})),
};
}
export function createProjectReleaseGateResultMatrix({
gateResults = {},
observedOutputs = [],
manifest = createProjectReleaseGateManifest(),
} = {}) {
const outputs = Array.isArray(observedOutputs) ? observedOutputs : [];
const gates = Array.isArray(manifest?.gates) ? manifest.gates : [];
const rows = gates.map((gate) => {
const passed = readGatePassed({ gate, gateResults, observedOutputs: outputs });
return {
id: gate.id,
label: gate.label,
command: gate.command,
expectedOutput: gate.expectedOutput ?? null,
status: passed ? "passed" : "unknown",
passed,
};
});
return {
apiName: "project-release-gate-result-matrix",
matrixVersion: 1,
phase: rows.every(({ passed }) => passed) ? "ready" : "waiting",
ready: rows.every(({ passed }) => passed),
gateCount: rows.length,
passedCount: rows.filter(({ passed }) => passed).length,
unknownCount: rows.filter(({ passed }) => !passed).length,
missingGateIds: rows.filter(({ passed }) => !passed).map(({ id }) => id),
manifest,
rows,
};
}
export function createProjectReleaseGateActionPlan({
gateResultMatrix = null,
gateResults = {},
observedOutputs = [],
manifest = createProjectReleaseGateManifest(),
} = {}) {
const matrix = gateResultMatrix && typeof gateResultMatrix === "object"
? gateResultMatrix
: createProjectReleaseGateResultMatrix({ gateResults, observedOutputs, manifest });
const rows = Array.isArray(matrix.rows) ? matrix.rows : [];
const pendingRows = rows.filter(({ passed }) => passed !== true);
const completedRows = rows.filter(({ passed }) => passed === true);
const commands = pendingRows.map((gate, index) => ({
step: index + 1,
gateId: gate.id,
label: gate.label,
command: gate.command,
expectedOutput: gate.expectedOutput ?? null,
}));
const nextGate = commands[0] ?? null;
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: "# Project release gates are already marked passed.\n";
return {
apiName: "project-release-gate-action-plan",
planVersion: 1,
phase: commands.length === 0 ? "ready" : "waiting",
ready: commands.length === 0,
gateCount: rows.length,
completedCount: completedRows.length,
pendingCount: pendingRows.length,
completedGateIds: completedRows.map(({ id }) => id),
pendingGateIds: pendingRows.map(({ id }) => id),
nextGateId: nextGate?.gateId ?? null,
nextCommand: nextGate?.command ?? null,
commands,
shellScript,
rows: commands.map(({ gateId, label, command, expectedOutput }) => ({
id: gateId,
label,
value: command,
expectedOutput,
})),
};
}
const BATCH_ACCEPTANCE_CAPABILITY_TYPES = [
"api",
"workflow",
"gate",
"browser",
];
function normalizeBatchCapabilities(capabilities) {
return arrayOrEmpty(capabilities)
.map((capability) => objectOrEmpty(capability))
.filter(({ id, type }) => typeof id === "string" && typeof type === "string")
.map((capability) => ({
id: capability.id,
type: capability.type,
label: capability.label ?? capability.id,
evidence: capability.evidence ?? null,
command: capability.command ?? null,
}));
}
export function createProjectBatchAcceptanceCapabilityMatrix({
capabilities = [],
} = {}) {
const normalizedCapabilities = normalizeBatchCapabilities(capabilities);
const acceptedCapabilities = normalizedCapabilities.filter(({ type }) =>
BATCH_ACCEPTANCE_CAPABILITY_TYPES.includes(type)
);
const rejectedCapabilities = normalizedCapabilities.filter(({ type }) =>
!BATCH_ACCEPTANCE_CAPABILITY_TYPES.includes(type)
);
const capabilityTypes = [...new Set(acceptedCapabilities.map(({ type }) => type))];
const countsByType = BATCH_ACCEPTANCE_CAPABILITY_TYPES.reduce((counts, type) => ({
...counts,
[type]: acceptedCapabilities.filter((capability) => capability.type === type).length,
}), {});
const missing = acceptedCapabilities.length > 0 ? [] : ["batch-capability"];
return {
apiName: "project-batch-acceptance-capability-matrix",
matrixVersion: 1,
phase: missing.length === 0 ? "ready" : "blocked",
ready: missing.length === 0,
acceptedCapabilityTypes: BATCH_ACCEPTANCE_CAPABILITY_TYPES,
capabilityTypes,
capabilityCount: acceptedCapabilities.length,
acceptedCount: acceptedCapabilities.length,
rejectedCount: rejectedCapabilities.length,
countsByType,
capabilities: acceptedCapabilities,
rejectedCapabilities,
missing,
rows: [
{
id: "accepted-capabilities",
label: "Accepted capabilities",
value: `${acceptedCapabilities.length}`,
},
{
id: "accepted-types",
label: "Accepted capability types",
value: capabilityTypes.join(", ") || "none",
},
{
id: "rejected-capabilities",
label: "Rejected capabilities",
value: `${rejectedCapabilities.length}`,
},
{
id: "missing",
label: "Missing capability evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceWorkflow({
batchId = "current-batch",
capabilities = [],
gateResults = {},
observedOutputs = [],
} = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const gateManifest = createProjectReleaseGateManifest();
const gateResultMatrix = createProjectReleaseGateResultMatrix({
gateResults,
observedOutputs,
manifest: gateManifest,
});
const batchGatePassed = gateResultMatrix.rows
.find(({ id }) => id === "project-batch-acceptance")?.passed === true;
const ready = capabilityMatrix.ready && batchGatePassed;
const missing = [
...capabilityMatrix.missing,
...(batchGatePassed ? [] : ["project-batch-acceptance"]),
];
return {
apiName: "project-batch-acceptance-workflow",
workflowVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
batchId,
acceptedCapabilityTypes: capabilityMatrix.acceptedCapabilityTypes,
capabilityTypes: capabilityMatrix.capabilityTypes,
capabilityCount: capabilityMatrix.capabilityCount,
capabilityMatrix,
capabilities: capabilityMatrix.capabilities,
rejectedCapabilities: capabilityMatrix.rejectedCapabilities,
gateResultMatrix,
missing,
rows: [
{
id: "batch",
label: "Batch",
value: batchId,
},
{
id: "capabilities",
label: "Accepted capabilities",
value: capabilityMatrix.capabilityTypes.join(", ") || "none",
},
{
id: "project-batch-acceptance",
label: "Batch acceptance gate",
value: batchGatePassed ? "passed" : "unknown",
},
{
id: "missing",
label: "Missing acceptance evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceSummaryViewModel(
workflow = createProjectBatchAcceptanceWorkflow(),
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
? "Batch acceptance evidence complete"
: (missing.length > 0 ? missing.join(", ") : "batch acceptance evidence missing");
return {
apiName: "project-batch-acceptance-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
title: "Project batch acceptance",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "batch",
label: "Batch",
value: workflow?.batchId ?? "current-batch",
},
{
id: "capability-count",
label: "Accepted capability count",
value: `${workflow?.capabilityCount ?? 0}`,
},
{
id: "capability-types",
label: "Accepted capability types",
value: arrayOrEmpty(workflow?.capabilityTypes).join(", ") || "none",
},
{
id: "missing",
label: "Missing acceptance evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceActionPlan(
workflow = createProjectBatchAcceptanceWorkflow(),
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const commands = ready ? [] : [
...(missing.includes("project-batch-acceptance")
? [{
step: 1,
id: "project-batch-acceptance",
label: "Run project batch acceptance workflow smoke",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
expectedOutput: "project_batch_acceptance_workflow_node_smoke=ok",
}]
: []),
];
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: (ready
? "# Project batch acceptance workflow is already ready.\n"
: "# Project batch acceptance workflow requires at least one API, workflow, gate, or browser capability.\n");
return {
apiName: "project-batch-acceptance-action-plan",
planVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
commandCount: commands.length,
nextActionId: commands[0]?.id ?? (missing[0] ?? null),
nextCommand: commands[0]?.command ?? null,
commands,
shellScript,
rows: [
{
id: "batch-acceptance",
label: "Batch acceptance",
value: ready ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing acceptance evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
{
id: "next-command",
label: "Next command",
value: commands[0]?.command ?? "none",
},
],
};
}
export function createProjectBatchAcceptanceChecklist({
workflow = createProjectBatchAcceptanceWorkflow(),
actionPlan = createProjectBatchAcceptanceActionPlan(workflow),
} = {}) {
const capabilityMatrix = objectOrEmpty(workflow?.capabilityMatrix);
const ready = workflow?.ready === true;
const items = [
{
id: "accepted-capability",
label: "At least one API, workflow, gate, or browser capability",
status: capabilityMatrix.ready === true ? "pass" : "blocked",
passed: capabilityMatrix.ready === true,
detail: arrayOrEmpty(workflow?.capabilityTypes).join(", ") || "none",
},
{
id: "batch-acceptance-gate",
label: "Batch acceptance gate evidence",
status: !arrayOrEmpty(workflow?.missing).includes("project-batch-acceptance") ? "pass" : "blocked",
passed: !arrayOrEmpty(workflow?.missing).includes("project-batch-acceptance"),
detail: workflow?.gateResultMatrix?.rows
?.find?.(({ id }) => id === "project-batch-acceptance")?.status ?? "unknown",
},
{
id: "next-action",
label: "Next acceptance action",
status: actionPlan?.nextActionId ? "blocked" : "pass",
passed: !actionPlan?.nextActionId,
detail: actionPlan?.nextCommand ?? "none",
},
];
return {
apiName: "project-batch-acceptance-checklist",
checklistVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
batchId: workflow?.batchId ?? "current-batch",
passedCount: items.filter(({ passed }) => passed).length,
blockedCount: items.filter(({ passed }) => !passed).length,
items,
rows: items.map(({ id, label, status, detail }) => ({
id,
label,
value: `${status}: ${detail}`,
})),
};
}
export function createProjectBatchAcceptanceReport({
batchId = "current-batch",
capabilities = [],
gateResults = {},
observedOutputs = [],
} = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const workflow = createProjectBatchAcceptanceWorkflow({
batchId,
capabilities,
gateResults,
observedOutputs,
});
const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow);
const actionPlan = createProjectBatchAcceptanceActionPlan(workflow);
const checklist = createProjectBatchAcceptanceChecklist({ workflow, actionPlan });
return {
apiName: "project-batch-acceptance-report",
reportVersion: 1,
phase: workflow.phase,
ready: workflow.ready,
batchId: workflow.batchId,
missing: [...workflow.missing],
capabilityMatrix,
workflow,
summaryViewModel,
actionPlan,
checklist,
rows: [
{
id: "summary",
label: "Batch acceptance summary",
value: summaryViewModel.statusLine,
},
{
id: "capabilities",
label: "Accepted capabilities",
value: capabilityMatrix.capabilityTypes.join(", ") || "none",
},
{
id: "checklist",
label: "Checklist",
value: `${checklist.passedCount}/${checklist.items.length} passed`,
},
{
id: "next-command",
label: "Next command",
value: actionPlan.nextCommand ?? "none",
},
],
};
}
export function createProjectBatchAcceptanceReportValidation(report = {}) {
const reportObject = objectOrEmpty(report);
const capabilityMatrix = objectOrEmpty(reportObject.capabilityMatrix);
const workflow = objectOrEmpty(reportObject.workflow);
const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel);
const actionPlan = objectOrEmpty(reportObject.actionPlan);
const checklist = objectOrEmpty(reportObject.checklist);
const rows = arrayOrEmpty(reportObject.rows);
const missing = [
...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]),
...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]),
...(reportObject.phase === "ready" ? [] : ["phase"]),
...(reportObject.ready === true ? [] : ["ready"]),
...(Array.isArray(reportObject.missing) && reportObject.missing.length === 0 ? [] : ["missing"]),
...(capabilityMatrix.apiName === "project-batch-acceptance-capability-matrix"
&& capabilityMatrix.ready === true
? []
: ["capabilityMatrix"]),
...(workflow.apiName === "project-batch-acceptance-workflow" && workflow.ready === true
? []
: ["workflow"]),
...(summaryViewModel.apiName === "project-batch-acceptance-summary-view-model"
&& summaryViewModel.ready === true
? []
: ["summaryViewModel"]),
...(actionPlan.apiName === "project-batch-acceptance-action-plan" && actionPlan.ready === true
? []
: ["actionPlan"]),
...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true
? []
: ["checklist"]),
...(rows.find(({ id, value }) => id === "checklist" && value === "3/3 passed")
? []
: ["rows.checklist"]),
];
return {
apiName: "project-batch-acceptance-report-validation",
validationVersion: 1,
phase: missing.length === 0 ? "ready" : "blocked",
ready: missing.length === 0,
missing,
batchId: reportObject.batchId ?? null,
reportApiName: reportObject.apiName ?? null,
reportVersion: reportObject.reportVersion ?? null,
rows: [
{
id: "report",
label: "Batch acceptance report",
value: reportObject.apiName ?? "missing",
},
{
id: "capability-matrix",
label: "Capability matrix",
value: capabilityMatrix.ready === true ? "ready" : "missing",
},
{
id: "workflow",
label: "Batch workflow",
value: workflow.ready === true ? "ready" : "missing",
},
{
id: "checklist",
label: "Checklist",
value: checklist.ready === true ? "ready" : "missing",
},
{
id: "missing",
label: "Missing report evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceReportValidationSummaryViewModel(
validation = createProjectBatchAcceptanceReportValidation(),
) {
const missing = arrayOrEmpty(validation?.missing);
const ready = validation?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
? "Batch acceptance report evidence complete"
: (missing.length > 0 ? missing.join(", ") : "batch acceptance report evidence missing");
return {
apiName: "project-batch-acceptance-report-validation-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
title: "Project batch acceptance report validation",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "report",
label: "Batch acceptance report",
value: validation?.reportApiName ?? "missing",
},
{
id: "batch",
label: "Batch",
value: validation?.batchId ?? "missing",
},
{
id: "validation",
label: "Report validation",
value: ready ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceReportValidationActionPlan(
validation = createProjectBatchAcceptanceReportValidation(),
) {
const missing = arrayOrEmpty(validation?.missing);
const ready = validation?.ready === true;
const commands = ready ? [] : [
{
step: 1,
id: "project-batch-acceptance",
label: "Run project batch acceptance workflow and artifact gate",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
expectedOutput: "project_batch_acceptance_artifact_node_smoke=ok",
},
];
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: "# Project batch acceptance report validation is already ready.\n";
return {
apiName: "project-batch-acceptance-report-validation-action-plan",
planVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
missingCount: missing.length,
commandCount: commands.length,
nextActionId: commands[0]?.id ?? null,
nextCommand: commands[0]?.command ?? null,
commands,
shellScript,
rows: [
{
id: "report-validation",
label: "Report validation",
value: ready ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
{
id: "next-command",
label: "Next command",
value: commands[0]?.command ?? "none",
},
],
};
}
export function createProjectBatchAcceptanceReportJsonWorkflow({ reportJson = "" } = {}) {
try {
const report = JSON.parse(String(reportJson));
const validation = createProjectBatchAcceptanceReportValidation(report);
return {
apiName: "project-batch-acceptance-report-json-workflow",
workflowVersion: 1,
phase: validation.ready ? "ready" : "blocked",
ready: validation.ready,
parsed: true,
parseError: null,
report,
validation,
missing: validation.missing,
rows: [
{
id: "parse",
label: "Batch report JSON parse",
value: "ready",
},
{
id: "validation",
label: "Batch report validation",
value: validation.ready ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: validation.missing.length > 0 ? validation.missing.join(", ") : "none",
},
],
};
} catch (error) {
const validation = createProjectBatchAcceptanceReportValidation({});
return {
apiName: "project-batch-acceptance-report-json-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
parsed: false,
parseError: error?.message ?? "batch acceptance report JSON parse error",
report: null,
validation,
missing: ["report-json", ...validation.missing],
rows: [
{
id: "parse",
label: "Batch report JSON parse",
value: "blocked",
},
{
id: "validation",
label: "Batch report validation",
value: "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: ["report-json", ...validation.missing].join(", "),
},
],
};
}
}
export async function loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl = "",
fetchRef = globalThis.fetch,
} = {}) {
if (!reportUrl || typeof fetchRef !== "function") {
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
const missing = [
...(reportUrl ? [] : ["report-url"]),
...(typeof fetchRef === "function" ? [] : ["fetch"]),
...jsonWorkflow.missing,
];
return {
apiName: "project-batch-acceptance-report-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
reportUrl,
fetched: false,
httpStatus: null,
fetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
missing,
rows: [
{
id: "fetch",
label: "Batch report URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Batch report JSON workflow",
value: "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: missing.join(", "),
},
],
};
}
try {
const response = await fetchRef(reportUrl);
const httpStatus = response?.status ?? null;
if (!response?.ok) {
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
const fetchError = `batch report URL fetch failed${httpStatus ? `: ${httpStatus}` : ""}`;
return {
apiName: "project-batch-acceptance-report-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
reportUrl,
fetched: false,
httpStatus,
fetchError,
jsonWorkflow,
validation: jsonWorkflow.validation,
missing: ["report-fetch", ...jsonWorkflow.missing],
rows: [
{
id: "fetch",
label: "Batch report URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Batch report JSON workflow",
value: "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: ["report-fetch", ...jsonWorkflow.missing].join(", "),
},
],
};
}
const reportJson = await response.text();
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson });
return {
apiName: "project-batch-acceptance-report-url-workflow",
workflowVersion: 1,
phase: jsonWorkflow.ready === true ? "ready" : "blocked",
ready: jsonWorkflow.ready === true,
reportUrl,
fetched: true,
httpStatus,
fetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
missing: jsonWorkflow.missing,
rows: [
{
id: "fetch",
label: "Batch report URL fetch",
value: "ready",
},
{
id: "json-workflow",
label: "Batch report JSON workflow",
value: jsonWorkflow.ready === true ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: jsonWorkflow.missing.length > 0 ? jsonWorkflow.missing.join(", ") : "none",
},
],
};
} catch (error) {
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
return {
apiName: "project-batch-acceptance-report-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
reportUrl,
fetched: false,
httpStatus: null,
fetchError: error?.message ?? "batch report URL fetch error",
jsonWorkflow,
validation: jsonWorkflow.validation,
missing: ["report-fetch", ...jsonWorkflow.missing],
rows: [
{
id: "fetch",
label: "Batch report URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Batch report JSON workflow",
value: "blocked",
},
{
id: "missing",
label: "Missing report evidence",
value: ["report-fetch", ...jsonWorkflow.missing].join(", "),
},
],
};
}
}
export function createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
workflow = {},
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
? "Batch report URL workflow complete"
: (missing.length > 0 ? missing.join(", ") : workflow?.fetchError ?? "batch report URL workflow blocked");
return {
apiName: "project-batch-acceptance-report-url-workflow-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
title: "Project batch acceptance report URL workflow",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "report-url",
label: "Report URL",
value: workflow?.reportUrl || "missing",
},
{
id: "fetch",
label: "Fetch",
value: workflow?.fetched === true ? `ready${workflow?.httpStatus ? ` (${workflow.httpStatus})` : ""}` : "blocked",
},
{
id: "json-workflow",
label: "JSON workflow",
value: workflow?.jsonWorkflow?.ready === true ? "ready" : "blocked",
},
{
id: "report-validation",
label: "Report validation",
value: workflow?.validation?.ready === true ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing workflow evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
workflow = {},
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const inputActions = [
...(missing.includes("report-url")
? [{
id: "provide-report-url",
label: "Provide project batch acceptance report URL",
kind: "input",
command: null,
expectedOutput: null,
}]
: []),
...(missing.includes("fetch")
? [{
id: "provide-fetch",
label: "Provide fetch implementation",
kind: "input",
command: null,
expectedOutput: null,
}]
: []),
...(missing.includes("report-fetch")
? [{
id: "verify-report-url-fetch",
label: "Verify project batch acceptance report URL fetch",
kind: "fetch",
command: null,
expectedOutput: null,
}]
: []),
];
const commandActions = ready ? [] : [
{
id: "project-batch-acceptance",
label: "Run project batch acceptance workflow smoke",
kind: "command",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
expectedOutput: "project_batch_acceptance_workflow_node_smoke=ok",
},
];
const actions = ready ? [] : [...inputActions, ...commandActions].map((action, index) => ({
step: index + 1,
...action,
}));
const commands = actions.filter(({ command }) => typeof command === "string" && command.length > 0);
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: (ready
? "# Project batch acceptance report URL workflow is already ready.\n"
: "# Project batch acceptance report URL workflow requires non-shell input.\n");
return {
apiName: "project-batch-acceptance-report-url-workflow-action-plan",
planVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
actionCount: actions.length,
commandCount: commands.length,
nextActionId: actions[0]?.id ?? null,
nextCommand: commands[0]?.command ?? null,
actions,
commands,
shellScript,
rows: actions.map(({ id, label, kind, command, expectedOutput }) => ({
id,
label,
kind,
value: command ?? kind,
expectedOutput,
})),
};
}
export function createProjectReleaseReadinessReport({
gateResults = {},
observedOutputs = [],
simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES,
promotedRuntimeFamilies = [],
axisScreenshotArtifacts = [],
} = {}) {
const gateManifest = createProjectReleaseGateManifest();
const gateExecutionManifest = createProjectReleaseGateExecutionManifest({
gateResults,
observedOutputs,
manifest: gateManifest,
});
const gateExecutionSummaryViewModel = createProjectReleaseGateExecutionSummaryViewModel(
gateExecutionManifest,
);
const gateResultMatrix = createProjectReleaseGateResultMatrix({
gateResults,
observedOutputs,
manifest: gateManifest,
});
const gateActionPlan = createProjectReleaseGateActionPlan({
gateResultMatrix,
manifest: gateManifest,
});
const gateRows = gateResultMatrix.rows;
const releaseGatePassed = gateRows.find(({ id }) => id === "project-release-gate")?.passed === true;
const inventory = {
...DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
...(simConfigInventory && typeof simConfigInventory === "object" ? simConfigInventory : {}),
};
const blockedFamilies = [...blockedRuntimeFamilies];
const promotedFamilies = [...promotedRuntimeFamilies];
const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family));
const inventoryReady = inventory.unexpectedFail === 0;
const blockedRuntimeReady = promotedBlockedFamilies.length === 0;
const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts);
const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready;
const ready = releaseGatePassed && inventoryReady && blockedRuntimeReady && axisScreenshotArtifactsReady;
const missing = [
...(releaseGatePassed ? [] : ["project-release-gate"]),
...(inventoryReady ? [] : ["sim-config-inventory"]),
...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]),
...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]),
];
return {
apiName: PROJECT_RELEASE_READINESS_ARTIFACT_API,
reportVersion: PROJECT_RELEASE_READINESS_ARTIFACT_VERSION,
phase: ready ? "ready" : "waiting",
ready,
missing,
releaseGate: {
command: "wasm-port/tests/host/verify_project_release_gate.sh",
passed: releaseGatePassed,
expectedOutput: "project_release_gate=ok",
},
simConfigInventory: inventory,
blockedRuntimeFamilies: blockedFamilies,
promotedRuntimeFamilies: promotedFamilies,
promotedBlockedFamilies,
axisScreenshotArtifactSummary,
gateManifest,
gateExecutionManifest,
gateExecutionSummaryViewModel,
gateResultMatrix,
gateActionPlan,
gates: gateRows,
rows: [
{
id: "project-release-gate",
label: "Project release gate",
value: releaseGatePassed ? "passed" : "unknown",
},
{
id: "sim-config-inventory",
label: "Sim config inventory",
value: `unexpected_fail=${inventory.unexpectedFail}`,
},
{
id: "blocked-runtime-families",
label: "Blocked runtime families",
value: blockedRuntimeReady ? blockedFamilies.join(", ") : `promoted: ${promotedBlockedFamilies.join(", ")}`,
},
{
id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts",
value: axisScreenshotArtifactSummary.artifactCount > 0
? `${axisScreenshotArtifactSummary.artifactCount}/${axisScreenshotArtifactSummary.expectedViewports.length} captured`
: "not captured",
},
],
};
}
export function createProjectReleaseReadinessSummaryViewModel(
report = createProjectReleaseReadinessReport(),
) {
const missing = Array.isArray(report?.missing) ? report.missing : [];
const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix);
const simConfigInventory = objectOrEmpty(report?.simConfigInventory);
const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary);
const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies);
const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies);
const ready = report?.ready === true;
const statusText = ready ? "Ready" : "Waiting";
const detailText = ready ? "No blocking reasons" : (missing.length > 0 ? missing.join(", ") : "release evidence missing");
return {
apiName: "project-release-readiness-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "waiting",
ready,
title: "Project release readiness",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "gate-results",
label: "Release gates",
value: `${gateResultMatrix.passedCount ?? 0}/${gateResultMatrix.gateCount ?? 0} passed`,
},
{
id: "unknown-gates",
label: "Unknown gates",
value: `${gateResultMatrix.unknownCount ?? 0}`,
},
{
id: "sim-config-inventory",
label: "Sim config inventory",
value: `unexpected_fail=${simConfigInventory.unexpectedFail ?? "unknown"}`,
},
{
id: "blocked-runtime-families",
label: "Blocked runtime families",
value: promotedBlockedFamilies.length > 0
? `promoted: ${promotedBlockedFamilies.join(", ")}`
: (blockedRuntimeFamilies.join(", ") || "none"),
},
{
id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts",
value: Number(axisScreenshotArtifactSummary.artifactCount ?? 0) > 0
? `${axisScreenshotArtifactSummary.artifactCount}/${arrayOrEmpty(axisScreenshotArtifactSummary.expectedViewports).length} captured`
: "not captured",
},
{
id: "missing",
label: "Missing readiness",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
validation = createProjectReleaseReadinessArtifactValidation(),
) {
const missing = arrayOrEmpty(validation?.missing);
const ready = validation?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
? "Artifact evidence complete"
: (missing.length > 0 ? missing.join(", ") : "artifact evidence missing");
return {
apiName: "project-release-readiness-artifact-validation-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
title: "Project release artifact validation",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "artifact",
label: "Readiness artifact",
value: validation?.artifactApiName ?? "missing",
},
{
id: "gate-count",
label: "Gate count",
value: `${validation?.gateCount ?? 0}/${validation?.expectedGateCount ?? 0}`,
},
{
id: "gate-manifest",
label: "Gate manifest",
value: validation?.gateManifestReady === true ? "ready" : "missing",
},
{
id: "gate-execution-manifest",
label: "Gate execution manifest",
value: validation?.gateExecutionManifestReady === true ? "ready" : "missing",
},
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: validation?.gateExecutionSummaryReady === true ? "ready" : "missing",
},
{
id: "gate-result-matrix",
label: "Gate result matrix",
value: validation?.gateResultMatrixReady === true ? "ready" : "missing",
},
{
id: "gate-action-plan",
label: "Gate action plan",
value: validation?.gateActionPlanReady === true ? "ready" : "missing",
},
{
id: "missing",
label: "Missing artifact evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectReleaseReadinessArtifactValidationActionPlan(
validation = createProjectReleaseReadinessArtifactValidation(),
) {
const missing = arrayOrEmpty(validation?.missing);
const ready = validation?.ready === true;
const commands = ready ? [] : [
{
step: 1,
id: "project-release-gate",
label: "Run project release gate",
command: "wasm-port/tests/host/verify_project_release_gate.sh",
expectedOutput: "project_release_gate=ok",
},
{
step: 2,
id: "artifact-validation",
label: "Validate project release readiness artifact",
command: "wasm-port/tests/host/verify_project_release_readiness_artifact.sh build/project-release-readiness.json",
expectedOutput: "project_release_readiness_artifact_node_smoke=ok",
},
];
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: "# Project release readiness artifact validation is already ready.\n";
return {
apiName: "project-release-readiness-artifact-validation-action-plan",
planVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
missingCount: missing.length,
commandCount: commands.length,
nextActionId: commands[0]?.id ?? null,
nextCommand: commands[0]?.command ?? null,
commands,
shellScript,
rows: [
{
id: "artifact-validation",
label: "Artifact validation",
value: ready ? "ready" : "blocked",
},
{
id: "missing",
label: "Missing artifact evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
{
id: "next-command",
label: "Next command",
value: commands[0]?.command ?? "none",
},
],
};
}
export function parseProjectReleaseReadinessArtifactJson(text) {
return JSON.parse(String(text));
}
export function createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson = "" } = {}) {
try {
const artifact = parseProjectReleaseReadinessArtifactJson(artifactJson);
const validation = createProjectReleaseReadinessArtifactValidation(artifact);
const summaryViewModel = createProjectReleaseReadinessArtifactValidationSummaryViewModel(validation);
const actionPlan = createProjectReleaseReadinessArtifactValidationActionPlan(validation);
return {
apiName: "project-release-readiness-artifact-json-workflow",
workflowVersion: 1,
phase: validation.ready === true ? "ready" : "blocked",
ready: validation.ready === true,
parsed: true,
parseError: null,
artifactApiName: validation.artifactApiName,
validation,
summaryViewModel,
actionPlan,
missing: validation.missing,
rows: [
{
id: "parse",
label: "Artifact JSON parse",
value: "ready",
},
{
id: "validation",
label: "Artifact validation",
value: validation.ready === true ? "ready" : "blocked",
},
{
id: "next-command",
label: "Next command",
value: actionPlan.nextCommand ?? "none",
},
],
};
} catch (error) {
const validation = createProjectReleaseReadinessArtifactValidation({});
const summaryViewModel = createProjectReleaseReadinessArtifactValidationSummaryViewModel(validation);
const actionPlan = createProjectReleaseReadinessArtifactValidationActionPlan(validation);
const parseError = error?.message ?? "artifact JSON parse error";
return {
apiName: "project-release-readiness-artifact-json-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
parsed: false,
parseError,
artifactApiName: null,
validation,
summaryViewModel,
actionPlan,
missing: ["artifact-json", ...validation.missing],
rows: [
{
id: "parse",
label: "Artifact JSON parse",
value: "blocked",
},
{
id: "validation",
label: "Artifact validation",
value: "blocked",
},
{
id: "next-command",
label: "Next command",
value: actionPlan.nextCommand ?? "none",
},
],
};
}
}
export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl = "",
fetchRef = globalThis.fetch,
} = {}) {
if (!artifactUrl || typeof fetchRef !== "function") {
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" });
const missing = [
...(artifactUrl ? [] : ["artifact-url"]),
...(typeof fetchRef === "function" ? [] : ["fetch"]),
...jsonWorkflow.missing,
];
return {
apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
artifactUrl,
fetched: false,
httpStatus: null,
fetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing,
rows: [
{
id: "fetch",
label: "Artifact URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Artifact JSON workflow",
value: "blocked",
},
{
id: "next-command",
label: "Next command",
value: jsonWorkflow.actionPlan.nextCommand ?? "none",
},
],
};
}
try {
const response = await fetchRef(artifactUrl);
const httpStatus = response?.status ?? null;
if (!response?.ok) {
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" });
const fetchError = `artifact URL fetch failed${httpStatus ? `: ${httpStatus}` : ""}`;
return {
apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
artifactUrl,
fetched: false,
httpStatus,
fetchError,
jsonWorkflow,
validation: jsonWorkflow.validation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing],
rows: [
{
id: "fetch",
label: "Artifact URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Artifact JSON workflow",
value: "blocked",
},
{
id: "next-command",
label: "Next command",
value: jsonWorkflow.actionPlan.nextCommand ?? "none",
},
],
};
}
const artifactJson = await response.text();
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson });
return {
apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1,
phase: jsonWorkflow.ready === true ? "ready" : "blocked",
ready: jsonWorkflow.ready === true,
artifactUrl,
fetched: true,
httpStatus,
fetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: jsonWorkflow.missing,
rows: [
{
id: "fetch",
label: "Artifact URL fetch",
value: "ready",
},
{
id: "json-workflow",
label: "Artifact JSON workflow",
value: jsonWorkflow.ready === true ? "ready" : "blocked",
},
{
id: "next-command",
label: "Next command",
value: jsonWorkflow.actionPlan.nextCommand ?? "none",
},
],
};
} catch (error) {
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" });
return {
apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1,
phase: "blocked",
ready: false,
artifactUrl,
fetched: false,
httpStatus: null,
fetchError: error?.message ?? "artifact URL fetch error",
jsonWorkflow,
validation: jsonWorkflow.validation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing],
rows: [
{
id: "fetch",
label: "Artifact URL fetch",
value: "blocked",
},
{
id: "json-workflow",
label: "Artifact JSON workflow",
value: "blocked",
},
{
id: "next-command",
label: "Next command",
value: jsonWorkflow.actionPlan.nextCommand ?? "none",
},
],
};
}
}
export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
workflow = {},
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const statusText = ready ? "Ready" : "Blocked";
const detailText = ready
? "Artifact URL workflow complete"
: (missing.length > 0 ? missing.join(", ") : workflow?.fetchError ?? "artifact URL workflow blocked");
return {
apiName: "project-release-readiness-artifact-url-workflow-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
title: "Project release artifact URL workflow",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows: [
{
id: "artifact-url",
label: "Artifact URL",
value: workflow?.artifactUrl || "missing",
},
{
id: "fetch",
label: "Fetch",
value: workflow?.fetched === true ? `ready${workflow?.httpStatus ? ` (${workflow.httpStatus})` : ""}` : "blocked",
},
{
id: "json-workflow",
label: "JSON workflow",
value: workflow?.jsonWorkflow?.ready === true ? "ready" : "blocked",
},
{
id: "artifact-validation",
label: "Artifact validation",
value: workflow?.validation?.ready === true ? "ready" : "blocked",
},
{
id: "next-command",
label: "Next command",
value: workflow?.actionPlan?.nextCommand ?? "none",
},
{
id: "missing",
label: "Missing workflow evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
export function createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
workflow = {},
) {
const missing = arrayOrEmpty(workflow?.missing);
const ready = workflow?.ready === true;
const validationCommands = arrayOrEmpty(workflow?.actionPlan?.commands);
const inputActions = [
...(missing.includes("artifact-url")
? [{
id: "provide-artifact-url",
label: "Provide release readiness artifact URL",
kind: "input",
command: null,
expectedOutput: null,
}]
: []),
...(missing.includes("fetch")
? [{
id: "provide-fetch",
label: "Provide fetch implementation",
kind: "input",
command: null,
expectedOutput: null,
}]
: []),
...(missing.includes("artifact-fetch")
? [{
id: "verify-artifact-url-fetch",
label: "Verify release readiness artifact URL fetch",
kind: "fetch",
command: null,
expectedOutput: null,
}]
: []),
];
const commandActions = validationCommands.map((command) => ({
id: command.id ?? command.gateId ?? `command-${command.step}`,
label: command.label,
kind: "command",
command: command.command,
expectedOutput: command.expectedOutput ?? null,
}));
const actions = ready ? [] : [...inputActions, ...commandActions].map((action, index) => ({
step: index + 1,
...action,
}));
const commands = actions.filter(({ command }) => typeof command === "string" && command.length > 0);
const shellScript = commands.length > 0
? [
"#!/usr/bin/env bash",
"set -euo pipefail",
"",
...commands.map(({ command }) => command),
"",
].join("\n")
: (ready
? "# Project release readiness artifact URL workflow is already ready.\n"
: "# Project release readiness artifact URL workflow requires non-shell input.\n");
return {
apiName: "project-release-readiness-artifact-url-workflow-action-plan",
planVersion: 1,
phase: ready ? "ready" : "blocked",
ready,
missing,
actionCount: actions.length,
commandCount: commands.length,
nextActionId: actions[0]?.id ?? null,
nextCommand: commands[0]?.command ?? null,
actions,
commands,
shellScript,
rows: actions.map(({ id, label, kind, command, expectedOutput }) => ({
id,
label,
kind,
value: command ?? kind,
expectedOutput,
})),
};
}
export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact);
const releaseGate = objectOrEmpty(artifactObject.releaseGate);
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel);
const gateResultMatrix = objectOrEmpty(artifactObject.gateResultMatrix);
const gateActionPlan = objectOrEmpty(artifactObject.gateActionPlan);
const axisScreenshotArtifactSummary = objectOrEmpty(artifactObject.axisScreenshotArtifactSummary);
const gates = arrayOrEmpty(artifactObject.gates);
const manifestGateIds = arrayOrEmpty(gateManifest.gateIds);
const executionRows = arrayOrEmpty(gateExecutionManifest.rows);
const matrixRows = arrayOrEmpty(gateResultMatrix.rows);
const actionPlanCommands = arrayOrEmpty(gateActionPlan.commands);
const rows = arrayOrEmpty(artifactObject.rows);
const expectedGateIds = createProjectReleaseGateManifest().gateIds;
const gateCountReady = gates.length === REQUIRED_RELEASE_GATES.length;
const gatesPassed = gateCountReady && gates.every(({ passed }) => passed === true);
const manifestReady = gateManifest.apiName === "project-release-gate-manifest"
&& gateManifest.manifestVersion === 1
&& gateManifest.gateCount === REQUIRED_RELEASE_GATES.length
&& manifestGateIds.join(",") === expectedGateIds.join(",");
const matrixReady = gateResultMatrix.apiName === "project-release-gate-result-matrix"
&& gateResultMatrix.matrixVersion === 1
&& gateResultMatrix.ready === true
&& gateResultMatrix.passedCount === REQUIRED_RELEASE_GATES.length
&& gateResultMatrix.unknownCount === 0
&& matrixRows.length === REQUIRED_RELEASE_GATES.length;
const executionManifestReady = gateExecutionManifest.apiName === "project-release-gate-execution-manifest"
&& gateExecutionManifest.manifestVersion === 1
&& gateExecutionManifest.ready === true
&& gateExecutionManifest.passedCount === REQUIRED_RELEASE_GATES.length
&& gateExecutionManifest.unknownCount === 0
&& executionRows.length === REQUIRED_RELEASE_GATES.length
&& executionRows.every(({ command, status }) => typeof command === "string" && status === "passed");
const executionSummaryReady = gateExecutionSummaryViewModel.apiName === "project-release-gate-execution-summary-view-model"
&& gateExecutionSummaryViewModel.viewModelVersion === 1
&& gateExecutionSummaryViewModel.ready === true
&& gateExecutionSummaryViewModel.passedCount === REQUIRED_RELEASE_GATES.length
&& gateExecutionSummaryViewModel.unknownCount === 0
&& gateExecutionSummaryViewModel.nextGateId === null
&& arrayOrEmpty(gateExecutionSummaryViewModel.gateRows).length === REQUIRED_RELEASE_GATES.length;
const actionPlanReady = gateActionPlan.apiName === "project-release-gate-action-plan"
&& gateActionPlan.planVersion === 1
&& gateActionPlan.ready === true
&& gateActionPlan.pendingCount === 0
&& gateActionPlan.nextCommand === null
&& actionPlanCommands.length === 0;
const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts);
const axisScreenshotSummaryReady = !axisScreenshotArtifactSummary.apiName
|| axisScreenshotArtifacts.length === 0
|| (
axisScreenshotArtifactSummary.apiName === "project-release-axis-screenshot-artifact-summary"
&& axisScreenshotArtifactSummary.summaryVersion === 1
&& axisScreenshotArtifactSummary.ready === true
&& axisScreenshotArtifacts.length === 4
&& arrayOrEmpty(axisScreenshotArtifactSummary.missingViewports).length === 0
&& arrayOrEmpty(axisScreenshotArtifactSummary.undersizedViewports).length === 0
&& axisScreenshotArtifacts.every((artifact) =>
artifact.apiName === "real-browser-simulation-axis-screenshot-artifact"
&& artifact.screenshotBytes >= 10000
&& artifact.validatedBy === "browser_real_simulation_page_smoke"
&& (
artifact.fullDiagnosticsPath === null
|| (
artifact.fullDiagnosticsApiName === "real-browser-simulation-diagnostics-artifact"
&& artifact.previewRenderer === "threejs"
&& artifact.threePathPoints > 0
)
)
)
);
const missing = [
...(artifactObject.apiName === PROJECT_RELEASE_READINESS_ARTIFACT_API ? [] : ["apiName"]),
...(artifactObject.reportVersion === PROJECT_RELEASE_READINESS_ARTIFACT_VERSION ? [] : ["reportVersion"]),
...(artifactObject.phase === "ready" ? [] : ["phase"]),
...(artifactObject.ready === true ? [] : ["ready"]),
...(Array.isArray(artifactObject.missing) && artifactObject.missing.length === 0 ? [] : ["missing"]),
...(releaseGate.command === "wasm-port/tests/host/verify_project_release_gate.sh" ? [] : ["releaseGate.command"]),
...(releaseGate.passed === true ? [] : ["releaseGate.passed"]),
...(releaseGate.expectedOutput === "project_release_gate=ok" ? [] : ["releaseGate.expectedOutput"]),
...(simConfigInventory.executed === 28 ? [] : ["simConfigInventory.executed"]),
...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]),
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]),
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]),
...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",")
? []
: ["blockedRuntimeFamilies"]),
...(arrayOrEmpty(artifactObject.promotedBlockedFamilies).length === 0 ? [] : ["promotedBlockedFamilies"]),
...(manifestReady ? [] : ["gateManifest"]),
...(executionManifestReady ? [] : ["gateExecutionManifest"]),
...(executionSummaryReady ? [] : ["gateExecutionSummaryViewModel"]),
...(matrixReady ? [] : ["gateResultMatrix"]),
...(actionPlanReady ? [] : ["gateActionPlan"]),
...(axisScreenshotSummaryReady ? [] : ["axisScreenshotArtifactSummary"]),
...(gateCountReady ? [] : ["gates.length"]),
...(gatesPassed ? [] : ["gates.passed"]),
...(rows.find(({ id, value }) => id === "project-release-gate" && value === "passed")
? []
: ["rows.project-release-gate"]),
];
return {
apiName: "project-release-readiness-artifact-validation",
validationVersion: 1,
phase: missing.length === 0 ? "ready" : "blocked",
ready: missing.length === 0,
missing,
artifactApiName: artifactObject.apiName ?? null,
artifactVersion: artifactObject.reportVersion ?? null,
gateCount: gates.length,
expectedGateCount: REQUIRED_RELEASE_GATES.length,
expectedGateIds,
gateManifestReady: manifestReady,
gateExecutionManifestReady: executionManifestReady,
gateExecutionSummaryReady: executionSummaryReady,
gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady,
axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady,
axisScreenshotArtifactCount: axisScreenshotArtifacts.length,
blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
rows: [
{
id: "artifact",
label: "Readiness artifact",
value: artifactObject.apiName ?? "missing",
},
{
id: "project-release-gate",
label: "Project release gate",
value: releaseGate.passed === true ? "passed" : "missing",
},
{
id: "gate-manifest",
label: "Gate manifest",
value: manifestReady ? "ready" : "missing",
},
{
id: "gate-execution-manifest",
label: "Gate execution manifest",
value: executionManifestReady ? "ready" : "missing",
},
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: executionSummaryReady ? "ready" : "missing",
},
{
id: "gate-result-matrix",
label: "Gate result matrix",
value: matrixReady ? "ready" : "missing",
},
{
id: "gate-action-plan",
label: "Gate action plan",
value: actionPlanReady ? "ready" : "missing",
},
{
id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts",
value: axisScreenshotArtifacts.length > 0
? `${axisScreenshotArtifacts.length} captured`
: "not captured",
},
{
id: "validation",
label: "Artifact validation",
value: missing.length === 0 ? "ready" : missing.join(", "),
},
],
};
}