@ai-ecoverse/slicc-kernel 1.133.1 → 1.133.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 CHANGED
@@ -1179,7 +1179,7 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
1179
1179
  Every change is certified in its pull request, before it merges (`cert-review-pr.yml`, next section), and `cert-review` is a required check of the merge queue, so every commit on `main`, and every release built from it, was certified first. A release is then **verified on npm** (`release / verify on npm` in `release.yml`, `tools/cert-review/verify.mjs`), in code, for every tag the run made:
1180
1180
 
1181
1181
  - It waits up to an hour for npm to serve the version (an accepted publish can take a while to appear; 1.125.4 took 55 min). `release-recover` waits 25 min for a version it just published and then hands it to this job instead of failing, and a version npm still doesn't serve after the hour is a "not served yet, re-run" failure, never a supply-chain alert. It then downloads the tarball and checks `dist.integrity` (sha512), the SLSA v1 provenance (the tarball's sha512 is its subject; `release.yml` on `refs/heads/main`, a GitHub-hosted builder; `gitCommit` = `gitHead` = the tag, on `main`), and `npm audit signatures`. npm serves a version's attestations a little after the version itself, so a 404 from the attestations, a missing `dist.attestations`, an `npm view` or network error while looking for them, or `npm audit signatures`' E404 on them, is retried within the same hour: it's a supply-chain alert only once the version has been served for 30 min, its packument has stopped changing for 10, and the attestations still answer 404, or the hour has run out. Any other status, or attestations that don't match, fail at once. A version that `release-recover.mjs` skipped because npm's `latest` is newer (#837) has nothing to verify.
1182
- - It then writes `certified @ai-ecoverse/slicc-kernel@<v> sha256=<64 hex> (covers <first>–<v>; ci: <run>; pr-certified: <each merged pull request's cert-review at its merge>; known: <open known-issue issues>)` into the GitHub release's notes, between `<!-- cert-verify -->` markers. homescoop's ci reads exactly that prefix and re-hashes the npm tarball against it. `covers` starts after the last version npm serves, so a skipped version is covered by the next. Notes that already carry the identical line stay unchanged. A pull request's `cert-review` is the newest finished one (success, failure or error) on its head at or before its merge, or the newest pending one if none had finished. A re-review that a later `merge-ok` re-label starts on the same head, still pending at the merge or ending after it, doesn't change what the merge queue saw, and a status posted after the merge can't change a release's line.
1182
+ - It then writes `certified @ai-ecoverse/slicc-kernel@<v> sha256=<64 hex> (covers <first>–<v>; ci: <run>; pr-certified: <each merged pull request's cert-review at its merge>; known: <open known-issue issues>)` into the GitHub release's notes, between `<!-- cert-verify -->` markers. homescoop's ci reads exactly that prefix and re-hashes the npm tarball against it. `covers` starts after the last version npm serves, so a skipped version is covered by the next. Notes that already carry the identical line stay unchanged. A pull request's `cert-review` is the newest finished one (success, failure or error) on its head at or before its merge, or the newest pending one if none had finished. A re-review that a later `ready_for_review`, `reopened` or `cert-review-approved` event starts on the same head, still pending at the merge or ending after it, doesn't change what the merge queue saw, and a status posted after the merge can't change a release's line.
1183
1183
  - A mismatch writes no line. The job is red with a `supply-chain alert`, and so is a line already in the notes with another sha256.
1184
1184
 
1185
1185
  The tools in `tools/cert-review/`:
@@ -1207,13 +1207,13 @@ The tools in `tools/cert-review/`:
1207
1207
 
1208
1208
  ### Certification in the pull request (`cert-review`)
1209
1209
 
1210
- `.github/workflows/cert-review-pr.yml` certifies a pull request before it merges, so `main`, and with it npm's `latest`, only gets certified changes. It reports as the `cert-review` commit status, a required check of `main`'s merge queue. Every same-repo pull request gets one (`not behaviour`, `waits for merge-ok`, the review's verdict, or the self-guard's hold). **A pull request from a fork gets none**, because fork code never reaches the pools or the key, so it can't enter the queue: only Lars merges one, after reading it.
1210
+ `.github/workflows/cert-review-pr.yml` certifies a pull request before it merges, so `main`, and with it npm's `latest`, only gets certified changes. It reports as the `cert-review` commit status, a required check of `main`'s merge queue. Every same-repo pull request gets one (`not behaviour`, `draft: not reviewed`, the review's verdict, or the self-guard's hold). **A pull request from a fork gets none**, because fork code never reaches the pools or the key, so it can't enter the queue: only Lars merges one, after reading it.
1211
1211
 
