@xagents_in/unmask 0.0.0-stage → 0.1.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.
@@ -0,0 +1,280 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes, createHash } from "node:crypto";
3
+ import { createServer, createConnection } from "node:net";
4
+ import { constants } from "node:fs";
5
+ import {
6
+ mkdir,
7
+ readdir,
8
+ lstat,
9
+ chmod,
10
+ realpath,
11
+ open,
12
+ rm,
13
+ } from "node:fs/promises";
14
+ import path from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { validateReport } from "../core/report.mjs";
17
+ import { parsePackageSpec } from "../core/acquire.mjs";
18
+
19
+ const JOB_NAME = /^job-[a-f0-9]{32}$/;
20
+ const fail = (code) => new Error(code);
21
+ function boundedReport(report, cap) {
22
+ validateReport(report);
23
+ if (Buffer.byteLength(JSON.stringify(report)) > cap)
24
+ throw fail("analysis-result-cap");
25
+ return report;
26
+ }
27
+
28
+ function leasePath(root, name) {
29
+ const rootHash = createHash("sha256").update(root).digest("hex").slice(0, 16);
30
+ return `/tmp/um-${process.getuid()}-${rootHash}-${name.slice(4)}.sock`;
31
+ }
32
+ async function jobLease(root, name) {
33
+ const filename = leasePath(root, name);
34
+ const server = createServer((socket) => socket.destroy());
35
+ await new Promise((resolve, reject) => {
36
+ server.once("error", reject);
37
+ server.listen(filename, () => {
38
+ server.removeListener("error", reject);
39
+ resolve();
40
+ });
41
+ });
42
+ const close = () =>
43
+ new Promise((resolve, reject) =>
44
+ server.close((error) => (error ? reject(error) : resolve())),
45
+ );
46
+ try {
47
+ await chmod(filename, 0o600);
48
+ // Node 24 exposes the listening descriptor here; the worker must inherit it.
49
+ const fd = server._handle?.fd;
50
+ if (!Number.isInteger(fd) || fd < 0)
51
+ throw fail("analysis-lease-unavailable");
52
+ server.unref();
53
+ return { close, fd };
54
+ } catch (error) {
55
+ await close();
56
+ throw error;
57
+ }
58
+ }
59
+ async function abandonedLease(filename) {
60
+ try {
61
+ const stat = await lstat(filename);
62
+ if (!stat.isSocket() || stat.uid !== process.getuid()) return false;
63
+ } catch (error) {
64
+ if (error.code === "ENOENT") return false;
65
+ throw error;
66
+ }
67
+ return new Promise((resolve) => {
68
+ const socket = createConnection(filename);
69
+ let done = false;
70
+ const timer = setTimeout(() => finish(false), 500);
71
+ function finish(stale) {
72
+ if (done) return;
73
+ done = true;
74
+ clearTimeout(timer);
75
+ socket.destroy();
76
+ resolve(stale);
77
+ }
78
+ socket.once("connect", () => finish(false));
79
+ socket.on("error", (error) => finish(error.code === "ECONNREFUSED"));
80
+ });
81
+ }
82
+
83
+ async function readResult(workDir, cap) {
84
+ const handle = await open(
85
+ path.join(workDir, "report.json"),
86
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
87
+ );
88
+ try {
89
+ const stat = await handle.stat();
90
+ if (!stat.isFile()) throw fail("analysis-worker-invalid-result");
91
+ if (stat.size > cap) throw fail("analysis-result-cap");
92
+ const bytes = Buffer.alloc(stat.size);
93
+ let offset = 0;
94
+ while (offset < bytes.length) {
95
+ const { bytesRead } = await handle.read(
96
+ bytes,
97
+ offset,
98
+ bytes.length - offset,
99
+ offset,
100
+ );
101
+ if (!bytesRead) throw fail("analysis-worker-invalid-result");
102
+ offset += bytesRead;
103
+ }
104
+ return boundedReport(
105
+ JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)),
106
+ cap,
107
+ );
108
+ } finally {
109
+ await handle.close();
110
+ }
111
+ }
112
+
113
+ function runWorker(workerURL, request, signal, deadline, leaseFd) {
114
+ return new Promise((resolve, reject) => {
115
+ let stopped, timer, child;
116
+ const stop = (reason) => {
117
+ stopped ||= reason;
118
+ child?.kill("SIGKILL");
119
+ };
120
+ const abort = () => stop("analysis-aborted");
121
+ if (signal?.aborted) return reject(fail("analysis-aborted"));
122
+ try {
123
+ child = spawn(process.execPath, ["--max-old-space-size=512", workerURL], {
124
+ cwd: request.workDir,
125
+ env: { LANG: "C.UTF-8", TMPDIR: request.workDir },
126
+ stdio: ["pipe", "ignore", "ignore", leaseFd],
127
+ });
128
+ } catch {
129
+ return reject(fail("analysis-worker-failed"));
130
+ }
131
+ signal?.addEventListener("abort", abort, { once: true });
132
+ timer = setTimeout(() => stop("analysis-timeout"), deadline);
133
+ child.on("error", () => {
134
+ stopped ||= "analysis-worker-failed";
135
+ });
136
+ child.stdin.on("error", () => stop("analysis-worker-failed"));
137
+ child.once("close", (code) => {
138
+ clearTimeout(timer);
139
+ signal?.removeEventListener("abort", abort);
140
+ if (stopped || code !== 0)
141
+ reject(fail(stopped || "analysis-worker-failed"));
142
+ else resolve();
143
+ });
144
+ child.stdin.end(JSON.stringify(request));
145
+ if (signal?.aborted) abort();
146
+ });
147
+ }
148
+
149
+ async function explainReport(report, client, signal, cap) {
150
+ const controller = new AbortController();
151
+ let timer, abort;
152
+ const cancellation = new Promise((resolve, reject) => {
153
+ abort = () => {
154
+ controller.abort();
155
+ reject(fail("analysis-aborted"));
156
+ };
157
+ signal?.addEventListener("abort", abort, { once: true });
158
+ timer = setTimeout(() => {
159
+ controller.abort();
160
+ resolve({ status: "timeout" });
161
+ }, 135000);
162
+ });
163
+ try {
164
+ if (signal?.aborted) throw fail("analysis-aborted");
165
+ const operation = Promise.resolve().then(() =>
166
+ client.explain(structuredClone(report), { signal: controller.signal }),
167
+ );
168
+ let model;
169
+ try {
170
+ model = await Promise.race([operation, cancellation]);
171
+ if (signal?.aborted) throw fail("analysis-aborted");
172
+ return boundedReport({ ...report, model }, cap);
173
+ } catch {
174
+ if (signal?.aborted) throw fail("analysis-aborted");
175
+ for (const model of [
176
+ { status: "unavailable", reason: "model-failed" },
177
+ { status: "unavailable" },
178
+ { status: "failed" },
179
+ ]) {
180
+ const fallback = { ...report, model };
181
+ if (Buffer.byteLength(JSON.stringify(fallback)) <= cap)
182
+ return boundedReport(fallback, cap);
183
+ }
184
+ throw fail("analysis-result-cap");
185
+ }
186
+ } finally {
187
+ clearTimeout(timer);
188
+ signal?.removeEventListener("abort", abort);
189
+ }
190
+ }
191
+
192
+ export async function createRunner({
193
+ tempRoot,
194
+ workerURL = new URL("./worker.mjs", import.meta.url),
195
+ deterministicMs = 120000,
196
+ maxResultBytes = 67108864,
197
+ modelClient,
198
+ } = {}) {
199
+ if (typeof tempRoot !== "string" || !path.isAbsolute(tempRoot))
200
+ throw new TypeError("An absolute private tempRoot is required");
201
+ if (
202
+ !Number.isSafeInteger(deterministicMs) ||
203
+ deterministicMs < 1 ||
204
+ deterministicMs > 120000 ||
205
+ !Number.isSafeInteger(maxResultBytes) ||
206
+ maxResultBytes < 1 ||
207
+ maxResultBytes > 67108864
208
+ )
209
+ throw new TypeError("Invalid runner limits");
210
+ if (modelClient !== undefined && typeof modelClient.explain !== "function")
211
+ throw new TypeError("Invalid model client");
212
+ const filename =
213
+ workerURL instanceof URL ? fileURLToPath(workerURL) : workerURL;
214
+ if (typeof filename !== "string" || !path.isAbsolute(filename))
215
+ throw new TypeError("Worker must be an absolute trusted module path");
216
+ await mkdir(tempRoot, { recursive: true, mode: 0o700 });
217
+ const rootStat = await lstat(tempRoot);
218
+ if (
219
+ !rootStat.isDirectory() ||
220
+ rootStat.isSymbolicLink() ||
221
+ rootStat.uid !== process.getuid?.()
222
+ )
223
+ throw new TypeError("tempRoot must be a private directory");
224
+ await chmod(tempRoot, 0o700);
225
+ const privateRoot = await realpath(tempRoot);
226
+ for (const entry of await readdir(privateRoot, { withFileTypes: true })) {
227
+ if (!entry.isDirectory() || !JOB_NAME.test(entry.name)) continue;
228
+ const owner = leasePath(privateRoot, entry.name);
229
+ if (await abandonedLease(owner)) {
230
+ await rm(path.join(privateRoot, entry.name), {
231
+ recursive: true,
232
+ force: true,
233
+ });
234
+ await rm(owner, { force: true });
235
+ }
236
+ }
237
+ let active = false;
238
+ return async function runAnalysis(spec, { signal } = {}) {
239
+ parsePackageSpec(spec);
240
+ if (signal !== undefined && !(signal instanceof AbortSignal))
241
+ throw new TypeError("Invalid analysis signal");
242
+ if (active) throw fail("analysis-busy");
243
+ active = true;
244
+ let workDir, lease;
245
+ try {
246
+ if (signal?.aborted) throw fail("analysis-aborted");
247
+ const owned = path.join(
248
+ privateRoot,
249
+ `job-${randomBytes(16).toString("hex")}`,
250
+ );
251
+ lease = await jobLease(privateRoot, path.basename(owned));
252
+ await mkdir(owned, { mode: 0o700 });
253
+ workDir = owned;
254
+ if (signal?.aborted) throw fail("analysis-aborted");
255
+ await runWorker(
256
+ filename,
257
+ { spec, workDir, maxResultBytes },
258
+ signal,
259
+ deterministicMs,
260
+ lease.fd,
261
+ );
262
+ if (signal?.aborted) throw fail("analysis-aborted");
263
+ const report = await readResult(workDir, maxResultBytes);
264
+ if (signal?.aborted) throw fail("analysis-aborted");
265
+ return modelClient
266
+ ? await explainReport(report, modelClient, signal, maxResultBytes)
267
+ : report;
268
+ } finally {
269
+ try {
270
+ if (workDir) await rm(workDir, { recursive: true, force: true });
271
+ } finally {
272
+ try {
273
+ if (lease) await lease.close();
274
+ } finally {
275
+ active = false;
276
+ }
277
+ }
278
+ }
279
+ };
280
+ }
@@ -0,0 +1,105 @@
1
+ const deterministicCaps = new Set([
2
+ "compressed-cap",
3
+ "expanded-cap",
4
+ "ratio-cap",
5
+ "entry-cap",
6
+ "path-cap",
7
+ "path-depth-cap",
8
+ "path-node-cap",
9
+ "metadata-cap",
10
+ ]);
11
+ const transientModelStatuses = new Set([
12
+ "unavailable",
13
+ "timeout",
14
+ "failed",
15
+ "invalid",
16
+ "quota-exceeded",
17
+ ]);
18
+
19
+ function reusable(record) {
20
+ if (!record.report || transientModelStatuses.has(record.report.model?.status))
21
+ return false;
22
+ if (record.status === "done") return record.report.policy?.exitCode !== 2;
23
+ return (
24
+ record.status === "incomplete" &&
25
+ record.report.policy?.exitCode === 2 &&
26
+ record.report.failures?.length > 0 &&
27
+ record.report.failures.every(
28
+ (failure) =>
29
+ deterministicCaps.has(failure.code) ||
30
+ (failure.code === "source-selection-unresolved" &&
31
+ failure.reason === "ambiguous-manifest"),
32
+ )
33
+ );
34
+ }
35
+
36
+ export function createReportStore({
37
+ maxEntries = 200,
38
+ maxBytes = 64 * 1024 * 1024,
39
+ ttlMs = 1800000,
40
+ clock = { now: Date.now },
41
+ } = {}) {
42
+ for (const value of [maxEntries, maxBytes, ttlMs])
43
+ if (!Number.isSafeInteger(value) || value < 1)
44
+ throw new TypeError("Store limits must be positive integers");
45
+ const records = new Map();
46
+ let bytes = 0;
47
+ function remove(id) {
48
+ const record = records.get(id);
49
+ if (record) {
50
+ bytes -= record.bytes;
51
+ records.delete(id);
52
+ }
53
+ }
54
+ function expire() {
55
+ for (const [id, record] of records)
56
+ if (clock.now() >= record.expiresAt) remove(id);
57
+ }
58
+ function get(id) {
59
+ expire();
60
+ const record = records.get(id);
61
+ if (!record) return undefined;
62
+ records.delete(id);
63
+ records.set(id, record);
64
+ return JSON.parse(record.json);
65
+ }
66
+ return {
67
+ get,
68
+ put(record) {
69
+ expire();
70
+ const json = JSON.stringify(record);
71
+ const size = Buffer.byteLength(json);
72
+ if (size > maxBytes) return false;
73
+ remove(record.id);
74
+ while (records.size >= maxEntries || bytes + size > maxBytes)
75
+ remove(records.keys().next().value);
76
+ const now = clock.now();
77
+ const completedAt =
78
+ Number.isSafeInteger(record.finishedAt) && record.finishedAt >= 0
79
+ ? Math.min(record.finishedAt, now)
80
+ : now;
81
+ records.set(record.id, {
82
+ json,
83
+ bytes: size,
84
+ expiresAt: completedAt + ttlMs,
85
+ });
86
+ bytes += size;
87
+ return true;
88
+ },
89
+ findKey(key) {
90
+ expire();
91
+ for (const [id, record] of records) {
92
+ const item = JSON.parse(record.json);
93
+ if (item.key === key && reusable(item)) return get(id);
94
+ }
95
+ },
96
+ get bytes() {
97
+ expire();
98
+ return bytes;
99
+ },
100
+ get size() {
101
+ expire();
102
+ return records.size;
103
+ },
104
+ };
105
+ }
@@ -0,0 +1,57 @@
1
+ import { lstat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { parsePackageSpec } from "../core/acquire.mjs";
5
+ import { validateReport } from "../core/report.mjs";
6
+
7
+ export async function workerMain() {
8
+ let input = "";
9
+ for await (const chunk of process.stdin) {
10
+ if (Buffer.byteLength(input) + chunk.length > 8192)
11
+ throw new Error("invalid-worker-request");
12
+ input += chunk.toString("utf8");
13
+ }
14
+ const request = JSON.parse(input);
15
+ if (
16
+ !request ||
17
+ typeof request !== "object" ||
18
+ Array.isArray(request) ||
19
+ Object.keys(request).some(
20
+ (key) => !["spec", "workDir", "maxResultBytes"].includes(key),
21
+ ) ||
22
+ typeof request.workDir !== "string" ||
23
+ request.workDir !== process.cwd() ||
24
+ !/^job-[a-f0-9]{32}$/.test(path.basename(request.workDir)) ||
25
+ !Number.isSafeInteger(request.maxResultBytes) ||
26
+ request.maxResultBytes < 1 ||
27
+ request.maxResultBytes > 67108864
28
+ )
29
+ throw new Error("invalid-worker-request");
30
+ parsePackageSpec(request.spec);
31
+ const stat = await lstat(request.workDir);
32
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 0o077)
33
+ throw new Error("invalid-worker-directory");
34
+ const { analyzeRegistry } = await import("../core/online.mjs");
35
+ const report = validateReport(
36
+ await analyzeRegistry(request.spec, {
37
+ workDir: request.workDir,
38
+ persistCache: false,
39
+ }),
40
+ );
41
+ report.run.surface = "web";
42
+ const result = JSON.stringify(report);
43
+ if (Buffer.byteLength(result) > request.maxResultBytes)
44
+ throw new Error("analysis-result-cap");
45
+ await writeFile(path.join(request.workDir, "report.json"), result, {
46
+ flag: "wx",
47
+ mode: 0o600,
48
+ });
49
+ }
50
+
51
+ if (
52
+ process.argv[1] &&
53
+ pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url
54
+ )
55
+ workerMain().catch(() => {
56
+ process.exitCode = 1;
57
+ });
@@ -0,0 +1 @@
1
+ {"buildSchema":"1","version":"0.1.0","dirty":false,"commit":"f011778208a3482298332e925953af6801c358b2","sourceSha256":"fb3242f543b59e7c0655e87588dcc57b1ecf43ee099dfc762bd6f052b5ea537d","packageSha256":"3ab84be2e0a0d13b691a655dec4be52ab6c08ca8d4d87f32688aea76588ecfdc","lockfileSha256":"7d00d517bc2fc689d89a91e5488505bbe9b7e64a352c3dc72ea4bd84d9dd0f97","files":{"cli/hosted.mjs":{"sha256":"dea7b512a2e811102521565e9f218c77aab916fe05e838f55a9aeb7b43cd0852","size":7436},"cli/main.mjs":{"sha256":"3bff23e18025d73dbfd8eb3a9571a7797702f03ed14e8d2d1edd6a12304c579e","size":6597},"core/acquire.mjs":{"sha256":"aee5ed002292edf1b03385ceec8ebfb03de452dba36bbdced96f888eaafa1d67","size":9549},"core/archive.mjs":{"sha256":"80aa4373c0324458fe322a83af18a4cfd0ffec4b39ebdeffc9084639a8655410","size":16193},"core/compare.mjs":{"sha256":"540614266e06a2875010793d036d256ddfac72f978157cd3d147ffb99015d8e4","size":7579},"core/manifests.mjs":{"sha256":"29c40e01ff1e1dfaed444600d8c78380d23d4bc9be71960251e39ea3594de2f1","size":17891},"core/online.mjs":{"sha256":"368a3f2558e16db0701d2eaf068b48e2661e7e01ab7cf5605cdd22766aeb5cca","size":9741},"core/pipeline.mjs":{"sha256":"9c4ddc5c258a99455a1230c978fd132c135057ad7bb6c7baa201ae530ee136fd","size":11060},"core/policy.mjs":{"sha256":"4281c626c00a6424992f5e1eba26876e2b70f4b968450a8ca52fa42f5a121ba5","size":2954},"core/report.mjs":{"sha256":"aa167c94c257a5ff6d830630a37a51a9a9e47f82aa4a0a7b96ead91a5967294b","size":15844},"core/resolve.mjs":{"sha256":"d5575da49ff78e8fea8df1f2fc10a39540adea285eddfa347746e848f9fdf957","size":5202},"core/source.mjs":{"sha256":"798fe74e3fba836b82576cd46e1b91e6f6e0a4e3cf273c533b5e074247c81373","size":3238},"core/syntax.mjs":{"sha256":"2d50f2e73e17aedbcbccd92af280e171de4ef78b55acccd23d6b6ef11e891776","size":10450},"model/client.mjs":{"sha256":"ddda56b2080d6508ba85e105d21d3709dee07b80ed4ca8c492c824456205b422","size":41854},"model/config.mjs":{"sha256":"127605979a299616be15f4a47e94dcd82928b00ad2af71744ffaa6e359516486","size":3245},"server/app.mjs":{"sha256":"36c265269bdf3f063e98997f26efed077b2b3a3113e709b06a91af69d8376b3c","size":9392},"server/main.mjs":{"sha256":"bffc78cbb973d0dd9900bbdc307d86b15de91b1ea2b0f312a16c2b673a0801bc","size":4044},"server/queue.mjs":{"sha256":"37290655ef70c35383381ec5e76f10922b918f40224fca51a92abd714c095cf7","size":5094},"server/runner.mjs":{"sha256":"a6ca5bfbd7b239f7110639d854ca9a263f4cf3aaf06701ea54493b29f8244706","size":9071},"server/store.mjs":{"sha256":"efa2a35c7e9d0344c26e24ba360a457cdf80f4f48975f0186f81844b9fb7a097","size":2706},"server/worker.mjs":{"sha256":"1df839608cd1c691fc2801d89e698daeef3e48d7f4b8843ce1252a21fde9730a","size":1902},"viewer/render.mjs":{"sha256":"c77442c27041a5fe28269b8c80c9d24c72b73a226e8d4491e6d7285a9feb5309","size":19443},"viewer/shell.mjs":{"sha256":"5f800d0a97fe416d02cf9abef766d651b444b4745ad3a998926599469358ca79","size":9408}}}