diff --git a/PROJECT_COMPLETION_TRACKER.md b/PROJECT_COMPLETION_TRACKER.md index f95e3ba..889e159 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 gate validation closure +- Latest completed batch: project release readiness SDK report - 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,11 +43,11 @@ 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, 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 readiness, 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, and report OPFS/session readiness, OPFS/session persistence summary, 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. | -| Documentation/release gate | Stable for current scope | `docs/project-release-handoff.md` links README, panel entry, SDK, OPFS/session, sim-config, host/runtime, source reuse, drift, and tracker acceptance paths. | +| Documentation/release gate | Stable for current scope | `docs/project-release-handoff.md` links README, panel entry, SDK, OPFS/session, sim-config, host/runtime, source reuse, drift, tracker acceptance paths, and the machine-readable release readiness report. | | Project release gate wrapper | Stable | `tests/host/verify_project_release_gate.sh` runs the documented minimum release validation commands and ends with `project_release_gate=ok`. | ## Blocked Runtime Families @@ -111,7 +111,7 @@ Completion criteria: Work items: - Audit `runtime/sdk/src/index.js`. Done. -- Export stable helpers that external callers should use. Done for core, OPFS/session, sim-config staging, INI panel entry/launcher/control-page/shell/workflow overview helpers. +- Export stable helpers that external callers should use. Done for core, OPFS/session, sim-config staging, project release readiness, INI panel entry/launcher/control-page/shell/workflow overview helpers. - Add or update SDK smoke for export shape and manifest compatibility. Done via `tests/sdk/node/verify_sdk_surface.sh`. - Update `runtime/sdk/README.md` with usage examples and unsupported areas. Done for the current SDK surface. @@ -186,6 +186,8 @@ Work items: - Update `docs/drift-report.md`. Done. - Add or refresh final acceptance checklist. Done via `docs/project-release-handoff.md`. +- Add a machine-readable release readiness SDK report for CI/external callers. + Done via `createProjectReleaseReadinessReport()`. - Add docs smoke for project release handoff. Done via `tests/docs/node/verify_project_release_handoff_docs.sh`. diff --git a/text14.txt b/text14.txt index 22219e9..76d518c 100644 --- a/text14.txt +++ b/text14.txt @@ -2771,3 +2771,80 @@ project_release_gate=ok 整理并提交当前批次。提交后若工作树 clean,则当前 scope 已达到 `PROJECT_COMPLETION_TRACKER.md` 定义的发布验证闭环;后续只在新 scope 请求或真实 LinuxCNC host runtime 可用时继续推进 blocked runtime family。 + +三十五、2026-06-16 继续执行记录:project release readiness SDK report + +本轮继续以功能实质性推进为主,把当前 release gate、sim-config baseline 和 blocked runtime family +从文档/脚本说明推进为 SDK 可调用的机器可读 readiness report,方便 CI dashboard 或外部调用方在不解析 +Markdown 的情况下判断当前 scope 是否具备发布证据。 + +完成内容: + +- 新增 `runtime/sdk/src/project-release-readiness.js`; +- 新增 `createProjectReleaseReadinessReport()`; +- report 输出: + - `apiName: "project-release-readiness-report"`; + - `reportVersion`; + - `phase`; + - `ready`; + - `missing`; + - `releaseGate.command`; + - `releaseGate.expectedOutput`; + - `simConfigInventory` baseline; + - `blockedRuntimeFamilies`; + - `promotedRuntimeFamilies`; + - `promotedBlockedFamilies`; + - `gates`; + - `rows`; +- 默认 report 不声称发布完成,只有调用方提供 `project_release_gate=ok` 等 observed output 或 gate result + 时才会将 project release gate 判定为 passed; +- 若调用方传入被阻塞 runtime family 的 promotion,report 会保留 `blocked-runtime-families` missing; +- `runtime/sdk/src/index.js` re-export 新 helper; +- `runtime/sdk/README.md` 新增 Project release readiness export 说明; +- `docs/project-release-handoff.md` 说明外部 CI/dashboard 使用方式; +- `verify_sdk_surface.mjs` 覆盖 waiting、ready、blocked promotion 三种 report; +- `verify_project_release_handoff_docs.mjs` 覆盖文档中的新 helper; +- `PROJECT_COMPLETION_TRACKER.md` 同步 latest completed batch 与 SDK/release gate 状态; +- 未新增控制按钮; +- 未执行 shell command helper; +- 未解析 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。 + +验证已通过: + +```bash +git diff --check +wasm-port/tests/sdk/node/verify_sdk_surface.sh +wasm-port/tests/docs/node/verify_project_release_handoff_docs.sh +wasm-port/tools/verify_vendor_sync.sh +wasm-port/tools/verify_no_standalone_cnc_semantics.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh +SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.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_host_smokes.sh +``` + +关键输出: + +```text +sdk_surface_node_smoke=ok +project_release_handoff_docs_node_smoke=ok +vendor sync up to date +standalone CNC semantics guard complete +interp_wasm_node_smoke=ok +sim_configs_wasm_node_inventory_unexpected_fail=0 +ui_node_smokes=ok +browser_ini_shell_integration_workflow_smoke=ok +host_wasm_opfs_browser_smokes=ok +``` + +下一步工作计划: + +整理并提交当前批次。提交后如果继续推进,可优先把 release readiness report 暴露给 project release gate +wrapper 或生成 artifact;仍保持只读和证据驱动,不执行 action-plan,不扩大 CNC 语义边界。 diff --git a/wasm-port/docs/project-release-handoff.md b/wasm-port/docs/project-release-handoff.md index d7ed566..6f47e45 100644 --- a/wasm-port/docs/project-release-handoff.md +++ b/wasm-port/docs/project-release-handoff.md @@ -34,6 +34,12 @@ continuation records are in `../text14.txt`. `createIniPanelShellSessionReadinessWorkflowReport()` when they need machine-readable session readiness phase and missing reasons. The workflow overview exposes the API surface inventory as a read-only summary row. +- CI dashboards and external SDK callers can use + `createProjectReleaseReadinessReport()` for a machine-readable release + readiness report covering required gate commands, expected smoke outputs, the + sim-config inventory baseline, and blocked runtime families. The helper is + evidence driven and only reports `ready: true` when the caller supplies + release gate evidence such as `project_release_gate=ok`. - OPFS/session callers can use `createMachineSessionPersistenceSummary()` or `linuxCncIniPanelApi.getMachineSessionPersistenceSummary()` to inspect machine files, session snapshot, session readiness, session load, readonly diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 5e93862..41a5b78 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -47,6 +47,7 @@ import { createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, createMachineSessionSnapshotPayload, + createProjectReleaseReadinessReport, createSessionSnapshot, defaultMachinePaths, gcodeFilenameFromProgramPath, @@ -176,6 +177,20 @@ path but keeps the runner loop going after LinuxCNC reports an error, matching upstream `rs274 -n 0` regression tests such as `tests/interp/oword-unwind`. It does not implement or reinterpret LinuxCNC error semantics. +## Project release readiness export + +`createProjectReleaseReadinessReport()` returns a machine-readable release +readiness report for CI dashboards and external SDK callers. The report lists +the required project release gate command, expected smoke outputs, the current +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. + +This helper does not execute shell commands and does not inspect or implement +CNC behavior. It only packages project-level release evidence and blocked +runtime policy that are already documented in `docs/project-release-handoff.md` +and `../PROJECT_COMPLETION_TRACKER.md`. + ## OPFS/session persistence exports The SDK entrypoint re-exports the OPFS/session persistence helpers used by the diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index c2f2675..46b02c8 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -1,5 +1,6 @@ export { createLinuxCncIniSdk } from "./linuxcnc-ini.js"; export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js"; +export { createProjectReleaseReadinessReport } from "./project-release-readiness.js"; export { analyzeIniRuntimeBoundaries, planIniFileContextStaging, diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js new file mode 100644 index 0000000..9a9588e --- /dev/null +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -0,0 +1,154 @@ +const REQUIRED_RELEASE_GATES = [ + { + id: "diff-check", + label: "Git diff check", + command: "git diff --check", + }, + { + id: "vendor-sync", + label: "Vendor sync guard", + command: "wasm-port/tools/verify_vendor_sync.sh", + expectedOutput: "vendor sync up to date", + }, + { + id: "standalone-cnc-semantics", + label: "Standalone CNC semantics guard", + command: "wasm-port/tools/verify_no_standalone_cnc_semantics.sh", + expectedOutput: "standalone CNC semantics guard complete", + }, + { + id: "interp-wasm", + label: "Interpreter WASM Node smoke", + command: "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh", + expectedOutput: "interp_wasm_node_smoke=ok", + }, + { + id: "sim-config-inventory", + label: "Sim config inventory WASM Node smoke", + command: "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh", + expectedOutput: "sim_configs_wasm_node_inventory_unexpected_fail=0", + }, + { + id: "ini-panel-browser", + label: "INI panel browser smoke", + command: "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh", + expectedOutput: "browser_ini_shell_integration_workflow_smoke=ok", + }, + { + id: "ui-node-smokes", + label: "UI Node smokes", + command: "wasm-port/tests/ui/node/verify_ui_node_smokes.sh", + expectedOutput: "ui_node_smokes=ok", + }, + { + id: "host-smokes", + label: "Host aggregate smoke", + command: "wasm-port/tests/host/verify_host_smokes.sh", + expectedOutput: "host_wasm_opfs_browser_smokes=ok", + }, + { + id: "project-release-gate", + label: "Project release gate", + command: "wasm-port/tests/host/verify_project_release_gate.sh", + expectedOutput: "project_release_gate=ok", + }, +]; + +const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = { + executed: 28, + passed: 28, + skipped: 131, + unexpectedFail: 0, +}; + +const BLOCKED_RUNTIME_FAMILIES = [ + "L4-USER-M-PROCESS", + "L4-TOOL-DB", + "L4-PYTHON-REMAP", +]; + +function outputContains(observedOutputs, expectedOutput) { + return observedOutputs.some((output) => String(output).includes(expectedOutput)); +} + +function readGatePassed({ gate, gateResults, observedOutputs }) { + if (gateResults?.[gate.id] === true) { + return true; + } + if (gate.expectedOutput && outputContains(observedOutputs, gate.expectedOutput)) { + return true; + } + return false; +} + +export function createProjectReleaseReadinessReport({ + gateResults = {}, + observedOutputs = [], + simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE, + blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES, + promotedRuntimeFamilies = [], +} = {}) { + const outputs = Array.isArray(observedOutputs) ? observedOutputs : []; + const gateRows = REQUIRED_RELEASE_GATES.map((gate) => { + const passed = readGatePassed({ gate, gateResults, observedOutputs: outputs }); + return { + id: gate.id, + label: gate.label, + command: gate.command, + expectedOutput: gate.expectedOutput ?? null, + status: passed ? "passed" : "unknown", + passed, + }; + }); + const releaseGatePassed = gateRows.find(({ id }) => id === "project-release-gate")?.passed === true; + const inventory = { + ...DEFAULT_SIM_CONFIG_INVENTORY_BASELINE, + ...(simConfigInventory && typeof simConfigInventory === "object" ? simConfigInventory : {}), + }; + const blockedFamilies = [...blockedRuntimeFamilies]; + const promotedFamilies = [...promotedRuntimeFamilies]; + const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family)); + const inventoryReady = inventory.unexpectedFail === 0; + const blockedRuntimeReady = promotedBlockedFamilies.length === 0; + const ready = releaseGatePassed && inventoryReady && blockedRuntimeReady; + const missing = [ + ...(releaseGatePassed ? [] : ["project-release-gate"]), + ...(inventoryReady ? [] : ["sim-config-inventory"]), + ...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]), + ]; + + return { + apiName: "project-release-readiness-report", + reportVersion: 1, + phase: ready ? "ready" : "waiting", + ready, + missing, + releaseGate: { + command: "wasm-port/tests/host/verify_project_release_gate.sh", + passed: releaseGatePassed, + expectedOutput: "project_release_gate=ok", + }, + simConfigInventory: inventory, + blockedRuntimeFamilies: blockedFamilies, + promotedRuntimeFamilies: promotedFamilies, + promotedBlockedFamilies, + gates: gateRows, + rows: [ + { + id: "project-release-gate", + label: "Project release gate", + value: releaseGatePassed ? "passed" : "unknown", + }, + { + id: "sim-config-inventory", + label: "Sim config inventory", + value: `unexpected_fail=${inventory.unexpectedFail}`, + }, + { + id: "blocked-runtime-families", + label: "Blocked runtime families", + value: blockedRuntimeReady ? blockedFamilies.join(", ") : `promoted: ${promotedBlockedFamilies.join(", ")}`, + }, + ], + }; +} 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 ea89f1d..435e2b2 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 @@ -31,6 +31,7 @@ for (const phrase of [ "docs/drift-report.md", "PROJECT_COMPLETION_TRACKER.md", "runtime/sdk/src/index.js", + "createProjectReleaseReadinessReport()", "createIniPanelShellApiSurfaceInventory()", "createIniPanelShellSessionReadinessWorkflowReport()", "workflow overview embedding mount DOM", @@ -81,6 +82,8 @@ for (const phrase of [ for (const phrase of [ "OPFS/session persistence exports", + "Project release readiness export", + "createProjectReleaseReadinessReport()", "INI panel shell handoff exports", "createIniPanelShellApiSurfaceInventory()", "createIniPanelShellSessionReadinessWorkflowReport()", diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 119a0fe..1c173eb 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -28,6 +28,7 @@ import { createMachineSessionPersistenceDisplayViewModel, createMachineSessionPersistenceRenderState, createMachineSessionPersistenceSummary, + createProjectReleaseReadinessReport, createSessionSnapshot, defaultMachinePaths, gcodeFilenameFromProgramPath, @@ -72,6 +73,7 @@ const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md const requiredExports = [ ["createLinuxCncIniSdk", createLinuxCncIniSdk], ["createLinuxCncInterpSdk", createLinuxCncInterpSdk], + ["createProjectReleaseReadinessReport", createProjectReleaseReadinessReport], ["planIniFileContextStaging", planIniFileContextStaging], ["planSimConfigStaging", planSimConfigStaging], ["analyzeIniRuntimeBoundaries", analyzeIniRuntimeBoundaries], @@ -153,6 +155,29 @@ assert.equal(sessionSnapshotPath("sdk-session"), "linuxcnc/sessions/sdk-session/ assert.equal(normalizeOpfsPath("linuxcnc/machines/sdk-mill/tool.tbl"), "linuxcnc/machines/sdk-mill/tool.tbl"); assert.equal(gcodeFilenameFromProgramPath("linuxcnc/gcode/demo.ngc"), "demo.ngc"); +const releaseReadinessWaiting = createProjectReleaseReadinessReport(); +assert.equal(releaseReadinessWaiting.apiName, "project-release-readiness-report"); +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.rows.find(({ id }) => id === "blocked-runtime-families")?.value, + "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP", +); +const releaseReadinessReady = createProjectReleaseReadinessReport({ + observedOutputs: ["project_release_gate=ok"], +}); +assert.equal(releaseReadinessReady.ready, true); +assert.equal(releaseReadinessReady.releaseGate.passed, true); +assert.equal( + createProjectReleaseReadinessReport({ + observedOutputs: ["project_release_gate=ok"], + promotedRuntimeFamilies: ["L4-TOOL-DB"], + }).missing.includes("blocked-runtime-families"), + true, +); + assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES); assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES); assert.deepEqual(getIniPanelEntryByHref("./control-page.html"), {