1212
- - **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. The merge group's lookup loads its tools from the protected branch itself (`merge_group.base_ref`), not from the group's `base_sha`, which can be the speculative commit of the group ahead and carry the reviewer changes of the pull requests queued before it; `base_sha` is only the lookup's input. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts with `merge-ok`. The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. Node's glob `**` skips dot segments, so the guard's paths list the dot forms of each reviewer directory too (`REVIEWER_GLOBS` in `lib.mjs`), and the job's `cert` step reads the guard's receipt and fails closed: a reviewer path the globs still miss (any path under `scripts/cert/`, `tools/cert-review/` or the guard's directory, by prefix) without the label on that head is a FAIL, and a PASS without a receipt, or with a member that lacks its `paths` or `touched` list, is INCOMPLETE. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
1212
+ - **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. The merge group's lookup loads its tools from the protected branch itself (`merge_group.base_ref`), not from the group's `base_sha`, which can be the speculative commit of the group ahead and carry the reviewer changes of the pull requests queued before it; `base_sha` is only the lookup's input. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts (on a pull request that isn't a draft). The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. Node's glob `**` skips dot segments, so the guard's paths list the dot forms of each reviewer directory too (`REVIEWER_GLOBS` in `lib.mjs`), and the job's `cert` step reads the guard's receipt and fails closed: a reviewer path the globs still miss (any path under `scripts/cert/`, `tools/cert-review/` or the guard's directory, by prefix) without the label on that head is a FAIL, and a PASS without a receipt, or with a member that lacks its `paths` or `touched` list, is INCOMPLETE. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
1213
1213
  - **A gate's silence never reads as a pass:** every tool a workflow gates on (`pr.mjs plan`, `same`, `lookup` and `notice`, `downstream.mjs`, `same.mjs`, `pack.mjs`, `delta.mjs`, `suite.mjs`, `compat.mjs`, `findings.mjs`, `record.mjs`, `verify.mjs`) ends with one verdict line, `<tool>: PASS <what>` or `<tool>: FAIL <why>`, from the shared `verdict()` in `lib.mjs`. `tools/cert-review/gate.sh [--either|--evidence] <tool> <command>` runs a tool, passes its output through, and exits 70 with an error when the tool's last line starting `<tool>: ` isn't a PASS or FAIL verdict, so a tool replaced by `true`, an early exit 0 or an empty run fails its step. By default the step passes only on PASS with exit 0; a PASS with a non-zero exit, or a FAIL with exit 0, fails it. `--either` keeps the tool's own exit, for tools whose FAIL is evidence that record judges later (suite, compat). `--evidence` needs only the verdict, for the NEGATIVE run on main's tree, which is expected to fail. `suite: PASS` means exit 0, no failures and every expected file has results; `compat: PASS` means a run turned up with no regressions. `prompt.mjs` writes the review prompt to stdout, which goes into a step output, so it prints no verdict; a missing prompt leaves the review without `verdict.json`, and record marks that INCOMPLETE. The gate reads the tool's stdout, and no tool passes the pull request's output there: suite pipes the tests' output into its log file, and compat prints only its own lines, so a test that prints `suite: PASS` can't stand in for a silent suite. A tool that let such output through would need its verdict on a separate descriptor. Because the workflow of a pull request runs main's tools, the tools gain their verdicts first, and the workflows call them through `gate.sh` in the pull request after that.
1214
1214
  - **Evidence comes by id from the job that made it:** the jobs that run the pull request's code (`pack candidate`, which builds it, and both `suite` legs, which run its tests) share the run's artifact store with the jobs that don't, and code in them can reach the runner's artifact token. So no consumer trusts a name or a pattern. Each producer job hands on its artifact's `artifact-id` and `artifact-digest` as job outputs, consumers download by `artifact-ids` with `digest-mismatch: error`, and no upload uses `overwrite`, so a second upload of a name fails instead of replacing it. The base build has its own job, `pack base`, which runs only main's code, so the candidate's job can't speak for it; it restores no Actions cache either, because the candidate job shares the run's cache scope (npm would refuse a tampered tarball against the lockfile's integrity anyway). `tools/cert-review/artifacts.mjs` lists the run's artifacts and fails when one in our namespaces (`kernel-tgz-`, `cert-review-`) wasn't uploaded by its producer, with that producer's id, name and digest, or when a producer's artifact is gone. `same` runs it on the two tarballs before it compares them, so the "not behaviour (identical dist)" skip can't be bought with a planted base tarball. `record` runs it on every artifact before it writes the line, and a failure there makes the record INCOMPLETE. **What this protects:** artifacts of jobs that run no pull-request code (the base build, the delta, kernel-compat, the same-dist evidence, the review's verdict and evidence) can't be forged or replaced. **What it doesn't:** the candidate's own artifacts, its tarball and both suites' results (the base leg runs the pull request's tests on main's tree, for the NEGATIVE), stay the pull request's by design, because code that runs on a runner can always lie about its own results. They're consumed by their exact names, at most one each. That's the same deliberate-malice residual as the workflow-rewrite one above.
