diff --git a/text15.txt b/text15.txt index 116b300..aeeba41 100644 --- a/text15.txt +++ b/text15.txt @@ -299,3 +299,58 @@ wasm-port/tests/native/verify_sim_configs.sh - 增加 OPFS/session persistence 的独立 SDK/browser gate,减少只依赖聚合 smoke 的排障成本; - 若真实 LinuxCNC host runtime 可用,再按 native -> Node/WASM -> browser/host 顺序处理 blocked runtime family promotion。 + +五十七、2026-06-16 继续执行记录:project release gate execution manifest SDK API + +本批继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力” +推进,选择 `text15.txt` 下一步建议中的 machine-readable release gate execution manifest/artifact。 + +本批新增实质能力: + +- 新增 SDK API:`createProjectReleaseGateExecutionManifest()`; +- 该 API 复用 release gate manifest,按 gate 输出每个 gate 的 `command`、`expectedOutput`、 + `observedOutput`、`evidence`、`passed/unknown` 状态、缺失 gate ID 与 pass/unknown 计数; +- `createProjectReleaseReadinessReport()` 现在嵌入 `gateExecutionManifest`,让外部 CI/browser + dashboard 可直接展示 release gate 执行证据,而不需要解析 shell 脚本; +- `createProjectReleaseReadinessArtifactValidation()` 现在要求 ready artifact 包含 + `gateExecutionManifest`,并暴露 `gateExecutionManifestReady`; +- workflow overview release artifact validation summary/render rows 增加 + `gate-execution-manifest` evidence 行; +- 更新 `tests/fixtures/project-release-readiness-ready.json`,使 browser artifact URL workflow 使用 + 新 schema; +- 更新 SDK README 与 `docs/project-release-handoff.md`,说明 execution manifest、artifact validation + evidence chain 与外部 CI/browser dashboard 用法。 + +本批新增/更新可执行验证: + +```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 +SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh +wasm-port/tests/ui/node/verify_ui_node_smokes.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 +browser_release_artifact_url_workflow_smoke=ok +ui_node_smokes=ok +browser_ini_shell_integration_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 packaging、SDK/API surface、browser/UI artifact validation + render rows、docs 和测试 gate; +- blocked runtime families 仍不 promotion。 diff --git a/wasm-port/docs/project-release-handoff.md b/wasm-port/docs/project-release-handoff.md index f66328d..88a3cc6 100644 --- a/wasm-port/docs/project-release-handoff.md +++ b/wasm-port/docs/project-release-handoff.md @@ -19,8 +19,8 @@ Use these documents as the current handoff set: reuse and non-drift rules. - `../PROJECT_COMPLETION_TRACKER.md` for project-level acceptance tracking. -Old turn logs `text1.txt` through `text13.txt` are historical context. Current -continuation records are in `../text14.txt`. +Old turn logs `text1.txt` through `text14.txt` are historical context. Current +continuation records are in `../text15.txt`. ## Supported current workflows @@ -39,10 +39,13 @@ continuation records are in `../text14.txt`. expected smoke outputs, `createProjectReleaseGateResultMatrix()` to map observed outputs onto passed/unknown gate rows, `createProjectReleaseGateActionPlan()` to derive pending gate commands, the - next command, display rows, and a shell script from that matrix, and + next command, display rows, and a shell script from that matrix, + `createProjectReleaseGateExecutionManifest()` to package each gate command, + expected output, observed-output evidence, and passed/unknown status, and `createProjectReleaseReadinessReport()` for a machine-readable release - readiness report covering the same manifest, result matrix, action plan, - sim-config inventory baseline, and blocked runtime families. + readiness report covering the same manifest, execution manifest, result + matrix, action plan, sim-config inventory baseline, and blocked runtime + families. `createProjectReleaseReadinessSummaryViewModel()` turns that report into stable status text and rows for dashboards. The report helper is evidence driven and only reports `ready: true` when the caller supplies release gate @@ -52,8 +55,9 @@ continuation records are in `../text14.txt`. External tools can load that artifact with `parseProjectReleaseReadinessArtifactJson()` and validate it with `createProjectReleaseReadinessArtifactValidation()`. Artifact validation - requires the embedded gate manifest, result matrix, and action plan to be - ready, so stale release JSON cannot pass the executable artifact gate. + requires the embedded gate manifest, execution manifest, result matrix, and + action plan to be ready, so stale release JSON cannot pass the executable + artifact gate. `createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns the validation into stable dashboard rows without reinterpreting artifact fields. `createProjectReleaseReadinessArtifactValidationActionPlan()` turns a blocked diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 130eeb4..0b1f558 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -54,6 +54,7 @@ import { createMachineSessionPersistenceSummary, createMachineSessionSnapshotPayload, createProjectReleaseGateActionPlan, + createProjectReleaseGateExecutionManifest, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactJsonWorkflow, @@ -208,12 +209,17 @@ and explicit gate results onto that manifest as stable `passed`/`unknown` rows. `createProjectReleaseGateActionPlan()` turns that matrix into pending gate commands, the next gate command, stable display rows, and a shell script that external CI can execute or show without duplicating the manifest order. +`createProjectReleaseGateExecutionManifest()` packages the same gate manifest +with caller-provided explicit results and observed output evidence. Each row +includes the command, expected output, matched observed output when present, +evidence kind, and `passed`/`unknown` status so CI or browser dashboards can +render release-gate execution state without scraping shell scripts. `createProjectReleaseReadinessReport()` returns a machine-readable release readiness report that embeds the same manifest, result matrix, and action plan -beside the sim-config inventory baseline and blocked runtime families. It is -evidence driven: the default report is `ready: false` until the caller supplies -observed gate output such as `project_release_gate=ok` or explicit gate -results. +beside the gate execution manifest, sim-config inventory baseline, and blocked +runtime families. It is evidence driven: the default report is `ready: false` +until the caller supplies observed gate output such as `project_release_gate=ok` +or explicit gate results. `createProjectReleaseReadinessSummaryViewModel()` turns the report into stable status text, a status line, and display rows for dashboards without executing any gate. @@ -231,9 +237,9 @@ load that JSON with `parseProjectReleaseReadinessArtifactJson()` and validate it with `createProjectReleaseReadinessArtifactValidation()` without copying the host test assertions. The validation result includes `expectedGateCount` and `expectedGateIds` so external callers can compare an artifact against the -current manifest, plus `gateManifestReady`, `gateResultMatrixReady`, and -`gateActionPlanReady` so stale artifacts that omit the embedded gate evidence -chain fail the same executable gate. +current manifest, plus `gateManifestReady`, `gateExecutionManifestReady`, +`gateResultMatrixReady`, and `gateActionPlanReady` so stale artifacts that omit +the embedded gate evidence chain fail the same executable gate. `createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns that validation result into stable status text and display rows for dashboards or browser shells without reinterpreting the artifact fields. diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index 809839f..ff5192e 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -2,6 +2,7 @@ export { createLinuxCncIniSdk } from "./linuxcnc-ini.js"; export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js"; export { createProjectReleaseGateActionPlan, + createProjectReleaseGateExecutionManifest, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactJsonWorkflow, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index f1ed105..8716165 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -98,6 +98,16 @@ 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 []; +} + export function createProjectReleaseGateManifest() { return { apiName: "project-release-gate-manifest", @@ -113,6 +123,53 @@ export function createProjectReleaseGateManifest() { }; } +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 createProjectReleaseGateResultMatrix({ gateResults = {}, observedOutputs = [], @@ -206,6 +263,11 @@ export function createProjectReleaseReadinessReport({ promotedRuntimeFamilies = [], } = {}) { const gateManifest = createProjectReleaseGateManifest(); + const gateExecutionManifest = createProjectReleaseGateExecutionManifest({ + gateResults, + observedOutputs, + manifest: gateManifest, + }); const gateResultMatrix = createProjectReleaseGateResultMatrix({ gateResults, observedOutputs, @@ -249,6 +311,7 @@ export function createProjectReleaseReadinessReport({ promotedRuntimeFamilies: promotedFamilies, promotedBlockedFamilies, gateManifest, + gateExecutionManifest, gateResultMatrix, gateActionPlan, gates: gateRows, @@ -358,6 +421,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel( 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-result-matrix", label: "Gate result matrix", @@ -814,10 +882,12 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { const releaseGate = objectOrEmpty(artifactObject.releaseGate); const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory); const gateManifest = objectOrEmpty(artifactObject.gateManifest); + const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest); const gateResultMatrix = objectOrEmpty(artifactObject.gateResultMatrix); const gateActionPlan = objectOrEmpty(artifactObject.gateActionPlan); 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); @@ -834,6 +904,13 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { && 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 actionPlanReady = gateActionPlan.apiName === "project-release-gate-action-plan" && gateActionPlan.planVersion === 1 && gateActionPlan.ready === true @@ -858,6 +935,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { : ["blockedRuntimeFamilies"]), ...(arrayOrEmpty(artifactObject.promotedBlockedFamilies).length === 0 ? [] : ["promotedBlockedFamilies"]), ...(manifestReady ? [] : ["gateManifest"]), + ...(executionManifestReady ? [] : ["gateExecutionManifest"]), ...(matrixReady ? [] : ["gateResultMatrix"]), ...(actionPlanReady ? [] : ["gateActionPlan"]), ...(gateCountReady ? [] : ["gates.length"]), @@ -879,6 +957,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { expectedGateCount: REQUIRED_RELEASE_GATES.length, expectedGateIds, gateManifestReady: manifestReady, + gateExecutionManifestReady: executionManifestReady, gateResultMatrixReady: matrixReady, gateActionPlanReady: actionPlanReady, blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies), @@ -898,6 +977,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { label: "Gate manifest", value: manifestReady ? "ready" : "missing", }, + { + id: "gate-execution-manifest", + label: "Gate execution manifest", + value: executionManifestReady ? "ready" : "missing", + }, { id: "gate-result-matrix", label: "Gate result matrix", diff --git a/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html b/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html index b3def90..91d5bd3 100644 --- a/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html +++ b/wasm-port/tests/browser/ini_panel_shell_integration_workflow_smoke.html @@ -469,9 +469,14 @@ TOOL_TABLE = browser-shell-tool.tbl ); assertEqual( workflowOverviewReleaseArtifactRenderResult.rowCount, - 6, + 7, "shell workflow overview release artifact render rows", ); + assertEqual( + workflowOverviewReleaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), + true, + "shell workflow overview release artifact execution manifest row", + ); assertEqual( workflowOverviewReleaseArtifactMountResult.ready, true, diff --git a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html index 90f1bb1..4d5107d 100644 --- a/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html +++ b/wasm-port/tests/browser/ini_panel_workflow_overview_smoke.html @@ -262,9 +262,14 @@ .getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(releaseArtifactUrlWorkflow); assertEqual(releaseArtifactDomReadinessReady.ready, true, "workflow overview release artifact DOM readiness"); assertEqual(releaseArtifactRenderResult.rendered, true, "workflow overview release artifact render result"); - assertEqual(releaseArtifactRenderResult.rowCount, 6, "workflow overview release artifact render row count"); + assertEqual(releaseArtifactRenderResult.rowCount, 7, "workflow overview release artifact render row count"); + assertEqual( + releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), + true, + "workflow overview release artifact execution manifest row", + ); assertEqual(releaseArtifactMountResult.ready, true, "workflow overview release artifact mount readiness"); - assertEqual(releaseArtifactMountResult.renderResult.rowCount, 6, "workflow overview release artifact mount rows"); + assertEqual(releaseArtifactMountResult.renderResult.rowCount, 7, "workflow overview release artifact mount rows"); assertEqual(releaseArtifactUrlWorkflow.ready, true, "workflow overview release artifact URL workflow readiness"); assertEqual(releaseArtifactUrlWorkflow.httpStatus, 200, "workflow overview release artifact URL workflow status"); assertEqual( diff --git a/wasm-port/tests/docs/node/verify_project_release_handoff_docs.mjs b/wasm-port/tests/docs/node/verify_project_release_handoff_docs.mjs index 8614325..a11742b 100644 --- a/wasm-port/tests/docs/node/verify_project_release_handoff_docs.mjs +++ b/wasm-port/tests/docs/node/verify_project_release_handoff_docs.mjs @@ -34,6 +34,7 @@ for (const phrase of [ "createProjectReleaseGateManifest()", "createProjectReleaseGateResultMatrix()", "createProjectReleaseGateActionPlan()", + "createProjectReleaseGateExecutionManifest()", "createProjectReleaseReadinessReport()", "createProjectReleaseReadinessSummaryViewModel()", "parseProjectReleaseReadinessArtifactJson()", @@ -44,7 +45,7 @@ for (const phrase of [ "loadProjectReleaseReadinessArtifactUrlWorkflow()", "createProjectReleaseReadinessArtifactUrlWorkflowActionPlan()", "createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel()", - "embedded gate manifest, result matrix, and action plan", + "embedded gate manifest, execution manifest, result matrix, and", "validateWorkflowOverviewReleaseReadinessArtifactJson()", "loadWorkflowOverviewReleaseReadinessArtifactUrl()", "getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()", @@ -122,6 +123,7 @@ for (const phrase of [ "createProjectReleaseReadinessArtifactValidationActionPlan()", "createProjectReleaseReadinessArtifactValidationSummaryViewModel()", "gateManifestReady", + "gateExecutionManifestReady", "gateResultMatrixReady", "gateActionPlanReady", "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()", diff --git a/wasm-port/tests/fixtures/project-release-readiness-ready.json b/wasm-port/tests/fixtures/project-release-readiness-ready.json index b5c85aa..56d1c28 100644 --- a/wasm-port/tests/fixtures/project-release-readiness-ready.json +++ b/wasm-port/tests/fixtures/project-release-readiness-ready.json @@ -101,6 +101,207 @@ } ] }, + "gateExecutionManifest": { + "apiName": "project-release-gate-execution-manifest", + "manifestVersion": 1, + "phase": "ready", + "ready": true, + "gateCount": 10, + "passedCount": 10, + "unknownCount": 0, + "missingGateIds": [], + "manifest": { + "apiName": "project-release-gate-manifest", + "manifestVersion": 1, + "gateCount": 10, + "gateIds": [ + "diff-check", + "vendor-sync", + "standalone-cnc-semantics", + "interp-wasm", + "sim-config-inventory", + "ini-panel-browser", + "release-artifact-url-browser", + "ui-node-smokes", + "host-smokes", + "project-release-gate" + ], + "gates": [ + { + "id": "diff-check", + "label": "Git diff check", + "command": "git diff --check", + "expectedOutput": null + }, + { + "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": "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": "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" + } + ] + }, + "rows": [ + { + "id": "diff-check", + "label": "Git diff check", + "command": "git diff --check", + "expectedOutput": null, + "observedOutput": null, + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "explicit-result" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + }, + { + "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", + "observedOutputCount": 9, + "status": "passed", + "passed": true, + "evidence": "expected-output" + } + ] + }, "gateResultMatrix": { "apiName": "project-release-gate-result-matrix", "matrixVersion": 1, diff --git a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs index 1e365e5..5370345 100644 --- a/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs +++ b/wasm-port/tests/host/verify_project_release_readiness_artifact.mjs @@ -23,6 +23,7 @@ assert.equal(validation.artifactVersion, 1); assert.equal(validation.gateCount, 10); assert.equal(validation.expectedGateCount, 10); assert.equal(validation.gateManifestReady, true); +assert.equal(validation.gateExecutionManifestReady, true); assert.equal(validation.gateResultMatrixReady, true); assert.equal(validation.gateActionPlanReady, true); assert.deepEqual(validation.expectedGateIds, [ @@ -38,6 +39,8 @@ assert.deepEqual(validation.expectedGateIds, [ "project-release-gate", ]); assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest"); +assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); +assert.equal(artifact.gateExecutionManifest.passedCount, 10); assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix"); assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(artifact.gateActionPlan.pendingCount, 0); diff --git a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs index 6cde051..251bab6 100644 --- a/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs +++ b/wasm-port/tests/sdk/node/verify_project_release_gate_manifest.mjs @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import { createProjectReleaseGateActionPlan, + createProjectReleaseGateExecutionManifest, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactJsonWorkflow, @@ -85,6 +86,40 @@ assert.deepEqual( ], ); +const partialExecutionManifest = createProjectReleaseGateExecutionManifest({ + gateResults: { + "diff-check": true, + }, + observedOutputs: [ + "vendor sync up to date", + "browser_release_artifact_url_workflow_smoke=ok", + ], +}); +assert.equal(partialExecutionManifest.apiName, "project-release-gate-execution-manifest"); +assert.equal(partialExecutionManifest.manifestVersion, 1); +assert.equal(partialExecutionManifest.phase, "waiting"); +assert.equal(partialExecutionManifest.ready, false); +assert.equal(partialExecutionManifest.gateCount, 10); +assert.equal(partialExecutionManifest.passedCount, 3); +assert.equal(partialExecutionManifest.unknownCount, 7); +assert.deepEqual(partialExecutionManifest.missingGateIds, partialMatrix.missingGateIds); +assert.deepEqual( + partialExecutionManifest.rows.filter(({ passed }) => passed).map(({ id, evidence }) => [id, evidence]), + [ + ["diff-check", "explicit-result"], + ["vendor-sync", "expected-output"], + ["release-artifact-url-browser", "expected-output"], + ], +); +assert.equal( + partialExecutionManifest.rows.find(({ id }) => id === "vendor-sync")?.observedOutput, + "vendor sync up to date", +); +assert.equal( + partialExecutionManifest.rows.find(({ id }) => id === "standalone-cnc-semantics")?.observedOutput, + null, +); + const partialActionPlan = createProjectReleaseGateActionPlan({ gateResultMatrix: partialMatrix }); assert.equal(partialActionPlan.apiName, "project-release-gate-action-plan"); assert.equal(partialActionPlan.planVersion, 1); @@ -120,6 +155,9 @@ const report = createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), }); assert.deepEqual(report.gateManifest, manifest); +assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest"); +assert.equal(report.gateExecutionManifest.ready, true); +assert.equal(report.gateExecutionManifest.passedCount, manifest.gateCount); assert.equal(report.gateResultMatrix.ready, true); assert.equal(report.gateResultMatrix.passedCount, manifest.gateCount); assert.equal(report.gateActionPlan.ready, true); @@ -209,6 +247,7 @@ assert.deepEqual( ["artifact", "project-release-readiness-report"], ["gate-count", "10/10"], ["gate-manifest", "ready"], + ["gate-execution-manifest", "ready"], ["gate-result-matrix", "ready"], ["gate-action-plan", "ready"], ["missing", "none"], @@ -332,6 +371,10 @@ const failedArtifactUrlWorkflowSummary = createProjectReleaseReadinessArtifactUr assert.equal(staleArtifactValidationSummary.phase, "blocked"); assert.match(staleArtifactValidationSummary.statusLine, /^Blocked: apiName, reportVersion/); assert.equal(staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-action-plan")?.value, "missing"); +assert.equal( + staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-manifest")?.value, + "missing", +); assert.equal(staleArtifactValidationActionPlan.phase, "blocked"); assert.equal(staleArtifactValidationActionPlan.ready, false); assert.equal(staleArtifactValidationActionPlan.missingCount, staleArtifactValidation.missing.length); diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 453ba9f..112b6ee 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -35,6 +35,7 @@ import { createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, createProjectReleaseGateActionPlan, + createProjectReleaseGateExecutionManifest, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactJsonWorkflow, @@ -96,6 +97,7 @@ const requiredExports = [ ["createLinuxCncIniSdk", createLinuxCncIniSdk], ["createLinuxCncInterpSdk", createLinuxCncInterpSdk], ["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan], + ["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest], ["createProjectReleaseGateManifest", createProjectReleaseGateManifest], ["createProjectReleaseGateResultMatrix", createProjectReleaseGateResultMatrix], ["createProjectReleaseReadinessArtifactJsonWorkflow", createProjectReleaseReadinessArtifactJsonWorkflow], @@ -302,6 +304,7 @@ assert.deepEqual(releaseReadinessArtifactValidation, { expectedGateCount: 10, expectedGateIds: createProjectReleaseGateManifest().gateIds, gateManifestReady: true, + gateExecutionManifestReady: true, gateResultMatrixReady: true, gateActionPlanReady: true, blockedRuntimeFamilies: [ @@ -325,6 +328,11 @@ assert.deepEqual(releaseReadinessArtifactValidation, { label: "Gate manifest", value: "ready", }, + { + id: "gate-execution-manifest", + label: "Gate execution manifest", + value: "ready", + }, { id: "gate-result-matrix", label: "Gate result matrix", @@ -428,6 +436,7 @@ assert.deepEqual( "simConfigInventory.unexpectedFail", "blockedRuntimeFamilies", "gateManifest", + "gateExecutionManifest", "gateResultMatrix", "gateActionPlan", "gates.length", diff --git a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs index a31d5fc..d5bc4ad 100644 --- a/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs +++ b/wasm-port/tests/ui/node/verify_ini_panel_ui_shell.mjs @@ -1366,14 +1366,18 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead rowsNode: releaseArtifactRowsNode, }); assert.equal(releaseArtifactRenderResult.rendered, true); -assert.equal(releaseArtifactRenderResult.rowCount, 6); +assert.equal(releaseArtifactRenderResult.rowCount, 7); assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact"); +assert.equal( + releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"), + true, +); assert.equal( releaseArtifactRenderResult.rowIds.includes("gate-action-plan"), true, ); assert.equal(releaseArtifactMountResult.ready, true); -assert.equal(releaseArtifactMountResult.renderResult.rowCount, 6); +assert.equal(releaseArtifactMountResult.renderResult.rowCount, 7); assert.deepEqual( validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing, ["artifact-json"], @@ -1414,7 +1418,7 @@ assert.equal( ); assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons"); assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true); -assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 6); +assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 7); const releaseArtifactUrlWorkflowSummary = createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel( releaseArtifactUrlWorkflow,