@mcoda/mswarm 0.1.135-codali.4 → 0.1.135-codali.7
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/vendor/codali/cli/TerminalCommand.d.ts.map +1 -1
- package/dist/vendor/codali/cli/TerminalCommand.js +4 -2
- package/dist/vendor/codali/eval/TerminalWorkflowCorpus.d.ts +1 -1
- package/dist/vendor/codali/eval/TerminalWorkflowCorpus.js +2 -2
- package/dist/vendor/codali/eval/fixtures/MixedMediaRecoveryJourney.d.ts +6 -0
- package/dist/vendor/codali/eval/fixtures/MixedMediaRecoveryJourney.d.ts.map +1 -0
- package/dist/vendor/codali/eval/fixtures/MixedMediaRecoveryJourney.js +141 -0
- package/dist/vendor/codali/providers/ConfiguredModelScope.d.ts +1 -0
- package/dist/vendor/codali/providers/ConfiguredModelScope.d.ts.map +1 -1
- package/dist/vendor/codali/providers/ConfiguredModelScope.js +4 -0
- package/dist/vendor/codali/providers/ConfiguredProvider.d.ts +2 -0
- package/dist/vendor/codali/providers/ConfiguredProvider.d.ts.map +1 -1
- package/dist/vendor/codali/providers/ConfiguredProvider.js +16 -3
- package/dist/vendor/codali/providers/ProviderTypes.d.ts +2 -0
- package/dist/vendor/codali/providers/ProviderTypes.d.ts.map +1 -1
- package/dist/vendor/codali/providers/RequestUsage.d.ts +11 -0
- package/dist/vendor/codali/providers/RequestUsage.d.ts.map +1 -0
- package/dist/vendor/codali/providers/RequestUsage.js +33 -0
- package/dist/vendor/codali/runtime/ReviewSource.d.ts +3 -0
- package/dist/vendor/codali/runtime/ReviewSource.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/ReviewSource.js +17 -1
- package/dist/vendor/codali/runtime/SessionController.d.ts +2 -0
- package/dist/vendor/codali/runtime/SessionController.d.ts.map +1 -1
- package/dist/vendor/codali/runtime/SessionController.js +138 -24
- package/dist/vendor/codali/runtime/TaskReview.d.ts +23 -0
- package/dist/vendor/codali/runtime/TaskReview.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/TaskReview.js +66 -0
- package/dist/vendor/codali/runtime/UsageReconciliation.d.ts +28 -0
- package/dist/vendor/codali/runtime/UsageReconciliation.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/UsageReconciliation.js +68 -0
- package/dist/vendor/codali/runtime/VerificationReceiptSelection.d.ts +15 -0
- package/dist/vendor/codali/runtime/VerificationReceiptSelection.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/VerificationReceiptSelection.js +30 -0
- package/dist/vendor/codali/session/SessionStoragePath.d.ts.map +1 -1
- package/dist/vendor/codali/session/SessionStoragePath.js +28 -16
- package/package.json +3 -3
- package/scripts/media_gpu_lock.py +2 -2
- package/scripts/native_load_guard.py +257 -0
|
@@ -2,15 +2,23 @@ import { constants, promises as fs } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
/** Existing state files must not alias another file or an external endpoint. */
|
|
4
4
|
export async function assertRegularStateFile(file) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
6
|
+
try {
|
|
7
|
+
const stat = await fs.lstat(file);
|
|
8
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1)
|
|
9
|
+
throw new Error(`Unsafe session state file: ${path.basename(file)}`);
|
|
10
|
+
if (stat.nlink === 1)
|
|
11
|
+
return;
|
|
12
|
+
// An atomic rename may unlink the old inode while the asynchronous stat
|
|
13
|
+
// is completing. Validate the new named file; never accept the old one.
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code === "ENOENT")
|
|
17
|
+
return;
|
|
12
18
|
throw error;
|
|
19
|
+
}
|
|
13
20
|
}
|
|
21
|
+
throw new Error(`Session state file changed during validation: ${path.basename(file)}`);
|
|
14
22
|
}
|
|
15
23
|
/** Resolve trusted outer aliases (e.g. macOS /var) once, then reject symlinks
|
|
16
24
|
* beneath the storage boundary. The terminal sandbox prevents model processes
|
|
@@ -76,14 +84,18 @@ export async function sessionStoragePath(options) {
|
|
|
76
84
|
return resolved;
|
|
77
85
|
}
|
|
78
86
|
export async function readStateFile(file) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
88
|
+
const handle = await fs.open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
89
|
+
try {
|
|
90
|
+
const stat = await handle.stat();
|
|
91
|
+
if (!stat.isFile() || stat.nlink > 1)
|
|
92
|
+
throw new Error("Unsafe session state file");
|
|
93
|
+
if (stat.nlink === 1)
|
|
94
|
+
return await handle.readFile("utf8");
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await handle.close();
|
|
98
|
+
}
|
|
88
99
|
}
|
|
100
|
+
throw new Error(`Session state file changed during validation: ${path.basename(file)}`);
|
|
89
101
|
}
|
package/package.json
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/bekirdag/mcoda/issues"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@mcoda/db": "0.1.135-codali.
|
|
12
|
-
"@mcoda/shared": "0.1.135-codali.
|
|
11
|
+
"@mcoda/db": "0.1.135-codali.7",
|
|
12
|
+
"@mcoda/shared": "0.1.135-codali.7",
|
|
13
13
|
"fastify": "^5.7.4",
|
|
14
14
|
"undici": "^6.28.0"
|
|
15
15
|
},
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
},
|
|
58
58
|
"type": "module",
|
|
59
59
|
"types": "dist/server.d.ts",
|
|
60
|
-
"version": "0.1.135-codali.
|
|
60
|
+
"version": "0.1.135-codali.7"
|
|
61
61
|
}
|
|
@@ -34,9 +34,9 @@ class MediaGpuRecoveryRequired(MediaGpuError):
|
|
|
34
34
|
code = "media_gpu_recovery_required"
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def _open_scheduler() -> int:
|
|
37
|
+
def _open_scheduler(lock_path: str | None = None) -> int:
|
|
38
38
|
runtime_dir = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"))
|
|
39
|
-
path = os.environ.get("MCODA_MEDIA_GPU_LOCK_PATH", str(runtime_dir / "mcoda-media-gpu1.lock"))
|
|
39
|
+
path = lock_path or os.environ.get("MCODA_MEDIA_GPU_LOCK_PATH", str(runtime_dir / "mcoda-media-gpu1.lock"))
|
|
40
40
|
fd = -1
|
|
41
41
|
try:
|
|
42
42
|
fd = os.open(path, os.O_CREAT | os.O_RDWR | os.O_CLOEXEC | os.O_NOFOLLOW, 0o600)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""Opt-in Linux host RAM admission for native model loading and recovery.
|
|
2
|
+
|
|
3
|
+
The embedding runtime must confirm model readiness or confirmed process death
|
|
4
|
+
before leaving the context. Unknown outcomes retain the shared media fence.
|
|
5
|
+
This module never dispatches or replays inference.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import stat
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
import urllib.request
|
|
20
|
+
from urllib.parse import urlsplit
|
|
21
|
+
from contextlib import contextmanager
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
from .media_gpu_lock import _open_scheduler, _read_lease, _write_lease, MediaGpuLease, MediaGpuError
|
|
26
|
+
except ImportError:
|
|
27
|
+
from media_gpu_lock import _open_scheduler, _read_lease, _write_lease, MediaGpuLease, MediaGpuError
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NativeLoadError(RuntimeError):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_local = threading.local()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_policy(path: str) -> dict:
|
|
38
|
+
if not isinstance(path, str) or not os.path.isabs(path):
|
|
39
|
+
raise NativeLoadError("native load policy must be an absolute file path")
|
|
40
|
+
fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_CLOEXEC)
|
|
41
|
+
try:
|
|
42
|
+
info = os.fstat(fd)
|
|
43
|
+
if (not stat.S_ISREG(info.st_mode) or info.st_uid != os.geteuid()
|
|
44
|
+
or info.st_nlink != 1 or stat.S_IMODE(info.st_mode) & 0o077):
|
|
45
|
+
raise NativeLoadError("native load policy must be a private, owner-owned regular file")
|
|
46
|
+
data = os.read(fd, 32769)
|
|
47
|
+
if len(data) > 32768:
|
|
48
|
+
raise NativeLoadError("native load policy is too large")
|
|
49
|
+
policy = json.loads(data)
|
|
50
|
+
finally:
|
|
51
|
+
os.close(fd)
|
|
52
|
+
fields = {"schemaVersion", "lockPath", "waitSeconds", "minAvailableMiB",
|
|
53
|
+
"restoreReserveMiB", "serviceTimeoutSeconds", "services"}
|
|
54
|
+
if not isinstance(policy, dict) or set(policy) != fields or type(policy["schemaVersion"]) is not int or policy["schemaVersion"] != 1:
|
|
55
|
+
raise NativeLoadError("unsupported native load policy fields or version")
|
|
56
|
+
if not isinstance(policy["lockPath"], str) or not os.path.isabs(policy["lockPath"]):
|
|
57
|
+
raise NativeLoadError("native load policy requires an absolute shared lock path")
|
|
58
|
+
for key in ("waitSeconds", "minAvailableMiB", "restoreReserveMiB", "serviceTimeoutSeconds"):
|
|
59
|
+
value = policy[key]
|
|
60
|
+
if type(value) not in (int, float) or not math.isfinite(value) or value <= 0:
|
|
61
|
+
raise NativeLoadError(f"native load policy {key} must be finite and positive")
|
|
62
|
+
if key.endswith("Seconds") and value > 3600:
|
|
63
|
+
raise NativeLoadError(f"native load policy {key} exceeds one hour")
|
|
64
|
+
peers = policy["services"]
|
|
65
|
+
if not isinstance(peers, list) or len(peers) > 16:
|
|
66
|
+
raise NativeLoadError("native load policy services must be a bounded array")
|
|
67
|
+
seen = set()
|
|
68
|
+
for peer in peers:
|
|
69
|
+
if (not isinstance(peer, dict) or set(peer) != {"unit", "restoreMiB", "readyUrl"}
|
|
70
|
+
or not isinstance(peer["unit"], str)
|
|
71
|
+
or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.@-]{0,240}\.service", peer["unit"])
|
|
72
|
+
or peer["unit"] in seen or type(peer["restoreMiB"]) is not int or peer["restoreMiB"] <= 0):
|
|
73
|
+
raise NativeLoadError("invalid or duplicate native load peer")
|
|
74
|
+
url = urlsplit(peer["readyUrl"]) if isinstance(peer["readyUrl"], str) else None
|
|
75
|
+
if (url is None or url.scheme != "http" or url.hostname not in ("127.0.0.1", "::1")
|
|
76
|
+
or url.username or url.password or url.fragment):
|
|
77
|
+
raise NativeLoadError("native load peer readiness must use credential-free loopback HTTP")
|
|
78
|
+
seen.add(peer["unit"])
|
|
79
|
+
return policy
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def available_mib() -> int:
|
|
83
|
+
try:
|
|
84
|
+
for line in Path("/proc/meminfo").read_text().splitlines():
|
|
85
|
+
if line.startswith("MemAvailable:"):
|
|
86
|
+
fields = line.split()
|
|
87
|
+
if len(fields) == 3 and fields[2] == "kB":
|
|
88
|
+
return int(fields[1]) // 1024
|
|
89
|
+
except (OSError, ValueError):
|
|
90
|
+
pass
|
|
91
|
+
raise NativeLoadError("available host RAM cannot be measured")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def service_state(unit: str, timeout: float) -> str:
|
|
95
|
+
try:
|
|
96
|
+
result = subprocess.run(["/usr/bin/systemctl", "--user", "show", unit,
|
|
97
|
+
"--property=LoadState", "--property=ActiveState"],
|
|
98
|
+
check=True, capture_output=True, text=True, timeout=timeout)
|
|
99
|
+
except (OSError, subprocess.SubprocessError) as error:
|
|
100
|
+
raise NativeLoadError(f"cannot inspect native load peer: {unit}") from error
|
|
101
|
+
values = dict(line.split("=", 1) for line in result.stdout.splitlines() if "=" in line)
|
|
102
|
+
if values.get("LoadState") != "loaded":
|
|
103
|
+
raise NativeLoadError(f"configured native load peer is not loaded: {unit}")
|
|
104
|
+
return values.get("ActiveState", "unknown")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def service_action(action: str, unit: str, timeout: float) -> None:
|
|
108
|
+
try:
|
|
109
|
+
subprocess.run(["/usr/bin/systemctl", "--user", action, unit],
|
|
110
|
+
check=True, stdin=subprocess.DEVNULL, capture_output=True, timeout=timeout)
|
|
111
|
+
except (OSError, subprocess.SubprocessError) as error:
|
|
112
|
+
raise NativeLoadError(f"cannot {action} native load peer: {unit}") from error
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def wait_ready(peer: dict, timeout: float) -> None:
|
|
116
|
+
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
117
|
+
def redirect_request(self, req, fp, code, msg, headers, newurl):
|
|
118
|
+
return None
|
|
119
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
|
|
120
|
+
deadline = time.monotonic() + timeout
|
|
121
|
+
while time.monotonic() < deadline:
|
|
122
|
+
try:
|
|
123
|
+
with opener.open(peer["readyUrl"], timeout=min(2, max(.01, deadline - time.monotonic()))) as response:
|
|
124
|
+
if response.status == 200:
|
|
125
|
+
return
|
|
126
|
+
except (OSError, ValueError):
|
|
127
|
+
pass
|
|
128
|
+
if service_state(peer["unit"], min(5, timeout)) not in ("active", "activating"):
|
|
129
|
+
raise NativeLoadError(f"native load peer stopped during restoration: {peer['unit']}")
|
|
130
|
+
time.sleep(min(.1, max(0, deadline - time.monotonic())))
|
|
131
|
+
raise NativeLoadError(f"native load peer readiness timed out: {peer['unit']}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class NativeLoadLease:
|
|
135
|
+
def __init__(self, record: MediaGpuLease):
|
|
136
|
+
self.record = record
|
|
137
|
+
self.outcome = None
|
|
138
|
+
|
|
139
|
+
def confirm_ready(self) -> None:
|
|
140
|
+
self.outcome = "ready"
|
|
141
|
+
|
|
142
|
+
def confirm_stopped(self) -> None:
|
|
143
|
+
self.outcome = "stopped"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _restore(policy: dict, parked: list[dict]) -> None:
|
|
147
|
+
errors = []
|
|
148
|
+
for peer in parked:
|
|
149
|
+
try:
|
|
150
|
+
needed = policy["restoreReserveMiB"] + peer["restoreMiB"]
|
|
151
|
+
if available_mib() < needed:
|
|
152
|
+
raise NativeLoadError(f"insufficient host RAM to restore {peer['unit']}")
|
|
153
|
+
service_action("start", peer["unit"], policy["serviceTimeoutSeconds"])
|
|
154
|
+
if service_state(peer["unit"], policy["serviceTimeoutSeconds"]) != "active":
|
|
155
|
+
raise NativeLoadError(f"native load peer did not become active: {peer['unit']}")
|
|
156
|
+
wait_ready(peer, policy["serviceTimeoutSeconds"])
|
|
157
|
+
except Exception as error:
|
|
158
|
+
# Preserve the other original peers when one cannot restart.
|
|
159
|
+
errors.append(error)
|
|
160
|
+
if errors:
|
|
161
|
+
raise NativeLoadError("native load peer restoration failed; retain the admission fence") from errors[0]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@contextmanager
|
|
165
|
+
def native_load_guard(env: dict | None):
|
|
166
|
+
"""Use MCODA_NATIVE_LOAD_POLICY from trusted host configuration only.
|
|
167
|
+
|
|
168
|
+
The caller must have stopped its old model process before entering. Nested
|
|
169
|
+
retries in the same thread retain one lease; other threads/processes wait.
|
|
170
|
+
No configured policy means no lock, filesystem, systemctl or RAM probes.
|
|
171
|
+
"""
|
|
172
|
+
path = (env or {}).get("MCODA_NATIVE_LOAD_POLICY")
|
|
173
|
+
if path is None:
|
|
174
|
+
yield None
|
|
175
|
+
return
|
|
176
|
+
active = getattr(_local, "active", None)
|
|
177
|
+
if active is not None:
|
|
178
|
+
if active[0] != path:
|
|
179
|
+
raise NativeLoadError("cannot nest different native load policies")
|
|
180
|
+
active[1].outcome = None
|
|
181
|
+
yield active[1]
|
|
182
|
+
return
|
|
183
|
+
import fcntl
|
|
184
|
+
try:
|
|
185
|
+
policy = read_policy(path)
|
|
186
|
+
fd = _open_scheduler(policy["lockPath"])
|
|
187
|
+
except (OSError, ValueError, MediaGpuError) as error:
|
|
188
|
+
raise NativeLoadError("cannot open the native load policy or shared lease") from error
|
|
189
|
+
record = None
|
|
190
|
+
parked = []
|
|
191
|
+
entered_model = False
|
|
192
|
+
completed = False
|
|
193
|
+
try:
|
|
194
|
+
deadline = time.monotonic() + policy["waitSeconds"]
|
|
195
|
+
while True:
|
|
196
|
+
try:
|
|
197
|
+
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
198
|
+
break
|
|
199
|
+
except BlockingIOError as error:
|
|
200
|
+
if time.monotonic() >= deadline:
|
|
201
|
+
raise NativeLoadError("active media still owns the native load lease") from error
|
|
202
|
+
time.sleep(min(0.1, max(0, deadline - time.monotonic())))
|
|
203
|
+
try:
|
|
204
|
+
previous = _read_lease(fd)
|
|
205
|
+
except MediaGpuError as error:
|
|
206
|
+
raise NativeLoadError("shared native/media lease is unreadable; reconciliation is required") from error
|
|
207
|
+
if previous is not None:
|
|
208
|
+
raise NativeLoadError("an earlier native/media outcome must be reconciled before model loading")
|
|
209
|
+
record = MediaGpuLease(fd, "native-model-load")
|
|
210
|
+
lease = NativeLoadLease(record)
|
|
211
|
+
_local.active = (path, lease)
|
|
212
|
+
for peer in policy["services"]:
|
|
213
|
+
state = service_state(peer["unit"], policy["serviceTimeoutSeconds"])
|
|
214
|
+
if state in ("inactive", "failed"):
|
|
215
|
+
continue
|
|
216
|
+
if state != "active":
|
|
217
|
+
raise NativeLoadError(f"native load peer is changing state: {peer['unit']}")
|
|
218
|
+
# Record before stop: a timed-out stop may still complete.
|
|
219
|
+
parked.append(peer)
|
|
220
|
+
record.record["parked_units"] = [item["unit"] for item in parked]
|
|
221
|
+
_write_lease(fd, record.record)
|
|
222
|
+
service_action("stop", peer["unit"], policy["serviceTimeoutSeconds"])
|
|
223
|
+
if service_state(peer["unit"], policy["serviceTimeoutSeconds"]) != "inactive":
|
|
224
|
+
raise NativeLoadError(f"native load peer has not stopped: {peer['unit']}")
|
|
225
|
+
free = available_mib()
|
|
226
|
+
if free < policy["minAvailableMiB"]:
|
|
227
|
+
raise NativeLoadError(f"native model needs {policy['minAvailableMiB']} MiB available RAM; measured {free}")
|
|
228
|
+
logging.info("native_model_load_admitted available_mib=%d parked_peers=%d", free, len(parked))
|
|
229
|
+
entered_model = True
|
|
230
|
+
try:
|
|
231
|
+
yield lease
|
|
232
|
+
finally:
|
|
233
|
+
# A dead caller or an unconfirmed model process must never admit
|
|
234
|
+
# concurrent media. Keep a durable receipt for native reconciliation.
|
|
235
|
+
if lease.outcome is not None:
|
|
236
|
+
record.mark_uncertain("native_model_peer_restore_pending")
|
|
237
|
+
_restore(policy, parked)
|
|
238
|
+
completed = True
|
|
239
|
+
_write_lease(fd, None)
|
|
240
|
+
else:
|
|
241
|
+
record.mark_uncertain("native_model_load_outcome_unknown")
|
|
242
|
+
if sys.exc_info()[0] is None:
|
|
243
|
+
raise NativeLoadError("native model load returned without readiness or process-death proof")
|
|
244
|
+
finally:
|
|
245
|
+
try:
|
|
246
|
+
if record is not None and not entered_model:
|
|
247
|
+
record.mark_uncertain("native_model_peer_restore_pending")
|
|
248
|
+
_restore(policy, parked)
|
|
249
|
+
completed = True
|
|
250
|
+
_write_lease(fd, None)
|
|
251
|
+
if record is not None and not completed:
|
|
252
|
+
record.mark_uncertain("native_model_load_or_restore_unconfirmed")
|
|
253
|
+
finally:
|
|
254
|
+
if record is not None:
|
|
255
|
+
record._closed = True
|
|
256
|
+
_local.active = None
|
|
257
|
+
os.close(fd)
|