1215
1215
  - **Decide by what ships:** when a pull request's behaviour files are only `package.json` and/or `package-lock.json` (a Renovate devDependency bump, say), both sides are packed as usual, and then the `same dist` job (`same.mjs`, in code) compares the two tarballs before anything else runs. It checks the same paths, entry types (a symlink isn't a file) and modes. `package.json` must be equal once `devDependencies`, `version` and `gitHead` are removed, so `scripts` (they ship, a consumer's install runs `postinstall`, and `prepack`/`prepublishOnly` can make the published build differ from the packed one), runtime dependencies, `exports`, `bin`, `files` and `engines` must all match. Every other file must be byte-identical once each side's **exact** version string (build.mjs bakes it into `dist`) is replaced by one placeholder. A version string in one more place, or any looser match, is a difference. Source maps are compared as JSON without their `mappings`, whose columns the longer or shorter version string shifts; the code itself is compared byte for byte. If all of that matches, `cert-review` is green with `not behaviour (identical dist): <candidate sha256> = <base sha256> after normalising <v1>/<v2>`, and the suites, kernel-compat, the review and the record are skipped. `same.json` (uploaded as an artifact) names the rule and both tarballs' full sha256. Any difference, or a `same dist` job that doesn't finish, means the full review. The kernel has no runtime `dependencies`, since esbuild bundles everything into `dist`, so a devDependency that changes the product shows up as a byte difference. A test-only devDependency is checked by `ci / test`, on the pull request and on the merge group.
1216
- - **When:** only for a same-repo pull request with `merge-ok`, when the label is added and on every push while it is set. Each pull request has its own concurrency group, and a newer push cancels the older review. A behaviour change without `merge-ok` gets a red status, `waits for merge-ok to be reviewed`. Other labels don't start or cancel anything. Fork code never reaches the pools or the key.
1216
+ - **When:** every same-repo pull request that isn't a draft, on `opened`, `ready_for_review`, `reopened` and every push (`synchronize`). Draft status replaces the old `merge-ok` label: **mark a pull request ready = it gets reviewed, and merges on green**; a pull request held for anything stays a draft. A draft that changes shipped behaviour gets a red status, `draft: not reviewed, mark it ready for review`, and no review; `converted_to_draft` cancels a running review (one concurrency group per pull request) and sets that red status, so an older result on the same head can't satisfy the check. Each pull request has its own concurrency group, and a newer push cancels the older review. Labels other than `cert-review-approved` don't start or cancel anything. Fork code never reaches the pools or the key.
1217
1217
  - **What:** the checkout is the event's default `refs/pull/N/merge`, the pull request merged onto `main` at review time, so a review is of what would land, without pushing to the branch. Both sides are packed in the same run, on a GitHub-hosted runner, with no secrets: the candidate (the merge ref, `x.y.z-pr.N.<run>`) and the base (its first parent, `main`). Then:
1218
1218
  - the candidate's whole suite, and the base's;
1219
1219
  - the pull request's new or changed test files on `main`'s tree, with the pull request's `test/**` applied, and `main`'s build (the NEGATIVE: a fix's test must fail on `main` and pass on the candidate);
@@ -7034,7 +7034,7 @@ function satisfies(version, range2) {
7034
7034
  }
7035
7035
 
7036
7036
  // src/version.ts
7037
- var KERNEL_VERSION = true ? "1.133.1" : "0.0.0-dev";
7037
+ var KERNEL_VERSION = true ? "1.133.3" : "0.0.0-dev";
7038
7038
  var OS_RELEASE = "6.0.0-slicc";
7039
7039
  function procVersion() {
7040
7040
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -11664,7 +11664,11 @@ var MountFs = class _MountFs {
11664
11664
  }
11665
11665
  }
11666
11666
  async pread(path, offset, length2, version, redirects = 0) {
11667
- const real = await this.follow(path);
11667
+ const gone = (at) => (err) => {
11668
+ const stale = version !== void 0 && err.code === "ENOENT";
11669
+ throw stale ? errnoError("ESTALE", `${at} is gone from the host`) : err;
11670
+ };
11671
+ const real = await this.follow(path).catch(gone(path));
11668
11672
  const found = this.at(real);
11669
11673
  const { pread } = this.base;
11670
11674
  if (!found && pread) {
@@ -11677,7 +11681,7 @@ var MountFs = class _MountFs {
11677
11681
  }
11678
11682
  if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
11679
11683
  const { mount, rel: rel2 } = found;
11680
- if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
11684
+ if ((await this.table.getattr(mount, real, rel2).catch(gone(real))).kind === "directory") {
11681
11685
  throw errnoError("EISDIR", real);
11682
11686
  }
11683
11687
  await this.table.commitOverlapping(real, offset, length2);
@@ -11689,10 +11693,7 @@ var MountFs = class _MountFs {
11689
11693
  truncate: false,
11690
11694
  exclusive: false,
11691
11695
  ...version !== void 0 ? { ifMatch: version } : {}
11692
- }).catch((err) => {
11693
- const gone = version !== void 0 && err.code === "ENOENT";
11694
- throw gone ? errnoError("ESTALE", `${real} is gone from the host`) : err;
11695
- });
11696
+ }).catch(gone(real));
11696
11697
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
11697
11698
  const chunks = [];
11698
11699
  let total = 0;
@@ -19489,10 +19490,32 @@ var KEY_ERRORS = [
19489
19490
  [-15232, "ERR_OSSL_BAD_DECRYPT"],
19490
19491
  [-15360, "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED"]
19491
19492
  ];
19492
- function keyRefused(err) {
19493
- const code = err instanceof TlsError ? err.code : 0;
19493
+ var PBE_OIDS = [
19494
+ [42, 134, 72, 134, 247, 13, 1, 5],
19495
+ [42, 134, 72, 134, 247, 13, 1, 12, 1]
19496
+ ];
19497
+ function hasBytes(hay, needle) {
19498
+ for (let i = 0; i + needle.length <= hay.length; i++) {
19499
+ if (needle.every((b, j) => hay[i + j] === b)) return true;
19500
+ }
19501
+ return false;
19502
+ }
19503
+ function encryptedKey(key) {
19504
+ if (key[0] === 48) return PBE_OIDS.some((oid2) => hasBytes(key, oid2));
19505
+ return new TextDecoder().decode(key).includes("ENCRYPTED");
19506
+ }
19507
+ var KEY_PARSE_RANGES = [
19508
+ [-16383, -14336],
19509
+ [-5375, -4096],
19510
+ [-28671, -24576],
19511
+ [-127, -96]
19512
+ ];
19513
+ var keyParse = (code) => code !== -15360 && KEY_PARSE_RANGES.some(([lo, hi]) => code >= lo && code <= hi);
19514
+ function keyRefused(err, locked) {
19515
+ const raw2 = err instanceof TlsError ? err.code : 0;
19516
+ const code = locked && keyParse(raw2) ? -15232 : raw2;
19494
19517
  const name2 = KEY_ERRORS.find(([n]) => n === code)?.[1] ?? "ERR_OSSL_UNSUPPORTED";
19495
- const text = code === -3 ? "the key does not match the certificate" : err.message;
19518
+ const text = code === -3 ? "the key does not match the certificate" : code === raw2 ? err.message : "bad decrypt";
19496
19519
  return new KernelError("EPROTO", `${name2}: ${text}`);
19497
19520
  }
19498
19521
  function takeCert(req) {
@@ -19523,7 +19546,7 @@ function withCert(session, cert) {
19523
19546
  try {
19524
19547
  session.ownCert(cert.chain, cert.key, cert.pass);
19525
19548
  } catch (err) {
19526
- throw keyRefused(err);
19549
+ throw keyRefused(err, cert.pass.length > 0 && encryptedKey(cert.key));
19527
19550
  }
19528
19551
  }
19529
19552
  function wipe(cert) {