Repository navigation
Sandbox platform: sandbox.create({ platform }) on every platform (#529) - #530
Open
yifanxuaaa wants to merge 13 commits into
Open
yifanxuaaa wants to merge 13 commits into
yifanxuaaa wants to merge 13 commits into
Conversation
The spec for #529: a Sandbox names its platform (docker, mac, linux, apple-container, microsandbox, lima, firecracker, ssh); sandbox.create takes it, records it, and later calls follow the record. LayerFS.init and LAYERFS_PLACEMENT do not change. Owner decisions P1-P6, allowed platforms per home, API and implementation changes, the Connect UI follow-on and the terminal pass. Production LOC: 123811 -> 123811 (delta 0). Docs only. Scope: core, engine and api production source; tools/production_loc.py (blob 686d269). Reference absent (retired R9c; last counted 65,417 at tree 498dd19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In @layerfs/runtime, `src/platform/` named where a home lives (docker/,
vm/), which clashes with a Sandbox's new `platform`. Moved whole to
`src/place/`; imports, test paths, fixture comments and runtime.md updated.
No behaviour changes. The Rust platform/{docker,native,namespaces} keeps
its name.
Checks: runtime.mjs (17 modes) and vm_placement.mjs (12 modes) pass.
Production LOC: 123811 -> 123811 (delta 0)
Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19).
Method: tools/production_loc.py --root on the first parent 71f825a and the staged tree; after: core 41405; engine 77639; api 4767.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sandbox.create({ image?, platform? }) -> { sandbox, image, platform }. The
platforms are docker, mac, linux, apple-container, microsandbox, lima,
firecracker, ssh (P4); there is no windows (P5) and linux is the
namespaces runtime (P6). The home decides which are allowed and the
default (spec §4): Linux docker + linux, macOS docker (mac with
LAYERFS_SANDBOX=native), Windows docker, vm:<provider> the provider. A
known platform the home does not allow is Unsupported, any other value
(windows, native, namespaces, unknown) InvalidInput, both before any call.
LayerFS.init and LAYERFS_PLACEMENT are unchanged.
- agent: platform.js (home, checks, each platform's deployment, the
read-time default of an old record); create/list_sandboxes/mount return
platform, and image null for a native Sandbox; the worker client says
which VM its endpoint names. sandboxHost is gone (platform.js decides).
- node: create checks platform against the deployment's backend before any
effect and records it beside runtime; reopen, close, reclaim, idle close
and listing follow the record for every runtime, with no deployment check
(one reopen path, #527 F5). A local record with no LAYERFS_LOCAL_DIR in
the call is checked against its own directory's name. list_sandboxes,
mount and runtime.sandbox.records report platform (null in an old record)
and runtime.
- runtime: status().platforms [{ id, allowed, ready, missing }]; Linux
settings localDir and imageArchive give its clients the linux platform;
sandbox.list reports platform; sweep also reclaims stopped native and
namespaces Sandboxes on Linux.
- CLI: sandbox create --platform; docs (agent.md Platforms section, the
"There is no placement" paragraph rewritten, runtime.md Status), skill.
Behaviour change: #527 step 4 (a record is reopened only by a deployment
of its own backend) is replaced; any client of the home reopens a Sandbox
by its record.
Tests changed or deleted (native_local.rs):
- a_native_record_is_not_reopened_by_a_namespaces_deployment: replaced by
a_native_record_is_reopened_by_its_runtime_under_any_deployment;
- a_native_record_is_used_only_with_its_deployment_s_absolute_local_dir:
rewritten as a_native_record_s_directory_is_checked_with_or_without_a_local_dir;
- close_refused_as_mixed: replaced by close_refused_as_unused; the
native-under-docker half of the former-local test now expects a reclaim.
native_deployment.mjs: the two sandboxHost assertions are deleted with
sandboxHost (covered by the new platform.mjs).
Checks: Clippy -D warnings on macOS and Linux (layerfs-node); cargo fmt;
check_product_boundary PASS; native_local 10/10, native_admission 2/2,
native_connection_wait 1/1 as root in Lima lfs529-linux; native_discovery
5/6 (the sixth needs cc, absent in that VM; untouched code). JS:
platform.mjs (new), calls (42 cases), native_deployment, exec_output,
packaging (7 modes), trace (9 modes), runtime.mjs (17 modes),
vm_placement.mjs (12 modes), cli.test.mjs 78/78.
Production LOC: 123811 -> 123966 (delta +155)
Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19).
Method: tools/production_loc.py --root on the first parent b3efa9b and the staged tree; after: core 41405; engine 77696; api 4865.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cell.mjs makes its Sandbox with sandbox.create({ platform }) and requires
the create and mount answers to name it; with LFS529_EXTRAS (C01) it then
checks the home's default and every refusal pair (spec §4), with the
project's Sandboxes unchanged. run.mjs maps each row to its platform,
allowed set and default, and adds two Linux rows that take the cell
`check`: linux-mixed (mixed.mjs: a docker and a linux Sandbox of one
project, one Store file by (dev, inode) through /proc, Commits seen both
ways, close one) and linux-oldrecords (make.mjs with the pre-#529 bundle,
then oldrec.mjs). Test files only.
Production LOC: 123966 -> 123966 (delta 0)
Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19).
Method: tools/production_loc.py --root on the first parent db4b8e4 and the staged tree; test files only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner kept `@layerfs/runtime`'s folder name: `src/place/` (b3efa9b, #529 §6.6) moves back to `src/platform/`, with its imports, comments, test paths and api/docs/runtime.md. No behaviour changes. The #529 spec records the reversal in §6.6. The §8 cells run on bundles sealed at 6f242b7, which differs from this tree only by this move. No tests deleted. Production LOC: 123966 -> 123966 (delta 0) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 6f242b7 and the staged tree; after: core 41405; engine 77696; api 4865. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLATFORM-PASS.md and its 111 receipts (receipts/6f242b72bf/): C01-C12 PASS on nine platforms (docker on macOS and Linux, mac, linux, apple-container, microsandbox, lima, firecracker nested in Lima vz, ssh), 108 cells; the mixed project (one Store file, held by both Sandboxes' daemons), the default on ten homes, 107 refusals and the old records PASS. Windows NOT_RUN. Connect and cleanup sections follow in a later commit. Production LOC: 123966 -> 123966 (delta 0) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 33733d0 and the staged tree; docs only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLATFORM-PASS.md gains its Connect section (harness PASS on ten cases, narrow dark and wide light; live NOT_RUN; the changes are uncommitted in layerfs-connect on feat/connect-ui-polish) and its cleanup record (the three Lima VMs, pulled images, caches and the Apple Container service removed or stopped; host free 219.9 GB). Production LOC: 123966 -> 123966 (delta 0) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 71ecd9f and the staged tree; docs only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#529) Parent decision 5 (2026-11-01): `image` defaults per platform. `docker` keeps LAYERFS_IMAGE; `linux` and the VM platforms use the image that LAYERFS_IMAGE_ARCHIVE holds (its config digest) when that is set. An explicit `image` still wins. The archive must name exactly one image when no image is given; otherwise the start is refused before any effect. - layerfs-sandbox: rootfs::image_of(archive) reads the archive's one config digest; manifest() takes an optional image. - layerfs-node: namespaces_image() derives the image from the archive when deploy.image is absent. - agent: platformDeployment() drops the home's image for linux and VM platforms unless the call gave one; localDeployment() leaves deploy.image unset when an archive is set. - runtime: the linux platform no longer needs LAYERFS_IMAGE. - mixed check (parent decision 3): records the Store's (dev, ino) and each daemon's pid and (dev, ino); creates linux with no image, then with an explicit image, and refuses the Docker digest for linux. - docs: agent.md (Images bullet, settings, LAYERFS_IMAGE row), the SDK skill's sandbox.create default. Tests: new namespaces_rootfs::an_archive_names_its_one_image_by_its_config_digest; platform.mjs adds vm, linux and native-on-vm cases; native_deployment.mjs expectations changed (the namespaces deployment has no image unless given). No tests deleted. Checks: Clippy -D warnings on Linux (layerfs-sandbox, layerfs-node) and macOS (layerfs-node); fmt; namespaces_rootfs 14/14 on Linux; agent platform, native_deployment, calls 42; runtime surface, sandboxes, backend, docker-default; vm_placement settings, flow, bundle; CLI 78/78. Production LOC: 123966 -> 123993 (delta +27) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 2fe92d2 and the staged tree 0c935fd; after: core 41405; engine 77721; api 4867. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#529) Parent decision (2026-11-01). A native or namespaces record reopened by a client that sets no LAYERFS_LOCAL_DIR is used only when: (a) its directory is its own real path (no symlink in it); (b) its last component is the name this home gives that Sandbox; (c) the directory's `owner` marker, written once at create (mode 0600, create-new, not followed if a symlink), equals the real path of the record's own directory LAYERFS_HOME/<project>/sandboxes/<n>. Anything else is refused InvalidInput before any effect, naming LAYERFS_LOCAL_DIR as the fix, and left in place. A record made before #529 has no marker, so a client with no LAYERFS_LOCAL_DIR is refused on it. With LAYERFS_LOCAL_DIR set nothing changes. - local.rs: mark_owner() at create, before the record is saved; owned() at reopen. - discovery.rs: local_record_of() calls owned() when localDir is unset; the namespaces_record doc no longer says only its backend uses it. - agent.md: the native and namespaces record rules, and that a refused record fails the call that reads it (listing included) rather than being left out. - oldrec.mjs: a reclaim with no LAYERFS_LOCAL_DIR must be refused on the old records and keep them; the rest keeps the home's LAYERFS_LOCAL_DIR, as a pre-#529 home had. Tests: new native_local::with_no_local_dir_a_record_s_directory_must_be_real_and_owned_by_it (a symlink above the directory, the directory itself a symlink, a wrong last component, no marker, a marker naming Sandbox 2, a symlinked marker, then an owned record reclaimed). Changed: two native_local tests write the owner marker their records stand for (a_native_record_s_directory_is_checked_with_or_without_a_local_dir, every_backend_s_record_is_listed_with_its_runtime_and_state). No tests deleted. Checks: native_local 11/11 (Linux, in the Node runtime image); Clippy -D warnings on Linux and macOS (layerfs-node); fmt. Production LOC: 123993 -> 124051 (delta +58) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 91f3161 and the staged tree 507fb35; after: core 41405; engine 77779; api 4867. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct (#529) Found by the rerun's mixed check: a `linux` Sandbox given Docker's index digest (sha256:afa1ef9c...) was refused only at the provider's Validate step, as Failed, after a Sandbox number was claimed (and released by the failed start's cleanup). namespaces_image() now checks that the archive names the given image (rootfs::holds, the same manifest read the no-image default does), so it is InvalidInput while the deployment is built. Tests: namespaces_rootfs::an_archive_names_its_one_image_by_its_config_digest also checks holds() for a held and a missing digest. No tests deleted. Checks: namespaces_rootfs 14/14 on Linux; Clippy -D warnings on Linux (layerfs-sandbox, layerfs-node) and macOS (layerfs-node); fmt. Production LOC: 124051 -> 124059 (delta +8) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 4e89998 and the staged tree 40e814b; after: core 41405; engine 77787; api 4867. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLATFORM-PASS.md: - Decisions 5 and 6 rewritten (the owner marker; image defaults per platform), plus 13 (native on a vm: home maps to the provider), 14 (the Store identity receipt) and 15 (a given image the archive lacks is InvalidInput). - Finding 1 is now fixed by default; new findings 6 (a refused record fails the call that reads it) and 7 (with no LAYERFS_LOCAL_DIR a stopped record whose directory is gone is not reclaimed). - A rerun section, the receipt identities, commits and cleanup. Receipts (7, lfs527-cell/1), all kept: - receipts/4e8999872b/: macos-native C01 PASS; linux-mixed FAIL (Docker's digest refused Failed after a number was claimed; fixed by 78b63e8). - receipts/78b63e86eb/: linux-mixed PASS, linux-oldrecords PASS twice (the first one's no-LOCAL_DIR step reached the name check, not the marker), linux-namespaces C01 PASS, vm-lima C01 PASS. oldrec.mjs: the no-LAYERFS_LOCAL_DIR step keeps the home's container prefix and requires the owner-marker refusal. Production LOC: 124059 -> 124059 (delta 0) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 78b63e8 and the staged tree be6fdef; after: core 41405; engine 77787; api 4867. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…call (#529 finding 6) Parent, with the owner's go-ahead. A Sandbox record that cannot be read for a call (a damaged file, or a local record failing its directory or owner marker checks) is now left out instead of failing the call for the project: - project.list_sandboxes leaves it out; the agent's listing shape is unchanged (the binding's answer adds `skipped`). - runtime.sandbox.records keeps it with state "refused" and refusal { code, message }. A host that cannot run a namespaces record (Unsupported) is no fault of the record: its process is read as before. - runtime.sandbox.reclaim, sandbox.create's reclaim and the sweep skip it and leave it in place (nothing deleted, opened or stopped); reclaim and create answer `skipped`. - the agent's trace writes one `phase: "skipped"` line per skipped record. - a call naming that Sandbox (mount, exec, close, list_workspaces) is still refused with its own code. - a malformed LAYERFS_LOCAL_DIR or LAYERFS_SANDBOX is the caller's setting and still refuses the call; a failure to inspect what a readable record names (Docker not answering) still fails it. - runtime: records are read with the home's containerPrefix and, on a Linux local home, its localDir (no backend), so local records are checked as the home's clients check them; a refused record counts among a Store's holders. Finding 7 stays a finding: removing a record whose ownership cannot be checked is a manual step (agent.md, runtime.md). Tests: new native_local::a_record_refused_when_read_is_left_out_reported_and_kept (a good running record, a pre-#529 one with no marker read with no LAYERFS_LOCAL_DIR, a damaged one; listing, records, reclaim, close), trace mode `skipped`, and a runtime sandboxes check (the sweep starts no reclaim for a refused record; holding counts it). Changed to the new contract: native_local's reclaim assertions on refused records (SKIPPED instead of a refusal; RECLAIMED, LEFT_OUT and the listing gain `skipped: []`), native_discovery's three reclaim answers (`skipped: []`), runtime.mjs's records-call assertion (a deploy of only the home's name prefix), and oldrec.mjs's no-LAYERFS_LOCAL_DIR step. None deleted. Checks: Clippy -D warnings on Linux and macOS (layerfs-node); fmt; check_product_boundary; native_local 12/12, native_admission 2/2, native_connection_wait 1/1, native_discovery 5/6 (the fork fixture's C source is not mounted in the runtime image: finding 2) on Linux in the Node runtime image; agent trace (10 modes), platform, native_deployment, calls 42, exec_output, packaging (6 modes; dispatch needs the built addon); runtime.mjs (16 modes), vm_placement (12 modes); CLI 78/78. Production LOC: 124059 -> 124168 (delta +109) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent 7bfb4d6 and the staged tree c0bba4f; after: core 41405; engine 77884; api 4879. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLATFORM-PASS.md: finding 6 marked fixed by a3e956d, with what now happens to a record refused when read; finding 7 notes that such a record is skipped and kept, and that removing it stays manual. Added a3e956d to the commits (over #529: 123811 -> 124168, delta +357), its tests and checks, the rerun's receipt identity and cleanup. Receipt: receipts/a3e956dd40/linux-oldrecords-check-...-vx635i336i.json PASS (Linux bundle build-linux-ab4a0253..., manifest 27923eca..., oldrec.mjs 04b028c6). With no LAYERFS_LOCAL_DIR the old namespaces record is `refused` in runtime.sandbox.records, left out of listing and skipped by reclaim, and stays running; with the home's LAYERFS_LOCAL_DIR both old Sandboxes list, mount, run, unmount and close. Production LOC: 124168 -> 124168 (delta 0) Scope: core, engine, api (dsh 0; reference absent, retired R9c; last counted 65,417 at tree 498dd19). Method: tools/production_loc.py --root on the first parent a3e956d and the staged tree c0b6e31; after: core 41405; engine 77884; api 4879. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #529. Stacked on #528: the base is #528's branch, so this diff shows only #529. Merge #528 first, then retarget this PR to
layerfs_v2_0_0.What changes
A Sandbox now names the platform it runs on.
LayerFS.initand where a home keeps its Stores (LAYERFS_PLACEMENT) don't change.docker,mac,linux,apple-container,microsandbox,lima,firecracker,ssh. There is nowindowsplatform: Windows usesdockerthrough the WSL2 worker.docker.mac.dockerandlinux, mixed in one project on one Store file.vm:X:X.Unsupported. Anything else, includingwindows,nativeandnamespaces, isInvalidInput.LAYERFS_SANDBOX. That setting is now only the home's default. Old records with noplatformare read with a default; nothing is rewritten.linuxor VM Sandbox defaults to its archive's image. An image the archive doesn't have isInvalidInputbefore any effect.LAYERFS_LOCAL_DIR, a native record must own its directory, through a mode-0600ownermarker.list_sandboxesand cleanup. It is reported asrefusedinruntime.sandbox.recordsand kept on disk.Spec:
core/docs/issues/529/README.md. Pass record:core/docs/issues/529/PLATFORM-PASS.md.Terminal pass
sandbox.create({ platform }).Open findings
LAYERFS_IMAGEalone can't serve both Linux platforms. The per-platform default image covers it.cc.discovery.rsis 985 lines, close to the 999-line limit.LAYERFS_LOCAL_DIRisn't set. It has to be removed by hand.Production LOC
tools/production_loc.py, first-party product code. Reference absent (retired R9c; last counted 65,417 at tree 498dd1917812).place/platform/, move only🤖 Generated with Claude Code