按text25.txt规划,实现L4-PYTHON-REMAP接入数控系统仿真系统

结论:已接入Python remap runtime proof chain,覆盖native、WASM、browser与release gate证据链;继续保持promotion_allowed=0,不批量解锁L4-PYTHON-REMAP。
This commit is contained in:
2026-06-19 18:52:24 +08:00
parent 51ae5d9a9a
commit fbf9dade9c
38 changed files with 2252 additions and 335 deletions

View File

@@ -986,7 +986,7 @@ TOOL_TABLE = browser-shell-tool.tbl
);
assertEqual(
workflowOverviewReleaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20",
"evidence-ready=8 inventory-ready=19",
"shell workflow overview release artifact URL workflow candidate layers",
);
assertEqual(

View File

@@ -421,7 +421,7 @@
preferredGcodePath: "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc",
blockedCandidateIds: [],
explicitBrowserDiagnosticsCount: 8,
inventoryBaseline: "executed=28 passed=28 skipped=131 unexpected_fail=0",
inventoryBaseline: "executed=29 passed=29 skipped=130 unexpected_fail=0",
},
virtualHalSimConfigMacroLoadFixtures: createVirtualHalSimConfigMacroLoadFixtureReport({
manifestEntries: [
@@ -637,7 +637,7 @@
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "promotion-candidate-layers")?.value,
"evidence-ready=8 inventory-ready=20",
"evidence-ready=8 inventory-ready=19",
"workflow overview release artifact URL workflow candidate layers",
);
assertEqual(
@@ -772,7 +772,7 @@
);
assertEqual(
releaseArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "virtual-hal-promotion-candidate-baseline")?.value,
"executed=28 passed=28 skipped=131 unexpected_fail=0",
"executed=29 passed=29 skipped=130 unexpected_fail=0",
"workflow overview release artifact URL workflow candidate baseline",
);
assertEqual(
@@ -948,7 +948,7 @@
);
assertEqual(
workflowDoc.querySelector('[data-workflow-overview-release-readiness-artifact-url-workflow-value="promotion-candidate-layers"]').textContent,
"evidence-ready=8 inventory-ready=20",
"evidence-ready=8 inventory-ready=19",
"workflow overview fixed URL workflow form candidate layer row",
);
assertEqual(

View File

@@ -0,0 +1,45 @@
self.linuxCncPythonRemapRuntimeProvider = {
async start() {},
async initializePython() {
return { value: "python-runtime-initialized" };
},
async applyIniPythonPath(message) {
return { value: message.pythonPathPrepend };
},
async executeTopLevel(message) {
return { value: message.topLevelPath };
},
async importModule(message) {
return { value: message.modulePath };
},
async lookupCallable(message) {
return { value: message.callableName };
},
async invokeGenerator() {
return { value: "generator-returned" };
},
async observeFirstYield(message) {
return { value: message.expectedYield };
},
async finishGenerator() {
return { value: "generator-finished" };
},
async exportInterpreterState() {
return { value: "interpreter-state-bound" };
},
async exportDiagnostics() {
return { value: "diagnostics-exported" };
},
async close() {},
};

View File

@@ -0,0 +1,114 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Python Remap Runtime Browser Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
import {
createLinuxCncPythonRemapBrowserWorkerAdapter,
createLinuxCncPythonRemapRuntimePort,
createPythonRemapRuntimeDiagnostics,
validatePythonRemapLifecycleTranscript,
} from "../../runtime/sdk/src/index.js";
const status = document.getElementById("status");
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
try {
const contractOnlyPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: "contract-only",
});
const contractOnlyStart = await contractOnlyPort.start();
assertEqual(contractOnlyStart.runtimeExecutionReady, false, "contract-only runtime execution");
const contractOnlyPlan = await contractOnlyPort.runLifecyclePlan();
assertEqual(contractOnlyPlan.status, "blocked_runtime_adapter_required", "contract-only lifecycle plan");
assertEqual(contractOnlyPlan.promotionAllowed, false, "contract-only promotion guard");
await contractOnlyPort.close();
const workerAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
});
const workerPort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: workerAdapter.runtimeMode,
runtimeAdapter: workerAdapter,
});
const workerStart = await workerPort.start();
assertEqual(workerStart.runtimeMode, "browser-python-wasm-worker", "worker runtime mode");
assertEqual(workerStart.runtimeExecutionReady, false, "worker Python runtime readiness");
assertEqual(workerStart.status, "blocked_browser_python_wasm_runtime_missing", "worker runtime status");
const workerPlan = await workerPort.runLifecyclePlan();
assertEqual(workerPlan.status, "blocked_browser_python_wasm_runtime_missing", "worker plan blocked");
assertEqual(workerPlan.promotionAllowed, false, "worker promotion guard");
await workerPort.close();
const missingModuleAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
pythonRuntimeModuleUrl: new URL("./missing_python_remap_runtime_provider.js", import.meta.url),
});
const missingModulePort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: missingModuleAdapter.runtimeMode,
runtimeAdapter: missingModuleAdapter,
});
const missingModuleStart = await missingModulePort.start();
assertEqual(missingModuleStart.runtimeExecutionReady, false, "missing module runtime readiness");
assertEqual(
missingModuleStart.status,
"blocked_browser_python_wasm_runtime_load_failed",
"missing module runtime status",
);
await missingModulePort.close();
const fakeRuntimeAdapter = createLinuxCncPythonRemapBrowserWorkerAdapter({
workerUrl: new URL("../../runtime/workers/python-remap-worker.js", import.meta.url),
pythonRuntimeModuleUrl: new URL("./python_remap_fake_runtime_worker.js", import.meta.url),
});
const fakeRuntimePort = createLinuxCncPythonRemapRuntimePort({
runtimeMode: fakeRuntimeAdapter.runtimeMode,
runtimeAdapter: fakeRuntimeAdapter,
});
const fakeRuntimeStart = await fakeRuntimePort.start();
assertEqual(fakeRuntimeStart.runtimeExecutionReady, true, "fake provider runtime readiness");
assertEqual(fakeRuntimeStart.status, "browser_python_remap_wasm_runtime_ready", "fake provider runtime status");
const fakeRuntimePlan = await fakeRuntimePort.runLifecyclePlan();
assertEqual(fakeRuntimePlan.status, "runtime_adapter_lifecycle_plan_executed", "fake provider lifecycle plan");
assertEqual(fakeRuntimePlan.promotionAllowed, false, "fake provider promotion guard");
assertEqual(fakeRuntimePlan.bulkPromotionAllowed, false, "fake provider bulk promotion guard");
const transcript = fakeRuntimePort.exportTranscript();
assertEqual(validatePythonRemapLifecycleTranscript(transcript).ready, true, "lifecycle transcript readiness");
const diagnostics = createPythonRemapRuntimeDiagnostics({
runtimeMode: fakeRuntimeAdapter.runtimeMode,
runtimeExecutionReady: fakeRuntimeStart.runtimeExecutionReady,
transcript,
});
assertEqual(diagnostics.fixtureFamily, "axis/remap/stop-lookahead/nc_files", "diagnostics fixture family");
assertEqual(diagnostics.iniPath, "axis/remap/stop-lookahead/demo.ini", "diagnostics INI path");
assertEqual(diagnostics.pythonPathPrepend, "python", "diagnostics Python path");
assertEqual(diagnostics.topLevelPath, "python/toplevel.py", "diagnostics TOPLEVEL");
assertEqual(diagnostics.lifecycleTranscriptReady, true, "diagnostics transcript");
assertEqual(diagnostics.callableLookupReady, true, "diagnostics callable lookup");
assertEqual(diagnostics.generatorLifecycleReady, true, "diagnostics generator lifecycle");
assertEqual(diagnostics.interpreterStateBindingReady, true, "diagnostics interpreter binding");
assertEqual(diagnostics.linuxCncOwnedLifecycle, true, "diagnostics LinuxCNC lifecycle owner");
assertEqual(diagnostics.jsCncSemantics, false, "diagnostics JS CNC semantic guard");
assertEqual(diagnostics.ngcOnlySubroutinePromoted, false, "diagnostics NGC-only guard");
assertEqual(diagnostics.executionEnabled, false, "diagnostics execution guard");
assertEqual(diagnostics.promotionAllowed, false, "diagnostics promotion guard");
assertEqual(diagnostics.bulkPromotionAllowed, false, "diagnostics bulk promotion guard");
await fakeRuntimePort.close();
status.textContent = "browser_python_remap_runtime_smoke=ok";
} catch (error) {
status.textContent = `browser_python_remap_runtime_smoke=fail\n${error.stack || error}`;
throw error;
}
</script>
</body>
</html>

