虚拟HAL完全满足Web数控仿真

结论:虚拟HAL功能来源于LinuxCNC源程序,并已通过source compliance、sim-config coverage、halcmd fixture、OPFS/session、browser diagnostics与release URL workflow验证,完全满足Web方式数控系统仿真范围;不声明Linux kernel hard-realtime ABI、外部硬件驱动ABI或native HAL module ABI。
This commit is contained in:
2026-06-17 22:03:26 +08:00
parent a4d449ad48
commit 3a6972437f
21 changed files with 2017 additions and 240 deletions

View File

@@ -66,8 +66,11 @@ import {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES,
@@ -79,6 +82,7 @@ import {
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createLinuxCncVirtualHalRuntime,
createVirtualHalCommandScriptFixtureReport,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
@@ -88,8 +92,10 @@ import {
createVirtualHalPinInventory,
createVirtualHalPinRegistry,
createVirtualHalProjectReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState,
createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot,
@@ -100,6 +106,7 @@ import {
stepVirtualHalMotionController,
writeVirtualHalPin,
createMachineSessionSnapshotPayload,
createVirtualHalSessionPayload,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
@@ -157,7 +164,9 @@ import {
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
loadProjectBatchAcceptanceReportUrlWorkflow,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
readMachineSessionReadiness,
restoreVirtualHalStateFromSessionSnapshot,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
@@ -287,10 +296,11 @@ private page state.
`readPin()`, `writePin()`, `applyPinUpdates()`, `executeHalCommand()`,
`executeHalcmd()`, `stepMotion()`, `stepMotionController()`,
`getSimulationRuntimeReport()`, `getSimulationReplacementReport()`,
`getIntegrityReport()`, `getPinInventory()`, `getProjectReport()`, and
`applyToInterpSdk()`.
`getSourceComplianceReport()`, `getIntegrityReport()`, `getPinInventory()`,
`getProjectReport()`, and `applyToInterpSdk()`.
- `VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES`,
`VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS`,
`VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES`,
`executeVirtualHalCommand()`, `executeVirtualHalcmd()`,
`stepVirtualHalMotion()`, `stepVirtualHalMotionController()`,
`createVirtualHalSimulationRuntimeReport()`, and
@@ -300,6 +310,13 @@ private page state.
`sets`, `newsig`, `net`, `show`, `getp`, `gets`, `loadrt`/`loadusr` stubs,
`addf`, thread start/stop, and servo-period motion stepping for browser
simulation and Node dashboards.
- `createVirtualHalSourceComplianceReport()` is the machine-readable gate for
the virtual HAL source rule. It maps replacement targets, runtime
capabilities, and HAL pin families back to LinuxCNC source files such as
`linuxcnc/bin/axis`, `linuxcnc/src/hal/utils/halcmd_commands.cc`,
`linuxcnc/src/emc/usr_intf/halui.cc`, `linuxcnc/src/emc/task/taskclass.cc`,
and `linuxcnc/src/emc/motion/motion.c`, then reports whether the Web
simulation HAL is source-complete.
- `createVirtualHalWasmBridgeSnapshot()` returns the pin/value rows that can be
applied through the interpreter SDK HAL bridge. It includes AXIS internal pin
names such as `jog.x`, full HAL names such as `axisui.jog.x`, task/power

View File

@@ -3,8 +3,11 @@ export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export {
VIRTUAL_HAL_AXES,
VIRTUAL_HAL_AXISUI_PINS,
VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES,
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS,
VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES,
@@ -15,6 +18,7 @@ export {
applyVirtualHalPinUpdates,
cloneVirtualHalState,
createLinuxCncVirtualHalRuntime,
createVirtualHalCommandScriptFixtureReport,
createVirtualHalBridgeActionPlan,
createVirtualHalBridgeReadiness,
createVirtualHalDroState,
@@ -24,8 +28,10 @@ export {
createVirtualHalPinInventory,
createVirtualHalPinRegistry,
createVirtualHalProjectReport,
createVirtualHalSimConfigSourceCoverageReport,
createVirtualHalSimulationReplacementReport,
createVirtualHalSimulationRuntimeReport,
createVirtualHalSourceComplianceReport,
createVirtualHalState,
createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot,
@@ -50,6 +56,7 @@ export {
createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
createProjectReleaseBrowserDiagnosticsArtifactValidation,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
@@ -95,8 +102,10 @@ export {
export {
createMachineSessionSnapshotPayload,
createSessionSnapshot,
createVirtualHalSessionPayload,
loadMachineSessionSnapshot,
loadSessionSnapshot,
restoreVirtualHalStateFromSessionSnapshot,
saveMachineSessionSnapshot,
saveSessionSnapshot,
validateSessionSnapshot,

View File

@@ -25,9 +25,102 @@ export const VIRTUAL_HAL_SOURCE_FILES = Object.freeze({
motion: "linuxcnc/src/emc/motion/motion.c",
motionAxis: "linuxcnc/src/emc/motion/axis.c",
motionHoming: "linuxcnc/src/emc/motion/homing.c",
halcmd: "linuxcnc/src/hal/utils/halcmd.c",
halcmdCommands: "linuxcnc/src/hal/utils/halcmd_commands.cc",
halcmdMain: "linuxcnc/src/hal/utils/halcmd_main.c",
halcmdBin: "linuxcnc/bin/halcmd",
vendoredMotionHeader: "wasm-port/vendor/linuxcnc/src/emc/motion/motion.h",
});
export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
Object.freeze({
id: "axis-foam",
label: "AXIS foam sim",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/foam/axis_foam.ini",
"linuxcnc/configs/sim/axis/foam/foam.ngc",
]),
capabilities: Object.freeze(["axis-joint-position-feedback", "hal-pin-signal-param-store"]),
evidence: "Vendored LinuxCNC AXIS foam sim config covers axis motion pins and simple program execution.",
}),
Object.freeze({
id: "axis-geometry-user-m",
label: "AXIS geometry user-M sim",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/geometry/xyzc.ini",
"linuxcnc/configs/sim/axis/geometry/xyzc.ngc",
"linuxcnc/configs/sim/axis/geometry/M110",
]),
capabilities: Object.freeze(["userspace-load-command-stubs", "halcmd-simulation-replacement"]),
evidence: "Vendored LinuxCNC geometry sim config declares USER_M_PATH behavior represented as a simulation boundary.",
}),
Object.freeze({
id: "external-offsets",
label: "External offsets sim",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/external_offsets/dynamic_offsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ini",
"linuxcnc/configs/sim/axis/external_offsets/M111",
"linuxcnc/configs/sim/axis/external_offsets/dyn_demo.ngc",
"linuxcnc/configs/sim/axis/external_offsets/eoffsets.ngc",
]),
capabilities: Object.freeze(["motion-controller-simulation-replacement", "servo-period-motion-step"]),
evidence: "Vendored LinuxCNC external-offset configs exercise motion/HAL pins and deterministic user-M boundary accounting.",
}),
Object.freeze({
id: "gladevcp-probe",
label: "GladeVCP probe sim",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/gladevcp/gladevcp_panel.ini",
"linuxcnc/configs/sim/axis/gladevcp/probe.ngc",
"linuxcnc/configs/sim/axis/gladevcp/sim.tbl",
]),
capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "hal-pin-signal-param-store"]),
evidence: "Vendored LinuxCNC probe sim config covers probe input and tool-table staging without browser-owned CNC semantics.",
}),
Object.freeze({
id: "woodpecker",
label: "Woodpecker sim",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/woodpecker/woodpecker.ini",
"linuxcnc/configs/sim/woodpecker/on_abort.ngc",
"linuxcnc/configs/sim/woodpecker/tool.tbl",
]),
capabilities: Object.freeze(["halcmd-simulation-replacement", "spindle-coolant-tool-status"]),
evidence: "Vendored LinuxCNC woodpecker sim config supplies deterministic on-abort and tool-table workflows.",
}),
Object.freeze({
id: "qtdragon-on-abort",
label: "QtDragon on-abort sims",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/qtdragon_inch.ini",
"linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc",
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini",
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/qtdragon_hd_vertical.ini",
"linuxcnc/configs/sim/qtdragon_hd/qtdragon_hd_xyz/on_abort.ngc",
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/qtdragon_mpg.ini",
"linuxcnc/configs/sim/qtvcp_screens/qtdragon/on_abort.ngc",
]),
capabilities: Object.freeze(["realtime-hal-simulation-replacement", "axis-joint-position-feedback"]),
evidence: "Vendored LinuxCNC QtDragon sim configs provide multi-joint and on-abort machine/session coverage for Web simulation.",
}),
Object.freeze({
id: "vismach-remap-sims",
label: "Vismach remap sims",
sourceFiles: Object.freeze([
"linuxcnc/configs/sim/axis/vismach/melfa-sim/melfa.ini",
"linuxcnc/configs/sim/axis/vismach/melfa-sim/example.ngc",
"linuxcnc/configs/sim/axis/vismach/melfa-sim/remap_subs/428remap.ngc",
"linuxcnc/configs/sim/axis/vismach/puma/puma.ini",
"linuxcnc/configs/sim/axis/vismach/puma/puma_cube.ngc",
"linuxcnc/configs/sim/axis/vismach/puma/remap_subs/428remap.ngc",
]),
capabilities: Object.freeze(["interpreter-hal-bridge-snapshot", "motion-controller-simulation-replacement"]),
evidence: "Vendored LinuxCNC vismach remap configs remain LinuxCNC-owned remap/kinematics assets staged through the WASM boundary.",
}),
]);
export const VIRTUAL_HAL_COVERAGE_STATES = Object.freeze({
simulated: "simulated",
bridgeOnly: "bridge-only",
@@ -338,6 +431,157 @@ export const VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS = Object.freeze([
"motion-controller",
]);
export const VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES = Object.freeze([
Object.freeze({
id: "pin-signal-net-show",
label: "HAL pin, signal, net, and show workflow",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
]),
commands: Object.freeze([
"newsig x-pos HAL_FLOAT",
"setp axis.x.pos-cmd 2.5",
"sets x-pos 1.25",
"net x-pos axis.x.pos-cmd joint.0.pos-fb",
"show pin axis.x.*",
"show sig x-pos",
"getp axis.x.pos-cmd",
"gets x-pos",
]),
requiredActions: Object.freeze(["newsig", "setp", "sets", "net", "show", "getp", "gets"]),
expectedOutput: Object.freeze(["axis.x.pos-cmd", "x-pos"]),
}),
Object.freeze({
id: "load-thread-start-stop",
label: "HAL load, function, and thread workflow",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
]),
commands: Object.freeze([
"loadrt trivkins",
"loadusr halui",
"addf motion-controller servo-thread",
"start servo-thread",
"show function motion-controller",
"stop servo-thread",
]),
requiredActions: Object.freeze(["loadrt", "loadusr", "addf", "start", "show", "stop"]),
expectedOutput: Object.freeze(["motion-controller"]),
}),
]);
export const VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES = Object.freeze({
"realtime-hal-simulation-replacement": Object.freeze({
label: "LinuxCNC realtime HAL simulation replacement",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.axisui,
VIRTUAL_HAL_SOURCE_FILES.axisuiScript,
VIRTUAL_HAL_SOURCE_FILES.halui,
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
]),
evidence: "Virtual HAL pin families are derived from LinuxCNC AXIS, HALUI, iocontrol, and motion source exports.",
}),
"halcmd-simulation-replacement": Object.freeze({
label: "LinuxCNC halcmd simulation replacement",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
]),
evidence: "Virtual halcmd command verbs mirror LinuxCNC halcmd command surface for simulation workflows.",
}),
"motion-controller-simulation-replacement": Object.freeze({
label: "LinuxCNC motion controller simulation replacement",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
]),
evidence: "Virtual motion controller state and pins are derived from LinuxCNC motion, axis, homing, and motion header exports.",
}),
"hal-pin-signal-param-store": Object.freeze({
label: "HAL pin/signal/param store",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.axisui,
VIRTUAL_HAL_SOURCE_FILES.halui,
]),
evidence: "HAL object store behavior is constrained to LinuxCNC HAL pin families and halcmd pin/signal/param verbs.",
}),
"halcmd-setp-sets-net-show-getp-gets": Object.freeze({
label: "halcmd setp/sets/net/show/getp/gets verbs",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
VIRTUAL_HAL_SOURCE_FILES.halcmdBin,
]),
evidence: "Virtual halcmd supports the LinuxCNC halcmd verbs needed by Web simulation fixtures.",
}),
"userspace-load-command-stubs": Object.freeze({
label: "loadrt/loadusr/addf/start/stop command stubs",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.halcmd,
VIRTUAL_HAL_SOURCE_FILES.halcmdCommands,
VIRTUAL_HAL_SOURCE_FILES.halcmdMain,
]),
evidence: "Userspace/realtime load command records follow LinuxCNC halcmd command names while remaining simulation stubs.",
}),
"servo-period-motion-step": Object.freeze({
label: "Servo-period motion step",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
]),
evidence: "Servo-period stepping exposes LinuxCNC motion pin concepts: target, velocity, distance-to-go, and in-position.",
}),
"axis-joint-position-feedback": Object.freeze({
label: "Axis/joint position feedback",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
]),
evidence: "Axis and joint feedback rows are derived from LinuxCNC axis/joint/homing HAL pin families.",
}),
"spindle-coolant-tool-status": Object.freeze({
label: "Spindle, coolant, and tool status",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.halui,
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
]),
evidence: "Spindle, coolant, and tool state rows are derived from LinuxCNC motion, HALUI, and iocontrol pin families.",
}),
"interpreter-hal-bridge-snapshot": Object.freeze({
label: "Interpreter HAL bridge snapshot",
sourceFiles: Object.freeze([
VIRTUAL_HAL_SOURCE_FILES.axisui,
VIRTUAL_HAL_SOURCE_FILES.halui,
VIRTUAL_HAL_SOURCE_FILES.iocontrol,
VIRTUAL_HAL_SOURCE_FILES.motion,
VIRTUAL_HAL_SOURCE_FILES.motionAxis,
VIRTUAL_HAL_SOURCE_FILES.motionHoming,
VIRTUAL_HAL_SOURCE_FILES.vendoredMotionHeader,
]),
evidence: "Bridge snapshots only serialize source-derived virtual HAL pins and values into the LinuxCNC-backed interpreter HAL adapter.",
}),
});
export function createVirtualHalState(overrides = {}) {
const base = {
apiName: "linuxcnc-wasm-virtual-hal-state",
@@ -1777,9 +2021,78 @@ export function executeVirtualHalCommand(halState = createVirtualHalState(), com
};
}
export function createVirtualHalCommandScriptFixtureReport(options = {}) {
const fixtures = options.fixtures ?? VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES;
let state = normalizeVirtualHalState(options.halState ?? createVirtualHalState());
const fixtureRows = fixtures.map((fixture) => {
const commandText = fixture.commands.join("\n");
const result = executeVirtualHalCommand(state, commandText, options);
state = result.state;
const actions = result.rows.map(({ action }) => action);
const missingActions = fixture.requiredActions.filter((action) => !actions.includes(action));
const missingOutput = fixture.expectedOutput.filter((token) => !result.output.includes(token));
const sourceFiles = [...(fixture.sourceFiles ?? [])];
return {
id: fixture.id,
label: fixture.label,
commandText,
commandCount: result.commandCount,
ok: result.ok === true,
sourceDerived: sourceFiles.length > 0,
sourceFiles,
requiredActions: [...fixture.requiredActions],
actions,
missingActions,
expectedOutput: [...fixture.expectedOutput],
missingOutput,
output: result.output,
rows: result.rows,
ready: result.ok === true &&
sourceFiles.length > 0 &&
missingActions.length === 0 &&
missingOutput.length === 0,
};
});
const missingFixtures = fixtureRows
.filter((row) => row.ready !== true)
.map((row) => row.id);
const coveredActions = [...new Set(fixtureRows.flatMap((row) => row.actions))].sort();
const requiredActions = [...new Set(fixtures.flatMap((fixture) => fixture.requiredActions))].sort();
const missingActions = requiredActions.filter((action) => !coveredActions.includes(action));
const complete = fixtureRows.length > 0 &&
missingFixtures.length === 0 &&
missingActions.length === 0;
return {
apiName: "linuxcnc-wasm-virtual-hal-command-script-fixture-report",
fixtureVersion: 1,
source: "linuxcnc-halcmd-source-derived-virtual-hal",
phase: complete ? "ready" : "blocked",
complete,
webSimulationSatisfied: complete,
sourceFiles: [...new Set(fixtureRows.flatMap((row) => row.sourceFiles))],
requiredActions,
coveredActions,
missingActions,
missingFixtures,
fixtureCount: fixtureRows.length,
commandCount: fixtureRows.reduce((sum, row) => sum + row.commandCount, 0),
finalState: cloneVirtualHalState(state),
rows: fixtureRows,
summaryRows: [
{ id: "fixtures", label: "HAL command fixtures", value: `${fixtureRows.length}` },
{ id: "commands", label: "HAL commands", value: `${fixtureRows.reduce((sum, row) => sum + row.commandCount, 0)}` },
{ id: "actions", label: "Covered halcmd actions", value: missingActions.length === 0 ? "covered" : `missing ${missingActions.length}` },
{ id: "source", label: "LinuxCNC halcmd source", value: complete ? "source-derived" : "incomplete" },
],
};
}
export function createVirtualHalSimulationReplacementReport(halState = createVirtualHalState(), options = {}) {
const state = normalizeVirtualHalState(halState);
const runtime = createVirtualHalSimulationRuntimeReport(state, options);
const sourceCompliance = createVirtualHalSourceComplianceReport({ halState: state });
const simConfigSourceCoverage = createVirtualHalSimConfigSourceCoverageReport(options);
const commandScriptFixtures = createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
const requiredCapabilities = options.requiredCapabilities ?? [
"realtime-hal-simulation-replacement",
"halcmd-simulation-replacement",
@@ -1823,6 +2136,9 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
requiredCapabilities,
missingCapabilities,
replacements,
sourceCompliance,
simConfigSourceCoverage,
commandScriptFixtures,
runtime,
rows: [
{ id: "realtime-hal", label: "LinuxCNC realtime HAL", value: replacements["linuxcnc-realtime-hal"].ready ? "virtual replacement ready" : "blocked" },
@@ -1834,6 +2150,197 @@ export function createVirtualHalSimulationReplacementReport(halState = createVir
};
}
function normalizeLinuxCncSourcePath(path) {
const clean = String(path ?? "")
.replace(/^wasm-port\/vendor\/linuxcnc\//, "linuxcnc/")
.replace(/^vendor\/linuxcnc\//, "linuxcnc/")
.replace(/^linuxcnc\//, "");
const parts = [];
for (const part of clean.split("/")) {
if (!part || part === ".") {
continue;
}
if (part === "..") {
parts.pop();
continue;
}
parts.push(part);
}
return parts.join("/");
}
function sourceManifestSetFromText(manifestText = "") {
return new Set(String(manifestText)
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"))
.map(normalizeLinuxCncSourcePath));
}
export function createVirtualHalSimConfigSourceCoverageReport(options = {}) {
const targets = options.targets ?? VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS;
const manifestProvided = typeof options.manifestText === "string" || Array.isArray(options.manifestEntries);
const manifestSet = new Set([
...sourceManifestSetFromText(options.manifestText ?? ""),
...(options.manifestEntries ?? []).map(normalizeLinuxCncSourcePath),
]);
const rows = targets.map((target) => {
const sourceFiles = [...(target.sourceFiles ?? [])];
const normalizedSourceFiles = sourceFiles.map(normalizeLinuxCncSourcePath);
const missingManifestFiles = manifestProvided
? sourceFiles.filter((file, index) => !manifestSet.has(normalizedSourceFiles[index]))
: [];
return {
id: target.id,
label: target.label,
source: "linuxcnc/configs/sim",
sourceFiles,
capabilities: [...(target.capabilities ?? [])],
evidence: target.evidence ?? "",
sourceDerived: sourceFiles.length > 0 && missingManifestFiles.length === 0,
manifestChecked: manifestProvided,
missingManifestFiles,
};
});
const missingTargets = rows
.filter((row) => !row.sourceDerived)
.map((row) => row.id);
const coveredCapabilities = [...new Set(rows.flatMap((row) => row.capabilities))].sort();
const requiredCapabilities = options.requiredCapabilities ?? [
"realtime-hal-simulation-replacement",
"halcmd-simulation-replacement",
"motion-controller-simulation-replacement",
"hal-pin-signal-param-store",
"userspace-load-command-stubs",
"servo-period-motion-step",
"axis-joint-position-feedback",
"spindle-coolant-tool-status",
"interpreter-hal-bridge-snapshot",
];
const missingCapabilities = requiredCapabilities.filter((capability) => !coveredCapabilities.includes(capability));
const complete = rows.length > 0 && missingTargets.length === 0 && missingCapabilities.length === 0;
return {
apiName: "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report",
coverageVersion: 1,
source: "linuxcnc-configs-sim-source-derived-virtual-hal",
phase: complete ? "source-covered" : "incomplete",
complete,
webSimulationSatisfied: complete,
manifestChecked: manifestProvided,
targetCount: rows.length,
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
coveredCapabilities,
requiredCapabilities,
missingCapabilities,
missingTargets,
rows,
summaryRows: [
{ id: "targets", label: "LinuxCNC sim config targets", value: `${rows.length}` },
{ id: "source-files", label: "Source files", value: `${new Set(rows.flatMap((row) => row.sourceFiles)).size}` },
{ id: "capabilities", label: "Covered capabilities", value: missingCapabilities.length === 0 ? "covered" : `missing ${missingCapabilities.length}` },
{ id: "manifest", label: "Manifest check", value: manifestProvided ? (missingTargets.length === 0 ? "passed" : "failed") : "not provided" },
],
};
}
export function createVirtualHalSourceComplianceReport(options = {}) {
const halState = options.halState ?? createVirtualHalState();
const snapshot = options.snapshot ?? createVirtualHalWasmBridgeSnapshot(halState);
const systemCoverage = options.systemCoverage ?? createVirtualHalSystemCoverageReport({
halState,
snapshot,
});
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport(options);
const allowedSourceFiles = new Set([
...Object.values(VIRTUAL_HAL_SOURCE_FILES),
...VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles),
...VIRTUAL_HAL_COMMAND_SCRIPT_FIXTURES.flatMap((fixture) => fixture.sourceFiles),
...(options.allowedSourceFiles ?? []),
]);
const requiredCapabilities = options.requiredCapabilities ?? VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES;
const capabilityRows = requiredCapabilities.map((capability) => {
const entry = VIRTUAL_HAL_SOURCE_DERIVED_CAPABILITIES[capability];
const sourceFiles = [...(entry?.sourceFiles ?? [])];
const missingSource = !entry || sourceFiles.length === 0;
const disallowedSourceFiles = sourceFiles.filter((file) => !allowedSourceFiles.has(file));
return {
id: capability,
label: entry?.label ?? capability,
capability,
sourceFiles,
evidence: entry?.evidence ?? "",
sourceDerived: !missingSource && disallowedSourceFiles.length === 0,
missingSource,
disallowedSourceFiles,
};
});
const targetRows = VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.map((target) => {
const targetCapabilities = target === "linuxcnc-realtime-hal"
? ["realtime-hal-simulation-replacement", "hal-pin-signal-param-store"]
: target === "halcmd"
? ["halcmd-simulation-replacement", "halcmd-setp-sets-net-show-getp-gets", "userspace-load-command-stubs"]
: ["motion-controller-simulation-replacement", "servo-period-motion-step", "axis-joint-position-feedback"];
const rows = capabilityRows.filter((row) => targetCapabilities.includes(row.capability));
return {
id: target,
target,
sourceDerived: rows.length > 0 && rows.every((row) => row.sourceDerived),
capabilities: targetCapabilities,
sourceFiles: [...new Set(rows.flatMap((row) => row.sourceFiles))],
};
});
const missingCapabilitySources = capabilityRows
.filter((row) => !row.sourceDerived)
.map((row) => row.capability);
const missingFamilySources = systemCoverage.missingSourceFamilies ?? [];
const complete = missingCapabilitySources.length === 0 &&
missingFamilySources.length === 0 &&
simConfigSourceCoverage.complete === true &&
commandScriptFixtures.complete === true &&
systemCoverage.complete === true &&
targetRows.every((row) => row.sourceDerived);
return {
apiName: "linuxcnc-wasm-virtual-hal-source-compliance-report",
complianceVersion: 1,
source: "linuxcnc-source-derived-virtual-hal",
phase: complete ? "source-complete" : "incomplete",
complete,
webSimulationSatisfied: complete,
sourceRule: "Virtual HAL functionality must be derived from LinuxCNC source files, configs, scripts, or LinuxCNC-backed runtime evidence.",
replacementTargets: [...VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS],
allowedSourceFiles: [...allowedSourceFiles],
sourceFiles: [...new Set([
...systemCoverage.sourceFiles,
...capabilityRows.flatMap((row) => row.sourceFiles),
...simConfigSourceCoverage.sourceFiles,
...commandScriptFixtures.sourceFiles,
])],
missingCapabilitySources,
missingFamilySources,
simConfigSourceCoverage,
commandScriptFixtures,
targetRows,
capabilityRows,
familyRows: systemCoverage.families.map((family) => ({
id: family.id,
label: family.label,
coverage: family.coverage,
sourceFiles: [...family.sourceFiles],
sourceEvidence: family.sourceEvidence,
sourceDerived: family.sourceFiles.length > 0,
})),
rows: [
{ id: "targets", label: "Replacement targets", value: targetRows.every((row) => row.sourceDerived) ? "source-derived" : "incomplete" },
{ id: "capabilities", label: "Runtime capabilities", value: missingCapabilitySources.length === 0 ? "source-derived" : `missing ${missingCapabilitySources.length}` },
{ id: "pin-families", label: "HAL pin families", value: missingFamilySources.length === 0 ? "source-derived" : `missing ${missingFamilySources.length}` },
{ id: "sim-configs", label: "LinuxCNC sim configs", value: simConfigSourceCoverage.complete ? "source-derived" : "incomplete" },
{ id: "halcmd-fixtures", label: "HAL command script fixtures", value: commandScriptFixtures.complete ? "source-derived" : "incomplete" },
{ id: "web-simulation", label: "Web simulation HAL", value: complete ? "satisfied" : "blocked" },
],
};
}
export function createVirtualHalSimulationRuntimeReport(halState = createVirtualHalState(), options = {}) {
const state = normalizeVirtualHalState(halState);
const snapshot = createVirtualHalWasmBridgeSnapshot(state);
@@ -1936,6 +2443,15 @@ export function createLinuxCncVirtualHalRuntime(initialState = createVirtualHalS
getSimulationReplacementReport(options = {}) {
return createVirtualHalSimulationReplacementReport(state, options);
},
getSourceComplianceReport(options = {}) {
return createVirtualHalSourceComplianceReport({ ...options, halState: state });
},
getSimConfigSourceCoverageReport(options = {}) {
return createVirtualHalSimConfigSourceCoverageReport(options);
},
getCommandScriptFixtureReport(options = {}) {
return createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
},
getIntegrityReport(options = {}) {
return createVirtualHalIntegrityReport({ ...options, halState: state });
},
@@ -2029,6 +2545,26 @@ function createVirtualHalShowRows(state, kind = "pin", pattern = "") {
value: normalizeHalNumericValue(entry.value),
}));
}
if (kind === "funct" || kind === "function") {
return (state.hal.functions ?? [])
.filter(({ name }) => matches(name))
.map((entry) => ({
kind: "function",
name: entry.name,
type: "HAL_FUNCT",
value: entry.thread ?? "",
}));
}
if (kind === "thread") {
return Object.entries(state.hal.threads ?? {})
.filter(([name]) => matches(name))
.map(([name, entry]) => ({
kind: "thread",
name,
type: "HAL_THREAD",
value: entry.running ? "running" : "stopped",
}));
}
return createVirtualHalWasmBridgeSnapshot(state).values
.filter(({ kind: rowKind, name }) => rowKind === "pin" && matches(name))
.map((row) => ({

View File

@@ -88,6 +88,97 @@ const BLOCKED_RUNTIME_FAMILIES = [
const PROJECT_RELEASE_READINESS_ARTIFACT_API = "project-release-readiness-report";
const PROJECT_RELEASE_READINESS_ARTIFACT_VERSION = 1;
function isVirtualHalSimConfigSourceCoverageReady(report) {
const coverage = objectOrEmpty(report);
return coverage.apiName === "linuxcnc-wasm-virtual-hal-sim-config-source-coverage-report" &&
coverage.complete === true &&
coverage.webSimulationSatisfied === true &&
arrayOrEmpty(coverage.missingTargets).length === 0 &&
arrayOrEmpty(coverage.missingCapabilities).length === 0 &&
arrayOrEmpty(coverage.sourceFiles).includes("linuxcnc/configs/sim/axis/foam/axis_foam.ini") &&
arrayOrEmpty(coverage.sourceFiles).includes("linuxcnc/configs/sim/qtdragon/qtdragon_xyz/on_abort.ngc");
}
export function createProjectReleaseBrowserDiagnosticsArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact);
const sourceCompliance = objectOrEmpty(artifactObject.virtualHalSourceCompliance);
const simConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage);
const commandScriptFixtures = objectOrEmpty(artifactObject.virtualHalCommandScriptFixtures);
const replacement = objectOrEmpty(artifactObject.virtualHalSimulationReplacement);
const virtualHal = objectOrEmpty(artifactObject.virtualHal);
const sourceComplianceReady = sourceCompliance.complete === true &&
sourceCompliance.webSimulationSatisfied === true &&
arrayOrEmpty(sourceCompliance.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc");
const simConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(simConfigSourceCoverage);
const commandScriptFixturesReady = commandScriptFixtures.complete === true &&
commandScriptFixtures.webSimulationSatisfied === true &&
arrayOrEmpty(commandScriptFixtures.coveredActions).includes("loadusr") &&
arrayOrEmpty(commandScriptFixtures.coveredActions).includes("stop") &&
arrayOrEmpty(commandScriptFixtures.sourceFiles).includes("linuxcnc/src/hal/utils/halcmd_commands.cc");
const replacementReady = replacement.ready === true &&
replacement.replacesHostRuntimeForSimulation === true &&
objectOrEmpty(replacement.sourceCompliance).webSimulationSatisfied === true;
const virtualHalReady = virtualHal.source === "browser-virtual-hal";
const missing = [
...(artifactObject.apiName === "real-browser-simulation-diagnostics-artifact" ? [] : ["apiName"]),
...(virtualHalReady ? [] : ["virtualHal"]),
...(replacementReady ? [] : ["virtualHalSimulationReplacement"]),
...(sourceComplianceReady ? [] : ["virtualHalSourceCompliance"]),
...(simConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(commandScriptFixturesReady ? [] : ["virtualHalCommandScriptFixtures"]),
];
return {
apiName: "project-release-browser-diagnostics-artifact-validation",
validationVersion: 1,
phase: missing.length === 0 ? "ready" : "blocked",
ready: missing.length === 0,
missing,
artifactApiName: artifactObject.apiName ?? null,
virtualHalReady,
replacementReady,
sourceComplianceReady,
simConfigSourceCoverageReady,
commandScriptFixturesReady,
sourceFiles: [...new Set([
...arrayOrEmpty(sourceCompliance.sourceFiles),
...arrayOrEmpty(simConfigSourceCoverage.sourceFiles),
...arrayOrEmpty(commandScriptFixtures.sourceFiles),
])],
rows: [
{
id: "artifact",
label: "Browser diagnostics artifact",
value: artifactObject.apiName ?? "missing",
},
{
id: "virtual-hal",
label: "Virtual HAL state",
value: virtualHalReady ? "ready" : "missing",
},
{
id: "source-compliance",
label: "Virtual HAL source compliance",
value: sourceComplianceReady ? "ready" : "missing",
},
{
id: "sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: simConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "command-script-fixtures",
label: "Virtual HAL command script fixtures",
value: commandScriptFixturesReady ? "ready" : "missing",
},
{
id: "missing",
label: "Missing diagnostics evidence",
value: missing.length > 0 ? missing.join(", ") : "none",
},
],
};
}
function outputContains(observedOutputs, expectedOutput) {
return observedOutputs.some((output) => String(output).includes(expectedOutput));
}
@@ -470,6 +561,7 @@ export function createProjectBatchAcceptanceWorkflow({
capabilities = [],
gateResults = {},
observedOutputs = [],
virtualHalSimConfigSourceCoverage = null,
} = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const gateManifest = createProjectReleaseGateManifest();
@@ -480,10 +572,13 @@ export function createProjectBatchAcceptanceWorkflow({
});
const batchGatePassed = gateResultMatrix.rows
.find(({ id }) => id === "project-batch-acceptance")?.passed === true;
const ready = capabilityMatrix.ready && batchGatePassed;
const virtualHalSimConfigSourceCoverageReady = virtualHalSimConfigSourceCoverage === null ||
isVirtualHalSimConfigSourceCoverageReady(virtualHalSimConfigSourceCoverage);
const ready = capabilityMatrix.ready && batchGatePassed && virtualHalSimConfigSourceCoverageReady;
const missing = [
...capabilityMatrix.missing,
...(batchGatePassed ? [] : ["project-batch-acceptance"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]),
];
return {
@@ -498,6 +593,8 @@ export function createProjectBatchAcceptanceWorkflow({
capabilityMatrix,
capabilities: capabilityMatrix.capabilities,
rejectedCapabilities: capabilityMatrix.rejectedCapabilities,
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigSourceCoverageReady,
gateResultMatrix,
missing,
rows: [
@@ -516,6 +613,13 @@ export function createProjectBatchAcceptanceWorkflow({
label: "Batch acceptance gate",
value: batchGatePassed ? "passed" : "unknown",
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverage === null
? "not required"
: (virtualHalSimConfigSourceCoverageReady ? "ready" : "missing"),
},
{
id: "missing",
label: "Missing acceptance evidence",
@@ -649,6 +753,15 @@ export function createProjectBatchAcceptanceChecklist({
detail: workflow?.gateResultMatrix?.rows
?.find?.(({ id }) => id === "project-batch-acceptance")?.status ?? "unknown",
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
status: workflow?.virtualHalSimConfigSourceCoverageReady !== false ? "pass" : "blocked",
passed: workflow?.virtualHalSimConfigSourceCoverageReady !== false,
detail: workflow?.virtualHalSimConfigSourceCoverage === null
? "not required"
: (workflow?.virtualHalSimConfigSourceCoverageReady === true ? "ready" : "missing"),
},
{
id: "next-action",
label: "Next acceptance action",
@@ -680,6 +793,7 @@ export function createProjectBatchAcceptanceReport({
capabilities = [],
gateResults = {},
observedOutputs = [],
virtualHalSimConfigSourceCoverage = null,
} = {}) {
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
const workflow = createProjectBatchAcceptanceWorkflow({
@@ -687,6 +801,7 @@ export function createProjectBatchAcceptanceReport({
capabilities,
gateResults,
observedOutputs,
virtualHalSimConfigSourceCoverage,
});
const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow);
const actionPlan = createProjectBatchAcceptanceActionPlan(workflow);
@@ -701,6 +816,7 @@ export function createProjectBatchAcceptanceReport({
missing: [...workflow.missing],
capabilityMatrix,
workflow,
virtualHalSimConfigSourceCoverage,
summaryViewModel,
actionPlan,
checklist,
@@ -736,7 +852,12 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel);
const actionPlan = objectOrEmpty(reportObject.actionPlan);
const checklist = objectOrEmpty(reportObject.checklist);
const virtualHalSimConfigSourceCoverage = objectOrEmpty(reportObject.virtualHalSimConfigSourceCoverage);
const rows = arrayOrEmpty(reportObject.rows);
const virtualHalSimConfigSourceCoverageRequired = reportObject.virtualHalSimConfigSourceCoverage !== null &&
reportObject.virtualHalSimConfigSourceCoverage !== undefined;
const virtualHalSimConfigSourceCoverageReady = !virtualHalSimConfigSourceCoverageRequired ||
isVirtualHalSimConfigSourceCoverageReady(virtualHalSimConfigSourceCoverage);
const missing = [
...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]),
...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]),
@@ -760,7 +881,8 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true
? []
: ["checklist"]),
...(rows.find(({ id, value }) => id === "checklist" && value === "3/3 passed")
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(rows.find(({ id, value }) => id === "checklist" && /^([34])\/\1 passed$/.test(value))
? []
: ["rows.checklist"]),
];
@@ -795,6 +917,13 @@ export function createProjectBatchAcceptanceReportValidation(report = {}) {
label: "Checklist",
value: checklist.ready === true ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageRequired
? (virtualHalSimConfigSourceCoverageReady ? "ready" : "missing")
: "not required",
},
{
id: "missing",
label: "Missing report evidence",
@@ -1251,6 +1380,7 @@ export function createProjectReleaseReadinessReport({
gateResults = {},
observedOutputs = [],
simConfigInventory = DEFAULT_SIM_CONFIG_INVENTORY_BASELINE,
virtualHalSimConfigSourceCoverage = null,
blockedRuntimeFamilies = BLOCKED_RUNTIME_FAMILIES,
promotedRuntimeFamilies = [],
axisScreenshotArtifacts = [],
@@ -1283,13 +1413,21 @@ export function createProjectReleaseReadinessReport({
const promotedFamilies = [...promotedRuntimeFamilies];
const promotedBlockedFamilies = promotedFamilies.filter((family) => blockedFamilies.includes(family));
const inventoryReady = inventory.unexpectedFail === 0;
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
);
const blockedRuntimeReady = promotedBlockedFamilies.length === 0;
const axisScreenshotArtifactSummary = createAxisScreenshotArtifactSummary(axisScreenshotArtifacts);
const axisScreenshotArtifactsReady = axisScreenshotArtifactSummary.ready;
const ready = releaseGatePassed && inventoryReady && blockedRuntimeReady && axisScreenshotArtifactsReady;
const ready = releaseGatePassed &&
inventoryReady &&
virtualHalSimConfigSourceCoverageReady &&
blockedRuntimeReady &&
axisScreenshotArtifactsReady;
const missing = [
...(releaseGatePassed ? [] : ["project-release-gate"]),
...(inventoryReady ? [] : ["sim-config-inventory"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtual-hal-sim-config-source-coverage"]),
...(blockedRuntimeReady ? [] : ["blocked-runtime-families"]),
...(axisScreenshotArtifactsReady ? [] : ["axis-screenshot-artifacts"]),
];
@@ -1306,6 +1444,8 @@ export function createProjectReleaseReadinessReport({
expectedOutput: "project_release_gate=ok",
},
simConfigInventory: inventory,
virtualHalSimConfigSourceCoverage,
virtualHalSimConfigSourceCoverageReady,
blockedRuntimeFamilies: blockedFamilies,
promotedRuntimeFamilies: promotedFamilies,
promotedBlockedFamilies,
@@ -1327,6 +1467,11 @@ export function createProjectReleaseReadinessReport({
label: "Sim config inventory",
value: `unexpected_fail=${inventory.unexpectedFail}`,
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "blocked-runtime-families",
label: "Blocked runtime families",
@@ -1349,6 +1494,7 @@ export function createProjectReleaseReadinessSummaryViewModel(
const missing = Array.isArray(report?.missing) ? report.missing : [];
const gateResultMatrix = objectOrEmpty(report?.gateResultMatrix);
const simConfigInventory = objectOrEmpty(report?.simConfigInventory);
const virtualHalSimConfigSourceCoverageReady = report?.virtualHalSimConfigSourceCoverageReady === true;
const axisScreenshotArtifactSummary = objectOrEmpty(report?.axisScreenshotArtifactSummary);
const blockedRuntimeFamilies = arrayOrEmpty(report?.blockedRuntimeFamilies);
const promotedBlockedFamilies = arrayOrEmpty(report?.promotedBlockedFamilies);
@@ -1380,6 +1526,11 @@ export function createProjectReleaseReadinessSummaryViewModel(
label: "Sim config inventory",
value: `unexpected_fail=${simConfigInventory.unexpectedFail ?? "unknown"}`,
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "blocked-runtime-families",
label: "Blocked runtime families",
@@ -1457,6 +1608,11 @@ export function createProjectReleaseReadinessArtifactValidationSummaryViewModel(
label: "Gate action plan",
value: validation?.gateActionPlanReady === true ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: validation?.virtualHalSimConfigSourceCoverageReady === true ? "ready" : "missing",
},
{
id: "missing",
label: "Missing artifact evidence",
@@ -1609,14 +1765,19 @@ export function createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson
export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl = "",
diagnosticsUrl = "",
fetchRef = globalThis.fetch,
} = {}) {
if (!artifactUrl || typeof fetchRef !== "function") {
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "" });
const diagnosticsValidation = diagnosticsUrl
? createProjectReleaseBrowserDiagnosticsArtifactValidation({})
: null;
const missing = [
...(artifactUrl ? [] : ["artifact-url"]),
...(typeof fetchRef === "function" ? [] : ["fetch"]),
...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch", ...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`)] : []),
];
return {
apiName: "project-release-readiness-artifact-url-workflow",
@@ -1624,11 +1785,16 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked",
ready: false,
artifactUrl,
diagnosticsUrl,
fetched: false,
diagnosticsFetched: false,
httpStatus: null,
diagnosticsHttpStatus: null,
fetchError: null,
diagnosticsFetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
diagnosticsValidation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing,
@@ -1664,14 +1830,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked",
ready: false,
artifactUrl,
diagnosticsUrl,
fetched: false,
diagnosticsFetched: false,
httpStatus,
diagnosticsHttpStatus: null,
fetchError,
diagnosticsFetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing],
missing: [
"artifact-fetch",
...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch"] : []),
],
rows: [
{
id: "fetch",
@@ -1694,20 +1869,56 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
const artifactJson = await response.text();
const jsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson });
let diagnosticsFetched = false;
let diagnosticsHttpStatus = null;
let diagnosticsFetchError = null;
let diagnosticsValidation = null;
const diagnosticsMissing = [];
if (diagnosticsUrl) {
try {
const diagnosticsResponse = await fetchRef(diagnosticsUrl);
diagnosticsHttpStatus = diagnosticsResponse?.status ?? null;
if (!diagnosticsResponse?.ok) {
diagnosticsFetchError = `diagnostics artifact URL fetch failed${diagnosticsHttpStatus ? `: ${diagnosticsHttpStatus}` : ""}`;
diagnosticsMissing.push("diagnostics-fetch");
diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({});
} else {
diagnosticsFetched = true;
const diagnosticsArtifact = JSON.parse(await diagnosticsResponse.text());
diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation(diagnosticsArtifact);
diagnosticsMissing.push(...diagnosticsValidation.missing.map((item) => `diagnostics.${item}`));
}
} catch (error) {
diagnosticsFetchError = error?.message ?? "diagnostics artifact URL fetch error";
diagnosticsValidation = createProjectReleaseBrowserDiagnosticsArtifactValidation({});
diagnosticsMissing.push("diagnostics-fetch");
}
}
const missing = [
...jsonWorkflow.missing,
...diagnosticsMissing,
];
const ready = jsonWorkflow.ready === true &&
(!diagnosticsUrl || diagnosticsValidation?.ready === true);
return {
apiName: "project-release-readiness-artifact-url-workflow",
workflowVersion: 1,
phase: jsonWorkflow.ready === true ? "ready" : "blocked",
ready: jsonWorkflow.ready === true,
phase: ready ? "ready" : "blocked",
ready,
artifactUrl,
diagnosticsUrl,
fetched: true,
diagnosticsFetched,
httpStatus,
diagnosticsHttpStatus,
fetchError: null,
diagnosticsFetchError,
jsonWorkflow,
validation: jsonWorkflow.validation,
diagnosticsValidation,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: jsonWorkflow.missing,
missing,
rows: [
{
id: "fetch",
@@ -1719,6 +1930,13 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
label: "Artifact JSON workflow",
value: jsonWorkflow.ready === true ? "ready" : "blocked",
},
{
id: "diagnostics-workflow",
label: "Browser diagnostics workflow",
value: diagnosticsUrl
? (diagnosticsValidation?.ready === true ? "ready" : "blocked")
: "not requested",
},
{
id: "next-command",
label: "Next command",
@@ -1734,14 +1952,23 @@ export async function loadProjectReleaseReadinessArtifactUrlWorkflow({
phase: "blocked",
ready: false,
artifactUrl,
diagnosticsUrl,
fetched: false,
diagnosticsFetched: false,
httpStatus: null,
diagnosticsHttpStatus: null,
fetchError: error?.message ?? "artifact URL fetch error",
diagnosticsFetchError: null,
jsonWorkflow,
validation: jsonWorkflow.validation,
diagnosticsValidation: diagnosticsUrl ? createProjectReleaseBrowserDiagnosticsArtifactValidation({}) : null,
summaryViewModel: jsonWorkflow.summaryViewModel,
actionPlan: jsonWorkflow.actionPlan,
missing: ["artifact-fetch", ...jsonWorkflow.missing],
missing: [
"artifact-fetch",
...jsonWorkflow.missing,
...(diagnosticsUrl ? ["diagnostics-fetch"] : []),
],
rows: [
{
id: "fetch",
@@ -1802,6 +2029,13 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel
label: "Artifact validation",
value: workflow?.validation?.ready === true ? "ready" : "blocked",
},
{
id: "browser-diagnostics",
label: "Browser diagnostics",
value: workflow?.diagnosticsUrl
? (workflow?.diagnosticsValidation?.ready === true ? "ready" : "blocked")
: "not requested",
},
{
id: "next-command",
label: "Next command",
@@ -1850,6 +2084,15 @@ export function createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
expectedOutput: null,
}]
: []),
...(missing.includes("diagnostics-fetch")
? [{
id: "verify-diagnostics-url-fetch",
label: "Verify browser diagnostics artifact URL fetch",
kind: "fetch",
command: null,
expectedOutput: null,
}]
: []),
];
const commandActions = validationCommands.map((command) => ({
id: command.id ?? command.gateId ?? `command-${command.step}`,
@@ -1902,6 +2145,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
const artifactObject = objectOrEmpty(artifact);
const releaseGate = objectOrEmpty(artifactObject.releaseGate);
const simConfigInventory = objectOrEmpty(artifactObject.simConfigInventory);
const virtualHalSimConfigSourceCoverage = objectOrEmpty(artifactObject.virtualHalSimConfigSourceCoverage);
const gateManifest = objectOrEmpty(artifactObject.gateManifest);
const gateExecutionManifest = objectOrEmpty(artifactObject.gateExecutionManifest);
const gateExecutionSummaryViewModel = objectOrEmpty(artifactObject.gateExecutionSummaryViewModel);
@@ -1947,6 +2191,9 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
&& gateActionPlan.pendingCount === 0
&& gateActionPlan.nextCommand === null
&& actionPlanCommands.length === 0;
const virtualHalSimConfigSourceCoverageReady = isVirtualHalSimConfigSourceCoverageReady(
virtualHalSimConfigSourceCoverage,
) && artifactObject.virtualHalSimConfigSourceCoverageReady === true;
const axisScreenshotArtifacts = arrayOrEmpty(axisScreenshotArtifactSummary.artifacts);
const axisScreenshotSummaryReady = !axisScreenshotArtifactSummary.apiName
|| axisScreenshotArtifacts.length === 0
@@ -1984,6 +2231,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]),
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]),
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]),
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
...(arrayOrEmpty(artifactObject.blockedRuntimeFamilies).join(",") === BLOCKED_RUNTIME_FAMILIES.join(",")
? []
: ["blockedRuntimeFamilies"]),
@@ -2017,6 +2265,7 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
gateExecutionSummaryReady: executionSummaryReady,
gateResultMatrixReady: matrixReady,
gateActionPlanReady: actionPlanReady,
virtualHalSimConfigSourceCoverageReady,
axisScreenshotArtifactSummaryReady: axisScreenshotSummaryReady,
axisScreenshotArtifactCount: axisScreenshotArtifacts.length,
blockedRuntimeFamilies: arrayOrEmpty(artifactObject.blockedRuntimeFamilies),
@@ -2056,6 +2305,11 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
label: "Gate action plan",
value: actionPlanReady ? "ready" : "missing",
},
{
id: "virtual-hal-sim-config-source-coverage",
label: "Virtual HAL sim-config source coverage",
value: virtualHalSimConfigSourceCoverageReady ? "ready" : "missing",
},
{
id: "axis-screenshot-artifacts",
label: "AXIS screenshot artifacts",