From 9abca15d60bbbef7f71b67ba5d06c576e9853d12 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Tue, 16 Jun 2026 10:51:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A1=B9=E7=9B=AE=E5=8F=91?= =?UTF-8?q?=E5=B8=83=20gate=20action=20plan=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PROJECT_COMPLETION_TRACKER.md | 4 +- text14.txt | 61 +++++++++++++++++++ wasm-port/docs/project-release-handoff.md | 8 ++- wasm-port/runtime/sdk/README.md | 13 ++-- wasm-port/runtime/sdk/src/index.js | 1 + .../sdk/src/project-release-readiness.js | 58 ++++++++++++++++++ .../verify_project_release_handoff_docs.mjs | 2 + .../verify_project_release_gate_manifest.mjs | 36 +++++++++++ .../tests/sdk/node/verify_sdk_surface.mjs | 11 ++++ 9 files changed, 185 insertions(+), 9 deletions(-) diff --git a/PROJECT_COMPLETION_TRACKER.md b/PROJECT_COMPLETION_TRACKER.md index 1e9fb6c..5907c48 100644 --- a/PROJECT_COMPLETION_TRACKER.md +++ b/PROJECT_COMPLETION_TRACKER.md @@ -9,7 +9,7 @@ turn-by-turn continuation notes and next-batch execution records. ## Current Status - Active continuation file: `text14.txt` -- Latest completed batch: project release readiness summary view-model API +- Latest completed batch: project release gate action-plan API - Latest relevant commit: use `git log -1 --oneline` after each committed batch - Working tree at tracker creation: clean - Required policy: LinuxCNC remains the only CNC semantic source @@ -43,7 +43,7 @@ The project is complete for the current scope when all of the following are true | Native nc_files baseline | Stable | Last recorded Layer 1: `total 107`, `pass 101`, `expected_fail 6`, `unexpected_fail 0`. | | Native sim configs baseline | Stable | Last recorded Layer 2: `total 159`, `pass 151`, `expected_fail 8`, `unexpected_fail 0`. | | OPFS/session persistence | Stable for current scope | Browser smoke passes, SDK re-exports OPFS/session helpers including `readMachineSessionReadiness()` and `createMachineSessionPersistenceSummary()`, the UI exposes `getMachineSessionPersistenceSummary()`, and `docs/opfs-session-persistence.md` defines the project-level release gate. | -| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session readiness/load helpers, sim-config staging, project release gate manifest/result matrix/readiness summary view-model, project release readiness report/artifact validation plus URL loading workflow, INI panel entry/launcher/control-page/shell/workflow overview helpers, the session readiness workflow report, and the shell API surface inventory with a Node surface smoke. | +| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session readiness/load helpers, sim-config staging, project release gate manifest/result matrix/action-plan/readiness summary view-model, project release readiness report/artifact validation plus URL loading workflow, INI panel entry/launcher/control-page/shell/workflow overview helpers, the session readiness workflow report, and the shell API surface inventory with a Node surface smoke. | | Browser/UI workflow | Stable for current scope | Workflow overview embedding mount DOM closure is complete, and INI panel session workflows plus read-only control/shell handoff expose, render, fetch, validate, and report OPFS/session readiness, OPFS/session persistence summary, release readiness artifact validation/rendering, and API surface inventory before loading or reading WASM session state. | | Workflow overview embedding | Stable for current scope | Report/display/render, DOM contract/readiness/renderer, mount result/display/render, mount DOM contract/readiness/renderer/wrapper exist. | | Host/runtime boundary proof | Stable blocked state | Host smoke passes; runtime families remain blocked and `docs/host-runtime-boundary-handoff.md` records host readiness, promotion blockers, dispatch, and evidence gates. | diff --git a/text14.txt b/text14.txt index defcf9d..8d3215e 100644 --- a/text14.txt +++ b/text14.txt @@ -3308,6 +3308,67 @@ ui_node_smokes=ok 继续只做实质能力推进。优先选择可执行 gate、SDK/API surface、或真实 browser workflow; 不要做只整理断言或局部常量化的批次。 +四十五、2026-06-16 继续执行记录:project release gate action-plan SDK API + +本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进, +把 project release gate matrix 进一步提升为外部 CI/SDK 可直接消费的 action-plan API。外部工具现在可以 +从 observed outputs、explicit gate results 或现成 matrix 得到 pending gate commands、next command、 +display rows 和可展示/可执行的 shell script,而不需要复制 release gate manifest 顺序或匹配逻辑。 + +完成内容: + +- `runtime/sdk/src/project-release-readiness.js` 新增: + - `createProjectReleaseGateActionPlan()`; +- 新 action plan 返回: + - `apiName: project-release-gate-action-plan`; + - `planVersion: 1`; + - `phase/ready`; + - `gateCount/completedCount/pendingCount`; + - `completedGateIds/pendingGateIds`; + - `nextGateId/nextCommand`; + - `commands[]`; + - `rows[]`; + - `shellScript`; +- `createProjectReleaseReadinessReport()` 现在嵌入 `gateActionPlan`,与 `gateManifest` 和 + `gateResultMatrix` 使用同一份 evidence; +- `runtime/sdk/src/index.js` re-export 新 action-plan API; +- `verify_project_release_gate_manifest.mjs` 覆盖 partial matrix action plan、next command、pending + commands 和 ready shellScript; +- `verify_sdk_surface.mjs` 覆盖 SDK export、默认 waiting action plan、逐项 full-pass shellScript; +- `runtime/sdk/README.md`、`docs/project-release-handoff.md`、`verify_project_release_handoff_docs.mjs` + 记录 action-plan API; +- `PROJECT_COMPLETION_TRACKER.md` 同步 latest completed batch 和 SDK/API surface 状态; +- 未新增控制按钮; +- 未在 SDK 中执行 shell command; +- 未解析 G-code; +- 未解释 canonical events; +- 未新增 JS CNC 语义; +- 未运行 opt-in runtime probes; +- 未 promotion blocked runtime family; +- 未改变 LinuxCNC interpreter、tool、parameter、planner、kinematics、remap 或 LinuxCNC-owned + runtime semantics。 + +验证已通过(先行 targeted): + +```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 +``` + +关键输出: + +```text +project_release_gate_manifest_node_smoke=ok +sdk_surface_node_smoke=ok +project_release_handoff_docs_node_smoke=ok +``` + +下一步工作计划: + +继续只做实质能力推进。优先选择可执行 gate、SDK/API surface、或真实 browser workflow; +不要做只整理断言或局部常量化的批次。 + 四十二、2026-06-16 继续执行记录:project release gate manifest SDK API/gate 本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进, diff --git a/wasm-port/docs/project-release-handoff.md b/wasm-port/docs/project-release-handoff.md index aea05c1..34fc973 100644 --- a/wasm-port/docs/project-release-handoff.md +++ b/wasm-port/docs/project-release-handoff.md @@ -37,10 +37,12 @@ continuation records are in `../text14.txt`. - CI dashboards and external SDK callers can use `createProjectReleaseGateManifest()` to discover required gate commands and expected smoke outputs, `createProjectReleaseGateResultMatrix()` to map - observed outputs onto passed/unknown gate rows, and + 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 `createProjectReleaseReadinessReport()` for a machine-readable release - readiness report covering the same manifest, result matrix, sim-config - inventory baseline, and blocked runtime families. + readiness report covering the same 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 diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index d22b549..b285ae9 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -51,6 +51,7 @@ import { createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, createMachineSessionSnapshotPayload, + createProjectReleaseGateActionPlan, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactValidation, @@ -196,11 +197,15 @@ It does not implement or reinterpret LinuxCNC error semantics. commands, expected smoke outputs, and gate count for CI dashboards and external SDK callers. `createProjectReleaseGateResultMatrix()` maps observed gate output 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. `createProjectReleaseReadinessReport()` returns a machine-readable release -readiness report that embeds the same manifest and result matrix 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. +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. `createProjectReleaseReadinessSummaryViewModel()` turns the report into stable status text, a status line, and display rows for dashboards without executing any gate. diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index 46ce265..1ef7954 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -1,6 +1,7 @@ export { createLinuxCncIniSdk } from "./linuxcnc-ini.js"; export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js"; export { + createProjectReleaseGateActionPlan, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactValidation, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index 39178fd..6c8d5e9 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -145,6 +145,59 @@ export function createProjectReleaseGateResultMatrix({ }; } +export function createProjectReleaseGateActionPlan({ + gateResultMatrix = null, + gateResults = {}, + observedOutputs = [], + manifest = createProjectReleaseGateManifest(), +} = {}) { + const matrix = gateResultMatrix && typeof gateResultMatrix === "object" + ? gateResultMatrix + : createProjectReleaseGateResultMatrix({ gateResults, observedOutputs, manifest }); + const rows = Array.isArray(matrix.rows) ? matrix.rows : []; + const pendingRows = rows.filter(({ passed }) => passed !== true); + const completedRows = rows.filter(({ passed }) => passed === true); + const commands = pendingRows.map((gate, index) => ({ + step: index + 1, + gateId: gate.id, + label: gate.label, + command: gate.command, + expectedOutput: gate.expectedOutput ?? null, + })); + const nextGate = commands[0] ?? null; + const shellScript = commands.length > 0 + ? [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "", + ...commands.map(({ command }) => command), + "", + ].join("\n") + : "# Project release gates are already marked passed.\n"; + + return { + apiName: "project-release-gate-action-plan", + planVersion: 1, + phase: commands.length === 0 ? "ready" : "waiting", + ready: commands.length === 0, + gateCount: rows.length, + completedCount: completedRows.length, + pendingCount: pendingRows.length, + completedGateIds: completedRows.map(({ id }) => id), + pendingGateIds: pendingRows.map(({ id }) => id), + nextGateId: nextGate?.gateId ?? null, + nextCommand: nextGate?.command ?? null, + commands, + shellScript, + rows: commands.map(({ gateId, label, command, expectedOutput }) => ({ + id: gateId, + label, + value: command, + expectedOutput, + })), + }; +} + export function createProjectReleaseReadinessReport({ gateResults = {}, observedOutputs = [], @@ -158,6 +211,10 @@ export function createProjectReleaseReadinessReport({ observedOutputs, manifest: gateManifest, }); + const gateActionPlan = createProjectReleaseGateActionPlan({ + gateResultMatrix, + manifest: gateManifest, + }); const gateRows = gateResultMatrix.rows; const releaseGatePassed = gateRows.find(({ id }) => id === "project-release-gate")?.passed === true; const inventory = { @@ -193,6 +250,7 @@ export function createProjectReleaseReadinessReport({ promotedBlockedFamilies, gateManifest, gateResultMatrix, + gateActionPlan, gates: gateRows, rows: [ { 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 b2147eb..0395c4a 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 @@ -33,6 +33,7 @@ for (const phrase of [ "runtime/sdk/src/index.js", "createProjectReleaseGateManifest()", "createProjectReleaseGateResultMatrix()", + "createProjectReleaseGateActionPlan()", "createProjectReleaseReadinessReport()", "createProjectReleaseReadinessSummaryViewModel()", "parseProjectReleaseReadinessArtifactJson()", @@ -101,6 +102,7 @@ for (const phrase of [ "Project release readiness export", "createProjectReleaseGateManifest()", "createProjectReleaseGateResultMatrix()", + "createProjectReleaseGateActionPlan()", "createProjectReleaseReadinessReport()", "createProjectReleaseReadinessSummaryViewModel()", "parseProjectReleaseReadinessArtifactJson()", 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 b730bf1..494cb55 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 @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { + createProjectReleaseGateActionPlan, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessReport, @@ -78,12 +79,47 @@ assert.deepEqual( ], ); +const partialActionPlan = createProjectReleaseGateActionPlan({ gateResultMatrix: partialMatrix }); +assert.equal(partialActionPlan.apiName, "project-release-gate-action-plan"); +assert.equal(partialActionPlan.planVersion, 1); +assert.equal(partialActionPlan.phase, "waiting"); +assert.equal(partialActionPlan.ready, false); +assert.equal(partialActionPlan.gateCount, 10); +assert.equal(partialActionPlan.completedCount, 3); +assert.equal(partialActionPlan.pendingCount, 7); +assert.deepEqual(partialActionPlan.completedGateIds, [ + "diff-check", + "vendor-sync", + "release-artifact-url-browser", +]); +assert.deepEqual(partialActionPlan.pendingGateIds, [ + "standalone-cnc-semantics", + "interp-wasm", + "sim-config-inventory", + "ini-panel-browser", + "ui-node-smokes", + "host-smokes", + "project-release-gate", +]); +assert.equal(partialActionPlan.nextGateId, "standalone-cnc-semantics"); +assert.equal(partialActionPlan.nextCommand, "wasm-port/tools/verify_no_standalone_cnc_semantics.sh"); +assert.equal(partialActionPlan.commands.length, 7); +assert.equal(partialActionPlan.commands[0].step, 1); +assert.equal(partialActionPlan.commands[0].gateId, "standalone-cnc-semantics"); +assert.equal(partialActionPlan.rows[0].id, "standalone-cnc-semantics"); +assert.match(partialActionPlan.shellScript, /^#!\/usr\/bin\/env bash\nset -euo pipefail\n/); +assert.match(partialActionPlan.shellScript, /wasm-port\/tests\/host\/verify_project_release_gate\.sh/); + const report = createProjectReleaseReadinessReport({ gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])), }); assert.deepEqual(report.gateManifest, manifest); assert.equal(report.gateResultMatrix.ready, true); assert.equal(report.gateResultMatrix.passedCount, manifest.gateCount); +assert.equal(report.gateActionPlan.ready, true); +assert.equal(report.gateActionPlan.pendingCount, 0); +assert.equal(report.gateActionPlan.nextCommand, null); +assert.equal(report.gateActionPlan.shellScript, "# Project release gates are already marked passed.\n"); assert.deepEqual(report.gates.map(({ id }) => id), manifest.gateIds); assert.equal(report.gates.length, manifest.gateCount); assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), { diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index d65600f..c600d59 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -32,6 +32,7 @@ import { createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, + createProjectReleaseGateActionPlan, createProjectReleaseGateManifest, createProjectReleaseGateResultMatrix, createProjectReleaseReadinessArtifactValidation, @@ -86,6 +87,7 @@ const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md const requiredExports = [ ["createLinuxCncIniSdk", createLinuxCncIniSdk], ["createLinuxCncInterpSdk", createLinuxCncInterpSdk], + ["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan], ["createProjectReleaseGateManifest", createProjectReleaseGateManifest], ["createProjectReleaseGateResultMatrix", createProjectReleaseGateResultMatrix], ["createProjectReleaseReadinessArtifactValidation", createProjectReleaseReadinessArtifactValidation], @@ -211,6 +213,9 @@ assert.equal(releaseReadinessWaiting.ready, false); assert.deepEqual(releaseReadinessWaiting.missing, ["project-release-gate"]); assert.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0); assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []); +assert.equal(releaseReadinessWaiting.gateActionPlan.apiName, "project-release-gate-action-plan"); +assert.equal(releaseReadinessWaiting.gateActionPlan.pendingCount, 10); +assert.equal(releaseReadinessWaiting.gateActionPlan.nextGateId, "diff-check"); assert.equal( releaseReadinessWaiting.rows.find(({ id }) => id === "blocked-runtime-families")?.value, "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP", @@ -222,6 +227,12 @@ assert.equal(releaseReadinessReady.ready, true); assert.equal(releaseReadinessReady.releaseGate.passed, true); assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest()); assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, 10); +assert.equal(createProjectReleaseGateActionPlan({ + gateResultMatrix: releaseReadinessReady.gateResultMatrix, +}).ready, false); +assert.equal(createProjectReleaseGateActionPlan({ + gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])), +}).shellScript, "# Project release gates are already marked passed.\n"); assert.equal(createProjectReleaseReadinessSummaryViewModel(releaseReadinessReady).statusLine, "Ready: No blocking reasons"); assert.equal( createProjectReleaseGateResultMatrix({