Skip to content

feat(kernel-platforms): compile fs config into a narrowing - #1058

Draft
ci-belphegor wants to merge 1 commit into
grypez/narrowing-9c-fs-segmentsfrom
grypez/narrowing-10-fs-config
Draft

feat(kernel-platforms): compile fs config into a narrowing#1058
ci-belphegor wants to merge 1 commit into
grypez/narrowing-9c-fs-segmentsfrom
grypez/narrowing-10-fs-config

Conversation

@ci-belphegor

Copy link
Copy Markdown

Explanation

The payoff. Per-vat platform config already expressed narrowings in an ad-hoc vocabulary — promises: { readFile: true } restricted the method set, rootDir restricted the argument. Now it says the same things in the same vocabulary the library uses, and the capability factory applies the very same narrow it exports:

"platformConfig": { "fs": { "root": ["srv", "data"], "methods": ["readFile"] } }

compiles to { readFile: [pathUnder(['srv', 'data'])] }. So the config-time bound is provably the root of the narrowing tree rather than a parallel mechanism.

makeRootCaveat and the hand-rolled method selection are deleted — dropping methods absent from the delta is what narrowInterfaceGuard already does. The compile step is a separate named function so that the general JSON delta encoding on the roadmap can replace it without rewriting the capability.

Retires the last three ratchet cases; no it.fails remains.

Notes for reviewers

readFile now takes a required encoding and returns a string, and this was forced rather than chosen. No typed array can cross an exo boundary: @endo/pass-style rejects Buffer and Uint8Array alike as mutable, and harden does not help. The result check runs mustMatch, which requires Passability first, so returns(M.any()) as shipped in 9b could never have worked — a read came back Cannot pass mutable typed arrays like {"data":[...],"type":"Buffer"}. A required encoding makes the limitation a call-site error rather than a rejection on the way back; a silent utf8 default would have hidden it. Raw-byte reads are recoverable via harden(buffer.transferToImmutable()), which is Passable as byteArray — on the roadmap, deferred because transferToImmutable is not guaranteed at the declared engines: >=22.

assertPlainSegments cannot be dropped now that the root check is a pattern, and there is a test proving it end to end through the narrowed capability. A call passes the narrowed exo's pathUnder guard first and only then reaches the base, so ['srv','data','x/../../etc'] satisfies the prefix pattern — the prefix positions match — and is caught downstream. The pattern alone confines nothing; that layering is deliberate, and there is a comment recording it.

An empty root is a config error, rejected and tested. pathUnder([]) stays total at the library layer, so this is the config layer's job.

kernel-platforms can no longer construct the capability under its own test setup: narrow forwards over E(), which binds globalThis.HandledPromise at load, and mock-endoify sets that to plain Promise. Rather than gut the suite, makeFsBase is split out — module-internal only, since a holder of it holds the whole filesystem — and stays fully unit-tested in-package (guards, forwarding, toPath, symlinks, traversal, separator rejection). Only the narrow composition moved to @ocap/kernel-test. nodejs.ts exports toPath/makeNoSymlinksCaveat and browser.ts exports browserFsOptions so those tests build the real base rather than a lookalike copy.

Not breaking — 9b and 9c already settled the config shape; this changes the enforcement mechanism.

Stack

PR 12 of 12. Base: #1057.

The capability factory builds the full fs exo and narrows it by a delta
compiled from the config, so `root` and the method set are enforced by the
same `narrow` a holder would use. A holder narrowing further flattens onto
that base instead of stacking a second mechanism on it. `makeRootCaveat` and
the hand-rolled method selection are gone; `narrowInterfaceGuard` already
drops methods absent from the delta.

`assertPlainSegments` stays and is load-bearing: `pathUnder` matches segment
by segment and cannot see inside one, so `['srv', 'x/../../etc']` satisfies
the config's pattern and is caught only by the base's well-formedness check,
which the narrowing inherits by forwarding.

`readFile` now requires an encoding. Without one Node resolves a `Buffer`,
and no typed array is Passable even frozen, so the result could never cross
the exo boundary — a limit only real lockdown reveals.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ci-belphegor
ci-belphegor force-pushed the grypez/narrowing-10-fs-config branch from 7f6cde9 to a871971 Compare September 11, 2026 11:42
@ci-belphegor
ci-belphegor added this pull request to stack #1060 September 11, 2026 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant