解锁L4-TOOL-DB主机运行环境

结论:已识别RIP版LinuxCNC主机运行时,Tool DB native protocol probe可通过,inventory baseline保持28/28/131/0且promotion继续锁定。
This commit is contained in:
2026-06-19 06:28:38 +08:00
parent f101932bf1
commit d9ac3770a7
18 changed files with 3041 additions and 184 deletions

View File

@@ -1,4 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createProjectReleaseBrowserDiagnosticsArtifactValidation,
@@ -18,9 +21,6 @@ import {
createVirtualHalState,
} from "../../../runtime/sdk/src/linuxcnc-hal.js";
const VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES =
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []);
import {
INI_PANEL_ENTRIES,
controlPageStatusText,
@@ -161,6 +161,30 @@ import {
renderMachineSessionControlView,
} from "../../../runtime/ui/ini-panel/ui-shell.js";
const VIRTUAL_HAL_MOTION_MATRIX_MANIFEST_ENTRIES =
VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS.flatMap((target) => target.sourceFiles ?? []);
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
function parseTsvRows(text) {
const [headerLine, ...dataLines] = text.trim().split(/\r?\n/);
const headers = headerLine.split("\t");
return dataLines
.filter((line) => line.trim().length > 0)
.map((line) => {
const values = line.split("\t");
return Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""]));
});
}
const promotionCandidateRows = parseTsvRows(
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/promotion-candidates.tsv"), "utf8"),
);
const evidenceExpansionRows = parseTsvRows(
readFileSync(resolve(root, "build/wasm/sim-configs-inventory/evidence-expansion-candidates.tsv"), "utf8"),
);
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES);
assert.equal(controlPageStatusText({ lastAction: "x", runStatus: "ok", error: null }), "x | ok | -");
@@ -1454,6 +1478,8 @@ const releaseReadinessArtifactJson = JSON.stringify(createProjectReleaseReadines
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
virtualHalSimConfigPromotionCandidates: releaseReadinessPromotionCandidates,
virtualHalSimConfigEvidenceExpansion: releaseReadinessEvidenceExpansion,
promotionCandidateRows,
evidenceExpansionRows,
virtualHalSimConfigMacroLoadFixtures: releaseReadinessMacroLoadFixtures,
virtualHalMotionControllerMatrix: releaseReadinessMotionControllerMatrix,
}));
@@ -1807,7 +1833,7 @@ const releaseArtifactMountResult = mountIniPanelShellWorkflowOverviewReleaseRead
rowsNode: releaseArtifactRowsNode,
});
assert.equal(releaseArtifactRenderResult.rendered, true);
assert.equal(releaseArtifactRenderResult.rowCount, 28);
assert.equal(releaseArtifactRenderResult.rowCount, 30);
assert.equal(releaseArtifactRenderResult.dataset.handoffScope, "workflow-overview-release-readiness-artifact");
assert.equal(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
@@ -1858,7 +1884,7 @@ assert.equal(
true,
);
assert.equal(releaseArtifactMountResult.ready, true);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 28);
assert.equal(releaseArtifactMountResult.renderResult.rowCount, 30);
assert.deepEqual(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson("{").missing,
["artifact-json"],
@@ -1917,7 +1943,7 @@ assert.equal(
);
assert.equal(releaseArtifactUrlWorkflow.renderState.statusLine, "Ready: No blocking reasons");
assert.equal(releaseArtifactUrlWorkflow.mountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 28);
assert.equal(releaseArtifactUrlWorkflow.mountResult.renderResult.rowCount, 30);
const releaseArtifactUrlWorkflowSummary =
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseArtifactUrlWorkflow,
@@ -1981,10 +2007,38 @@ assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-browser-diagnostics-count")?.value,
"8 diagnostics-ready",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-rows")?.value,
"8",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview")?.value,
"qtdragon-multi-joint-on-abort",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-preview-gcode")?.value,
"linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-promotion-allowed")?.value,
"0",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-ready-candidate-baseline-changing")?.value,
"no",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-next-evidence")?.value,
"browser-diagnostics-binding",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-artifact-rows")?.value,
"28",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-artifact-rows")?.value,
"13",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "evidence-expansion-baseline-changing")?.value,
"no",
@@ -2025,6 +2079,42 @@ assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promoted-blocked-family-count")?.value,
"0 promoted",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-rows")?.value,
"19",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-locked-preview")?.value,
"L4-TOOL-DB:axis/db_demo/base.ngc, L4-PYTHON-REMAP:axis/laser/raster_test.ngc, L4-PYTHON-REMAP:axis/laser/vector_test.ngc",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-promotion-allowed")?.value,
"0",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-count")?.value,
"3",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-summary")?.value,
"L4-USER-M-PROCESS:1 locked=yes, L4-TOOL-DB:1 locked=yes, L4-PYTHON-REMAP:17 locked=yes",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-row-count")?.value,
"19",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-detail-preview-1")?.value,
"L4-TOOL-DB:axis/db_demo/base.ngc; reason=design_tooldata_db_protocol_boundary; next=db_program_v2_1_handshake_getall_load_unload_or_put; promotion_allowed=0",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-PYTHON-REMAP")?.value,
"17 locked rows; promotion_allowed=0; first=axis/laser/raster_test.ngc; reason=design_linuxcnc_python_runtime_boundary; next=python_runtime_owner_and_fixture",
);
assert.equal(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "hard-block-runtime-family-L4-USER-M-PROCESS-paths")?.value,
"axis/vismach/millturn/example.ngc",
);
const promotionCandidateDrilldownViewModel =
createIniPanelShellWorkflowOverviewPromotionCandidateDrilldownViewModel(
releaseArtifactUrlWorkflowSummary,
@@ -2062,6 +2152,18 @@ assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "blocked-family-exclusion")?.value,
"passed",
);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "candidate-layer")?.value,
"evidence-ready",
);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "inventory-promotion-allowed")?.value,
"0",
);
assert.equal(
promotionCandidateDrilldownViewModel.rows.find(({ id }) => id === "inventory-baseline-changing")?.value,
"no",
);
assert.equal(promotionCandidateDrilldownViewModel.sourceFileCount, 2);
assert.equal(promotionCandidateDrilldownViewModel.evidenceRowCount, 8);
assert.equal(
@@ -2091,7 +2193,7 @@ assert.deepEqual(
],
);
assert.equal(promotionCandidateDrilldownRenderState.ready, true);
assert.equal(promotionCandidateDrilldownRenderState.rowCount, 19);
assert.equal(promotionCandidateDrilldownRenderState.rowCount, 22);
assert.equal(promotionCandidateDrilldownRenderState.sourceFileCount, 2);
assert.equal(promotionCandidateDrilldownRenderState.evidenceRowCount, 8);
assert.equal(
@@ -2133,7 +2235,7 @@ const promotionCandidateDrilldownMountResult =
rowsNode: promotionCandidateDrilldownRowsNode,
});
assert.equal(promotionCandidateDrilldownRenderResult.rendered, true);
assert.equal(promotionCandidateDrilldownRenderResult.rowCount, 19);
assert.equal(promotionCandidateDrilldownRenderResult.rowCount, 22);
assert.equal(promotionCandidateDrilldownRenderResult.evidenceRowCount, 8);
assert.deepEqual(promotionCandidateDrilldownRenderResult.dataset, {
handoffPhase: "ready",
@@ -2145,6 +2247,18 @@ assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "blocked-family-exclusion" &&
node.textContent === "passed"
), true);
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "candidate-layer" &&
node.textContent === "evidence-ready"
), true);
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "inventory-promotion-allowed" &&
node.textContent === "0"
), true);
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownValue === "inventory-baseline-changing" &&
node.textContent === "no"
), true);
assert.equal(promotionCandidateDrilldownRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionCandidateDrilldownSourceKind === "gcode" &&
node.dataset?.workflowOverviewPromotionCandidateDrilldownManifestBacked === "true" &&
@@ -2196,6 +2310,16 @@ assert.equal(promotionFamilyRowsRenderState.statusLine, "Ready: 3 promotion fami
assert.equal(promotionFamilyRowsRenderState.rows.length, 3);
assert.equal(promotionFamilyRowsRenderState.sourceFileCount, 17);
assert.equal(promotionFamilyRowsRenderState.sourceRows.length, 17);
assert.equal(promotionFamilyRowsRenderState.candidateCount, 8);
assert.equal(promotionFamilyRowsRenderState.candidateRows.length, 8);
assert.equal(
promotionFamilyRowsRenderState.candidateRows.every(({ candidateStatus, promotionAllowed, baselineChanging }) =>
candidateStatus === "evidence-ready" &&
promotionAllowed === false &&
baselineChanging === false
),
true,
);
assert.equal(
promotionFamilyRowsRenderState.rows.find(({ id }) => id === "virtual-hal-promotion-family-qtdragon-on-abort")
?.sourceKindSummary,
@@ -2215,6 +2339,8 @@ assert.equal(promotionFamilySourceFilterViewModel.ready, true);
assert.equal(promotionFamilySourceFilterViewModel.statusLine, "Ready: 17/17 source files selected");
assert.equal(promotionFamilySourceFilterViewModel.filteredSourceFileCount, 17);
assert.equal(promotionFamilySourceFilterViewModel.totalSourceFileCount, 17);
assert.equal(promotionFamilySourceFilterViewModel.filteredCandidateCount, 8);
assert.equal(promotionFamilySourceFilterViewModel.totalCandidateCount, 8);
assert.deepEqual(
promotionFamilySourceFilterViewModel.familyFilters.map(({ id, count, selected }) => [id, count, selected]),
[
@@ -2249,6 +2375,7 @@ assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.selectedSourceKind, "gc
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.statusLine, "Ready: 6/17 source files selected");
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.scopedSourceFileCount, 12);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.filteredSourceFileCount, 6);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.filteredCandidateCount, 6);
assert.equal(promotionFamilyQtDragonGcodeFilterViewModel.familyRows.length, 1);
assert.equal(
promotionFamilyQtDragonGcodeFilterViewModel.familyRows[0]?.sourceKindSummary,
@@ -2325,7 +2452,8 @@ assert.equal(promotionFamilySourceFilterRenderResult.statusLine, "Ready: 6/17 so
assert.equal(promotionFamilySourceFilterRenderResult.controlCount, 2);
assert.equal(promotionFamilySourceFilterRenderResult.filterRowCount, 7);
assert.equal(promotionFamilySourceFilterRenderResult.sourceFileCount, 6);
assert.equal(promotionFamilySourceFilterRenderResult.rowCount, 13);
assert.equal(promotionFamilySourceFilterRenderResult.candidateCount, 6);
assert.equal(promotionFamilySourceFilterRenderResult.rowCount, 19);
assert.deepEqual(promotionFamilySourceFilterRenderResult.dataset, {
handoffPhase: "ready",
handoffReady: "true",
@@ -2385,8 +2513,20 @@ assert.equal(
),
true,
);
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
node.dataset?.workflowOverviewPromotionFamilySourceFilterType === "candidate-row" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterFamily ===
"virtual-hal-promotion-family-qtdragon-on-abort" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterCandidateStatus === "evidence-ready" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterPromotionAllowed === "0" &&
node.dataset?.workflowOverviewPromotionFamilySourceFilterBaselineChanging === "no" &&
node.textContent === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc"
),
true,
);
assert.equal(promotionFamilySourceFilterMountResult.ready, true);
assert.equal(promotionFamilySourceFilterMountResult.renderResult.rowCount, 13);
assert.equal(promotionFamilySourceFilterMountResult.renderResult.rowCount, 19);
const promotionFamilySourceFilterSelectionActionPlan =
createIniPanelShellWorkflowOverviewPromotionFamilySourceFilterSelectionActionPlan(
promotionFamilyRowsRenderState,
@@ -2436,6 +2576,8 @@ assert.equal(promotionFamilySourceFilterSelectionChange.selectedSourceKind, "all
assert.equal(promotionFamilySourceFilterSelectionChange.actionPlan.changed, true);
assert.equal(promotionFamilySourceFilterSelectionChange.viewModel.filteredSourceFileCount, 2);
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.sourceFileCount, 2);
assert.equal(promotionFamilySourceFilterSelectionChange.viewModel.filteredCandidateCount, 1);
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.candidateCount, 1);
assert.equal(promotionFamilySourceFilterSelectionChange.renderResult.dataset.selectedSourceKind, "all");
assert.equal(
promotionFamilySourceFilterRowsNode.children.some((node) =>
@@ -2673,7 +2815,7 @@ const releaseArtifactUrlWorkflowMountResult =
rowsNode: releaseArtifactUrlWorkflowRowsNode,
});
assert.equal(releaseArtifactUrlWorkflowRenderResult.rendered, true);
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 35);
assert.equal(releaseArtifactUrlWorkflowRenderResult.rowCount, 59);
assert.equal(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("browser-diagnostics"),
true,
@@ -2723,7 +2865,7 @@ assert.equal(
true,
);
assert.equal(releaseArtifactUrlWorkflowMountResult.ready, true);
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 35);
assert.equal(releaseArtifactUrlWorkflowMountResult.renderResult.rowCount, 59);
const releaseArtifactUrlWorkflowMissingDom = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({