@ai-ecoverse/slicc-kernel 1.143.1 → 1.143.3
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/README.md +3 -3
- package/dist/kernel-worker.js +57 -8
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +4 -2
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +57 -8
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +4 -2
- package/dist/process-worker.js.map +2 -2
- package/dist/types/fs/watch.d.ts +13 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -375,7 +375,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
375
375
|
- `'change'`: an existing entry's data or metadata changed (a write, `truncate`, `chmod`, `chown`, `utimes`). A write that creates a file gives `'rename'` and then `'change'`, as on Linux.
|
|
376
376
|
- `filename` is the entry's name relative to the watched directory (`sub/a.txt` when recursive), or the file's basename for a file watch.
|
|
377
377
|
- **Overflow:** a watch holds 1,000 events between reads. Beyond that, the rest are dropped, and `{ overflow: true }` follows the ones kept.
|
|
378
|
-
- **The watched path itself:**
|
|
378
|
+
- **The watched path itself:** like inotify, a watch follows the file or directory it was opened on, not its name. When a directory above it is renamed, the watch stays on the moved path with no event, and later events keep the name it was opened with. When the path itself is renamed, the watch gets one `{ type: 'rename', filename, moved: true }` and stays on the new path. Only a removal ends it: `rm` of the path or a directory above it, or a rename of something else over it gives one `'rename'` with its own name, then `{ ended: true }`. Renames are followed as this kernel makes them ([#1356](https://github.com/ai-ecoverse/slicc-kernel/issues/1356)). `close()` is safe at any time and idempotent, and nothing is delivered after it returns.
|
|
379
379
|
- **Errors at watch time:** a path the program may not read (under `users`) is `EACCES`, and one that doesn't exist is `ENOENT`. Permission is checked first, so a path under a directory the program can't search is `EACCES` whether or not it exists. A process holds at most 8,192 watches; past that, `ENOSPC`.
|
|
380
380
|
- **Permissions per event:** under `users`, a program only hears about paths it may reach. An event about `P` below the watched directory is delivered only if the program's user can search every directory down to `P`'s parent and read that parent, as Node's recursive watch would need in order to add a watch there. The check runs when the program takes the events, so another user's `0700` directory stays silent until it's opened up. A consequence is that events inside a directory that is gone by the time the program takes them are dropped: after `rm -r sub` the program sees `rename sub` but not `sub/a`. A watcher should rescan on a directory's `rename`, as Node users do anyway.
|
|
381
381
|
- **Who counts as a writer:** every process in the kernel, the embedder (`kernel.writeFile` and the like) and the mounts that report changes. A `hostfs` change from the host comes as `'change'`, since the host doesn't say which kind it was.
|
|
@@ -547,7 +547,7 @@ The module's default export (or its `main` export) is called with a context and
|
|
|
547
547
|
- **The bound:** the server the value goes to is the one the vault rule names, verified against the kernel's own roots. Substitution trusts that server not to reflect the value back in a form the scrub doesn't know (encrypted under a key the program chose, say). That's the same residual as A1's.
|
|
548
548
|
- **Elsewhere:** a placeholder written into any other A2 session reaches the server as written. It isn't the secret, so nothing leaks.
|
|
549
549
|
- **Limits:** no revocation checking (neither OCSP nor CRLs, as with Node's default), no session resumption, and the clock is the page's `Date.now()`.
|
|
550
|
-
- `net.resolve(name, { family, signal })` asks the kernel's resolver, which WASIX `resolve` and Emscripten `getaddrinfo` use too: entries in a real `/etc/hosts`, the kernel's own names (`localhost`, the node name, `host.slicc.internal`) and then the uplink's DNS ([#913](https://github.com/ai-ecoverse/slicc-kernel/issues/913)). It answers the addresses, in the resolver's order, for `family` `4`, `6` or `0` (both, IPv4 first; the default). A name with no answer rejects with a `JsCallError` of code `ENOTFOUND`, as Node's `dns.lookup` does,
|
|
550
|
+
- `net.resolve(name, { family, signal })` asks the kernel's resolver, which WASIX `resolve` and Emscripten `getaddrinfo` use too: entries in a real `/etc/hosts`, the kernel's own names (`localhost`, the node name, `host.slicc.internal`) and then the uplink's DNS ([#913](https://github.com/ai-ecoverse/slicc-kernel/issues/913)). It answers the addresses, in the resolver's order, for `family` `4`, `6` or `0` (both, IPv4 first; the default). A name with no answer rejects with a `JsCallError` of code `ENOTFOUND`, as Node's `dns.lookup` does. A lookup the uplink fails or doesn't answer within 10 s rejects with `EAI_AGAIN` (the kernel's `sock-resolve` is `ETIMEDOUT` there, `ENOENT` for no answer), as Node's does for a transient failure, so a client can retry; the failure isn't cached ([#1363](https://github.com/ai-ecoverse/slicc-kernel/issues/1363)). Another family rejects with `EAI_FAMILY`, and an aborted `signal` with its reason.
|
|
551
551
|
- `websocket(url, { protocols, headers, signal })`, a WebSocket over the kernel's transport (its `websocket`, so `fetchTransport({ webSocket: true })` in a browser), shaped like `WebSocketStream`. It resolves once the socket is open, with `url`, the `protocol` the far end chose (always one the program offered: a transport that hands back a socket with another fails the open with `EPROTO`, while browsers and Node's `WebSocket`, which `fetchTransport()` uses, fail such a handshake themselves without saying why, so the open fails with `ECONNREFUSED`), a `readable` of messages (`string` for text, `Uint8Array` for binary), a `writable` for sending them, `closed` (the `{ code, reason }` of the close, from either side, whether or not the program reads) and `close({ code, reason })`. An `http(s):` URL is taken as `ws(s):`. Sending waits while more than 1 MiB is buffered. A signal aborts the open, and after it the socket. A transport without WebSockets rejects with a `TypeError` whose `cause.code` is `ENOTSUP`. One that cannot connect rejects with the `code` its error carries (an errno, such as `EAI_NONAME`, `ECONNREFUSED`, `ETIMEDOUT` or `EPROTO`), else `ECONNREFUSED`. `ws://host.slicc.internal:port` reaches the real machine's loopback, as it does through the proxy;
|
|
552
552
|
- `fetch(input, init)`, the web's `fetch` over the kernel's transport, the one the proxy uses, so the embedder's transport, its WebSocket and proxy settings and the kernel's routes apply. The response body streams as the program reads it. On a transport with `traits.streamingBodies` (below), a `ReadableStream` body (with `duplex: 'half'`, which `Request` requires) streams to it as the program produces it, with no size limit of the kernel's, and a redirect that would send it again (a 307 or 308) rejects with a `TypeError`, as the fetch standard has it. Other bodies (a string, a buffer, a `Blob`, a `FormData`) can be sent again, so they go whole even there. Otherwise the request body is read whole first, and one larger than the transport's `maxRequestBody` is answered `413`, as through the proxy. A signal aborts the request at any stage. Redirects follow the fetch rules: at most 20, a 301 or 302 after a `POST` and a 303 turn into a `GET` without a body, `Authorization` is dropped across origins, and `redirect: 'manual'` returns the redirect itself. On a transport that follows redirects on its own (`fetchTransport()` in a browser) only `'follow'` works: `'manual'` and `'error'` reject with a `TypeError` whose `cause.code` is `ENOTSUP`, since such a transport never shows the program a redirect. A status the transport chose comes back as a response whose body is the reason. An unreachable host, or no transport at all, rejects with a `TypeError` whose `cause.code` is `ECONNREFUSED` or `ENETUNREACH`, as Node's `fetch` does. A `data:` URL is answered by `fetch` itself, as the fetch standard decodes it, without a transport: for any method, a `HEAD` without a body, and its type parsed and serialized as a MIME type (lowercase, no spaces, invalid parameters dropped), as Node's `fetch` answers; any other scheme than `http:` and `https:` rejects with a `TypeError` whose `cause.code` is `EPROTONOSUPPORT`, at the start as for a redirect target. A URL on one of the fetch standard's bad ports (25, 6000 and the rest) rejects too, as Node's `fetch` does: a `TypeError` `fetch failed` whose `cause` is an `Error` `bad port` (no `code`), at the start and as a redirect target, on the kernel's own loopback too ([#822](https://github.com/ai-ecoverse/slicc-kernel/issues/822)). `redirect: 'error'` rejects with a `TypeError` whose `cause` is an `Error` `unexpected redirect`, and a body whose connection ends early fails its read with `TypeError` `terminated` whose `cause` is undici's: `name` `SocketError`, `message` `other side closed`, `code` `UND_ERR_SOCKET`. The request goes out with its headers in the order the program gave them (an object's or an array's order; a `Headers` object's is sorted), lowercase as `Headers` keeps them. The global `fetch` stays withheld;
|
|
553
553
|
- **`fetch` and `websocket` to the kernel's own loopback:** a URL whose host is `127.0.0.0/8`, `0.0.0.0`, `[::1]`, `localhost` or `*.localhost` goes to the kernel's own loopback, as a guest's sockets do (`<port>.kernel.localhost` takes its port from the name). So it reaches whatever listens on that port in the kernel, the CDP facade at `SLICC_CDP_URL` and its `/json` included. It never goes to the transport: with nothing listening, it rejects with a `TypeError` whose `cause.code` is `ECONNREFUSED`, with or without a transport. The kernel speaks HTTP/1.1 and WebSocket (RFC 6455) to the listener itself, and `fetch` handles the redirects and compressed bodies it answers, as for any server. Its request head starts as undici's does, `host` then `connection`, then the program's headers in their order, then `content-length`. It sends `connection: close` where undici sends `keep-alive`, because each request is a connection of its own; a server sees no other difference. A WebSocket's `closed` settles after the closing handshake once the server has closed the connection (RFC 6455 §7.1.4), as undici's `close` event does, or at the latest 5 s after the Close frames. A refused upgrade rejects with a `TypeError` whose `cause` is an `Error` naming the status, `the server answered the upgrade with 503 Service Unavailable`, with `code` `EPROTO`. `https:` and `wss:` there reject with `ENOTSUP`. The listener sees a connection from `127.0.0.1` whose `SO_PEERCRED` is the program's pid, uid and gid, as if the program had connected itself. `host.slicc.internal` still goes to the transport as the real machine's loopback, for `fetch` too, under the transport's policy (`nodeTransport({ hostLoopback })`). A redirect from any other URL into the kernel's loopback rejects with `cause.code` `EACCES`, so a server elsewhere can't steer a program's request to the kernel's own servers; `redirect: 'manual'` still returns it;
|
|
@@ -1292,7 +1292,7 @@ The tools in `tools/cert-review/`:
|
|
|
1292
1292
|
- and record, which re-hashes the candidate's artifact against the pack job's sha256.
|
|
1293
1293
|
- **The verdict** goes into the commit status on the pull request's head and a comment on the pull request. Certified is green. A FAIL is red with the line, and the pull request needs a fix. **INCOMPLETE** (a job that didn't succeed, no verdict, a suite without results) is red with `re-run the failed jobs`, never a silent pass. Main moving on after a review doesn't trigger another one; a push does.
|
|
1294
1294
|
- **Merge groups only look up.** On `merge_group`, the job lists the group's pull requests (its merge commits) and requires, for each one, a green status on the head being merged, or no behaviour change. Otherwise it's red, naming the pull request. Then it adds the group's downstream verdict. The group's `ci / test` covers the combined tree.
|
|
1295
|
-
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`
|
|
1295
|
+
- **Downstream, once per merge group:** the `merge group pack` job packs the group's tree with `main`'s tools (`x.y.z-group.<run>`), and `downstream bios canary` calls slicc-bios's `canary.yml` at a pinned full sha (`42cdf3a8…`, never a tag) with that tarball, bound by artifact id, digest and sha256, on the `sweep`, `guard-prompt`, `guard-lagging`, `k4-upgrade`, `k4-agent-modes` and `link-serve` shards, each run once, on the runner the `canary runner` job picks: `.github/runner.mjs`'s `decide()` from `main`, as `ci.yml` uses it, gives `vars.CERT_CANARY_RUNNER` only to a merge group whose entries are all same-repo PRs, and `ubuntu-latest` otherwise, without the variable, or if the decider fails (bios honours a runner only for merge groups and pushes to main in ai-ecoverse, so rolling back is unsetting the variable). The verdict line names the shards, or their count past 32 characters (`6 shards`), so a status description (140 characters) keeps the reason. `downstream.mjs` judges its result (`downstreamOf` in `lib.mjs`), and the lookup requires it. The `merge group` job reads that result as `result.json` from the canary's own artifact, downloaded by the call's `artifact-id` with `digest-mismatch: error`, not from the `result` output: that output can be up to 512 KB, and one environment variable holds 128 KB, so passing it that way stopped the step from starting at all (`Argument list too long`, runs 39166647175 and 39168823799). A missing or unreadable artifact leaves no result, so the group is INCOMPLETE:
|
|
1296
1296
|
- **PASS:** the candidate passes, `downstream: PASS bios … N pass`;
|
|
1297
1297
|
- **FAIL:** a regression, or a failing test (by the counts, even without its rows) is **not certified**, and the group is red;
|
|
1298
1298
|
- **INCOMPLETE** blocks too: no result, a result from another bios than the pinned one, a missing shard, a shard that exited without a failing test, a refused candidate, 0 tests run, or no test passed;
|
package/dist/kernel-worker.js
CHANGED
|
@@ -7598,7 +7598,7 @@ function satisfies(version, range2) {
|
|
|
7598
7598
|
}
|
|
7599
7599
|
|
|
7600
7600
|
// src/version.ts
|
|
7601
|
-
var KERNEL_VERSION = true ? "1.143.
|
|
7601
|
+
var KERNEL_VERSION = true ? "1.143.3" : "0.0.0-dev";
|
|
7602
7602
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7603
7603
|
function procVersion() {
|
|
7604
7604
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -10509,7 +10509,7 @@ function route(watch, type, path) {
|
|
|
10509
10509
|
}
|
|
10510
10510
|
function routed(watch, root, type, path) {
|
|
10511
10511
|
if (path === root || type === "rename" && root !== "/" && under3(root, path)) {
|
|
10512
|
-
noted(watch, { type, filename: baseOf3(root) });
|
|
10512
|
+
noted(watch, { type, filename: watch.names.get(root) ?? baseOf3(root) });
|
|
10513
10513
|
if (type === "rename") noted(watch, { ended: true });
|
|
10514
10514
|
return true;
|
|
10515
10515
|
}
|
|
@@ -10519,6 +10519,11 @@ function routed(watch, root, type, path) {
|
|
|
10519
10519
|
} else return false;
|
|
10520
10520
|
return true;
|
|
10521
10521
|
}
|
|
10522
|
+
function remapped(root, from, to) {
|
|
10523
|
+
if (root === from) return to;
|
|
10524
|
+
if (from !== "/" && root.startsWith(`${from}/`)) return to + root.slice(from.length);
|
|
10525
|
+
return void 0;
|
|
10526
|
+
}
|
|
10522
10527
|
var FsWatchers = class {
|
|
10523
10528
|
watchers = /* @__PURE__ */ new Set();
|
|
10524
10529
|
typed = /* @__PURE__ */ new Set();
|
|
@@ -10553,6 +10558,7 @@ var FsWatchers = class {
|
|
|
10553
10558
|
const other = alias === void 0 ? main : normalizePath(alias);
|
|
10554
10559
|
const watch = {
|
|
10555
10560
|
roots: other === main ? [main] : [main, other],
|
|
10561
|
+
names: /* @__PURE__ */ new Map(),
|
|
10556
10562
|
recursive,
|
|
10557
10563
|
notes: [],
|
|
10558
10564
|
overflowed: false,
|
|
@@ -10602,6 +10608,35 @@ var FsWatchers = class {
|
|
|
10602
10608
|
for (const watch of [...this.typed]) route(watch, type, at);
|
|
10603
10609
|
}
|
|
10604
10610
|
}
|
|
10611
|
+
moved(from, to) {
|
|
10612
|
+
const source = normalizePath(from);
|
|
10613
|
+
const target = normalizePath(to);
|
|
10614
|
+
const followed2 = /* @__PURE__ */ new Set();
|
|
10615
|
+
for (const watch of this.typed) {
|
|
10616
|
+
const [main] = watch.roots;
|
|
10617
|
+
const roots = watch.roots.map((root) => remapped(root, source, target) ?? root);
|
|
10618
|
+
if (roots.every((root, i) => root === watch.roots[i])) continue;
|
|
10619
|
+
followed2.add(watch);
|
|
10620
|
+
const names2 = /* @__PURE__ */ new Map();
|
|
10621
|
+
watch.roots.forEach((root, i) => {
|
|
10622
|
+
names2.set(roots[i], watch.names.get(root) ?? baseOf3(root));
|
|
10623
|
+
});
|
|
10624
|
+
watch.names = names2;
|
|
10625
|
+
watch.roots = [...new Set(roots)];
|
|
10626
|
+
if (main === source) {
|
|
10627
|
+
noted(watch, { type: "rename", filename: names2.get(target), moved: true });
|
|
10628
|
+
}
|
|
10629
|
+
}
|
|
10630
|
+
return followed2;
|
|
10631
|
+
}
|
|
10632
|
+
renamed(from, to) {
|
|
10633
|
+
const followed2 = this.moved(from, to);
|
|
10634
|
+
if (this.typed.size === 0) return;
|
|
10635
|
+
for (const path of [from, to]) {
|
|
10636
|
+
const at = normalizePath(path);
|
|
10637
|
+
for (const watch of [...this.typed]) if (!followed2.has(watch)) route(watch, "rename", at);
|
|
10638
|
+
}
|
|
10639
|
+
}
|
|
10605
10640
|
changed(...paths) {
|
|
10606
10641
|
this.report(paths);
|
|
10607
10642
|
this.emit("change", paths);
|
|
@@ -10705,7 +10740,16 @@ var FsWatchers = class {
|
|
|
10705
10740
|
}
|
|
10706
10741
|
},
|
|
10707
10742
|
rm: reshaping(after(fs.rm.bind(fs), (path) => [path], "rename")),
|
|
10708
|
-
rename: reshaping(
|
|
10743
|
+
rename: reshaping(async (from, to) => {
|
|
10744
|
+
let done5 = false;
|
|
10745
|
+
try {
|
|
10746
|
+
await fs.rename(from, to);
|
|
10747
|
+
done5 = true;
|
|
10748
|
+
} finally {
|
|
10749
|
+
this.report([from, to]);
|
|
10750
|
+
if (done5) this.renamed(from, to);
|
|
10751
|
+
}
|
|
10752
|
+
}),
|
|
10709
10753
|
link: reshaping(
|
|
10710
10754
|
after(
|
|
10711
10755
|
(from, to, follow2) => fs.link(from, to, follow2),
|
|
@@ -17381,7 +17425,8 @@ var Resolver = class {
|
|
|
17381
17425
|
const abort = new AbortController();
|
|
17382
17426
|
lookup2 = { work: this.lookup(key, asked, id2, abort.signal), abort, waiters: 0 };
|
|
17383
17427
|
this.pending.set(id2, lookup2);
|
|
17384
|
-
|
|
17428
|
+
const done5 = () => this.pending.delete(id2);
|
|
17429
|
+
void lookup2.work.then(done5, done5);
|
|
17385
17430
|
}
|
|
17386
17431
|
return waitFor(lookup2, signal);
|
|
17387
17432
|
}
|
|
@@ -17399,7 +17444,7 @@ var Resolver = class {
|
|
|
17399
17444
|
])
|
|
17400
17445
|
);
|
|
17401
17446
|
} catch {
|
|
17402
|
-
|
|
17447
|
+
throw new KernelError("ETIMEDOUT", `resolve ${name2}: the uplink gave no answer`);
|
|
17403
17448
|
}
|
|
17404
17449
|
const addresses = answer2.addresses.filter((a) => this.usable(name2, a, family));
|
|
17405
17450
|
if (addresses.length > 0) this.remember(id2, addresses, answer2.ttl);
|
|
@@ -17432,12 +17477,16 @@ function waitFor(lookup2, signal) {
|
|
|
17432
17477
|
reject(new KernelError("EINTR"));
|
|
17433
17478
|
};
|
|
17434
17479
|
signal?.addEventListener("abort", abort, { once: true });
|
|
17435
|
-
|
|
17480
|
+
const settle3 = (finish) => {
|
|
17436
17481
|
if (signal?.aborted) return;
|
|
17437
17482
|
lookup2.waiters--;
|
|
17438
17483
|
signal?.removeEventListener("abort", abort);
|
|
17439
|
-
|
|
17440
|
-
}
|
|
17484
|
+
finish();
|
|
17485
|
+
};
|
|
17486
|
+
void lookup2.work.then(
|
|
17487
|
+
(value2) => settle3(() => resolve2(value2)),
|
|
17488
|
+
(err) => settle3(() => reject(err))
|
|
17489
|
+
);
|
|
17441
17490
|
});
|
|
17442
17491
|
}
|
|
17443
17492
|
|