From 9015de19610b371c28f177c83b89e05cc27fc340 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Tue, 16 Jun 2026 13:54:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20release=20artifact=20URL?= =?UTF-8?q?=20workflow=20SDK=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PROJECT_COMPLETION_TRACKER.md | 4 +- text14.txt | 63 +++++++ wasm-port/docs/project-release-handoff.md | 3 + wasm-port/runtime/sdk/README.md | 5 + wasm-port/runtime/sdk/src/index.js | 1 + .../sdk/src/project-release-readiness.js | 156 ++++++++++++++++++ .../verify_project_release_handoff_docs.mjs | 2 + .../verify_project_release_gate_manifest.mjs | 53 ++++++ .../tests/sdk/node/verify_sdk_surface.mjs | 20 +++ 9 files changed, 305 insertions(+), 2 deletions(-) diff --git a/PROJECT_COMPLETION_TRACKER.md b/PROJECT_COMPLETION_TRACKER.md index 0351c13..4aad083 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 artifact JSON workflow API +- Latest completed batch: project release readiness artifact URL workflow SDK 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/action-plan/readiness summary view-model, project release readiness report/artifact JSON workflow/artifact validation summary/action-plan 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 JSON workflow/artifact URL workflow/artifact validation summary/action-plan 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, summarize, and report OPFS/session readiness, OPFS/session persistence summary, release readiness artifact validation/rendering/URL workflow summaries, 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 6e9d757..76fbdd7 100644 --- a/text14.txt +++ b/text14.txt @@ -3308,6 +3308,69 @@ ui_node_smokes=ok 继续只做实质能力推进。优先选择可执行 gate、SDK/API surface、或真实 browser workflow; 不要做只整理断言或局部常量化的批次。 +五十三、2026-06-16 继续执行记录:project release readiness artifact URL workflow SDK API + +本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进, +把 release readiness artifact 从 JSON 文本 workflow 推进到 SDK 侧 URL workflow。外部 CI/dashboard +现在可以传入 artifact URL 和 caller-provided fetch,一次得到 fetch 状态、JSON workflow、artifact +validation、summary view-model、action-plan、missing 和 display rows。 + +完成内容: + +- `runtime/sdk/src/project-release-readiness.js` 新增: + - `loadProjectReleaseReadinessArtifactUrlWorkflow()`; +- 新 URL workflow 返回: + - `apiName: project-release-readiness-artifact-url-workflow`; + - `workflowVersion: 1`; + - `phase/ready`; + - `artifactUrl/fetched/httpStatus/fetchError`; + - `jsonWorkflow`; + - `validation`; + - `summaryViewModel`; + - `actionPlan`; + - `missing`; + - display `rows[]`; +- ready URL fetch 返回 ready workflow、ready JSON workflow 和空 action-plan commands; +- missing URL/fetch 返回 blocked workflow 和结构化 missing; +- HTTP failure 返回 blocked workflow、`artifact-fetch` missing 和 next-command action-plan; +- thrown fetch error 返回 blocked workflow,不向外抛异常; +- `runtime/sdk/src/index.js` re-export 新 URL workflow API; +- `verify_project_release_gate_manifest.mjs` 覆盖 ready URL、missing URL/fetch 和 HTTP failure; +- `verify_sdk_surface.mjs` 覆盖 SDK export、README 可发现性、ready summary 和 HTTP failure missing; +- `runtime/sdk/README.md`、`docs/project-release-handoff.md`、`verify_project_release_handoff_docs.mjs` + 记录 URL workflow API; +- `PROJECT_COMPLETION_TRACKER.md` 同步 latest completed batch 和 SDK/API surface 状态; +- 未新增控制按钮; +- 未执行 release gate/action-plan; +- 未解析 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 readiness artifact JSON workflow API 本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进, diff --git a/wasm-port/docs/project-release-handoff.md b/wasm-port/docs/project-release-handoff.md index 596d0b2..47234bf 100644 --- a/wasm-port/docs/project-release-handoff.md +++ b/wasm-port/docs/project-release-handoff.md @@ -62,6 +62,9 @@ continuation records are in `../text14.txt`. `createProjectReleaseReadinessArtifactJsonWorkflow()` is the one-call non-throwing JSON text workflow for dashboards that need parse, validation, summary, and action-plan output from the readiness artifact. + `loadProjectReleaseReadinessArtifactUrlWorkflow()` provides the same + SDK-side workflow from an artifact URL and caller-provided fetch + implementation, including fetch status and next-command output. Browser shells can also pass the JSON to `linuxCncIniPanelWorkflowOverviewApi.validateWorkflowOverviewReleaseReadinessArtifactJson()` through the workflow overview iframe and receive stable validation rows. They diff --git a/wasm-port/runtime/sdk/README.md b/wasm-port/runtime/sdk/README.md index 2ae8382..d89a217 100644 --- a/wasm-port/runtime/sdk/README.md +++ b/wasm-port/runtime/sdk/README.md @@ -74,6 +74,7 @@ import { isSupportedIniPanelLaunchApiManifest, isSupportedIniPanelShellIntegrationManifest, loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, + loadProjectReleaseReadinessArtifactUrlWorkflow, loadMachineSessionFromOpfs, loadMachineSessionSnapshot, loadMachineTextFiles, @@ -243,6 +244,10 @@ one-call wrapper for dashboards that receive artifact JSON text. It parses, validates, builds the validation summary, and builds the action plan. Invalid JSON returns a blocked workflow with `parsed: false` and the same next-command action plan instead of throwing. +`loadProjectReleaseReadinessArtifactUrlWorkflow()` is the SDK-side URL workflow +for callers that supply a `fetch` implementation. It fetches the artifact URL, +runs the JSON workflow, and returns fetch status, validation, summary, and +action-plan output without executing release gates. ## OPFS/session persistence exports diff --git a/wasm-port/runtime/sdk/src/index.js b/wasm-port/runtime/sdk/src/index.js index a3641d2..aa3d513 100644 --- a/wasm-port/runtime/sdk/src/index.js +++ b/wasm-port/runtime/sdk/src/index.js @@ -10,6 +10,7 @@ export { createProjectReleaseReadinessArtifactValidationSummaryViewModel, createProjectReleaseReadinessReport, createProjectReleaseReadinessSummaryViewModel, + loadProjectReleaseReadinessArtifactUrlWorkflow, parseProjectReleaseReadinessArtifactJson, } from "./project-release-readiness.js"; export { diff --git a/wasm-port/runtime/sdk/src/project-release-readiness.js b/wasm-port/runtime/sdk/src/project-release-readiness.js index 1b4e17f..03b0fa7 100644 --- a/wasm-port/runtime/sdk/src/project-release-readiness.js +++ b/wasm-port/runtime/sdk/src/project-release-readiness.js @@ -518,6 +518,162 @@ export function createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson } } +export async function loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl = "", + fetchRef = globalThis.fetch, +} = {}) { + if (!artifactUrl || typeof fetchRef !== "function") { + const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" }); + const missing = [ + ...(artifactUrl ? [] : ["artifact-url"]), + ...(typeof fetchRef === "function" ? [] : ["fetch"]), + ...jsonWorkflow.missing, + ]; + return { + apiName: "project-release-readiness-artifact-url-workflow", + workflowVersion: 1, + phase: "blocked", + ready: false, + artifactUrl, + fetched: false, + httpStatus: null, + fetchError: null, + jsonWorkflow, + validation: jsonWorkflow.validation, + summaryViewModel: jsonWorkflow.summaryViewModel, + actionPlan: jsonWorkflow.actionPlan, + missing, + rows: [ + { + id: "fetch", + label: "Artifact URL fetch", + value: "blocked", + }, + { + id: "json-workflow", + label: "Artifact JSON workflow", + value: "blocked", + }, + { + id: "next-command", + label: "Next command", + value: jsonWorkflow.actionPlan.nextCommand ?? "none", + }, + ], + }; + } + + try { + const response = await fetchRef(artifactUrl); + const httpStatus = response?.status ?? null; + if (!response?.ok) { + const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" }); + const fetchError = `artifact URL fetch failed${httpStatus ? `: ${httpStatus}` : ""}`; + return { + apiName: "project-release-readiness-artifact-url-workflow", + workflowVersion: 1, + phase: "blocked", + ready: false, + artifactUrl, + fetched: false, + httpStatus, + fetchError, + jsonWorkflow, + validation: jsonWorkflow.validation, + summaryViewModel: jsonWorkflow.summaryViewModel, + actionPlan: jsonWorkflow.actionPlan, + missing: ["artifact-fetch", ...jsonWorkflow.missing], + rows: [ + { + id: "fetch", + label: "Artifact URL fetch", + value: "blocked", + }, + { + id: "json-workflow", + label: "Artifact JSON workflow", + value: "blocked", + }, + { + id: "next-command", + label: "Next command", + value: jsonWorkflow.actionPlan.nextCommand ?? "none", + }, + ], + }; + } + + const artifactJson = await response.text(); + const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson }); + return { + apiName: "project-release-readiness-artifact-url-workflow", + workflowVersion: 1, + phase: jsonWorkflow.ready === true ? "ready" : "blocked", + ready: jsonWorkflow.ready === true, + artifactUrl, + fetched: true, + httpStatus, + fetchError: null, + jsonWorkflow, + validation: jsonWorkflow.validation, + summaryViewModel: jsonWorkflow.summaryViewModel, + actionPlan: jsonWorkflow.actionPlan, + missing: jsonWorkflow.missing, + rows: [ + { + id: "fetch", + label: "Artifact URL fetch", + value: "ready", + }, + { + id: "json-workflow", + label: "Artifact JSON workflow", + value: jsonWorkflow.ready === true ? "ready" : "blocked", + }, + { + id: "next-command", + label: "Next command", + value: jsonWorkflow.actionPlan.nextCommand ?? "none", + }, + ], + }; + } catch (error) { + const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" }); + return { + apiName: "project-release-readiness-artifact-url-workflow", + workflowVersion: 1, + phase: "blocked", + ready: false, + artifactUrl, + fetched: false, + httpStatus: null, + fetchError: error?.message ?? "artifact URL fetch error", + jsonWorkflow, + validation: jsonWorkflow.validation, + summaryViewModel: jsonWorkflow.summaryViewModel, + actionPlan: jsonWorkflow.actionPlan, + missing: ["artifact-fetch", ...jsonWorkflow.missing], + rows: [ + { + id: "fetch", + label: "Artifact URL fetch", + value: "blocked", + }, + { + id: "json-workflow", + label: "Artifact JSON workflow", + value: "blocked", + }, + { + id: "next-command", + label: "Next command", + value: jsonWorkflow.actionPlan.nextCommand ?? "none", + }, + ], + }; + } +} + export function createProjectReleaseReadinessArtifactValidation(artifact = {}) { const artifactObject = objectOrEmpty(artifact); const releaseGate = objectOrEmpty(artifactObject.releaseGate); 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 d09aefc..1044dee 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 @@ -41,6 +41,7 @@ for (const phrase of [ "createProjectReleaseReadinessArtifactValidation()", "createProjectReleaseReadinessArtifactValidationActionPlan()", "createProjectReleaseReadinessArtifactValidationSummaryViewModel()", + "loadProjectReleaseReadinessArtifactUrlWorkflow()", "embedded gate manifest, result matrix, and action plan", "validateWorkflowOverviewReleaseReadinessArtifactJson()", "loadWorkflowOverviewReleaseReadinessArtifactUrl()", @@ -119,6 +120,7 @@ for (const phrase of [ "gateResultMatrixReady", "gateActionPlanReady", "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()", + "loadProjectReleaseReadinessArtifactUrlWorkflow()", "build/project-release-readiness.json", "INI panel shell handoff exports", "createIniPanelShellApiSurfaceInventory()", 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 d39e15a..0b11fdc 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 @@ -10,6 +10,7 @@ import { createProjectReleaseReadinessArtifactValidationSummaryViewModel, createProjectReleaseReadinessReport, createProjectReleaseReadinessSummaryViewModel, + loadProjectReleaseReadinessArtifactUrlWorkflow, } from "../../../runtime/sdk/src/index.js"; const manifest = createProjectReleaseGateManifest(); @@ -184,6 +185,14 @@ const artifactValidationActionPlan = createProjectReleaseReadinessArtifactValida const artifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: JSON.stringify(report), }); +const artifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl: "/project-release-readiness.json", + fetchRef: async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify(report), + }), +}); assert.equal( artifactValidationSummary.apiName, "project-release-readiness-artifact-validation-summary-view-model", @@ -249,6 +258,26 @@ assert.deepEqual( ["next-command", "none"], ], ); +assert.equal(artifactUrlWorkflow.apiName, "project-release-readiness-artifact-url-workflow"); +assert.equal(artifactUrlWorkflow.workflowVersion, 1); +assert.equal(artifactUrlWorkflow.phase, "ready"); +assert.equal(artifactUrlWorkflow.ready, true); +assert.equal(artifactUrlWorkflow.artifactUrl, "/project-release-readiness.json"); +assert.equal(artifactUrlWorkflow.fetched, true); +assert.equal(artifactUrlWorkflow.httpStatus, 200); +assert.equal(artifactUrlWorkflow.fetchError, null); +assert.equal(artifactUrlWorkflow.jsonWorkflow.ready, true); +assert.equal(artifactUrlWorkflow.validation.ready, true); +assert.equal(artifactUrlWorkflow.summaryViewModel.statusLine, "Ready: Artifact evidence complete"); +assert.equal(artifactUrlWorkflow.actionPlan.commandCount, 0); +assert.deepEqual( + artifactUrlWorkflow.rows.map(({ id, value }) => [id, value]), + [ + ["fetch", "ready"], + ["json-workflow", "ready"], + ["next-command", "none"], + ], +); const staleArtifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel( createProjectReleaseReadinessArtifactValidation({}), ); @@ -260,6 +289,18 @@ const staleArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkf const invalidArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "{not-json", }); +const missingArtifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl: "", + fetchRef: null, +}); +const failedArtifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl: "/missing.json", + fetchRef: async () => ({ + ok: false, + status: 404, + text: async () => "", + }), +}); assert.equal(staleArtifactValidationSummary.phase, "blocked"); assert.match(staleArtifactValidationSummary.statusLine, /^Blocked: apiName, reportVersion/); assert.equal(staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-action-plan")?.value, "missing"); @@ -304,5 +345,17 @@ assert.deepEqual( ["next-command", "wasm-port/tests/host/verify_project_release_gate.sh"], ], ); +assert.equal(missingArtifactUrlWorkflow.ready, false); +assert.equal(missingArtifactUrlWorkflow.fetched, false); +assert.deepEqual(missingArtifactUrlWorkflow.missing.slice(0, 2), ["artifact-url", "fetch"]); +assert.equal(failedArtifactUrlWorkflow.ready, false); +assert.equal(failedArtifactUrlWorkflow.fetched, false); +assert.equal(failedArtifactUrlWorkflow.httpStatus, 404); +assert.match(failedArtifactUrlWorkflow.fetchError, /404/); +assert.equal(failedArtifactUrlWorkflow.missing[0], "artifact-fetch"); +assert.equal( + failedArtifactUrlWorkflow.actionPlan.nextCommand, + "wasm-port/tests/host/verify_project_release_gate.sh", +); console.log("project_release_gate_manifest_node_smoke=ok"); diff --git a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs index 0f6face..c69a4f5 100644 --- a/wasm-port/tests/sdk/node/verify_sdk_surface.mjs +++ b/wasm-port/tests/sdk/node/verify_sdk_surface.mjs @@ -56,6 +56,7 @@ import { isSupportedIniPanelLaunchApiManifest, isSupportedIniPanelShellIntegrationManifest, loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, + loadProjectReleaseReadinessArtifactUrlWorkflow, loadMachineSessionFromOpfs, loadMachineSessionSnapshot, loadMachineTextFiles, @@ -116,6 +117,7 @@ const requiredExports = [ "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl", loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, ], + ["loadProjectReleaseReadinessArtifactUrlWorkflow", loadProjectReleaseReadinessArtifactUrlWorkflow], ["planIniFileContextStaging", planIniFileContextStaging], ["planSimConfigStaging", planSimConfigStaging], ["analyzeIniRuntimeBoundaries", analyzeIniRuntimeBoundaries], @@ -355,6 +357,24 @@ assert.equal( createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "not-json" }).parsed, false, ); +assert.equal( + (await loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl: "/project-release-readiness.json", + fetchRef: async () => ({ + ok: true, + status: 200, + text: async () => JSON.stringify(releaseReadinessArtifact), + }), + })).summaryViewModel.statusLine, + "Ready: Artifact evidence complete", +); +assert.equal( + (await loadProjectReleaseReadinessArtifactUrlWorkflow({ + artifactUrl: "/missing.json", + fetchRef: async () => ({ ok: false, status: 404, text: async () => "" }), + })).missing[0], + "artifact-fetch", +); assert.deepEqual( createProjectReleaseReadinessArtifactValidation({}).missing, [