新增 release gate execution manifest API

This commit is contained in:
2026-06-16 17:20:21 +08:00
parent 2f6e8480bf
commit 741afc5103
13 changed files with 443 additions and 21 deletions

View File

@@ -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。

View File

@@ -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

View File

@@ -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.

View File

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

View File

@@ -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",

View File

@@ -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,

View File

@@ -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(

View File

@@ -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()",

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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",

View File

@@ -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,