新增项目发布 gate action plan API

This commit is contained in:
2026-06-16 10:51:18 +08:00
parent 1d383b475c
commit 9abca15d60
9 changed files with 185 additions and 9 deletions

View File

@@ -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.

View File

@@ -1,6 +1,7 @@
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export {
createProjectReleaseGateActionPlan,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactValidation,

View File

@@ -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: [
{