View File

@@ -521,13 +521,13 @@
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.candidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.readyCandidateCount !== 8 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.sourceFileCount !== 17 ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=28 passed=28 skipped=131 unexpected_fail=0" ||
diagnosticsArtifact.virtualHalPromotionCandidateSummary?.inventoryBaseline !== "executed=29 passed=29 skipped=130 unexpected_fail=0" ||
promotionCandidateSummary.ready !== true ||
promotionCandidateSummary.preferredIniPath !== "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" ||
doc.body.dataset.promotionCandidateReady !== "true" ||
doc.body.dataset.promotionCandidatePreferred !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="preferred-candidate"]')?.textContent !== "qtdragon-multi-joint-on-abort" ||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=20" ||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-layers"]')?.textContent !== "evidence-ready=8 inventory-ready=19" ||
doc.querySelector('[data-promotion-candidate-summary-value="inventory-baseline"]')?.textContent !== "28/28 pass; 131 skip unchanged" ||
doc.querySelector('[data-promotion-candidate-summary-value="promotion-allowed"]')?.textContent !== "0 baseline changes" ||
doc.querySelector('[data-promotion-candidate-summary-value="candidate-artifact"]')?.textContent !== "promotion-candidates.tsv" ||

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "Python remap runtime browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/python_remap_runtime_browser_smoke.html"
OUT="$TMP_DIR/chromium.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_python_remap_runtime_smoke=ok" "$OUT"; then
echo "Python remap runtime browser smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_python_remap_runtime_smoke=ok"