新增 release artifact URL workflow SDK API

This commit is contained in:
2026-06-16 13:54:53 +08:00
parent d789920d7f
commit 9015de1961
9 changed files with 305 additions and 2 deletions

View File

@@ -9,7 +9,7 @@ turn-by-turn continuation notes and next-batch execution records.
## Current Status ## Current Status
- Active continuation file: `text14.txt` - 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 - Latest relevant commit: use `git log -1 --oneline` after each committed batch
- Working tree at tracker creation: clean - Working tree at tracker creation: clean
- Required policy: LinuxCNC remains the only CNC semantic source - 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 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`. | | 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. | | 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. | | 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. | | 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. | | 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. |

View File

@@ -3308,6 +3308,69 @@ ui_node_smokes=ok
继续只做实质能力推进。优先选择可执行 gate、SDK/API surface、或真实 browser workflow 继续只做实质能力推进。优先选择可执行 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 五十二、2026-06-16 继续执行记录project release readiness artifact JSON workflow API
本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进, 本轮继续按“每批必须产出可调用 API、可验证 workflow、可执行 gate、或真实 UI/browser 能力”推进,

View File

@@ -62,6 +62,9 @@ continuation records are in `../text14.txt`.
`createProjectReleaseReadinessArtifactJsonWorkflow()` is the one-call `createProjectReleaseReadinessArtifactJsonWorkflow()` is the one-call
non-throwing JSON text workflow for dashboards that need parse, validation, non-throwing JSON text workflow for dashboards that need parse, validation,
summary, and action-plan output from the readiness artifact. 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 Browser shells can also pass the JSON to
`linuxCncIniPanelWorkflowOverviewApi.validateWorkflowOverviewReleaseReadinessArtifactJson()` `linuxCncIniPanelWorkflowOverviewApi.validateWorkflowOverviewReleaseReadinessArtifactJson()`
through the workflow overview iframe and receive stable validation rows. They through the workflow overview iframe and receive stable validation rows. They

View File

@@ -74,6 +74,7 @@ import {
isSupportedIniPanelLaunchApiManifest, isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest, isSupportedIniPanelShellIntegrationManifest,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
loadProjectReleaseReadinessArtifactUrlWorkflow,
loadMachineSessionFromOpfs, loadMachineSessionFromOpfs,
loadMachineSessionSnapshot, loadMachineSessionSnapshot,
loadMachineTextFiles, 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 validates, builds the validation summary, and builds the action plan. Invalid
JSON returns a blocked workflow with `parsed: false` and the same next-command JSON returns a blocked workflow with `parsed: false` and the same next-command
action plan instead of throwing. 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 ## OPFS/session persistence exports

View File

@@ -10,6 +10,7 @@ export {
createProjectReleaseReadinessArtifactValidationSummaryViewModel, createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport, createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel, createProjectReleaseReadinessSummaryViewModel,
loadProjectReleaseReadinessArtifactUrlWorkflow,
parseProjectReleaseReadinessArtifactJson, parseProjectReleaseReadinessArtifactJson,
} from "./project-release-readiness.js"; } from "./project-release-readiness.js";
export { export {

View File

@@ -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 = {}) { export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact); const artifactObject = objectOrEmpty(artifact);
const releaseGate = objectOrEmpty(artifactObject.releaseGate); const releaseGate = objectOrEmpty(artifactObject.releaseGate);

View File

@@ -41,6 +41,7 @@ for (const phrase of [
"createProjectReleaseReadinessArtifactValidation()", "createProjectReleaseReadinessArtifactValidation()",
"createProjectReleaseReadinessArtifactValidationActionPlan()", "createProjectReleaseReadinessArtifactValidationActionPlan()",
"createProjectReleaseReadinessArtifactValidationSummaryViewModel()", "createProjectReleaseReadinessArtifactValidationSummaryViewModel()",
"loadProjectReleaseReadinessArtifactUrlWorkflow()",
"embedded gate manifest, result matrix, and action plan", "embedded gate manifest, result matrix, and action plan",
"validateWorkflowOverviewReleaseReadinessArtifactJson()", "validateWorkflowOverviewReleaseReadinessArtifactJson()",
"loadWorkflowOverviewReleaseReadinessArtifactUrl()", "loadWorkflowOverviewReleaseReadinessArtifactUrl()",
@@ -119,6 +120,7 @@ for (const phrase of [
"gateResultMatrixReady", "gateResultMatrixReady",
"gateActionPlanReady", "gateActionPlanReady",
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()", "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()",
"loadProjectReleaseReadinessArtifactUrlWorkflow()",
"build/project-release-readiness.json", "build/project-release-readiness.json",
"INI panel shell handoff exports", "INI panel shell handoff exports",
"createIniPanelShellApiSurfaceInventory()", "createIniPanelShellApiSurfaceInventory()",

View File

@@ -10,6 +10,7 @@ import {
createProjectReleaseReadinessArtifactValidationSummaryViewModel, createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport, createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel, createProjectReleaseReadinessSummaryViewModel,
loadProjectReleaseReadinessArtifactUrlWorkflow,
} from "../../../runtime/sdk/src/index.js"; } from "../../../runtime/sdk/src/index.js";
const manifest = createProjectReleaseGateManifest(); const manifest = createProjectReleaseGateManifest();
@@ -184,6 +185,14 @@ const artifactValidationActionPlan = createProjectReleaseReadinessArtifactValida
const artifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ const artifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(report), 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( assert.equal(
artifactValidationSummary.apiName, artifactValidationSummary.apiName,
"project-release-readiness-artifact-validation-summary-view-model", "project-release-readiness-artifact-validation-summary-view-model",
@@ -249,6 +258,26 @@ assert.deepEqual(
["next-command", "none"], ["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( const staleArtifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel(
createProjectReleaseReadinessArtifactValidation({}), createProjectReleaseReadinessArtifactValidation({}),
); );
@@ -260,6 +289,18 @@ const staleArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkf
const invalidArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ const invalidArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: "{not-json", 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.equal(staleArtifactValidationSummary.phase, "blocked");
assert.match(staleArtifactValidationSummary.statusLine, /^Blocked: apiName, reportVersion/); 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-action-plan")?.value, "missing");
@@ -304,5 +345,17 @@ assert.deepEqual(
["next-command", "wasm-port/tests/host/verify_project_release_gate.sh"], ["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"); console.log("project_release_gate_manifest_node_smoke=ok");

View File

@@ -56,6 +56,7 @@ import {
isSupportedIniPanelLaunchApiManifest, isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest, isSupportedIniPanelShellIntegrationManifest,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
loadProjectReleaseReadinessArtifactUrlWorkflow,
loadMachineSessionFromOpfs, loadMachineSessionFromOpfs,
loadMachineSessionSnapshot, loadMachineSessionSnapshot,
loadMachineTextFiles, loadMachineTextFiles,
@@ -116,6 +117,7 @@ const requiredExports = [
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl", "loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl",
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl, loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
], ],
["loadProjectReleaseReadinessArtifactUrlWorkflow", loadProjectReleaseReadinessArtifactUrlWorkflow],
["planIniFileContextStaging", planIniFileContextStaging], ["planIniFileContextStaging", planIniFileContextStaging],
["planSimConfigStaging", planSimConfigStaging], ["planSimConfigStaging", planSimConfigStaging],
["analyzeIniRuntimeBoundaries", analyzeIniRuntimeBoundaries], ["analyzeIniRuntimeBoundaries", analyzeIniRuntimeBoundaries],
@@ -355,6 +357,24 @@ assert.equal(
createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "not-json" }).parsed, createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "not-json" }).parsed,
false, 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( assert.deepEqual(
createProjectReleaseReadinessArtifactValidation({}).missing, createProjectReleaseReadinessArtifactValidation({}).missing,
[ [