按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:
350
wasm-port/runtime/sdk/src/python-remap-runtime-port.js
Normal file
350
wasm-port/runtime/sdk/src/python-remap-runtime-port.js
Normal file
@@ -0,0 +1,350 @@
|
||||
export const PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION = 1;
|
||||
|
||||
export const PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE = Object.freeze({
|
||||
fixtureFamily: "axis/remap/stop-lookahead/nc_files",
|
||||
fixtureId: "stop_lookahead_python_runtime_lifecycle",
|
||||
iniPath: "axis/remap/stop-lookahead/demo.ini",
|
||||
pythonPathPrepend: "python",
|
||||
topLevelPath: "python/toplevel.py",
|
||||
modules: [
|
||||
"axis/remap/stop-lookahead/python/remap.py",
|
||||
"axis/remap/stop-lookahead/python/toplevel.py",
|
||||
],
|
||||
callableName: "queuebuster",
|
||||
expectedFirstYield: 2,
|
||||
});
|
||||
|
||||
export const PYTHON_REMAP_LIFECYCLE_PLAN = [
|
||||
{ phase: "initialize_python", method: "initializePython" },
|
||||
{ phase: "apply_ini_python_path", method: "applyIniPythonPath" },
|
||||
{ phase: "execute_toplevel", method: "executeTopLevel" },
|
||||
{ phase: "import_module", method: "importModule", modulePath: "python/remap.py" },
|
||||
{ phase: "callable_lookup", method: "lookupCallable", callableName: "queuebuster" },
|
||||
{ phase: "callable_invoke", method: "invokeGenerator", callableName: "queuebuster" },
|
||||
{ phase: "remap_phase_dispatch", method: "observeFirstYield", expectedYield: 2 },
|
||||
{ phase: "generator_finish", method: "finishGenerator" },
|
||||
{ phase: "export_interpreter_state", method: "exportInterpreterState" },
|
||||
{ phase: "export_diagnostics", method: "exportDiagnostics" },
|
||||
];
|
||||
|
||||
function requireString(value, label) {
|
||||
if (typeof value !== "string" || value.length === 0) {
|
||||
throw new Error(`${label} must be a non-empty string.`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function normalizeSourceFiles(sourceFiles = []) {
|
||||
if (!Array.isArray(sourceFiles)) {
|
||||
throw new Error("sourceFiles must be an array.");
|
||||
}
|
||||
return sourceFiles.map((sourceFile) => requireString(sourceFile, "source file"));
|
||||
}
|
||||
|
||||
function stableTextHash(text) {
|
||||
let hash = 0x811c9dc5;
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
hash ^= text.charCodeAt(index);
|
||||
hash = Math.imul(hash, 0x01000193) >>> 0;
|
||||
}
|
||||
return hash.toString(16).padStart(8, "0");
|
||||
}
|
||||
|
||||
function clonePlan(plan) {
|
||||
return plan.map((step) => ({ ...step }));
|
||||
}
|
||||
|
||||
function transcriptText(transcript) {
|
||||
return transcript
|
||||
.map((entry) => `${entry.phase}:${entry.status}:${entry.value ?? ""}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function normalizeLifecycleResult(phase, result = {}) {
|
||||
return {
|
||||
phase,
|
||||
status: result.status ?? "ok",
|
||||
value: result.value ?? null,
|
||||
ready: result.ready !== false,
|
||||
source: result.source ?? "runtime-adapter",
|
||||
};
|
||||
}
|
||||
|
||||
export function createPythonRemapLifecyclePlan() {
|
||||
return clonePlan(PYTHON_REMAP_LIFECYCLE_PLAN);
|
||||
}
|
||||
|
||||
export function validatePythonRemapLifecycleTranscript(transcript) {
|
||||
if (!Array.isArray(transcript)) {
|
||||
throw new Error("transcript must be an array.");
|
||||
}
|
||||
const byPhase = new Map(transcript.map((entry) => [entry?.phase, entry]));
|
||||
const requiredPhases = PYTHON_REMAP_LIFECYCLE_PLAN.map((step) => step.phase);
|
||||
const missingPhases = requiredPhases.filter((phase) => !byPhase.has(phase));
|
||||
const failedPhases = transcript
|
||||
.filter((entry) => entry?.ready === false || entry?.status === "blocked" || entry?.status === "failed")
|
||||
.map((entry) => entry.phase);
|
||||
const firstYield = byPhase.get("remap_phase_dispatch")?.value ?? null;
|
||||
const hasInterpreterState = byPhase.has("export_interpreter_state");
|
||||
const hasDiagnostics = byPhase.has("export_diagnostics");
|
||||
const ready = (
|
||||
missingPhases.length === 0 &&
|
||||
failedPhases.length === 0 &&
|
||||
Number(firstYield) === PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.expectedFirstYield &&
|
||||
hasInterpreterState &&
|
||||
hasDiagnostics
|
||||
);
|
||||
|
||||
return {
|
||||
contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
|
||||
fixtureFamily: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
|
||||
callableName: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName,
|
||||
requiredPhases,
|
||||
observedPhases: transcript.map((entry) => entry.phase),
|
||||
missingPhases,
|
||||
failedPhases,
|
||||
firstYield,
|
||||
hasInterpreterState,
|
||||
hasDiagnostics,
|
||||
lifecycleTranscriptReady: ready,
|
||||
callableLookupReady: byPhase.get("callable_lookup")?.ready === true,
|
||||
generatorLifecycleReady: byPhase.get("generator_finish")?.ready === true,
|
||||
interpreterStateBindingReady: hasInterpreterState,
|
||||
ready,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function createPythonRemapRuntimeDiagnostics({
|
||||
fixtureFamily = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
|
||||
iniPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath,
|
||||
pythonPathPrepend = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend,
|
||||
topLevelPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath,
|
||||
modules = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.modules,
|
||||
callableName = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.callableName,
|
||||
runtimeMode = "contract-only",
|
||||
runtimeExecutionReady = false,
|
||||
transcript = [],
|
||||
} = {}) {
|
||||
const validation = validatePythonRemapLifecycleTranscript(transcript);
|
||||
return {
|
||||
contractVersion: PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
|
||||
runtimeMode,
|
||||
runtimeExecutionReady: runtimeExecutionReady === true,
|
||||
fixtureFamily,
|
||||
fixtureId: PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureId,
|
||||
iniPath,
|
||||
pythonPathPrepend,
|
||||
topLevelPath,
|
||||
modules: [...modules],
|
||||
callableName,
|
||||
lifecycleTranscriptReady: validation.lifecycleTranscriptReady,
|
||||
callableLookupReady: validation.callableLookupReady,
|
||||
generatorLifecycleReady: validation.generatorLifecycleReady,
|
||||
interpreterStateBindingReady: validation.interpreterStateBindingReady,
|
||||
transcriptHash: stableTextHash(transcriptText(transcript)),
|
||||
transcriptPhaseCount: transcript.length,
|
||||
linuxCncOwnedLifecycle: true,
|
||||
jsCncSemantics: false,
|
||||
ngcOnlySubroutinePromoted: false,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function createLinuxCncPythonRemapRuntimePort({
|
||||
fixtureFamily = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.fixtureFamily,
|
||||
iniPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.iniPath,
|
||||
pythonPathPrepend = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.pythonPathPrepend,
|
||||
topLevelPath = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.topLevelPath,
|
||||
sourceFiles = PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE.modules,
|
||||
runtimeMode = "contract-only",
|
||||
runtimeAdapter = null,
|
||||
} = {}) {
|
||||
requireString(fixtureFamily, "fixtureFamily");
|
||||
requireString(iniPath, "iniPath");
|
||||
requireString(pythonPathPrepend, "pythonPathPrepend");
|
||||
requireString(topLevelPath, "topLevelPath");
|
||||
const normalizedSourceFiles = normalizeSourceFiles(sourceFiles);
|
||||
const transcript = [];
|
||||
let started = false;
|
||||
let closed = false;
|
||||
let runtimeExecutionReady = false;
|
||||
let startStatus = null;
|
||||
|
||||
function assertOpen() {
|
||||
if (closed) {
|
||||
throw new Error("PythonRemapRuntimePort is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
async function callAdapterMethod(method, payload = {}) {
|
||||
if (!runtimeAdapter?.[method]) {
|
||||
return normalizeLifecycleResult(payload.phase ?? method, {
|
||||
status: "blocked",
|
||||
ready: false,
|
||||
source: "missing-runtime-adapter-method",
|
||||
});
|
||||
}
|
||||
return normalizeLifecycleResult(payload.phase ?? method, await runtimeAdapter[method](payload));
|
||||
}
|
||||
|
||||
async function runStep(step) {
|
||||
const payload = {
|
||||
...step,
|
||||
fixtureFamily,
|
||||
iniPath,
|
||||
pythonPathPrepend,
|
||||
topLevelPath,
|
||||
sourceFiles: normalizedSourceFiles,
|
||||
};
|
||||
const result = await callAdapterMethod(step.method, payload);
|
||||
transcript.push(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return {
|
||||
fixtureFamily,
|
||||
iniPath,
|
||||
pythonPathPrepend,
|
||||
topLevelPath,
|
||||
sourceFiles: normalizedSourceFiles,
|
||||
runtimeMode,
|
||||
|
||||
async start() {
|
||||
assertOpen();
|
||||
started = true;
|
||||
let adapterStartResult = {};
|
||||
if (runtimeAdapter?.start) {
|
||||
adapterStartResult = await runtimeAdapter.start({
|
||||
fixtureFamily,
|
||||
iniPath,
|
||||
pythonPathPrepend,
|
||||
topLevelPath,
|
||||
sourceFiles: normalizedSourceFiles,
|
||||
});
|
||||
}
|
||||
runtimeExecutionReady = adapterStartResult.runtimeExecutionReady === true;
|
||||
startStatus = adapterStartResult.status ?? (runtimeExecutionReady ? "python_remap_runtime_adapter_started" : null);
|
||||
return {
|
||||
runtimeMode,
|
||||
runtimeExecutionReady,
|
||||
status: startStatus,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
},
|
||||
|
||||
async initializePython() {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before initializePython().");
|
||||
}
|
||||
return runStep({ phase: "initialize_python", method: "initializePython" });
|
||||
},
|
||||
|
||||
async applyIniPythonPath() {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before applyIniPythonPath().");
|
||||
}
|
||||
return runStep({ phase: "apply_ini_python_path", method: "applyIniPythonPath" });
|
||||
},
|
||||
|
||||
async executeTopLevel() {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before executeTopLevel().");
|
||||
}
|
||||
return runStep({ phase: "execute_toplevel", method: "executeTopLevel" });
|
||||
},
|
||||
|
||||
async importModule(modulePath) {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before importModule().");
|
||||
}
|
||||
return runStep({ phase: "import_module", method: "importModule", modulePath });
|
||||
},
|
||||
|
||||
async lookupCallable(callableName) {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before lookupCallable().");
|
||||
}
|
||||
return runStep({ phase: "callable_lookup", method: "lookupCallable", callableName });
|
||||
},
|
||||
|
||||
async invokeGenerator(callableName, args = []) {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before invokeGenerator().");
|
||||
}
|
||||
return runStep({ phase: "callable_invoke", method: "invokeGenerator", callableName, args });
|
||||
},
|
||||
|
||||
async runLifecyclePlan(plan = createPythonRemapLifecyclePlan()) {
|
||||
assertOpen();
|
||||
if (!started) {
|
||||
throw new Error("PythonRemapRuntimePort must be started before runLifecyclePlan().");
|
||||
}
|
||||
if (!runtimeAdapter || runtimeExecutionReady !== true) {
|
||||
return {
|
||||
status: runtimeAdapter ? (startStatus ?? "blocked_runtime_execution_not_ready") : "blocked_runtime_adapter_required",
|
||||
plan: clonePlan(plan),
|
||||
runtimeExecutionReady: false,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
}
|
||||
for (const step of plan) {
|
||||
await runStep(step);
|
||||
}
|
||||
return {
|
||||
status: "runtime_adapter_lifecycle_plan_executed",
|
||||
plan: clonePlan(plan),
|
||||
transcript: this.exportTranscript(),
|
||||
validation: validatePythonRemapLifecycleTranscript(transcript),
|
||||
runtimeExecutionReady: true,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
},
|
||||
|
||||
exportTranscript() {
|
||||
return transcript.map((entry) => ({ ...entry }));
|
||||
},
|
||||
|
||||
exportDiagnostics() {
|
||||
return createPythonRemapRuntimeDiagnostics({
|
||||
fixtureFamily,
|
||||
iniPath,
|
||||
pythonPathPrepend,
|
||||
topLevelPath,
|
||||
modules: normalizedSourceFiles,
|
||||
runtimeMode,
|
||||
runtimeExecutionReady,
|
||||
transcript,
|
||||
});
|
||||
},
|
||||
|
||||
async close() {
|
||||
if (runtimeAdapter?.close) {
|
||||
await runtimeAdapter.close();
|
||||
}
|
||||
closed = true;
|
||||
return {
|
||||
closed,
|
||||
executionEnabled: false,
|
||||
promotionAllowed: false,
|
||||
bulkPromotionAllowed: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user