Files

5.1 KiB

Row 131 round 1 review (Filbert): changes

Candidate: manifest sha256 80b4ce21ac419bd3a57d5e4f842790f684a9a88ce1653da838c8953a2bbd27e5 (13 paths), base 0bdd573ff, packet agents/dewey/work/queue-131/r1/ in 09447eb67. My scope: the organization file and the loader.

Rebuild: a fresh worktree at 0bdd573ff with the packet's build.patch applied. Every manifest hash matched, the packet manifest matched and git diff --check was clean. The added lines have no dash, curly-quote or control characters. No commit from 0bdd573ff to HEAD touches a candidate path.

Most of this is right. plugins is optional, checked by checkPlugins and returned as a frozen copy. A JSON refusal throws its code before the writer runs and logs nothing. launch-settings clones the raw document, so a limits write keeps plugins. The loader keeps a kind's last good list on any refusal, switches nothing on for a kind refused on its first load, and loads one at a time. Two small defects block.

F1: the setter publishes a non-JSON plugins value, and the file then fails to read

set checks args.plugins with checkPlugins, which accepts any non-array object. The writer validates the in-memory object, then writes JSON.stringify of it. A Date has no own keys, so it passes both checks and serializes as a string. I ran this on the candidate:

  1. set("acme", {revision, plugins: new Date(0)}, "jason")
  2. The writer commits. The file holds "plugins": "1970-01-01T00:00:00.000Z", and changes.jsonl has a committed entry with that string as new.
  3. set then throws an uncoded BusinessError from its own read, after the commit.
  4. Every later read fails ("plugins must be an object with one key per kind"). A Map would have been written as {}.

No caller passes such a value today. But the consequence is an organization file the bus can't read, and ruling 2 promises a coded refusal before the writer runs. Fix: in set, refuse with invalid-plugins unless args.plugins is a plain object (prototype Object.prototype or null). Add a test with a Date and a Map that checks the bytes are unchanged and nothing is logged, and add one mutant. Alternatively, the writer could validate JSON.parse(bytes) instead of the object, which would cover every setter. That's organization.mjs (row 122), so it's Sage's call. I didn't check whether launch-settings has the same hole.

F2: an I/O error other than "missing" rejects load() unlogged, after earlier kinds have switched

component() turns only ENOENT and ENOTDIR into a PluginError and rethrows anything else. refused() rethrows non-PluginErrors. I ran this on the candidate:

  1. First load {usage-source: [a], limit-check: [cap]}.
  2. chmod 000 the limit-check directory.
  3. Load {usage-source: [b], limit-check: [cap]}. load() rejects with a raw EACCES Error. log is never called. loaded() is now {usage-source: [b], limit-check: [cap]}, so the first kind switched and the second did not, with no report.

limit-check keeping cap matches ruling 3. The gap is that the README says every refusal is logged and load() reports what each kind has on, and neither holds here. realpathSync(ROOT) on line 113 can throw the same way. Fix: map any other lstat or realpath error to a PluginError (for example unreadable), so the kind keeps its modules, log runs once and load() resolves with the report. Add a test (skip it when running as root) and one mutant.

Doc-only, follow-up

packages/plugins/README.md, "Limits of the checks": a module's top-level code runs at import, before the export and interface checks. So a module refused for missing-export or interface-version has still run with the host's authority, and Node's cache keeps it. One sentence covers it. This is decision 137 doc-only: grep -rlF README.md packages/*/tests scripts/test-*.sh lists 13 files, and none of them reads packages/plugins/README.md. The webui pinned-copy test reads only packages/webui/README.md.

Notes (non-blocking)

  • The check-then-import gap is stated plainly in BUILD.md and the README, and the barrier test shows the realpath check catching a swap after lstat. That's Darkwing's part; I have nothing to add.
  • An unknown kind refuses only itself in the loader, while checkPlugins refuses the whole organization file. That's consistent: a file that validated can't reach the loader with an unknown kind.
  • MUTANTS.md and mutants.py don't record an unmutated control run. The five gate logs on the same tree are green, so the kills aren't from a broken baseline. Please add the control in round 2.

What I ran and what I took from receipts

I ran, as single files with TMPDIR set and Docker disabled:

Test file Result
plugins kinds 5/5
plugins loader 14/14
business plugin-settings 3/3
business business 15/15
business launch-settings 8/8

I also ran the F1 and F2 probes above.

From Dewey's receipts, not rerun by me:

  • the targeted gate: plugins 19, business 91, bus 171, webui 112 and cli 195, all at 0 failures (588 total). The five gate/ logs agree with gate-run.log.
  • 42 of 42 mutants killed. mutants/results.json has 42 entries, all KILLED.

I took no slot.