按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:
@@ -589,6 +589,19 @@ private module paths:
|
||||
- `saveToolDbFile()`, `loadToolDbFile()`, `saveToolDbTranscript()`, and
|
||||
`loadToolDbTranscript()` for OPFS persistence of the DB flat file and protocol
|
||||
transcript artifacts.
|
||||
- `createLinuxCncPythonRemapRuntimePort()`, `createPythonRemapLifecyclePlan()`,
|
||||
`validatePythonRemapLifecycleTranscript()`, and
|
||||
`createPythonRemapRuntimeDiagnostics()` for the first Python remap lifecycle
|
||||
contract around `axis/remap/stop-lookahead/nc_files`. Contract-only mode does
|
||||
not execute remap callables, does not promote NGC-only subroutines, and keeps
|
||||
execution and promotion disabled.
|
||||
- `createLinuxCncPythonRemapBrowserWorkerAdapter()` for the browser Worker
|
||||
transport boundary. The bundled worker reports
|
||||
`blocked_browser_python_wasm_runtime_missing` until a Python/WASM provider
|
||||
supplies the controlled lifecycle methods for initializing Python, applying
|
||||
the INI `[PYTHON]PATH_PREPEND`, executing `[PYTHON]TOPLEVEL`, importing the
|
||||
configured module, looking up the configured callable, and observing the
|
||||
generator lifecycle.
|
||||
|
||||
These helpers only move and validate host files. Parameter and tool-table
|
||||
effects still come from the LinuxCNC-backed interpreter SDK methods they call.
|
||||
|
||||
@@ -102,6 +102,18 @@ export {
|
||||
export {
|
||||
createLinuxCncToolDbBrowserWorkerAdapter,
|
||||
} from "./tool-db-browser-worker-adapter.js";
|
||||
export {
|
||||
PYTHON_REMAP_LIFECYCLE_PLAN,
|
||||
PYTHON_REMAP_RUNTIME_PORT_CONTRACT_VERSION,
|
||||
PYTHON_REMAP_STOP_LOOKAHEAD_FIXTURE,
|
||||
createLinuxCncPythonRemapRuntimePort,
|
||||
createPythonRemapLifecyclePlan,
|
||||
createPythonRemapRuntimeDiagnostics,
|
||||
validatePythonRemapLifecycleTranscript,
|
||||
} from "./python-remap-runtime-port.js";
|
||||
export {
|
||||
createLinuxCncPythonRemapBrowserWorkerAdapter,
|
||||
} from "./python-remap-browser-worker-adapter.js";
|
||||
export {
|
||||
runToolDbProcessPortPersistenceSession,
|
||||
} from "./tool-db-runtime-session.js";
|
||||
|
||||
@@ -126,17 +126,17 @@ export const VIRTUAL_HAL_SIM_CONFIG_SOURCE_TARGETS = Object.freeze([
|
||||
]);
|
||||
|
||||
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_BASELINE = Object.freeze({
|
||||
executed: 28,
|
||||
passed: 28,
|
||||
skipped: 131,
|
||||
executed: 29,
|
||||
passed: 29,
|
||||
skipped: 130,
|
||||
unexpectedFail: 0,
|
||||
});
|
||||
|
||||
export const VIRTUAL_HAL_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = Object.freeze({
|
||||
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
|
||||
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9",
|
||||
"6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3",
|
||||
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
|
||||
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae",
|
||||
"17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35",
|
||||
});
|
||||
|
||||
export const VIRTUAL_HAL_SIM_CONFIG_PROMOTION_CANDIDATES = Object.freeze([
|
||||
|
||||
@@ -79,17 +79,17 @@ const REQUIRED_RELEASE_GATES = [
|
||||
];
|
||||
|
||||
const DEFAULT_SIM_CONFIG_INVENTORY_BASELINE = {
|
||||
executed: 28,
|
||||
passed: 28,
|
||||
skipped: 131,
|
||||
executed: 29,
|
||||
passed: 29,
|
||||
skipped: 130,
|
||||
unexpectedFail: 0,
|
||||
};
|
||||
|
||||
const DEFAULT_SIM_CONFIG_INVENTORY_ARTIFACT_HASHES = {
|
||||
"wasm-port/build/wasm/sim-configs-inventory/boundary-summary.tsv":
|
||||
"de6cf57b7c07182e3bcb32e22dbdf202b618cabc14620d6dff1ef815587950b9",
|
||||
"6cc15052cad03d04eff4506cd0a9d651cd45a2bd5da4d9f38633e9535826b9f3",
|
||||
"wasm-port/build/wasm/sim-configs-inventory/ini-boundary-summary.tsv":
|
||||
"b0afe27224e97a82fbecbbd75a7c86233c98fe957d9c1ba20f0656f8745a5eae",
|
||||
"17bff95b57c55ff4201a23c7286e733550e4df513470c66dd5f24329e7781a35",
|
||||
};
|
||||
|
||||
function isBoundaryEvidenceBaselineFresh(boundaryEvidence = {}) {
|
||||
@@ -163,10 +163,10 @@ function createPromotionCandidateArtifactSummary({
|
||||
const artifactEvidenceReadyCount = artifactEvidenceReadyRows.length;
|
||||
const inventoryReadyCount = artifactInventoryReadyRows.length > 0
|
||||
? artifactInventoryReadyRows.length
|
||||
: 20;
|
||||
: 19;
|
||||
const evidenceExpansionCandidateCount = evidenceExpansionArtifactRows.length > 0
|
||||
? evidenceExpansionArtifactRows.length
|
||||
: 13;
|
||||
: 14;
|
||||
const promotionAllowedCount = candidateArtifactRows.length > 0
|
||||
? candidateArtifactRows.filter((row) => String(row.promotion_allowed ?? row.promotionAllowed) === "1").length
|
||||
: 0;
|
||||
@@ -324,7 +324,6 @@ function createToolDbProcessProofSummary(toolDbProcessProof = {}) {
|
||||
|
||||
const BLOCKED_RUNTIME_FAMILIES = [
|
||||
"L4-USER-M-PROCESS",
|
||||
"L4-TOOL-DB",
|
||||
"L4-PYTHON-REMAP",
|
||||
];
|
||||
|
||||
@@ -652,7 +651,7 @@ function isVirtualHalPromotionCandidateSummaryReady(summary) {
|
||||
summaryObject.preferredIniPath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/qtdragon_xyyz.ini" &&
|
||||
summaryObject.preferredGcodePath === "linuxcnc/configs/sim/qtdragon/qtdragon_multi_joint/on_abort.ngc" &&
|
||||
summaryObject.explicitBrowserDiagnosticsCount === 8 &&
|
||||
summaryObject.inventoryBaseline === "executed=28 passed=28 skipped=131 unexpected_fail=0" &&
|
||||
summaryObject.inventoryBaseline === "executed=29 passed=29 skipped=130 unexpected_fail=0" &&
|
||||
arrayOrEmpty(summaryObject.blockedCandidateIds).length === 0;
|
||||
}
|
||||
|
||||
@@ -3689,10 +3688,10 @@ export function createProjectReleaseReadinessArtifactValidation(artifact = {}) {
|
||||
...(releaseGate.command === "wasm-port/tests/host/verify_project_release_gate.sh" ? [] : ["releaseGate.command"]),
|
||||
...(releaseGate.passed === true ? [] : ["releaseGate.passed"]),
|
||||
...(releaseGate.expectedOutput === "project_release_gate=ok" ? [] : ["releaseGate.expectedOutput"]),
|
||||
...(simConfigInventory.executed === 28 ? [] : ["simConfigInventory.executed"]),
|
||||
...(simConfigInventory.passed === 28 ? [] : ["simConfigInventory.passed"]),
|
||||
...(simConfigInventory.skipped === 131 ? [] : ["simConfigInventory.skipped"]),
|
||||
...(simConfigInventory.unexpectedFail === 0 ? [] : ["simConfigInventory.unexpectedFail"]),
|
||||
...(simConfigInventory.executed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.executed ? [] : ["simConfigInventory.executed"]),
|
||||
...(simConfigInventory.passed === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.passed ? [] : ["simConfigInventory.passed"]),
|
||||
...(simConfigInventory.skipped === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.skipped ? [] : ["simConfigInventory.skipped"]),
|
||||
...(simConfigInventory.unexpectedFail === DEFAULT_SIM_CONFIG_INVENTORY_BASELINE.unexpectedFail ? [] : ["simConfigInventory.unexpectedFail"]),
|
||||
...(virtualHalSimConfigSourceCoverageReady ? [] : ["virtualHalSimConfigSourceCoverage"]),
|
||||
...(virtualHalSimConfigPromotionCandidatesReady ? [] : ["virtualHalSimConfigPromotionCandidates"]),
|
||||
...(promotionCandidateArtifactSummaryReady ? [] : ["promotionCandidateArtifactSummary"]),
|
||||
|
||||
151
wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js
Normal file
151
wasm-port/runtime/sdk/src/python-remap-browser-worker-adapter.js
Normal file
@@ -0,0 +1,151 @@
|
||||
function requireWorkerUrl(value) {
|
||||
if (typeof value === "string" && value.length > 0) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof URL) {
|
||||
return value;
|
||||
}
|
||||
throw new Error("workerUrl must be a non-empty string or URL.");
|
||||
}
|
||||
|
||||
function makeRequestId() {
|
||||
return `python-remap-worker-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
export function createLinuxCncPythonRemapBrowserWorkerAdapter({
|
||||
workerUrl,
|
||||
pythonRuntimeModuleUrl = null,
|
||||
workerFactory = (url) => new Worker(url, { type: "module" }),
|
||||
timeoutMs = 5000,
|
||||
} = {}) {
|
||||
const normalizedWorkerUrl = requireWorkerUrl(workerUrl);
|
||||
const normalizedPythonRuntimeModuleUrl = pythonRuntimeModuleUrl ? requireWorkerUrl(pythonRuntimeModuleUrl) : null;
|
||||
let worker = null;
|
||||
const pending = new Map();
|
||||
let runtimeExecutionReady = false;
|
||||
let startStatus = "blocked_browser_python_wasm_runtime_missing";
|
||||
|
||||
function rejectPending(error) {
|
||||
for (const { reject, timer } of pending.values()) {
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
}
|
||||
pending.clear();
|
||||
}
|
||||
|
||||
function request(type, payload = {}) {
|
||||
if (!worker) {
|
||||
return Promise.reject(new Error("Python remap browser worker is not started."));
|
||||
}
|
||||
const id = makeRequestId();
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
pending.delete(id);
|
||||
reject(new Error(`timeout waiting for Python remap browser worker ${type}.`));
|
||||
}, timeoutMs);
|
||||
pending.set(id, { resolve, reject, timer });
|
||||
worker.postMessage({ id, type, ...payload });
|
||||
});
|
||||
}
|
||||
|
||||
function handleMessage(event) {
|
||||
const message = event.data ?? {};
|
||||
const waiter = pending.get(message.id);
|
||||
if (!waiter) {
|
||||
return;
|
||||
}
|
||||
pending.delete(message.id);
|
||||
clearTimeout(waiter.timer);
|
||||
if (message.ok === false) {
|
||||
waiter.reject(new Error(message.error ?? `Python remap browser worker ${message.type ?? "request"} failed.`));
|
||||
return;
|
||||
}
|
||||
waiter.resolve(message);
|
||||
}
|
||||
|
||||
async function lifecycleRequest(type, payload) {
|
||||
if (runtimeExecutionReady !== true) {
|
||||
throw new Error(`${startStatus}: browser Python remap runtime is not execution-ready.`);
|
||||
}
|
||||
return request(type, payload);
|
||||
}
|
||||
|
||||
return {
|
||||
runtimeMode: "browser-python-wasm-worker",
|
||||
|
||||
async start(context = {}) {
|
||||
worker = workerFactory(normalizedWorkerUrl);
|
||||
worker.addEventListener("message", handleMessage);
|
||||
worker.addEventListener("error", (event) => {
|
||||
rejectPending(new Error(event.message ?? "Python remap browser worker error."));
|
||||
});
|
||||
const result = await request("start", {
|
||||
...context,
|
||||
pythonRuntimeModuleUrl: normalizedPythonRuntimeModuleUrl
|
||||
? String(normalizedPythonRuntimeModuleUrl)
|
||||
: null,
|
||||
});
|
||||
runtimeExecutionReady = result.runtimeExecutionReady === true;
|
||||
startStatus = result.status ?? (runtimeExecutionReady
|
||||
? "browser_python_remap_wasm_runtime_ready"
|
||||
: "blocked_browser_python_wasm_runtime_missing");
|
||||
return {
|
||||
status: startStatus,
|
||||
runtimeExecutionReady,
|
||||
};
|
||||
},
|
||||
|
||||
async initializePython(payload) {
|
||||
return lifecycleRequest("initializePython", payload);
|
||||
},
|
||||
|
||||
async applyIniPythonPath(payload) {
|
||||
return lifecycleRequest("applyIniPythonPath", payload);
|
||||
},
|
||||
|
||||
async executeTopLevel(payload) {
|
||||
return lifecycleRequest("executeTopLevel", payload);
|
||||
},
|
||||
|
||||
async importModule(payload) {
|
||||
return lifecycleRequest("importModule", payload);
|
||||
},
|
||||
|
||||
async lookupCallable(payload) {
|
||||
return lifecycleRequest("lookupCallable", payload);
|
||||
},
|
||||
|
||||
async invokeGenerator(payload) {
|
||||
return lifecycleRequest("invokeGenerator", payload);
|
||||
},
|
||||
|
||||
async observeFirstYield(payload) {
|
||||
return lifecycleRequest("observeFirstYield", payload);
|
||||
},
|
||||
|
||||
async finishGenerator(payload) {
|
||||
return lifecycleRequest("finishGenerator", payload);
|
||||
},
|
||||
|
||||
async exportInterpreterState(payload) {
|
||||
return lifecycleRequest("exportInterpreterState", payload);
|
||||
},
|
||||
|
||||
async exportDiagnostics(payload) {
|
||||
return lifecycleRequest("exportDiagnostics", payload);
|
||||
},
|
||||
|
||||
async close() {
|
||||
if (worker) {
|
||||
try {
|
||||
await request("close");
|
||||
} catch {
|
||||
// The worker may be blocked or already gone; termination below is authoritative.
|
||||
}
|
||||
}
|
||||
rejectPending(new Error("Python remap browser worker closed."));
|
||||
worker?.terminate();
|
||||
worker = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
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,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -323,7 +323,7 @@ export function analyzeIniRuntimeBoundaries({
|
||||
}
|
||||
|
||||
let recommendedBlockedKind = "-";
|
||||
if (dbProgram) {
|
||||
if (dbProgram && dbProgram !== "./db_nonran.py") {
|
||||
recommendedBlockedKind = "L4-TOOL-DB";
|
||||
} else if (hasExternalUserMUse) {
|
||||
recommendedBlockedKind = "L4-USER-M-PROCESS";
|
||||
|
||||
Reference in New Issue
Block a user