新增 release gate execution summary API

This commit is contained in:
2026-06-16 17:39:47 +08:00
parent 35bc25714d
commit 19eabdf06e
13 changed files with 411 additions and 14 deletions

View File

@@ -55,6 +55,7 @@ import {
createMachineSessionSnapshotPayload,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,
@@ -214,12 +215,15 @@ with caller-provided explicit results and observed output evidence. Each row
includes the command, expected output, matched observed output when present,
evidence kind, and `passed`/`unknown` status so CI or browser dashboards can
render release-gate execution state without scraping shell scripts.
`createProjectReleaseGateExecutionSummaryViewModel()` turns that execution
manifest into stable dashboard status text, evidence counts, next missing gate,
summary rows, and normalized per-gate rows.
`createProjectReleaseReadinessReport()` returns a machine-readable release
readiness report that embeds the same manifest, result matrix, and action plan
beside the gate execution manifest, 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, action plan,
gate execution manifest, gate execution summary view-model, 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.
@@ -238,8 +242,9 @@ it with `createProjectReleaseReadinessArtifactValidation()` without copying the
host test assertions. The validation result includes `expectedGateCount` and
`expectedGateIds` so external callers can compare an artifact against the
current manifest, plus `gateManifestReady`, `gateExecutionManifestReady`,
`gateResultMatrixReady`, and `gateActionPlanReady` so stale artifacts that omit
the embedded gate evidence chain fail the same executable gate.
`gateExecutionSummaryReady`, `gateResultMatrixReady`, and
`gateActionPlanReady` so stale artifacts that omit the embedded gate evidence
chain fail the same executable gate.
`createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns that
validation result into stable status text and display rows for dashboards or
browser shells without reinterpreting the artifact fields.

View File

@@ -3,6 +3,7 @@ export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export {
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,

View File

@@ -176,6 +176,81 @@ export function createProjectReleaseGateExecutionManifest({
};
}
export function createProjectReleaseGateExecutionSummaryViewModel(
executionManifest = createProjectReleaseGateExecutionManifest(),
) {
const rows = arrayOrEmpty(executionManifest?.rows);
const ready = executionManifest?.ready === true;
const missingGateIds = arrayOrEmpty(executionManifest?.missingGateIds);
const statusText = ready ? "Ready" : "Waiting";
const detailText = ready
? "All release gate evidence present"
: (missingGateIds.length > 0 ? missingGateIds.join(", ") : "release gate evidence missing");
const evidenceCounts = rows.reduce((counts, row) => ({
...counts,
[row.evidence ?? "missing"]: (counts[row.evidence ?? "missing"] ?? 0) + 1,
}), {});
return {
apiName: "project-release-gate-execution-summary-view-model",
viewModelVersion: 1,
phase: ready ? "ready" : "waiting",
ready,
title: "Project release gate execution",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
gateCount: executionManifest?.gateCount ?? rows.length,
passedCount: executionManifest?.passedCount ?? rows.filter(({ passed }) => passed === true).length,
unknownCount: executionManifest?.unknownCount ?? rows.filter(({ passed }) => passed !== true).length,
missingGateIds,
nextGateId: missingGateIds[0] ?? null,
evidenceCounts,
rows: [
{
id: "gate-results",
label: "Release gate evidence",
value: `${executionManifest?.passedCount ?? 0}/${executionManifest?.gateCount ?? rows.length} passed`,
},
{
id: "unknown-gates",
label: "Unknown gates",
value: `${executionManifest?.unknownCount ?? 0}`,
},
{
id: "next-gate",
label: "Next gate",
value: missingGateIds[0] ?? "none",
},
{
id: "expected-output-evidence",
label: "Expected-output evidence",
value: `${evidenceCounts["expected-output"] ?? 0}`,
},
{
id: "explicit-result-evidence",
label: "Explicit-result evidence",
value: `${evidenceCounts["explicit-result"] ?? 0}`,
},
{
id: "missing",
label: "Missing gate evidence",
value: missingGateIds.length > 0 ? missingGateIds.join(", ") : "none",
},
],
gateRows: rows.map((row) => ({
id: row.id,
label: row.label,
command: row.command,
expectedOutput: row.expectedOutput ?? null,
observedOutput: row.observedOutput ?? null,
evidence: row.evidence ?? "missing",
status: row.status ?? (row.passed === true ? "passed" : "unknown"),
passed: row.passed === true,
})),
};
}
export function createProjectReleaseGateResultMatrix({
gateResults = {},
observedOutputs = [],
@@ -274,6 +349,9 @@ export function createProjectReleaseReadinessReport({
observedOutputs,
manifest: gateManifest,
});
const gateExecutionSummaryViewModel = createProjectReleaseGateExecutionSummaryViewModel(
gateExecutionManifest,
);
const gateResultMatrix = createProjectReleaseGateResultMatrix({
gateResults,
observedOutputs,
@@ -318,6 +396,7 @@ export function createProjectReleaseReadinessReport({
promotedBlockedFamilies,
gateManifest,
gateExecutionManifest,
gateExecutionSummaryViewModel,
gateResultMatrix,
gateActionPlan,
gates: gateRows,
@@ -432,6 +511,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
label: "Gate execution manifest",
value: validation?.gateExecutionManifestReady === true ? "ready" : "missing",
},
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: validation?.gateExecutionSummaryReady === true ? "ready" : "missing",
},
{
id: "gate-result-matrix",
label: "Gate result matrix",
@@ -889,6 +973,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel);
const gateResultMatrix = objectOrEmpty(artifactObject.gateResultMatrix);
const gateActionPlan = objectOrEmpty(artifactObject.gateActionPlan);
const gates = arrayOrEmpty(artifactObject.gates);
@@ -917,6 +1002,13 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
&& gateExecutionManifest.unknownCount === 0
&& executionRows.length === REQUIRED_RELEASE_GATES.length
&& executionRows.every(({ command, status }) => typeof command === "string" && status === "passed");
const executionSummaryReady = gateExecutionSummaryViewModel.apiName === "project-release-gate-execution-summary-view-model"
&& gateExecutionSummaryViewModel.viewModelVersion === 1
&& gateExecutionSummaryViewModel.ready === true
&& gateExecutionSummaryViewModel.passedCount === REQUIRED_RELEASE_GATES.length
&& gateExecutionSummaryViewModel.unknownCount === 0
&& gateExecutionSummaryViewModel.nextGateId === null
&& arrayOrEmpty(gateExecutionSummaryViewModel.gateRows).length === REQUIRED_RELEASE_GATES.length;
const actionPlanReady = gateActionPlan.apiName === "project-release-gate-action-plan"
&& gateActionPlan.planVersion === 1
&& gateActionPlan.ready === true
@@ -942,6 +1034,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(arrayOrEmpty(artifactObject.promotedBlockedFamilies).length === 0 ? [] : ["promotedBlockedFamilies"]),
...(manifestReady ? [] : ["gateManifest"]),
...(executionManifestReady ? [] : ["gateExecutionManifest"]),
...(executionSummaryReady ? [] : ["gateExecutionSummaryViewModel"]),
...(matrixReady ? [] : ["gateResultMatrix"]),
...(actionPlanReady ? [] : ["gateActionPlan"]),
...(gateCountReady ? [] : ["gates.length"]),
@@ -964,6 +1057,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
expectedGateIds,
gateManifestReady: manifestReady,
gateExecutionManifestReady: executionManifestReady,
gateExecutionSummaryReady: executionSummaryReady,
gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady,
blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
@@ -988,6 +1082,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
label: "Gate execution manifest",
value: executionManifestReady ? "ready" : "missing",
},
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: executionSummaryReady ? "ready" : "missing",
},
{
id: "gate-result-matrix",
label: "Gate result matrix",