新增项目发布 gate action plan API
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
|
||||
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
|
||||
export {
|
||||
createProjectReleaseGateActionPlan,
|
||||
createProjectReleaseGateManifest,
|
||||
createProjectReleaseGateResultMatrix,
|
||||
createProjectReleaseReadinessArtifactValidation,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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()",
|
||||
|
||||
@@ -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), {
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user