新增 release gate execution summary API

This commit is contained in:
2026-06-16 17:39:47 +08:00
parent 35bc25714d
commit 19eabdf06e
13 changed files with 411 additions and 14 deletions

View File

@@ -416,3 +416,59 @@ project_release_gate=ok
- 新增内容只验证 host OPFS/session persistence、WASM staging、SDK/browser workflow 和 release gate - 新增内容只验证 host OPFS/session persistence、WASM staging、SDK/browser workflow 和 release gate
evidence evidence
- blocked runtime families 仍不 promotion。 - blocked runtime families 仍不 promotion。
五十九、2026-06-16 继续执行记录release gate execution summary view-model SDK API
本批继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”
推进,选择 release gate execution evidence 的 UI/CI 展示能力。
本批新增实质能力:
- 新增 SDK API`createProjectReleaseGateExecutionSummaryViewModel()`
- 该 API 复用 `createProjectReleaseGateExecutionManifest()` 输出,生成稳定 dashboard/view-model
- `statusLine`
- `passedCount`、`unknownCount`、`gateCount`
- `nextGateId`
- `evidenceCounts`
- summary rows
- normalized per-gate `gateRows`。
- `createProjectReleaseReadinessReport()` 现在嵌入 `gateExecutionSummaryViewModel`
- `createProjectReleaseReadinessArtifactValidation()` 现在要求 artifact 包含 ready
`gateExecutionSummaryViewModel`,并暴露 `gateExecutionSummaryReady`
- release artifact validation summary/render rows 增加 `gate-execution-summary` evidence 行;
- SDK README 与 project release handoff docs 记录该 summary view-model
- ready fixture 与 host artifact writer/validator 同步新 artifact schema。
本批新增/更新可执行验证:
```bash
wasm-port/tests/sdk/node/verify_project_release_gate_manifest.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/docs/node/verify_project_release_handoff_docs.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
wasm-port/tests/host/verify_project_release_gate.sh
```
关键通过输出:
```text
project_release_gate_manifest_node_smoke=ok
sdk_surface_node_smoke=ok
project_release_handoff_docs_node_smoke=ok
ui_node_smokes=ok
browser_release_artifact_url_workflow_smoke=ok
browser_ini_shell_integration_workflow_smoke=ok
browser_opfs_session_workflow_smoke=ok
project_release_readiness_artifact_node_smoke=ok
project_release_gate=ok
```
本批仍保持 LinuxCNC 语义边界:
- 未修改 vendored LinuxCNC 源码;
- 未在 JS/browser 中实现 G-code、tool、parameter、planner、kinematics、remap 或 canonical motion
语义;
- 新增内容只包装 release gate evidence 与 artifact validation display rows
- blocked runtime families 仍不 promotion。

View File

@@ -41,7 +41,10 @@ continuation records are in `../text15.txt`.
`createProjectReleaseGateActionPlan()` to derive pending gate commands, the `createProjectReleaseGateActionPlan()` to derive pending gate commands, the
next command, display rows, and a shell script from that matrix, next command, display rows, and a shell script from that matrix,
`createProjectReleaseGateExecutionManifest()` to package each gate command, `createProjectReleaseGateExecutionManifest()` to package each gate command,
expected output, observed-output evidence, and passed/unknown status, and expected output, observed-output evidence, and passed/unknown status,
`createProjectReleaseGateExecutionSummaryViewModel()` to expose dashboard
status text, evidence counts, next missing gate, and normalized per-gate rows,
and
`createProjectReleaseReadinessReport()` for a machine-readable release `createProjectReleaseReadinessReport()` for a machine-readable release
readiness report covering the same manifest, execution manifest, result readiness report covering the same manifest, execution manifest, result
matrix, action plan, sim-config inventory baseline, and blocked runtime matrix, action plan, sim-config inventory baseline, and blocked runtime

View File

