@alisio/core 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/janitor.d.ts +48 -0
- package/dist/analysis/janitor.js +170 -18
- package/dist/analysis/jobs.d.ts +11 -2
- package/dist/analysis/jobs.js +13 -0
- package/dist/analysis/oci.d.ts +11 -2
- package/dist/analysis/oci.js +20 -4
- package/dist/analysis/python/sources.js +2 -1
- package/dist/analysis/rerun.d.ts +1 -1
- package/dist/analysis/status.d.ts +3 -1
- package/dist/analysis/status.js +3 -0
- package/dist/application.d.ts +28 -2
- package/dist/application.js +189 -4
- package/dist/changelog/data.js +26 -0
- package/dist/config.d.ts +110 -2
- package/dist/config.js +188 -6
- package/dist/core/call-internals.d.ts +22 -0
- package/dist/core/call-internals.js +9 -0
- package/dist/core/contracts.d.ts +2 -2
- package/dist/core/runner.js +111 -38
- package/dist/forge/broker.d.ts +51 -0
- package/dist/forge/broker.js +244 -0
- package/dist/forge/budget.d.ts +27 -0
- package/dist/forge/budget.js +60 -0
- package/dist/forge/catalog-view.d.ts +24 -0
- package/dist/forge/catalog-view.js +43 -0
- package/dist/forge/errors.d.ts +26 -0
- package/dist/forge/errors.js +49 -0
- package/dist/forge/guides/javascript.d.ts +25 -0
- package/dist/forge/guides/javascript.js +90 -0
- package/dist/forge/guides/provider.d.ts +42 -0
- package/dist/forge/guides/provider.js +96 -0
- package/dist/forge/guides/python-environment.d.ts +15 -0
- package/dist/forge/guides/python-environment.js +81 -0
- package/dist/forge/guides/python.d.ts +38 -0
- package/dist/forge/guides/python.js +170 -0
- package/dist/forge/javascript/engine.d.ts +44 -0
- package/dist/forge/javascript/engine.js +229 -0
- package/dist/forge/javascript/hosts/factory.d.ts +25 -0
- package/dist/forge/javascript/hosts/factory.js +21 -0
- package/dist/forge/javascript/hosts/framing.d.ts +17 -0
- package/dist/forge/javascript/hosts/framing.js +49 -0
- package/dist/forge/javascript/hosts/isolate-main.d.ts +9 -0
- package/dist/forge/javascript/hosts/isolate-main.js +63 -0
- package/dist/forge/javascript/hosts/process-entry.d.ts +1 -0
- package/dist/forge/javascript/hosts/process-entry.js +3 -0
- package/dist/forge/javascript/hosts/process-host.d.ts +33 -0
- package/dist/forge/javascript/hosts/process-host.js +160 -0
- package/dist/forge/javascript/hosts/worker-entry.d.ts +1 -0
- package/dist/forge/javascript/hosts/worker-entry.js +3 -0
- package/dist/forge/javascript/hosts/worker-host.d.ts +18 -0
- package/dist/forge/javascript/hosts/worker-host.js +101 -0
- package/dist/forge/javascript/prelude.d.ts +2 -0
- package/dist/forge/javascript/prelude.js +333 -0
- package/dist/forge/javascript/probe.d.ts +2 -0
- package/dist/forge/javascript/probe.js +53 -0
- package/dist/forge/javascript/runtime-manifest.d.ts +14 -0
- package/dist/forge/javascript/runtime-manifest.js +14 -0
- package/dist/forge/javascript/vm-runner.d.ts +21 -0
- package/dist/forge/javascript/vm-runner.js +301 -0
- package/dist/forge/javascript/wasm-asset.d.ts +8 -0
- package/dist/forge/javascript/wasm-asset.js +8 -0
- package/dist/forge/javascript/wasm.d.ts +15 -0
- package/dist/forge/javascript/wasm.js +54 -0
- package/dist/forge/journal.d.ts +105 -0
- package/dist/forge/journal.js +174 -0
- package/dist/forge/kernel.d.ts +76 -0
- package/dist/forge/kernel.js +338 -0
- package/dist/forge/limits.d.ts +38 -0
- package/dist/forge/limits.js +73 -0
- package/dist/forge/nested.d.ts +96 -0
- package/dist/forge/nested.js +214 -0
- package/dist/forge/ports.d.ts +120 -0
- package/dist/forge/ports.js +1 -0
- package/dist/forge/progress.d.ts +24 -0
- package/dist/forge/progress.js +76 -0
- package/dist/forge/protocol.d.ts +110 -0
- package/dist/forge/protocol.js +171 -0
- package/dist/forge/python/backend-selector.d.ts +38 -0
- package/dist/forge/python/backend-selector.js +111 -0
- package/dist/forge/python/backend.d.ts +89 -0
- package/dist/forge/python/backend.js +1 -0
- package/dist/forge/python/engine.d.ts +84 -0
- package/dist/forge/python/engine.js +279 -0
- package/dist/forge/python/frame-parser.d.ts +24 -0
- package/dist/forge/python/frame-parser.js +107 -0
- package/dist/forge/python/frame-transport.d.ts +40 -0
- package/dist/forge/python/frame-transport.js +101 -0
- package/dist/forge/python/import-scan.d.ts +22 -0
- package/dist/forge/python/import-scan.js +133 -0
- package/dist/forge/python/managed-backend.d.ts +30 -0
- package/dist/forge/python/managed-backend.js +111 -0
- package/dist/forge/python/oci-backend.d.ts +27 -0
- package/dist/forge/python/oci-backend.js +84 -0
- package/dist/forge/python/pyodide-backend.d.ts +40 -0
- package/dist/forge/python/pyodide-backend.js +289 -0
- package/dist/forge/python/pyodide-install.d.ts +41 -0
- package/dist/forge/python/pyodide-install.js +167 -0
- package/dist/forge/python/pyodide-manifest.d.ts +42 -0
- package/dist/forge/python/pyodide-manifest.js +376 -0
- package/dist/forge/python/pyodide-runner-source.d.ts +2 -0
- package/dist/forge/python/pyodide-runner-source.js +3 -0
- package/dist/forge/python/rss.d.ts +2 -0
- package/dist/forge/python/rss.js +33 -0
- package/dist/forge/python/scratch-copy.d.ts +6 -0
- package/dist/forge/python/scratch-copy.js +61 -0
- package/dist/forge/python/settle.d.ts +6 -0
- package/dist/forge/python/settle.js +21 -0
- package/dist/forge/python/status.d.ts +8 -0
- package/dist/forge/python/status.js +15 -0
- package/dist/forge/python/wasm-node.d.ts +59 -0
- package/dist/forge/python/wasm-node.js +220 -0
- package/dist/forge/python/wiring.d.ts +39 -0
- package/dist/forge/python/wiring.js +47 -0
- package/dist/forge/registry.d.ts +26 -0
- package/dist/forge/registry.js +83 -0
- package/dist/forge/result.d.ts +58 -0
- package/dist/forge/result.js +86 -0
- package/dist/forge/routing-rule.d.ts +19 -0
- package/dist/forge/routing-rule.js +31 -0
- package/dist/forge/runtime.d.ts +64 -0
- package/dist/forge/runtime.js +88 -0
- package/dist/forge/scratch-files.d.ts +36 -0
- package/dist/forge/scratch-files.js +94 -0
- package/dist/forge/scratch-provider.d.ts +41 -0
- package/dist/forge/scratch-provider.js +79 -0
- package/dist/forge/scratch-quota.d.ts +8 -0
- package/dist/forge/scratch-quota.js +25 -0
- package/dist/forge/selector.d.ts +35 -0
- package/dist/forge/selector.js +51 -0
- package/dist/forge/session-dir.d.ts +59 -0
- package/dist/forge/session-dir.js +239 -0
- package/dist/forge/session-path.d.ts +31 -0
- package/dist/forge/session-path.js +61 -0
- package/dist/forge/slots.d.ts +16 -0
- package/dist/forge/slots.js +47 -0
- package/dist/forge/state-delta.d.ts +54 -0
- package/dist/forge/state-delta.js +99 -0
- package/dist/forge/state-machine.d.ts +20 -0
- package/dist/forge/state-machine.js +40 -0
- package/dist/forge/state-store.d.ts +25 -0
- package/dist/forge/state-store.js +50 -0
- package/dist/forge/task-log.d.ts +9 -0
- package/dist/forge/task-log.js +42 -0
- package/dist/forge/verdict.d.ts +20 -0
- package/dist/forge/verdict.js +28 -0
- package/dist/forge-isolate.d.ts +6 -0
- package/dist/forge-isolate.js +6 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/permissions/decide.d.ts +42 -0
- package/dist/permissions/decide.js +24 -0
- package/dist/resources/context.d.ts +2 -1
- package/dist/resources/context.js +2 -1
- package/dist/runtime/access.d.ts +34 -0
- package/dist/runtime/access.js +80 -0
- package/dist/runtime/process.d.ts +2 -1
- package/dist/runtime/process.js +5 -5
- package/dist/runtime/store.d.ts +11 -0
- package/dist/runtime/store.js +103 -0
- package/dist/tools/analysis.d.ts +25 -2
- package/dist/tools/analysis.js +315 -267
- package/dist/tools/execute.js +6 -7
- package/dist/tools/forge-guide.d.ts +6 -0
- package/dist/tools/forge-guide.js +60 -0
- package/dist/tools/js-run.d.ts +19 -0
- package/dist/tools/js-run.js +126 -0
- package/dist/tools/standard.d.ts +7 -1
- package/dist/tools/standard.js +44 -43
- package/package.json +7 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static import scan of a Python script (Python spec §6.2). Pure and interpreter-free: it reads the
|
|
3
|
+
* top-level module names a script imports so the backend selector can tell whether the Pyodide
|
|
4
|
+
* profiles that were downloaded cover it. It is NOT a security control: `__import__`,
|
|
5
|
+
* `importlib` and `exec` are invisible to it, and a wrong guess only means the run fails with
|
|
6
|
+
* `PACKAGE_UNAVAILABLE` and is never retried elsewhere (PD5).
|
|
7
|
+
*/
|
|
8
|
+
import { PYODIDE_PROFILES } from "./pyodide-manifest.js";
|
|
9
|
+
/** Source with comments and string contents removed; newlines and line structure stay. */
|
|
10
|
+
function stripStringsAndComments(source) {
|
|
11
|
+
let out = "";
|
|
12
|
+
let i = 0;
|
|
13
|
+
const n = source.length;
|
|
14
|
+
while (i < n) {
|
|
15
|
+
const c = source.charAt(i);
|
|
16
|
+
if (c === "#") {
|
|
17
|
+
while (i < n && source.charAt(i) !== "\n")
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (c === '"' || c === "'") {
|
|
22
|
+
const triple = source.startsWith(c.repeat(3), i);
|
|
23
|
+
const close = triple ? c.repeat(3) : c;
|
|
24
|
+
i += close.length;
|
|
25
|
+
while (i < n && !source.startsWith(close, i)) {
|
|
26
|
+
if (source.charAt(i) === "\\")
|
|
27
|
+
i++;
|
|
28
|
+
// A single-quoted string ends at the line, even when it was never closed.
|
|
29
|
+
else if (!triple && source.charAt(i) === "\n")
|
|
30
|
+
break;
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
if (source.startsWith(close, i))
|
|
34
|
+
i += close.length;
|
|
35
|
+
out += '""';
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
out += c;
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
const IMPORT = /^\s*(?:\w[^:]*:\s*)?import\s+(.+)$/;
|
|
44
|
+
const FROM = /^\s*(?:\w[^:]*:\s*)?from\s+(\.*)([\w.]*)\s+import\b/;
|
|
45
|
+
/** Top-level module names imported by `source`, in order of appearance, without repeats. */
|
|
46
|
+
export function scanImports(source) {
|
|
47
|
+
const found = new Set();
|
|
48
|
+
const code = stripStringsAndComments(source).replace(/\\\r?\n/g, " ");
|
|
49
|
+
for (const line of code.split(/\r?\n/)) {
|
|
50
|
+
for (const statement of line.split(";")) {
|
|
51
|
+
const plain = IMPORT.exec(statement);
|
|
52
|
+
if (plain) {
|
|
53
|
+
for (const part of (plain[1] ?? "").split(",")) {
|
|
54
|
+
const name = /^\s*([A-Za-z_]\w*)/.exec(part)?.[1];
|
|
55
|
+
if (name)
|
|
56
|
+
found.add(name);
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const from = FROM.exec(statement);
|
|
61
|
+
if (from && from[1] === "") {
|
|
62
|
+
const name = (from[2] ?? "").split(".")[0];
|
|
63
|
+
if (name)
|
|
64
|
+
found.add(name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [...found];
|
|
69
|
+
}
|
|
70
|
+
/** Import name -> package name, for the packages the Pyodide profiles carry. */
|
|
71
|
+
const MODULE_PACKAGES = {
|
|
72
|
+
numpy: "numpy",
|
|
73
|
+
pandas: "pandas",
|
|
74
|
+
dateutil: "python-dateutil",
|
|
75
|
+
pytz: "pytz",
|
|
76
|
+
six: "six",
|
|
77
|
+
matplotlib: "matplotlib",
|
|
78
|
+
mpl_toolkits: "matplotlib",
|
|
79
|
+
pylab: "matplotlib",
|
|
80
|
+
contourpy: "contourpy",
|
|
81
|
+
cycler: "cycler",
|
|
82
|
+
fontTools: "fonttools",
|
|
83
|
+
kiwisolver: "kiwisolver",
|
|
84
|
+
packaging: "packaging",
|
|
85
|
+
PIL: "pillow",
|
|
86
|
+
pyparsing: "pyparsing",
|
|
87
|
+
jinja2: "jinja2",
|
|
88
|
+
markupsafe: "markupsafe",
|
|
89
|
+
et_xmlfile: "et-xmlfile",
|
|
90
|
+
openpyxl: "openpyxl",
|
|
91
|
+
lxml: "lxml",
|
|
92
|
+
typing_extensions: "typing-extensions",
|
|
93
|
+
docx: "python-docx",
|
|
94
|
+
narwhals: "narwhals",
|
|
95
|
+
plotly: "plotly",
|
|
96
|
+
charset_normalizer: "charset-normalizer",
|
|
97
|
+
reportlab: "reportlab",
|
|
98
|
+
scipy: "scipy",
|
|
99
|
+
patsy: "patsy",
|
|
100
|
+
statsmodels: "statsmodels",
|
|
101
|
+
joblib: "joblib",
|
|
102
|
+
threadpoolctl: "threadpoolctl",
|
|
103
|
+
sklearn: "scikit-learn",
|
|
104
|
+
};
|
|
105
|
+
/** Top-level standard library modules of CPython 3.10-3.14 (a name list, no interpreter). */
|
|
106
|
+
const STDLIB = new Set("__future__ __main__ _thread abc aifc annotationlib antigravity argparse array ast asynchat asyncio asyncore atexit audioop base64 bdb binascii binhex bisect builtins bz2 cProfile calendar cgi cgitb chunk cmath cmd code codecs codeop collections colorsys compileall compression concurrent configparser contextlib contextvars copy copyreg crypt csv ctypes curses dataclasses datetime dbm decimal difflib dis distutils doctest email encodings ensurepip enum errno faulthandler fcntl filecmp fileinput fnmatch fractions ftplib functools gc genericpath getopt getpass gettext glob graphlib grp gzip hashlib heapq hmac html http idlelib imaplib imghdr imp importlib inspect io ipaddress itertools json keyword lib2to3 linecache locale logging lzma mailbox mailcap marshal math mimetypes mmap modulefinder msilib msvcrt multiprocessing netrc nis nntplib nt ntpath nturl2path numbers opcode operator optparse os ossaudiodev pathlib pdb pickle pickletools pipes pkgutil platform plistlib poplib posix posixpath pprint profile pstats pty pwd py_compile pyclbr pydoc pydoc_data pyexpat queue quopri random re readline reprlib resource rlcompleter runpy sched secrets select selectors shelve shlex shutil signal site smtpd smtplib sndhdr socket socketserver spwd sqlite3 sre_compile sre_constants sre_parse ssl stat statistics string stringprep struct subprocess sunau symtable sys sysconfig syslog tabnanny tarfile telnetlib tempfile termios textwrap this threading time timeit tkinter token tokenize tomllib trace traceback tracemalloc tty turtle turtledemo types typing unicodedata unittest urllib uu uuid venv warnings wave weakref webbrowser winreg winsound wsgiref xdrlib xml xmlrpc zipapp zipfile zipimport zlib zoneinfo".split(" "));
|
|
107
|
+
/** The helpers Alisio copies next to every script. */
|
|
108
|
+
const RUNTIME_MODULE = "alisio_runtime";
|
|
109
|
+
/** Modules Pyodide itself provides (the JavaScript bridge); nothing to download for them. */
|
|
110
|
+
const PYODIDE_MODULES = new Set(["js", "pyodide", "pyodide_js", "_pyodide"]);
|
|
111
|
+
/** Package name for an import name, when a Pyodide profile ships it. */
|
|
112
|
+
export const packageOfModule = (module) => MODULE_PACKAGES[module];
|
|
113
|
+
/** `science` when a package only that profile carries is imported, else `analysis`, else `base`. */
|
|
114
|
+
export function classifyModules(modules) {
|
|
115
|
+
const packages = [];
|
|
116
|
+
const unknown = [];
|
|
117
|
+
for (const module of modules) {
|
|
118
|
+
if (STDLIB.has(module) || PYODIDE_MODULES.has(module) || module === RUNTIME_MODULE)
|
|
119
|
+
continue;
|
|
120
|
+
const pkg = MODULE_PACKAGES[module];
|
|
121
|
+
if (!pkg)
|
|
122
|
+
unknown.push(module);
|
|
123
|
+
else if (!packages.includes(pkg))
|
|
124
|
+
packages.push(pkg);
|
|
125
|
+
}
|
|
126
|
+
const inAnalysis = new Set(PYODIDE_PROFILES.analysis);
|
|
127
|
+
const profile = packages.some((p) => !inAnalysis.has(p))
|
|
128
|
+
? "science"
|
|
129
|
+
: packages.length
|
|
130
|
+
? "analysis"
|
|
131
|
+
: "base";
|
|
132
|
+
return { profile, packages, unknown };
|
|
133
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ManagedBackend` (Python spec §4.3): the discovered host Python, as `python_run` has always run
|
|
3
|
+
* it. NOT a sandbox (`isolation: "none"`): the script has the user's permissions. This wraps
|
|
4
|
+
* `AnalysisRuntimeManager.interpreter` and `runProcess` without changing what they do.
|
|
5
|
+
*/
|
|
6
|
+
import type { EngineDescriptor } from "@alisio/sdk";
|
|
7
|
+
import type { AnalysisRuntimeManager } from "../../analysis/runtime-manager.ts";
|
|
8
|
+
import type { TerminationReason, TerminationReport } from "../ports.ts";
|
|
9
|
+
import type { BackendReadiness, PreparedJob, ProcessSink, PythonBackend, RunningProcess, SupportVerdict } from "./backend.ts";
|
|
10
|
+
/** Environment of the script: the usual whitelist, HOME/TMP inside work/, and the job paths. */
|
|
11
|
+
export declare function scriptEnv(job: {
|
|
12
|
+
input: string;
|
|
13
|
+
staging: string;
|
|
14
|
+
work: string;
|
|
15
|
+
id: string;
|
|
16
|
+
}): Record<string, string>;
|
|
17
|
+
export interface ManagedBackendDeps {
|
|
18
|
+
/** Discovery of the interpreter; the installed extras environment is part of its answer. */
|
|
19
|
+
runtime: Pick<AnalysisRuntimeManager, "interpreter">;
|
|
20
|
+
}
|
|
21
|
+
export declare class ManagedBackend implements PythonBackend {
|
|
22
|
+
#private;
|
|
23
|
+
readonly id: "managed";
|
|
24
|
+
constructor(deps: ManagedBackendDeps);
|
|
25
|
+
describe(): Promise<EngineDescriptor>;
|
|
26
|
+
check(signal?: AbortSignal): Promise<BackendReadiness>;
|
|
27
|
+
supports(): SupportVerdict;
|
|
28
|
+
launch(job: PreparedJob, sink: ProcessSink): Promise<RunningProcess>;
|
|
29
|
+
terminate(process: RunningProcess, _reason: TerminationReason): Promise<TerminationReport>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { runProcess } from "../../runtime/process.js";
|
|
2
|
+
import { settle } from "./settle.js";
|
|
3
|
+
/** Environment of the script: the usual whitelist, HOME/TMP inside work/, and the job paths. */
|
|
4
|
+
export function scriptEnv(job) {
|
|
5
|
+
const keep = [
|
|
6
|
+
"PATH",
|
|
7
|
+
"LANG",
|
|
8
|
+
"LC_ALL",
|
|
9
|
+
"SystemRoot",
|
|
10
|
+
"COMSPEC",
|
|
11
|
+
"PATHEXT",
|
|
12
|
+
"SYSTEMDRIVE",
|
|
13
|
+
"WINDIR",
|
|
14
|
+
];
|
|
15
|
+
return {
|
|
16
|
+
...Object.fromEntries(keep.flatMap((k) => (process.env[k] ? [[k, process.env[k]]] : []))),
|
|
17
|
+
HOME: job.work,
|
|
18
|
+
USERPROFILE: job.work,
|
|
19
|
+
TMPDIR: job.work,
|
|
20
|
+
TEMP: job.work,
|
|
21
|
+
TMP: job.work,
|
|
22
|
+
ALISIO_INPUT_DIR: job.input,
|
|
23
|
+
ALISIO_OUTPUT_DIR: job.staging,
|
|
24
|
+
ALISIO_WORK_DIR: job.work,
|
|
25
|
+
ALISIO_EXECUTION_ID: job.id,
|
|
26
|
+
MPLBACKEND: "Agg",
|
|
27
|
+
PYTHONDONTWRITEBYTECODE: "1",
|
|
28
|
+
PYTHONUNBUFFERED: "1",
|
|
29
|
+
PYTHONNOUSERSITE: "1",
|
|
30
|
+
PYTHONIOENCODING: "utf-8",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export class ManagedBackend {
|
|
34
|
+
id = "managed";
|
|
35
|
+
#deps;
|
|
36
|
+
constructor(deps) {
|
|
37
|
+
this.#deps = deps;
|
|
38
|
+
}
|
|
39
|
+
async describe() {
|
|
40
|
+
return {
|
|
41
|
+
id: "python-managed",
|
|
42
|
+
language: "python",
|
|
43
|
+
version: "host python",
|
|
44
|
+
isolation: "none",
|
|
45
|
+
ready: true,
|
|
46
|
+
capabilities: {
|
|
47
|
+
cooperativeInterrupt: false,
|
|
48
|
+
hardTermination: true,
|
|
49
|
+
hardMemoryLimit: false,
|
|
50
|
+
networkDeniedByOS: false,
|
|
51
|
+
toolCalls: false,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async check(signal) {
|
|
56
|
+
const resolved = await this.#deps.runtime.interpreter(signal);
|
|
57
|
+
return resolved.ok
|
|
58
|
+
? { ready: true }
|
|
59
|
+
: { ready: false, reason: resolved.reason, detail: resolved };
|
|
60
|
+
}
|
|
61
|
+
supports() {
|
|
62
|
+
return { ok: true };
|
|
63
|
+
}
|
|
64
|
+
async launch(job, sink) {
|
|
65
|
+
const interpreter = await this.#deps.runtime.interpreter(job.signal);
|
|
66
|
+
if (!interpreter.ok)
|
|
67
|
+
throw new Error(`runtime_unavailable: ${interpreter.reason}`);
|
|
68
|
+
const stop = new AbortController();
|
|
69
|
+
const args = [
|
|
70
|
+
...(interpreter.prefixArgs ?? []),
|
|
71
|
+
"-E",
|
|
72
|
+
"-s",
|
|
73
|
+
"-B",
|
|
74
|
+
"-u",
|
|
75
|
+
"-X",
|
|
76
|
+
"utf8",
|
|
77
|
+
job.paths.main,
|
|
78
|
+
];
|
|
79
|
+
const result = runProcess(interpreter.executable, args, {
|
|
80
|
+
cwd: job.paths.work,
|
|
81
|
+
signal: AbortSignal.any([job.signal, stop.signal]),
|
|
82
|
+
timeoutMs: job.backstopMs,
|
|
83
|
+
maxBytes: job.maxBytes,
|
|
84
|
+
onOverflow: "truncate",
|
|
85
|
+
logFiles: job.logFiles,
|
|
86
|
+
maxLogBytes: job.maxLogBytes,
|
|
87
|
+
env: {
|
|
88
|
+
...scriptEnv(job.paths),
|
|
89
|
+
...job.env,
|
|
90
|
+
// The scratchpad is the user's own folder: the variable names it, nothing enforces read-only.
|
|
91
|
+
...(job.scratch ? { ALISIO_SCRATCH_DIR: job.scratch } : {}),
|
|
92
|
+
},
|
|
93
|
+
onData: sink.onData,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
result,
|
|
97
|
+
stop,
|
|
98
|
+
runtime: {
|
|
99
|
+
mode: "managed",
|
|
100
|
+
python: interpreter.version,
|
|
101
|
+
extras: interpreter.extras ?? [],
|
|
102
|
+
...(interpreter.runtimeVersion ? { runtimeVersion: interpreter.runtimeVersion } : {}),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async terminate(process, _reason) {
|
|
107
|
+
const started = Date.now();
|
|
108
|
+
process.stop.abort();
|
|
109
|
+
return settle(process, "kill", started);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `OciBackend` (Python spec §4.3): Docker or Podman with a digest-pinned image, as `python_run` has
|
|
3
|
+
* always run it (`ociRunArgs`, `OciRuntime.run`). `isolation: "os-container"`: no network, a
|
|
4
|
+
* read-only root, no capabilities, memory and process limits; not a hardened sandbox.
|
|
5
|
+
*/
|
|
6
|
+
import type { EngineDescriptor } from "@alisio/sdk";
|
|
7
|
+
import { type OciRuntime } from "../../analysis/oci.ts";
|
|
8
|
+
import type { TerminationReason, TerminationReport } from "../ports.ts";
|
|
9
|
+
import type { BackendReadiness, PreparedJob, ProcessSink, PythonBackend, RunningProcess, SupportVerdict } from "./backend.ts";
|
|
10
|
+
export interface OciBackendDeps {
|
|
11
|
+
/** Read on every call: the settings can change between calls. `mode` is `analysis.runtime`. */
|
|
12
|
+
oci: {
|
|
13
|
+
runtime(): OciRuntime;
|
|
14
|
+
mode?(): string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare class OciBackend implements PythonBackend {
|
|
18
|
+
#private;
|
|
19
|
+
readonly id: "oci";
|
|
20
|
+
constructor(deps: OciBackendDeps);
|
|
21
|
+
describe(): Promise<EngineDescriptor>;
|
|
22
|
+
check(signal?: AbortSignal): Promise<BackendReadiness>;
|
|
23
|
+
supports(): SupportVerdict;
|
|
24
|
+
launch(job: PreparedJob, sink: ProcessSink): Promise<RunningProcess>;
|
|
25
|
+
/** Aborting the run kills the container by name (`OciRuntime.run` does it in its `finally`). */
|
|
26
|
+
terminate(process: RunningProcess, _reason: TerminationReason): Promise<TerminationReport>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { mountSource } from "../../analysis/oci.js";
|
|
2
|
+
import { settle } from "./settle.js";
|
|
3
|
+
export class OciBackend {
|
|
4
|
+
id = "oci";
|
|
5
|
+
#deps;
|
|
6
|
+
constructor(deps) {
|
|
7
|
+
this.#deps = deps;
|
|
8
|
+
}
|
|
9
|
+
async describe() {
|
|
10
|
+
const { settings } = this.#deps.oci.runtime();
|
|
11
|
+
return {
|
|
12
|
+
id: "python-oci",
|
|
13
|
+
language: "python",
|
|
14
|
+
version: settings.image ?? settings.engine,
|
|
15
|
+
isolation: "os-container",
|
|
16
|
+
ready: true,
|
|
17
|
+
capabilities: {
|
|
18
|
+
cooperativeInterrupt: false,
|
|
19
|
+
hardTermination: true,
|
|
20
|
+
hardMemoryLimit: true,
|
|
21
|
+
networkDeniedByOS: true,
|
|
22
|
+
toolCalls: false,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async check(signal) {
|
|
27
|
+
const reason = await this.#deps.oci
|
|
28
|
+
.runtime()
|
|
29
|
+
.unavailable(signal, { auto: this.#deps.oci.mode?.() === "auto" });
|
|
30
|
+
return reason === undefined ? { ready: true } : { ready: false, reason };
|
|
31
|
+
}
|
|
32
|
+
supports() {
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
async launch(job, sink) {
|
|
36
|
+
const runtime = this.#deps.oci.runtime();
|
|
37
|
+
const stop = new AbortController();
|
|
38
|
+
// A scratchpad path the engine would misparse as mount syntax is simply not exposed.
|
|
39
|
+
const scratch = job.scratch && mountable(job.scratch) ? job.scratch : undefined;
|
|
40
|
+
const result = runtime.run({
|
|
41
|
+
executionId: job.executionId,
|
|
42
|
+
mounts: {
|
|
43
|
+
script: job.paths.script,
|
|
44
|
+
input: job.paths.input,
|
|
45
|
+
work: job.paths.work,
|
|
46
|
+
staging: job.paths.staging,
|
|
47
|
+
...(scratch ? { scratch } : {}),
|
|
48
|
+
},
|
|
49
|
+
signal: AbortSignal.any([job.signal, stop.signal]),
|
|
50
|
+
timeoutMs: job.backstopMs,
|
|
51
|
+
maxBytes: job.maxBytes,
|
|
52
|
+
logFiles: job.logFiles,
|
|
53
|
+
maxLogBytes: job.maxLogBytes,
|
|
54
|
+
onData: sink.onData,
|
|
55
|
+
env: job.env,
|
|
56
|
+
});
|
|
57
|
+
const { settings } = runtime;
|
|
58
|
+
return {
|
|
59
|
+
result,
|
|
60
|
+
stop,
|
|
61
|
+
runtime: {
|
|
62
|
+
mode: "oci",
|
|
63
|
+
engine: settings.engine,
|
|
64
|
+
...(settings.image ? { image: settings.image } : {}),
|
|
65
|
+
extras: [],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** Aborting the run kills the container by name (`OciRuntime.run` does it in its `finally`). */
|
|
70
|
+
async terminate(process, _reason) {
|
|
71
|
+
const started = Date.now();
|
|
72
|
+
process.stop.abort();
|
|
73
|
+
return settle(process, "container-kill", started);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function mountable(path) {
|
|
77
|
+
try {
|
|
78
|
+
mountSource(path, process.platform);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { EngineDescriptor } from "@alisio/sdk";
|
|
2
|
+
import type { TerminationReason, TerminationReport } from "../ports.ts";
|
|
3
|
+
import type { BackendReadiness, PreparedJob, ProcessSink, PythonBackend, PythonRequirements, RunningProcess, SupportVerdict } from "./backend.ts";
|
|
4
|
+
import { type PyodideFile, type PyodideManifest, type PyodideProfile } from "./pyodide-manifest.ts";
|
|
5
|
+
import { type RssReader } from "./rss.ts";
|
|
6
|
+
import { type NodeEnvironment } from "./wasm-node.ts";
|
|
7
|
+
/** `analysis.wasm`, read on every call: the settings can change between calls. */
|
|
8
|
+
export interface WasmSettings {
|
|
9
|
+
memoryMb: number;
|
|
10
|
+
startupMs: number;
|
|
11
|
+
maxInputBytes: number;
|
|
12
|
+
nodePath: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface PyodideBackendDeps {
|
|
15
|
+
/** The state root; the distribution lives in `<root>/analysis/pyodide/<version>`. */
|
|
16
|
+
stateRoot: string;
|
|
17
|
+
settings: () => WasmSettings;
|
|
18
|
+
manifest?: PyodideManifest;
|
|
19
|
+
environment?: () => NodeEnvironment;
|
|
20
|
+
/** Replaceable in tests. */
|
|
21
|
+
rss?: RssReader;
|
|
22
|
+
sampleMs?: number;
|
|
23
|
+
}
|
|
24
|
+
/** Keys needed to load `wanted` (and what they depend on), in the manifest's load order. */
|
|
25
|
+
export declare function loadPlan(manifest: PyodideManifest, profile: PyodideProfile, wanted: readonly string[]): {
|
|
26
|
+
packages: string[];
|
|
27
|
+
wheels: string[];
|
|
28
|
+
files: PyodideFile[];
|
|
29
|
+
};
|
|
30
|
+
export declare class PyodideBackend implements PythonBackend {
|
|
31
|
+
#private;
|
|
32
|
+
readonly id: "wasm";
|
|
33
|
+
constructor(deps: PyodideBackendDeps);
|
|
34
|
+
check(_signal?: AbortSignal): Promise<BackendReadiness>;
|
|
35
|
+
/** Static facts plus the observed network answer; `ready` is the live state of `check()`. */
|
|
36
|
+
describe(): Promise<EngineDescriptor>;
|
|
37
|
+
supports(requirements: PythonRequirements): SupportVerdict;
|
|
38
|
+
launch(job: PreparedJob, sink: ProcessSink): Promise<RunningProcess>;
|
|
39
|
+
terminate(process: RunningProcess, _reason: TerminationReason): Promise<TerminationReport>;
|
|
40
|
+
}
|