@@ -55,6 +55,7 @@ import {
createMachineSessionSnapshotPayload, createMachineSessionSnapshotPayload,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix, createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow, createProjectReleaseReadinessArtifactJsonWorkflow,
@@ -214,12 +215,15 @@ with caller-provided explicit results and observed output evidence. Each row
includes the command, expected output, matched observed output when present, includes the command, expected output, matched observed output when present,
evidence kind, and `passed`/`unknown` status so CI or browser dashboards can evidence kind, and `passed`/`unknown` status so CI or browser dashboards can
render release-gate execution state without scraping shell scripts. render release-gate execution state without scraping shell scripts.
`createProjectReleaseGateExecutionSummaryViewModel()` turns that execution
manifest into stable dashboard status text, evidence counts, next missing gate,
summary rows, and normalized per-gate rows.
`createProjectReleaseReadinessReport()` returns a machine-readable release `createProjectReleaseReadinessReport()` returns a machine-readable release
readiness report that embeds the same manifest, result matrix, and action plan readiness report that embeds the same manifest, result matrix, action plan,
beside the gate execution manifest, sim-config inventory baseline, and blocked gate execution manifest, gate execution summary view-model, sim-config
runtime families. It is evidence driven: the default report is `ready: false` inventory baseline, and blocked runtime families. It is evidence driven: the
until the caller supplies observed gate output such as `project_release_gate=ok` default report is `ready: false` until the caller supplies observed gate output
or explicit gate results. such as `project_release_gate=ok` or explicit gate results.
`createProjectReleaseReadinessSummaryViewModel()` turns the report into stable `createProjectReleaseReadinessSummaryViewModel()` turns the report into stable
status text, a status line, and display rows for dashboards without executing status text, a status line, and display rows for dashboards without executing
any gate. any gate.
@@ -238,8 +242,9 @@ it with `createProjectReleaseReadinessArtifactValidation()` without copying the
host test assertions. The validation result includes `expectedGateCount` and host test assertions. The validation result includes `expectedGateCount` and
`expectedGateIds` so external callers can compare an artifact against the `expectedGateIds` so external callers can compare an artifact against the
current manifest, plus `gateManifestReady`, `gateExecutionManifestReady`, current manifest, plus `gateManifestReady`, `gateExecutionManifestReady`,
`gateResultMatrixReady`, and `gateActionPlanReady` so stale artifacts that omit `gateExecutionSummaryReady`, `gateResultMatrixReady`, and
the embedded gate evidence chain fail the same executable gate. `gateActionPlanReady` so stale artifacts that omit the embedded gate evidence
chain fail the same executable gate.
`createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns that `createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns that
validation result into stable status text and display rows for dashboards or validation result into stable status text and display rows for dashboards or
browser shells without reinterpreting the artifact fields. browser shells without reinterpreting the artifact fields.

View File

@@ -3,6 +3,7 @@ export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export { export {
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix, createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow, createProjectReleaseReadinessArtifactJsonWorkflow,

View File

@@ -176,6 +176,81 @@ export function createProjectReleaseGateExecutionManifest({
}; };
} }
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({ export function createProjectReleaseGateResultMatrix({
gateResults = {}, gateResults = {},
observedOutputs = [], observedOutputs = [],
@@ -274,6 +349,9 @@ export function createProjectReleaseReadinessReport({
observedOutputs, observedOutputs,
manifest: gateManifest, manifest: gateManifest,
}); });
const gateExecutionSummaryViewModel = createProjectReleaseGateExecutionSummaryViewModel(
gateExecutionManifest,
);
const gateResultMatrix = createProjectReleaseGateResultMatrix({ const gateResultMatrix = createProjectReleaseGateResultMatrix({
gateResults, gateResults,
observedOutputs, observedOutputs,
@@ -318,6 +396,7 @@ export function createProjectReleaseReadinessReport({
promotedBlockedFamilies, promotedBlockedFamilies,
gateManifest, gateManifest,
gateExecutionManifest, gateExecutionManifest,
gateExecutionSummaryViewModel,
gateResultMatrix, gateResultMatrix,
gateActionPlan, gateActionPlan,
gates: gateRows, gates: gateRows,
@@ -432,6 +511,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
label: "Gate execution manifest", label: "Gate execution manifest",
value: validation?.gateExecutionManifestReady === true ? "ready" : "missing", value: validation?.gateExecutionManifestReady === true ? "ready" : "missing",
}, },
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: validation?.gateExecutionSummaryReady === true ? "ready" : "missing",
},
{ {
id: "gate-result-matrix", id: "gate-result-matrix",
label: "Gate result matrix", label: "Gate result matrix",
@@ -889,6 +973,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory); const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const gateManifest = objectOrEmpty(artifactObject.gateManifest); const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest); const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel);
const gateResultMatrix = objectOrEmpty(artifactObject.gateResultMatrix); const gateResultMatrix = objectOrEmpty(artifactObject.gateResultMatrix);
const gateActionPlan = objectOrEmpty(artifactObject.gateActionPlan); const gateActionPlan = objectOrEmpty(artifactObject.gateActionPlan);
const gates = arrayOrEmpty(artifactObject.gates); const gates = arrayOrEmpty(artifactObject.gates);
@@ -917,6 +1002,13 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
&& gateExecutionManifest.unknownCount === 0 && gateExecutionManifest.unknownCount === 0
&& executionRows.length === REQUIRED_RELEASE_GATES.length && executionRows.length === REQUIRED_RELEASE_GATES.length
&& executionRows.every(({ command, status }) => typeof command === "string" && status === "passed"); && 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" const actionPlanReady = gateActionPlan.apiName === "project-release-gate-action-plan"
&& gateActionPlan.planVersion === 1 && gateActionPlan.planVersion === 1
&& gateActionPlan.ready === true && gateActionPlan.ready === true
@@ -942,6 +1034,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(arrayOrEmpty(artifactObject.promotedBlockedFamilies).length === 0 ? [] : ["promotedBlockedFamilies"]), ...(arrayOrEmpty(artifactObject.promotedBlockedFamilies).length === 0 ? [] : ["promotedBlockedFamilies"]),
...(manifestReady ? [] : ["gateManifest"]), ...(manifestReady ? [] : ["gateManifest"]),
...(executionManifestReady ? [] : ["gateExecutionManifest"]), ...(executionManifestReady ? [] : ["gateExecutionManifest"]),
...(executionSummaryReady ? [] : ["gateExecutionSummaryViewModel"]),
...(matrixReady ? [] : ["gateResultMatrix"]), ...(matrixReady ? [] : ["gateResultMatrix"]),
...(actionPlanReady ? [] : ["gateActionPlan"]), ...(actionPlanReady ? [] : ["gateActionPlan"]),
...(gateCountReady ? [] : ["gates.length"]), ...(gateCountReady ? [] : ["gates.length"]),
@@ -964,6 +1057,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
expectedGateIds, expectedGateIds,
gateManifestReady: manifestReady, gateManifestReady: manifestReady,
gateExecutionManifestReady: executionManifestReady, gateExecutionManifestReady: executionManifestReady,
gateExecutionSummaryReady: executionSummaryReady,
gateResultMatrixReady: matrixReady, gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady, gateActionPlanReady: actionPlanReady,
blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies), blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
@@ -988,6 +1082,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
label: "Gate execution manifest", label: "Gate execution manifest",
value: executionManifestReady ? "ready" : "missing", value: executionManifestReady ? "ready" : "missing",
}, },
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: executionSummaryReady ? "ready" : "missing",
},
{ {
id: "gate-result-matrix", id: "gate-result-matrix",
label: "Gate result matrix", label: "Gate result matrix",

View File

@@ -469,7 +469,7 @@ TOOL_TABLE = browser-shell-tool.tbl
); );
assertEqual( assertEqual(
workflowOverviewReleaseArtifactRenderResult.rowCount, workflowOverviewReleaseArtifactRenderResult.rowCount,
7, 8,
"shell workflow overview release artifact render rows", "shell workflow overview release artifact render rows",
); );
assertEqual( assertEqual(
@@ -477,6 +477,11 @@ TOOL_TABLE = browser-shell-tool.tbl
true, true,
"shell workflow overview release artifact execution manifest row", "shell workflow overview release artifact execution manifest row",
); );
assertEqual(
workflowOverviewReleaseArtifactRenderResult.rowIds.includes("gate-execution-summary"),
true,
"shell workflow overview release artifact execution summary row",
);
assertEqual( assertEqual(
workflowOverviewReleaseArtifactMountResult.ready, workflowOverviewReleaseArtifactMountResult.ready,
true, true,

View File

@@ -262,14 +262,19 @@
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(releaseArtifactUrlWorkflow); .getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(releaseArtifactUrlWorkflow);
assertEqual(releaseArtifactDomReadinessReady.ready, true, "workflow overview release artifact DOM readiness"); assertEqual(releaseArtifactDomReadinessReady.ready, true, "workflow overview release artifact DOM readiness");
assertEqual(releaseArtifactRenderResult.rendered, true, "workflow overview release artifact render result"); assertEqual(releaseArtifactRenderResult.rendered, true, "workflow overview release artifact render result");
assertEqual(releaseArtifactRenderResult.rowCount, 7, "workflow overview release artifact render row count"); assertEqual(releaseArtifactRenderResult.rowCount, 8, "workflow overview release artifact render row count");
assertEqual( assertEqual(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
true, true,
"workflow overview release artifact execution manifest row", "workflow overview release artifact execution manifest row",
); );
assertEqual(
releaseArtifactRenderResult.rowIds.includes("gate-execution-summary"),
true,
"workflow overview release artifact execution summary row",
);
assertEqual(releaseArtifactMountResult.ready, true, "workflow overview release artifact mount readiness"); assertEqual(releaseArtifactMountResult.ready, true, "workflow overview release artifact mount readiness");
assertEqual(releaseArtifactMountResult.renderResult.rowCount, 7, "workflow overview release artifact mount rows"); assertEqual(releaseArtifactMountResult.renderResult.rowCount, 8, "workflow overview release artifact mount rows");
assertEqual(releaseArtifactUrlWorkflow.ready, true, "workflow overview release artifact URL workflow readiness"); assertEqual(releaseArtifactUrlWorkflow.ready, true, "workflow overview release artifact URL workflow readiness");
assertEqual(releaseArtifactUrlWorkflow.httpStatus, 200, "workflow overview release artifact URL workflow status"); assertEqual(releaseArtifactUrlWorkflow.httpStatus, 200, "workflow overview release artifact URL workflow status");
assertEqual( assertEqual(

View File

@@ -35,6 +35,7 @@ for (const phrase of [
"createProjectReleaseGateResultMatrix()", "createProjectReleaseGateResultMatrix()",
"createProjectReleaseGateActionPlan()", "createProjectReleaseGateActionPlan()",
"createProjectReleaseGateExecutionManifest()", "createProjectReleaseGateExecutionManifest()",
"createProjectReleaseGateExecutionSummaryViewModel()",
"createProjectReleaseReadinessReport()", "createProjectReleaseReadinessReport()",
"createProjectReleaseReadinessSummaryViewModel()", "createProjectReleaseReadinessSummaryViewModel()",
"parseProjectReleaseReadinessArtifactJson()", "parseProjectReleaseReadinessArtifactJson()",
@@ -118,6 +119,7 @@ for (const phrase of [
"createProjectReleaseGateManifest()", "createProjectReleaseGateManifest()",
"createProjectReleaseGateResultMatrix()", "createProjectReleaseGateResultMatrix()",
"createProjectReleaseGateActionPlan()", "createProjectReleaseGateActionPlan()",
"createProjectReleaseGateExecutionSummaryViewModel()",
"createProjectReleaseReadinessReport()", "createProjectReleaseReadinessReport()",
"createProjectReleaseReadinessSummaryViewModel()", "createProjectReleaseReadinessSummaryViewModel()",
"parseProjectReleaseReadinessArtifactJson()", "parseProjectReleaseReadinessArtifactJson()",
@@ -127,6 +129,7 @@ for (const phrase of [
"createProjectReleaseReadinessArtifactValidationSummaryViewModel()", "createProjectReleaseReadinessArtifactValidationSummaryViewModel()",
"gateManifestReady", "gateManifestReady",
"gateExecutionManifestReady", "gateExecutionManifestReady",
"gateExecutionSummaryReady",
"gateResultMatrixReady", "gateResultMatrixReady",
"gateActionPlanReady", "gateActionPlanReady",
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()", "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()",

View File

@@ -327,6 +327,169 @@
} }
] ]
}, },
"gateExecutionSummaryViewModel": {
"apiName": "project-release-gate-execution-summary-view-model",
"viewModelVersion": 1,
"phase": "ready",
"ready": true,
"title": "Project release gate execution",
"statusText": "Ready",
"detailText": "All release gate evidence present",
"statusLine": "Ready: All release gate evidence present",
"gateCount": 11,
"passedCount": 11,
"unknownCount": 0,
"missingGateIds": [],
"nextGateId": null,
"evidenceCounts": {
"explicit-result": 1,
"expected-output": 10
},
"rows": [
{
"id": "gate-results",
"label": "Release gate evidence",
"value": "11/11 passed"
},
{
"id": "unknown-gates",
"label": "Unknown gates",
"value": "0"
},
{
"id": "next-gate",
"label": "Next gate",
"value": "none"
},
{
"id": "expected-output-evidence",
"label": "Expected-output evidence",
"value": "10"
},
{
"id": "explicit-result-evidence",
"label": "Explicit-result evidence",
"value": "1"
},
{
"id": "missing",
"label": "Missing gate evidence",
"value": "none"
}
],
"gateRows": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null,
"observedOutput": null,
"evidence": "explicit-result",
"status": "passed",
"passed": true
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date",
"observedOutput": "vendor sync up to date",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "standalone CNC semantics guard complete",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "interp_wasm_node_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "browser_opfs_session_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"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",
"observedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok",
"observedOutput": "ui_node_smokes=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok",
"observedOutput": "host_wasm_opfs_browser_smokes=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok",
"observedOutput": "project_release_gate=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
}
]
},
"gateResultMatrix": { "gateResultMatrix": {
"apiName": "project-release-gate-result-matrix", "apiName": "project-release-gate-result-matrix",
"matrixVersion": 1, "matrixVersion": 1,

View File

@@ -24,6 +24,7 @@ assert.equal(validation.gateCount, 11);
assert.equal(validation.expectedGateCount, 11); assert.equal(validation.expectedGateCount, 11);
assert.equal(validation.gateManifestReady, true); assert.equal(validation.gateManifestReady, true);
assert.equal(validation.gateExecutionManifestReady, true); assert.equal(validation.gateExecutionManifestReady, true);
assert.equal(validation.gateExecutionSummaryReady, true);
assert.equal(validation.gateResultMatrixReady, true); assert.equal(validation.gateResultMatrixReady, true);
assert.equal(validation.gateActionPlanReady, true); assert.equal(validation.gateActionPlanReady, true);
assert.deepEqual(validation.expectedGateIds, [ assert.deepEqual(validation.expectedGateIds, [
@@ -42,6 +43,8 @@ assert.deepEqual(validation.expectedGateIds, [
assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest"); assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest");
assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(artifact.gateExecutionManifest.passedCount, 11); assert.equal(artifact.gateExecutionManifest.passedCount, 11);
assert.equal(artifact.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(artifact.gateExecutionSummaryViewModel.rows[0].value, "11/11 passed");
assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix"); assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix");
assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan");
assert.equal(artifact.gateActionPlan.pendingCount, 0); assert.equal(artifact.gateActionPlan.pendingCount, 0);

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import { import {
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix, createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow, createProjectReleaseReadinessArtifactJsonWorkflow,
@@ -126,6 +127,37 @@ assert.equal(
partialExecutionManifest.rows.find(({ id }) => id === "standalone-cnc-semantics")?.observedOutput, partialExecutionManifest.rows.find(({ id }) => id === "standalone-cnc-semantics")?.observedOutput,
null, null,
); );
const partialExecutionSummary = createProjectReleaseGateExecutionSummaryViewModel(partialExecutionManifest);
assert.equal(partialExecutionSummary.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(partialExecutionSummary.viewModelVersion, 1);
assert.equal(partialExecutionSummary.phase, "waiting");
assert.equal(partialExecutionSummary.ready, false);
assert.equal(partialExecutionSummary.statusLine, "Waiting: standalone-cnc-semantics, interp-wasm, sim-config-inventory, ini-panel-browser, opfs-session-browser, ui-node-smokes, host-smokes, project-release-gate");
assert.equal(partialExecutionSummary.gateCount, 11);
assert.equal(partialExecutionSummary.passedCount, 3);
assert.equal(partialExecutionSummary.unknownCount, 8);
assert.equal(partialExecutionSummary.nextGateId, "standalone-cnc-semantics");
assert.deepEqual(partialExecutionSummary.evidenceCounts, {
"explicit-result": 1,
"expected-output": 2,
missing: 8,
});
assert.deepEqual(
partialExecutionSummary.rows.map(({ id, value }) => [id, value]),
[
["gate-results", "3/11 passed"],
["unknown-gates", "8"],
["next-gate", "standalone-cnc-semantics"],
["expected-output-evidence", "2"],
["explicit-result-evidence", "1"],
["missing", "standalone-cnc-semantics, interp-wasm, sim-config-inventory, ini-panel-browser, opfs-session-browser, ui-node-smokes, host-smokes, project-release-gate"],
],
);
assert.equal(partialExecutionSummary.gateRows.length, manifest.gateCount);
assert.equal(
partialExecutionSummary.gateRows.find(({ id }) => id === "vendor-sync")?.evidence,
"expected-output",
);
const partialActionPlan = createProjectReleaseGateActionPlan({ gateResultMatrix: partialMatrix }); const partialActionPlan = createProjectReleaseGateActionPlan({ gateResultMatrix: partialMatrix });
assert.equal(partialActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(partialActionPlan.apiName, "project-release-gate-action-plan");
@@ -166,6 +198,10 @@ assert.deepEqual(report.gateManifest, manifest);
assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(report.gateExecutionManifest.ready, true); assert.equal(report.gateExecutionManifest.ready, true);
assert.equal(report.gateExecutionManifest.passedCount, manifest.gateCount); assert.equal(report.gateExecutionManifest.passedCount, manifest.gateCount);
assert.equal(report.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(report.gateExecutionSummaryViewModel.statusLine, "Ready: All release gate evidence present");
assert.equal(report.gateExecutionSummaryViewModel.rows[0].value, "11/11 passed");
assert.equal(report.gateExecutionSummaryViewModel.nextGateId, null);
assert.equal(report.gateResultMatrix.ready, true); assert.equal(report.gateResultMatrix.ready, true);
assert.equal(report.gateResultMatrix.passedCount, manifest.gateCount); assert.equal(report.gateResultMatrix.passedCount, manifest.gateCount);
assert.equal(report.gateActionPlan.ready, true); assert.equal(report.gateActionPlan.ready, true);
@@ -256,6 +292,7 @@ assert.deepEqual(
["gate-count", "11/11"], ["gate-count", "11/11"],
["gate-manifest", "ready"], ["gate-manifest", "ready"],
["gate-execution-manifest", "ready"], ["gate-execution-manifest", "ready"],
["gate-execution-summary", "ready"],
["gate-result-matrix", "ready"], ["gate-result-matrix", "ready"],
["gate-action-plan", "ready"], ["gate-action-plan", "ready"],
["missing", "none"], ["missing", "none"],
@@ -383,6 +420,10 @@ assert.equal(
staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-manifest")?.value, staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-manifest")?.value,
"missing", "missing",
); );
assert.equal(
staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-summary")?.value,
"missing",
);
assert.equal(staleArtifactValidationActionPlan.phase, "blocked"); assert.equal(staleArtifactValidationActionPlan.phase, "blocked");
assert.equal(staleArtifactValidationActionPlan.ready, false); assert.equal(staleArtifactValidationActionPlan.ready, false);
assert.equal(staleArtifactValidationActionPlan.missingCount, staleArtifactValidation.missing.length); assert.equal(staleArtifactValidationActionPlan.missingCount, staleArtifactValidation.missing.length);

View File

@@ -36,6 +36,7 @@ import {
createMachineSessionPersistenceSummary, createMachineSessionPersistenceSummary,
createProjectReleaseGateActionPlan, createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest, createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest, createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix, createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow, createProjectReleaseReadinessArtifactJsonWorkflow,
@@ -98,6 +99,7 @@ const requiredExports = [
["createLinuxCncInterpSdk", createLinuxCncInterpSdk], ["createLinuxCncInterpSdk", createLinuxCncInterpSdk],
["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan], ["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan],
["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest], ["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest],
["createProjectReleaseGateExecutionSummaryViewModel", createProjectReleaseGateExecutionSummaryViewModel],
["createProjectReleaseGateManifest", createProjectReleaseGateManifest], ["createProjectReleaseGateManifest", createProjectReleaseGateManifest],
["createProjectReleaseGateResultMatrix", createProjectReleaseGateResultMatrix], ["createProjectReleaseGateResultMatrix", createProjectReleaseGateResultMatrix],
["createProjectReleaseReadinessArtifactJsonWorkflow", createProjectReleaseReadinessArtifactJsonWorkflow], ["createProjectReleaseReadinessArtifactJsonWorkflow", createProjectReleaseReadinessArtifactJsonWorkflow],
@@ -294,6 +296,7 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
expectedGateIds: createProjectReleaseGateManifest().gateIds, expectedGateIds: createProjectReleaseGateManifest().gateIds,
gateManifestReady: true, gateManifestReady: true,
gateExecutionManifestReady: true, gateExecutionManifestReady: true,
gateExecutionSummaryReady: true,
gateResultMatrixReady: true, gateResultMatrixReady: true,
gateActionPlanReady: true, gateActionPlanReady: true,
blockedRuntimeFamilies: [ blockedRuntimeFamilies: [
@@ -322,6 +325,11 @@ assert.deepEqual(releaseReadinessArtifactValidation, {
label: "Gate execution manifest", label: "Gate execution manifest",
value: "ready", value: "ready",
}, },
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: "ready",
},
{ {
id: "gate-result-matrix", id: "gate-result-matrix",
label: "Gate result matrix", label: "Gate result matrix",
@@ -426,6 +434,7 @@ assert.deepEqual(
"blockedRuntimeFamilies", "blockedRuntimeFamilies",
"gateManifest", "gateManifest",
"gateExecutionManifest", "gateExecutionManifest",
"gateExecutionSummaryViewModel",
"gateResultMatrix", "gateResultMatrix",
"gateActionPlan", "gateActionPlan",
"gates.length", "gates.length",

View File

@@ -1366,18 +1366,22 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
rowsNode: releaseArtifactRowsNode, rowsNode: releaseArtifactRowsNode,
}); });
assert.equal(releaseArtifactRenderResult.rendered, true); assert.equal(releaseArtifactRenderResult.rendered, true);
assert.equal(releaseArtifactRenderResult.rowCount, 7); assert.equal(releaseArtifactRenderResult.rowCount, 8);
assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact"); assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
assert.equal( assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
true, true,
); );
assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-summary"),
true,
);
assert.equal( assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-action-plan"), releaseArtifactRenderResult.rowIds.includes("gate-action-plan"),
true, true,
); );
assert.equal(releaseArtifactMountResult.ready, true); assert.equal(releaseArtifactMountResult.ready, true);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 7); assert.equal(releaseArtifactMountResult.renderResult.rowCount, 8);
assert.deepEqual( assert.deepEqual(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing, validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
["artifact-json"], ["artifact-json"],
@@ -1418,7 +1422,7 @@ assert.equal(
); );
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons"); assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true); assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 7); assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 8);
const releaseArtifactUrlWorkflowSummary = const releaseArtifactUrlWorkflowSummary =
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel( createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseArtifactUrlWorkflow, releaseArtifactUrlWorkflow,