Skip to content

#527: Sandbox platform consolidation and twelve-cell platform pass - #528

Open
yifanxuaaa wants to merge 37 commits into
layerfs_v2_0_0from
claude/issue527-platform-pass-e05aa3
Open

yifanxuaaa wants to merge 37 commits into
layerfs_v2_0_0from
claude/issue527-platform-pass-e05aa3

Conversation

@yifanxuaaa

Copy link
Copy Markdown
Contributor

Implements #527 (Sandbox platform integration and consolidation) on layerfs_v2_0_0, and adds its terminal pass: the twelve #516 cells (C01–C12) on every available platform.

Result

All 120 cells PASS on ten platform rows, and both expected refusals PASS.

  • Rows: macOS docker and native; Linux docker, native and namespaces; vm:apple-container, vm:microsandbox, vm:lima (vz), vm:firecracker (nested in Lima, pass/fail only) and vm:ssh.
  • Refusals: macOS namespaces and Docker on a vm: home.
  • Windows rows: NOT_RUN, because there's no Windows host.

Full table, receipts, identities and decisions: core/docs/issues/527/PLATFORM-PASS.md. The design: core/docs/issues/527/README.md.

What changes

  • Rust layerfs-sandbox: reorganised under platform/{docker,native,namespaces}/. Runtime::Windows is removed, and the crate builds for Windows.
  • JS @layerfs/runtime:
    • the local Docker placements sit under platform/docker/;
    • every Store volume is made through one Store-keyed createStore;
    • the F11 duplicates are merged;
    • Init branches on platform capabilities, not on platform.name.
  • One setting: LAYERFS_SANDBOX=docker|native|namespaces. The old pair still works for one release, and contradicting combinations are refused.
  • Discovery: every backend's Sandbox records are listed. Store removal refuses Busy while a Sandbox of another backend is running.
  • Refusals before any effect:
    • vm: homes refuse Store-level calls;
    • unsupported VM providers and native backends are refused on hosts they don't run on;
    • Docker deployments to vm: workers are refused.
  • New VM providers: vm:lima and vm:firecracker.
  • Fixes:
    • Apple Container copies the bundle through the home bind mount, so setup drops from 67–78 s to 7–19 s;
    • the macOS worker releases a Store before its volume goes;
    • Windows Store removal no longer throws when no worker ever started;
    • D5 adds the macOS-native APFS/HFS exception to AGENTS.md.
  • Behaviour change: Init refuses Exists when its Store volume already exists, instead of reusing it.

Production LOC: 123141 -> 123811 (delta +670), counted with tools/production_loc.py (blob 686d269d). Each commit's message records its own comparison.

Not covered

  • Windows was not run.
  • layerfs-store-service doesn't compile for Windows (14 errors).
  • Other open findings are listed in PLATFORM-PASS.md "Findings not fixed in this pass".
  • Timings in PLATFORM-PASS.md are diagnostic only: the rows ran in parallel.

🤖 Generated with Claude Code

yifanxuaaa and others added 30 commits November 1, 2026 03:53
The Docker create path in sandbox.rs and the native/namespaces path in
local.rs each built `<prefix>[<store>.]<project>-<id>` themselves. Both now
call one `sandbox::sandbox_name`, so a native or namespaces Sandbox's
directory and a container cannot drift apart. The name is unchanged.

Checks: cargo +1.85.1 clippy -p layerfs-node --all-targets -D warnings on
macOS and Linux (pass); Linux native_local 7/7 and native_discovery 6/6
(test binaries run in the runtime image, which has Node).

Production LOC: 123141 -> 123133 (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 81f8758 and the staged tree daf6518; after: core 41405; engine 77504; api 4224;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel route was named in three places: layerfs-node's native
deployment set LAYERFS_KERNEL, the native provider checked and re-set it,
and the namespaces backend only checked that the caller had set it to fuse.
Now only the provider names it: the native provider sets fuse on Linux and
nfs on macOS, the namespaces backend sets fuse when it starts the helper,
and a caller that names LAYERFS_KERNEL is refused at Validate (Protocol)
before anything is made. layerfs-node passes only the NFS helper
overrides. The daemon keeps its check as an assertion: at startup it
refuses any LAYERFS_KERNEL other than fuse or nfs, and anything but nfs on
macOS, instead of silently serving another route.

Tests: native_provider gains the refusal and the route the daemon receives;
namespaces_lifecycle no longer passes LAYERFS_KERNEL (it would now be
refused).

Checks: cargo +1.85.1 clippy -p layerfs-sandbox -p layerfs-daemon
-p layerfs-node --all-targets -D warnings on macOS and Linux (pass);
native_provider on macOS and on Linux as root (pass); Linux native_local
7/7. namespaces_lifecycle needs root with cgroup v2 and
user.max_user_namespaces=0: run in the platform pass's Linux VM.

Production LOC: 123133 -> 123145 (delta +12)
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 133145f and the staged tree 8c97c6f; after: core 41405; engine 77516; api 4224;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…step 9a)

A move, with no behaviour change (F8: provider/ was not a leaf).
- provider/capabilities.rs and provider/options.rs move to types/; LaunchPhase
  joins LaunchRequest in types/options.rs; SandboxId moves to types/mod.rs.
- LaunchFailure, which holds a backend Sandbox, moves to backend/runtime.rs.
- The From conversions between DockerRecord/SandboxRecord and Docker's
  SandboxRequest move to backend/docker/container_types.rs, so types/
  depends on no backend except through RuntimeError::Docker.
- The provider module is gone; its items are re-exported at the crate root
  (layerfs_sandbox::provider::X becomes layerfs_sandbox::X) and every caller
  in layerfs-sandbox, layerfs-node and layerfs-api (sources, examples,
  tests) is updated. Root names do not collide.
- Step 6's KERNEL_ROUTE constant moves from native/mod.rs into
  native/provider.rs (the product boundary guard keeps mod.rs files to
  declarations); PROVIDERS.md (both copies) describe steps 6 and 9a.

No tests deleted.

Checks: cargo +1.85.1 clippy -p layerfs-sandbox -p layerfs-node
-p layerfs-api -p layerfs-daemon --all-targets -D warnings on macOS and
Linux (pass); tools/check_product_boundary.py (PASS); Linux tests (run as
root in the runtime image): native_provider 1, native_platform 3,
namespaces_protocol 5, namespaces_rootfs 12, docker_provider 7,
engine_lifecycle 12, runtime_enum 1, container_labels 3, docker_naming 1,
native_local 7, native_discovery 6, layerfs-api sandbox_operations 7
(all pass). tools/test_check_product_boundary.py has one pre-existing
error (test_path_violations), the same on the base commit.

Production LOC: 123145 -> 123137 (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 527c32f and the staged tree 635fe05; after: core 41405; engine 77508; api 4224;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tep 2)

F3: on a `vm:<provider>` home each project's Store is in that project's
own VM, and the home keeps no Store of its own. Calls that name a Store,
a remote or the home rather than one project crashed or ran in the wrong
process: `store.create`, `store.start_load` and `start_init({ store })`
called `platform.init(settings)` with no project and the VM's `init`
threw a plain TypeError on `project.name`; `vm.storePath()` ignored the
volume, so every Store of the home mapped to one guest file; calls with
no project fell back to this host's in-process addon with the guest
home; a PostgreSQL project's client opened the VM's SQLite Store.

Now the VM platform declares `homeStores: false`, and:
- every `runtime.store.*` call except `list`, every `runtime.remote.*`
  call and `runtime.postgresql.status` refuse `Unsupported` (naming the
  call and the reason) before any effect, through one guard in index.js;
- `project.start_init` with `{ store }` refuses the same way in prepare;
- `vm.init` checks its project and refuses a call without one;
- `vm.storePath` refuses instead of mapping every Store to one file;
- the VM `deploymentEnv` refuses a project of a PostgreSQL Store before
  its client is made;
- `store.list` still lists the home's records, with null stats and no
  call (storeFields moved inside locals' try).
api/docs/runtime.md documents the refusals.

Tests: new `vm_placement.mjs stores` (every refused call, the hooks, the
PostgreSQL client, no provider call made, `store.list` answers); it
fails on the parent with F3's TypeError. No tests deleted.
Checks (each under the 120 s `to` wrapper, after `npm install` in api/,
which only linked the three workspaces offline):
- node api/runtime/test/runtime.mjs: all 15 cases PASS
- node api/runtime/test/vm_placement.mjs settings flow two bundle delete
  hosts stores apple-container ssh cleanup: all 10 PASS

Production LOC: 123137 -> 123155 (delta +18)
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 c6f5dd2 and the staged tree 1735769; after: core 41405; engine 77508; api 4242;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F4: the local deployment (clients.js) never set LAYERFS_SANDBOX,
_SANDBOX_BACKEND or _LOCAL_DIR, so whatever the runtime's own process
had in its environment reached every project client it made (a host
LAYERFS_SANDBOX=native turned a local home's Sandboxes native). The
`vm:` deployment read `process.env.LAYERFS_SANDBOX_BACKEND`, so a host
value of `native` replaced the VM's `namespaces` backend.

Now the local deployment, for SQLite and PostgreSQL projects, sets
LAYERFS_SANDBOX=docker and unsets LAYERFS_SANDBOX_BACKEND, _LOCAL_DIR
and _IMAGE_ARCHIVE (the native-only settings) for the client it makes;
the VM deployment sets SANDBOX_BACKEND=namespaces always. Setting names
are unchanged (D3's one-setting rename is step 11). api/docs/runtime.md
says so and drops "an explicit LAYERFS_SANDBOX_BACKEND wins".

Tests: new `runtime.mjs backend` (ambient native/namespaces settings,
the client's deploy says docker and carries no native-only field, the
ambient values are restored); `vm_placement.mjs flow` now sets a host
LAYERFS_SANDBOX_BACKEND=native and still expects namespaces. Both fail
on the parent. No tests deleted.
Checks (each under the 120 s `to` wrapper):
- node api/runtime/test/runtime.mjs: all 16 cases PASS (with backend)
- node api/runtime/test/vm_placement.mjs: all 10 cases PASS

Production LOC: 123155 -> 123158 (delta +3)
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 27d1784 and the staged tree ed50744; after: core 41405; engine 77508; api 4245;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… do not run on (#527 step 8)

Placement settings accepted any implemented VM provider on any host, and
the agent sent LAYERFS_SANDBOX=native (or the namespaces backend) from a
Windows process to its WSL2 worker's Linux addon, untested (§3.1 matrix).

- settings.js: each VM provider lists the host systems it runs on, and
  `resolveSettings` refuses another host `Unsupported` before anything is
  made: apple-container on macOS only; microsandbox (libkrun microVMs) on
  Linux (KVM) and macOS (Apple silicon); ssh on Linux and macOS. No
  provider runs on Windows: the VM placement maps host folders into the
  guest as POSIX paths (vm/index.js `path`).
- agent client.js: on Windows `deployment()` refuses
  LAYERFS_SANDBOX=native, naming LAYERFS_SANDBOX_BACKEND=namespaces when
  that is set, `Unsupported`; a Windows Sandbox is Docker through the WSL2
  worker. Docker deployments are unchanged.
api/docs/runtime.md and agent.md say so.

Tests: `vm_placement.mjs settings` covers each provider on each host;
`native_deployment.mjs` fakes process.platform=win32 and checks both
refusals, an unchanged Docker deployment, and that sandbox.create makes
no binding call. Both fail on the parent. No tests deleted.
Checks (each under the 120 s `to` wrapper):
- node api/runtime/test/runtime.mjs: all 16 cases PASS
- node api/runtime/test/vm_placement.mjs: all 10 cases PASS
- node api/agent/test/native_deployment.mjs, calls.mjs, exec_output.mjs,
  packaging.mjs (surface lifecycle copy sandbox worker-route
  worker-per-project transport-order), trace.mjs (all 9 cases): PASS
- NOT_RUN (need the real addon or the E2E launcher's Store, not built
  here; they fail the same way on the parent): binary_write.mjs,
  init_refusals.mjs, reinit.mjs, packaging.mjs dispatch

Production LOC: 123158 -> 123165 (delta +7)
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 18b0810 and the staged tree 485ce56; after: core 41405; engine 77508; api 4252;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#527 step 10d)

A move, with no behaviour change: project/init.js chose how to run and
judge an Init by comparing `platform.name` with "macos" and "windows"
(runInit and runInto, two places each). The platforms now declare it:
- macos `initCounts: false`: Init runs in its own container, whose
  counts this process cannot read, so Init is not watched;
- windows `uncertainInit: true`: any failed Init is kept and recorded in
  init-failures.jsonl, never cleaned up on a guess.
Absent, as on linux and vm:, Init is watched and only `Unknown` is
uncertain, as before. `platform.name` stays in init-failures.jsonl and
in messages. api/docs/runtime.md lists the two capabilities.

No tests deleted or changed.
Checks (each under the 120 s `to` wrapper):
- node api/runtime/test/runtime.mjs: all 16 cases PASS
- node api/runtime/test/vm_placement.mjs: all 10 cases PASS

Production LOC: 123165 -> 123167 (delta +2)
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 4083e75 and the staged tree b7021c5; after: core 41405; engine 77508; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rows of #527 §4 F7 that no later step changes. Not here: the reopen
text (agent.md, step 4), the CLI settings list (help.js, step 11) and
the macOS-native Store sentence in AGENTS.md (owner decision D5).

- api/docs/agent.md: `LAYERFS_SANDBOX=native` is Linux or macOS, not
  macOS only; the section, now "Native Sandboxes (Linux and macOS)",
  says Linux runs a root FUSE daemon with commands as 501:20 and no
  longer says Linux keeps Docker only. Namespace Sandboxes: the root is
  an overlay of the Sandbox image (LAYERFS_IMAGE, unpacked from
  LAYERFS_IMAGE_ARCHIVE into LAYERFS_LOCAL_DIR/images), not the VM's
  userland; `image` is reported as the digest, not `local`; the stale
  limits (VM userland, read-only binds) give way to the real one
  (compressed image layers are refused).
- AGENTS.md: the VM providers are microsandbox, apple-container and
  ssh, verified with stand-in providers (real runs: the #527 platform
  pass); the calls are `project.start_init` and `project.delete`, not
  `project.init`/`project.remove`.
- api/docs/runtime.md: the same "verified" and `project.remove`
  corrections, and the anchor `#placement-one-linux-vm-per-store`.
- layerfs-api/PROVIDERS.md and the layerfs-sandbox Windows README list
  `Runtime::Namespaces` among the variants.
- core/docs/architecture/76-native-mount-session.md and
  nfs-native-lifecycle.md link into engine/crates; three targets had
  also moved: bridge control_native.rs is control_mount.rs,
  overlay lifetime/native.rs is lifetime/kernel.rs (with
  `revoke_kernel_mount`), and maintenance/native.rs was removed by
  1ad1e90, which the text now says.

Checks: every relinked target exists (scripted check);
node --test api/cli/test/cli.test.mjs (reads agent.md's tables): 75/75
PASS under the 120 s `to` wrapper. No tests changed.

Production LOC: 123167 -> 123167 (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 7080ac9 and the staged tree 11f4b54; after: core 41405; engine 77508; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the #527 platform pass: the namespaces backend refused the
Sandbox image the twelve cells run in (layerfs-352-runtime, Debian based),
so linux-namespaces and every vm: placement could not start a Sandbox on
it.
- Its base layer has an entry for `./`, the root itself; the reader
  refused it as an empty path (tar.rs). A directory entry whose name is the
  root is now skipped: the root already exists.
- A symbolic link whose target does not fit the header (an absolute,
  non-ASCII path under /etc/ssl/certs) carries it in a pax `linkpath`
  (or GNU `K`) record, and the reader checked that as a relative path,
  unlike the same target in the header's own link field. Extended-header
  link targets are now taken as the header's are: a symbolic link's may be
  absolute (it is never followed while unpacking, and a later write through
  it is still refused), a hard link's is still checked when its kind is
  decided. A pax `path` is still checked like every name.

Test: namespaces_rootfs gains
a_debian_layers_root_entry_and_long_absolute_link_unpack (root entry, pax
linkpath with an absolute non-ASCII target, and a pax path still refused).

Checks: cargo +1.85.1 clippy -p layerfs-sandbox --all-targets -D warnings
(Linux, pass); namespaces_rootfs 13/13 (Linux, runtime image).

Production LOC: 123167 -> 123188 (delta +21)
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 9952d16 and the staged tree 20eef63; after: core 41405; engine 77529; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the #527 platform pass: on vm:ssh every project made from a Mac
held a file `._.`, so each cell's exact-names check failed. The SSH
provider copies a folder (an Init source, the bundle) as one `tar` stream
(ssh.js copy), and the Mac's tar writes an AppleDouble `._<name>` entry for
every entry with macOS metadata (com.apple.provenance is on every file a
sandboxed process makes). A Linux host's tar creates those as ordinary
files. The pack side now runs with COPYFILE_DISABLE=1, which stops them; it
does nothing on Linux.

Test: vm_placement ssh copies a folder whose entries carry
com.apple.provenance and checks the stream's entry names. The fake SSH host
now logs each copied stream's entry names (copies.jsonl), because the Mac
tar that unpacks there folds `._` entries back into attributes and hides
them. The test fails without the fix (`._.`, `./._a.txt`) and passes with
it.

Checks: api/runtime/test/vm_placement.mjs, all 10 modes, and
api/runtime/test/runtime.mjs, all 16 modes (macOS, pass).

Production LOC: 123188 -> 123188 (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 24c3c5f and the staged tree 4a074e2; after: core 41405; engine 77529; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F5: reopen was asymmetric. A namespaces record under a native deployment
was refused, but a native record under a namespaces deployment
(LAYERFS_SANDBOX=native, LAYERFS_SANDBOX_BACKEND=namespaces) was reopened
as a native Sandbox, since local_record checked only LAYERFS_SANDBOX.
local_record now also requires the native backend, so each backend reopens
only its own records and refuses the other's with InvalidInput, deleting
nothing.

Compatibility break: a native Sandbox record left in a home whose
deployment is now namespaces, including every vm: home (whose deployment
names namespaces since step 3), is no longer reopened or reclaimed by
that deployment. Its message says to remove what it left by hand and
delete the record, as for any other runtime's record.

Test: native_local gains
a_native_record_is_not_reopened_by_a_namespaces_deployment (refused,
record and directory kept; then reclaimed by a native deployment).
agent.md's native and namespaces record paragraphs say so.

Checks: cargo +1.85.1 clippy -p layerfs-node --all-targets -D warnings on
Linux and macOS (pass); Linux native_local 8/8 (runtime image).

Production LOC: 123188 -> 123194 (delta +6)
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 781e1c0 and the staged tree c8cdfd3; after: core 41405; engine 77535; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
api/runtime/test/verify/cells/ runs #516's twelve R7 cells (C01-C12,
bodies byte-exact from benchmark/cluster-two/cells/workloads.py at
196c714^, each body's sha256 recorded) through the current SDK on one
platform row per invocation: macos-docker, macos-native, linux-docker,
linux-native, linux-namespaces (run as root in a Linux VM), vm-apple-container,
vm-microsandbox, vm-ssh, plus the documented refusals.

Per cell (#516 class B): a fresh project and Store (Init from an empty
folder; a lane forked from main, since Init makes no Branch), an untimed
setup (the committed 64 MiB base for C09-C11), one untimed identical run
on an earlier mount unmounted with discard, then under a host-wide lock a
fresh mount, workspace.exec of the body, Commit and unmount, each timed;
then a verifier on a further fresh mount checks exact names, sizes and
contents. One receipt JSON per cell with every timing, the 1-minute load,
and the bundle, image, kernel, vCPU and memory identities; the project and
everything it made are removed, also on failure. Each invocation is
bounded at 115 s. Test-only; no product change.

Production LOC: 123194 -> 123194 (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 38ad8ab and the staged tree a0901d2; after: core 41405; engine 77535; api 4254;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the #527 platform pass's refusal check: on macOS,
LAYERFS_SANDBOX=native with LAYERFS_SANDBOX_BACKEND=namespaces is
documented as refused Unsupported (#527 §3.1), and the binding does refuse
it so, but only when LAYERFS_IMAGE and LAYERFS_IMAGE_ARCHIVE are set:
without them the client refused InvalidInput "deploy.image is not
configured" first, hiding the reason. The client now refuses namespaces off
Linux with the binding's own Unsupported message before it reads the image
settings, as step 8 does for Windows.

Test: native_deployment.mjs checks the macOS refusal with no image
settings (fails before this change), and fakes Linux for its existing
namespaces pass-through cases, which now run on any host.

Checks (macOS): api/agent/test/native_deployment.mjs, calls.mjs (39
cases) pass.

Production LOC: 123194 -> 123197 (delta +3)
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 50e2c2b and the staged tree c57a7dc; after: core 41405; engine 77535; api 4257;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#527 §10 listed, from code only: macOS releaseStore was a no-op although
the macOS worker caches open Stores, so removing a Store's volume
(project.delete, store removal) left the worker holding the deleted Store
file open until its quiet minute ended; a call in that window would act on
the unlinked file. Windows and vm: already ask their worker to close it
first. macOS now does the same: it sends the worker `releaseStore` for
the volume's Store path, refuses an invalid volume name, and does nothing
when no worker endpoint exists or the worker does not answer (nothing
holds the Store then).

Test: runtime.mjs macos-worker checks the call reaches the worker with the
Store path, and the refusal.

Checks (macOS): api/runtime/test/runtime.mjs, all 16 modes, pass:
 surface home-mode projects init state events leftovers sandboxes volumes transport-order macos-worker worker-frames docker-default worker-docker stores backend

Production LOC: 123197 -> 123206 (delta +9)
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 cb7dc81 and the staged tree 7ad794d; after: core 41405; engine 77535; api 4266;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the #527 platform pass (linux-namespaces, all twelve cells):
every sandbox.close failed before anything was sent, with
"the namespaces Sandbox runtime needs the layerfs-sandbox-helper
executable beside the daemon (or LAYERFS_SANDBOX_HELPER set)", and each
Sandbox's init, init-inner and daemon were left until their idle close.
create found the helper beside the deployment's daemon executable, but
reopen and reopen_stopped (which close goes through) looked only at
LAYERFS_SANDBOX_HELPER and beside the current executable; for the SDK in
process that is `node`. Only the vm: worker sets the variable, so only
SDK-only Linux deployments hit it.

create now copies the helper into the Sandbox's own bin/ beside its daemon
copy (both 0755, deleted with the directory), and reopen runs that copy,
the helper the Sandbox was started with. A Sandbox made before this change
falls back to the previous lookup.

Test: namespaces_lifecycle's reopen case checks the copy.

Checks: cargo +1.85.1 clippy -p layerfs-sandbox -p layerfs-node
--all-targets -D warnings (Linux, pass); as root in the Lima VM lfs527-linux
(Ubuntu 24.04, 6.8.0-147, cgroup v2, user.max_user_namespaces=0):
namespaces_lifecycle 5/5 and namespaces_userns 1/1 (the limit is 0 again
afterwards) — also #527 §10's root check at this commit.

Production LOC: 123206 -> 123217 (delta +11)
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 c8f5d83 and the staged tree 25f2ce2; after: core 41405; engine 77546; api 4266;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… step 7)

Owner decisions D2 and D6 (2026-11-01). A behaviour change only in that a
variant nobody could construct on Windows is gone.
- F2: Runtime::Windows, its WINDOWS refusal and its arms in every Runtime
  method go, with backend/windows/README.md. Runtime::capabilities keeps its
  Result signature. Both PROVIDERS.md files now say Windows has no backend
  and runs the Linux build through the WSL2 worker or a VM.
- F2: layerfs-node's cfg(not(any(linux, macos))) native_runtime in local.rs
  is removed: mod local is itself built on Linux and macOS only, so it was
  dead on every target. The similar branches in layerfs-node's sandbox.rs
  and discovery.rs stay: they are live on any other target that builds the
  binding, which is what they refuse.
- F1: layerfs-sandbox now builds for x86_64-pc-windows-msvc without
  warnings. After step 9a only the shared types are built there (backend
  stays cfg(unix)); CommandIdentity::check, ExecRequest::check and the
  STOPPED re-export, read by the backends only, are cfg(unix).
- A build-only windows-build job in .github/workflows/ci.yml runs
  `cargo clippy -p layerfs-sandbox -D warnings` on windows-2025. It reports
  only: continue-on-error, no test, not a required check.

layerfs-node and layerfs-api are not built for Windows: their dependency
layerfs-store-service does not compile there (14 errors: unix permission
modes, MetadataExt::uid, nix::sys::statvfs and layerfs_persistence's
STORE_HEADROOM_BYTES, which is not built for Windows), and layerfs-api's
Sandbox control uses the Unix-only Runtime. On this Mac their C build
scripts (blake3's ml64.exe, zstd-sys, libsqlite3-sys) also need a Windows C
toolchain; a check-only stand-in compiler was used to reach the Rust errors.

Deleted test: native_platform::windows_is_refused_before_any_effect (it
drove the removed variant; nothing it guarded survives).

Checks: cargo +1.85.1 fmt -p layerfs-sandbox -p layerfs-node --check
(pass); clippy -p layerfs-sandbox -p layerfs-node --all-targets
-D warnings on macOS and Linux (pass); cargo +1.85.1 clippy -p
layerfs-sandbox --target x86_64-pc-windows-msvc -D warnings (pass; rust-std
for that target installed with rustup); tools/check_product_boundary.py
(PASS); tools/test_check_product_boundary.py 34 pass, the pre-existing
test_path_violations error unchanged; Linux tests as root in the runtime
image: native_platform 2, runtime_enum 1, native_provider 1 (pass); macOS
native_platform 2, runtime_enum 1 (pass).

Production LOC: 123217 -> 123204 (delta -13)
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 44d41ef and the staged tree 9435d6c; after: core 41405; engine 77533; api 4266;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A move, with no behaviour change (D1 option C, D7).
- backend/docker, backend/native and backend/namespaces move to
  platform/{docker,native,namespaces}; backend/linux and backend/macos move
  under platform/native/, the only code that reads them, and so do the two
  local helpers backend/listener.rs and backend/streams.rs.
  platform/namespaces keeps importing native's lifecycle as a sibling; its
  mod.rs doc comment now says so (F9).
- backend/runtime.rs and backend/helper_spec.rs move to the crate root;
  backend/json.rs and backend/strings.rs become json.rs and
  json/strings.rs. The design listed json/mod.rs, but the product boundary
  guard keeps mod.rs files to declarations, so the parser keeps its own
  file beside the folder (as layerfs-content's filesystem/validate.rs does).
- backend/identity.rs becomes ContainerId::random in types/identity.rs
  (Linux and macOS, as before); backend/mod.rs's declarations move to
  lib.rs and the new platform/mod.rs.
- Modules are private. The public paths are the crate root and one module
  per backend: layerfs_sandbox::docker, ::native (Linux, macOS) and
  ::namespaces (Linux), re-exported from platform/ so no external path
  names it. The root also re-exports Docker, DockerProvider,
  NativeMacosProvider, NativeLinuxProvider, NamespaceProvider and HelperSpec.
  layerfs_sandbox::backend::X becomes layerfs_sandbox::X for each.
- Every caller is updated: layerfs-node (discovery, local, sandbox),
  layerfs-api (two examples, sandbox_operations, provider_ownership), the
  helper binary, layerfs-sandbox's example and tests.
- tools/check_product_boundary.py audits src/platform/native/macos/darwin.rs
  for unsafe, and its test names the new paths (Ruff-formatted).
- lib.rs's comment, both PROVIDERS.md files and the current architecture
  documents' source links (71, 76, nfs-native-lifecycle, the
  sandbox-command-launcher proposal) name the new paths. Historical
  core/docs/issues/** records keep theirs.

No tests deleted; no test files renamed.

Checks: cargo +1.85.1 fmt (pass); clippy -p layerfs-sandbox -p layerfs-node
-p layerfs-api -p layerfs-daemon --all-targets -D warnings on macOS and
Linux (pass); clippy -p layerfs-sandbox --target x86_64-pc-windows-msvc
-D warnings (pass); tools/check_product_boundary.py (PASS);
tools/test_check_product_boundary.py 34 pass, the pre-existing
test_path_violations error unchanged. Linux tests as root in the runtime
image: native_provider 1, native_platform 2, namespaces_protocol 5,
namespaces_rootfs 13, docker_provider 7, engine_lifecycle 12,
runtime_enum 1, container_labels 3, docker_naming 1, engine_protocol 18,
engine_keepalive 4, local_runtime 11, native_local 8, native_discovery 6,
layerfs-api sandbox_operations 7 (all pass). Root-only, in the Lima VM
lfs527-linux: namespaces_lifecycle 5, namespaces_userns 1 (pass). macOS:
native_provider 1, native_platform 2, local_search_path 1, runtime_enum 1,
docker_naming 1, namespaces_protocol 5, layerfs-api provider_ownership 1
(pass).

Production LOC: 123204 -> 123207 (delta +3)
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 ab00731 and the staged tree 42efec4; after: core 41405; engine 77536; api 4266;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tep 9c)

A move, with no behaviour change: the same file names for the same roles in
every backend (provider, create, sandbox, exec).
- docker/container.rs becomes create.rs (create and start); its stop,
  delete and their one-response helper move to sandbox.rs, the former
  container_types.rs, beside the Sandbox they act on.
- request.rs (Exec creation), inspect.rs (Exec inspection) and
  provider_exec.rs (the provider's noninteractive run) join in exec.rs;
  provider_failure.rs becomes failure.rs and archive.rs upload.rs.
- docker/client/ is the Engine HTTP client: http.rs, socket.rs, body.rs,
  mux.rs and version.rs; headers.rs's one header grammar joins body.rs.
  Items the rest of Docker reads become pub(in crate::platform::docker).
- native's `use linux|macos as platform` alias becomes `os`, so it no
  longer shadows crate::platform.
- native::LocalExit goes: native's Sandbox::run returns the shared ExecExit
  (pid Some, stdout_bytes, stderr_bytes), the shape Docker and namespaces
  already return, and Runtime's native arm no longer converts.
- The unused DockerInstance, NativeLinuxInstance and NativeMacosInstance
  aliases are removed.
- PROVIDERS.md describes the roles; the architecture document 76 and the
  sandbox-command-launcher proposal link the new Docker paths; the product
  boundary test names client/http.rs.

No tests deleted. Adapted: local_runtime reads exit.stdout_bytes and
exit.stderr_bytes (LocalExit's stdout/stderr).

Checks: cargo +1.85.1 fmt (pass); clippy -p layerfs-sandbox -p layerfs-node
-p layerfs-api -p layerfs-daemon --all-targets -D warnings on macOS and
Linux (pass); clippy -p layerfs-sandbox --target x86_64-pc-windows-msvc
-D warnings (pass); tools/check_product_boundary.py (PASS);
tools/test_check_product_boundary.py 34 pass, the pre-existing
test_path_violations error unchanged. Linux tests as root in the runtime
image: native_provider 1, native_platform 2, namespaces_protocol 5,
namespaces_rootfs 13, docker_provider 7, engine_lifecycle 12,
runtime_enum 1, container_labels 3, docker_naming 1, engine_protocol 18,
engine_keepalive 4, local_runtime 11, native_local 8, native_discovery 6,
layerfs-api sandbox_operations 7 (all pass). Root-only, in the Lima VM
lfs527-linux: namespaces_lifecycle 5, namespaces_userns 1 (pass). macOS:
native_provider 1, native_platform 2, local_search_path 1, runtime_enum 1,
docker_naming 1, namespaces_protocol 5, engine_protocol 18, layerfs-api
provider_ownership 1 (pass).

Production LOC: 123207 -> 123182 (delta -25)
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 ad7cb42 and the staged tree 34c14ee; after: core 41405; engine 77511; api 4266;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cb7dc81 refused LAYERFS_SANDBOX_BACKEND=namespaces whenever this
process was not on Linux, and step 8 refused native and namespaces
whenever it was on Windows. A client that sends its calls to a worker
(a vm: placement's VM, or the macOS route) has its Sandboxes made on
the worker's Linux host, so every vm:* row run from macOS was refused
at sandbox.create ("namespaces is Linux only"). Found by the vm:lima
real proof (vm_real.mjs, wt-vm), which passed VM create, bundle, worker
and Init of both projects and then stopped there.

sandbox.create now passes the client's Sandbox host to deployment():
Linux when its transport is a worker (the worker client says so), this
process's platform otherwise. Both refusals read that host.

Deleted tests: none. native_deployment.mjs adds the worker-host case.

Checks (each bounded at 110 s): native_deployment, calls (39), exec_output
ok; vm_placement settings, flow, two, bundle, delete, hosts, stores,
apple-container, ssh, cleanup all PASS; cli.test.mjs 75/75.

Production LOC: 123182 -> 123184 (delta +2)
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 2d6770f and the staged tree 39a98cb; after: core 41405; engine 77511; api 4268;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 step 10a)

F10, D4 and D7: Docker in @layerfs/runtime had no home. Its argv was built
in platform/shared.js and in six files outside platform/, and each caller
first checked a flag (hostDocker, hostSandboxes) that a vm: home set, while
vm supplied a docker() that threw.

Move only, no behaviour change:
- platform/linux/index.js, platform/macos/index.js and platform/windows/
  move to platform/docker/linux.js, macos.js and windows/ (D4: the three
  Docker placements nest under the local placement kind; platform/ keeps
  meaning placement, D7).
- platform/docker/index.js builds platform.docker, one Docker group per
  Docker platform from its own CLI call: run (the host socket on Linux and
  macOS, wsl.exe on Windows), version, sandboxes, stores and disk.
- The Docker argv moves into the group: sandboxes.js (the containers of
  sandbox/leftovers.js, the ps/inspect of sandbox/list.js, the rm of
  leftovers and project/delete.js, STOPPED), stores.js (the four Store
  hooks from shared.js, makeVolume from store/calls.js) and disk.js (the
  volume listing and removal of volume.js).
- hostDocker, hostSandboxes and vm's throwing docker() go: a vm: platform
  has no group, and callers that find none refuse with the same
  Unsupported messages as before (shared.js dockerOf), or, for sweep and
  status, answer as before ({ host: false }, docker "not-used").
- Every helper reaches Docker through runtime.platform.docker.run, so the
  tests replace that one call (runtime.mjs, verify/runtime_real.mjs).

api/docs/runtime.md describes the layout. No other path in the repository
named the moved files (worker/server.js keeps its path; worktree_build.py
copies api/runtime whole). Deleted tests: none.

Checks (each bounded at 120 s): node api/runtime/test/runtime.mjs
surface, home-mode, projects, init, state, events, leftovers, sandboxes,
volumes, transport-order, macos-worker, worker-frames, docker-default,
worker-docker, stores, backend: all pass. node
api/runtime/test/vm_placement.mjs settings, flow, two, bundle, delete,
hosts, stores, apple-container, ssh, cleanup: all pass. node
api/agent/test/{calls,exec_output,native_deployment}.mjs: pass;
packaging.mjs surface, lifecycle, copy, sandbox, worker-route,
transport-order: pass; packaging dispatch: FAIL before and after this
change (needs a built layerfs.node, absent in this worktree). node --test
api/cli/test/cli.test.mjs: pass. python3 tools/check_product_boundary.py:
PASS. verify/runtime_real.mjs: syntax only (needs the E2E launcher).

Production LOC: 123184 -> 123205 (delta +21)
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 34d49eb and the staged tree 84c1e87; after: core 41405; engine 77511; api 4289;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lume (#527 step 10b)

F3 (rest): the Store hooks took a project ({ name, volume }), so a
Store-level call handed them a Store as if it were a project, and a vm:
home's hooks read store.name as the name of a project's VM. A Store's
volume was also made in two places with two behaviours: Init's makeStore
(shared.js, no existence check) and store.create/start_load's makeVolume
(store/calls.js, refusing a volume that exists).

Now:
- Every hook (createStore, storeHolders, releaseStoreHolders,
  releaseStore, removeStore) takes a Store { name, volume, legacy };
  legacy marks the Store a project's own Init makes, named after it.
- createStore replaces makeStore and makeVolume: on a Docker placement
  it makes the labelled volume (the project's own, labelled
  layerfs.project, or <prefix>store-<name>, labelled layerfs.store) and
  answers its name. Init, store.create and store.start_load all use it.
- Behaviour change: Init now refuses Exists when its volume already
  exists, instead of creating over it (docker volume create reuses an
  existing volume); the refused Init makes and removes nothing.
- The vm: hooks hold only a project's own Store (holderOf) and refuse any
  other Unsupported, with step 2's noHomeStores refusal, before any
  provider call. Step 2's homeStores guard in index.js still refuses the
  Store-level calls first; the hook refusal is the same refusal one layer
  down, not a second guard.

api/docs/runtime.md describes the hooks. Deleted tests: none. Tests:
runtime.mjs init expects the volume lookup before each create and adds an
Init refused Exists on an existing volume; vm_placement.mjs stores adds
the refusal of createStore, storeHolders, removeStore and releaseStore for
a Store that is not a project's own, with no provider call made.

Checks (each bounded at 120 s): node api/runtime/test/runtime.mjs
surface, home-mode, projects, init, state, events, leftovers, sandboxes,
volumes, transport-order, macos-worker, worker-frames, docker-default,
worker-docker, stores, backend: all pass. node
api/runtime/test/vm_placement.mjs settings, flow, two, bundle, delete,
hosts, stores, apple-container, ssh, cleanup: all pass. node
api/agent/test/{calls,exec_output,native_deployment}.mjs: pass;
packaging.mjs surface, lifecycle, copy, sandbox, worker-route,
transport-order: pass; packaging dispatch: FAIL before and after (needs a
built layerfs.node, absent in this worktree). node --test
api/cli/test/cli.test.mjs: pass. python3 tools/check_product_boundary.py:
PASS.

Production LOC: 123205 -> 123209 (delta +4)
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 9b307f3 and the staged tree a478c17; after: core 41405; engine 77511; api 4293;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Move only, no behaviour change. Each duplicate of F11 now has one home:
- STOPPED: platform/docker/sandboxes.js; project/delete.js's copy goes.
- Docker's volumes directory and the Store file inside a volume:
  VOLUMES and volumeStore in platform/docker/index.js, used by linux.js
  (settings.volumes), macos.js, windows/ (storePath and releaseStore) and
  the settings.volumes default.
- df -kP parsing: dfFree in platform/shared.js, used by macos.js and
  windows/. The three VM providers (microsandbox.js, apple-container.js,
  ssh.js) keep their own copy for now: platform/vm/ is being extended in
  parallel (vm:lima, vm:firecracker), and they can adopt dfFree after that
  merge.
- The worker endpoint read (macOS status and releaseStore, Windows status,
  vm endpoint) and the releaseStore call (macOS, Windows, vm): readEndpoint
  and releaseStore in worker/call.js. Windows' releaseStore keeps its own
  read, which throws when no endpoint exists, as before.
- homeNames and removeHome of linux, macos and vm: localHome in
  platform/shared.js.

Deleted tests: none.

Checks (each bounded at 120 s): node api/runtime/test/runtime.mjs
surface, home-mode, projects, init, state, events, leftovers, sandboxes,
volumes, transport-order, macos-worker, worker-frames, docker-default,
worker-docker, stores, backend: all pass. node
api/runtime/test/vm_placement.mjs settings, flow, two, bundle, delete,
hosts, stores, apple-container, ssh, cleanup: all pass. node
api/agent/test/{calls,exec_output,native_deployment}.mjs: pass;
packaging.mjs surface, lifecycle, copy, sandbox, worker-route,
transport-order: pass; packaging dispatch: FAIL before and after (needs a
built layerfs.node, absent in this worktree). node --test
api/cli/test/cli.test.mjs: pass. python3 tools/check_product_boundary.py:
PASS. The Windows platform module only loads here (no WSL on this host).

Production LOC: 123209 -> 123216 (delta +7)
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 3f28127 and the staged tree 8b28c4d; after: core 41405; engine 77511; api 4300;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#527 step 12, sweep only)

Step 12's JavaScript-only part. runtime.sandbox.sweep chose the projects
to reclaim by an ended container's labels and also by rebuilding its name
in JavaScript (<prefix>[<store>.]<project>-), a third copy of the Sandbox
name format that the binding owns since step 5 (sandbox_name). It now
matches the labels alone (layerfs.project, layerfs.store, which give the
project key). Behaviour change: an ended container whose labels name a
project of this home but whose name does not follow that format now
starts that project's reclaim; the project's own records still decide
what goes, so nothing is deleted by name (the only effect is one more
reclaim call). The home's containerPrefix filter on the listing stays: a
Sandbox carries no home label.

The rest of step 12 (local discovery of native and namespaces Sandboxes
through project.list_sandboxes) is NOT in this commit: it needs a binding
change. The local placement's clients run a Docker deployment (step 3),
and under it project.list_sandboxes refuses InvalidInput on a native or
namespaces record (discovery.rs local_record / namespaces_record, step 4),
so the call cannot list those Sandboxes, and the whole listing fails for a
project that has one.

Test: runtime.mjs sandboxes adds a sweep that reclaims a project from a
labelled container named outside the format, and one that starts no
reclaim for a running container or one of no project of this home.
Deleted tests: none.

Checks (each bounded at 120 s): node api/runtime/test/runtime.mjs
surface, home-mode, projects, init, state, events, leftovers, sandboxes,
volumes, transport-order, macos-worker, worker-frames, docker-default,
worker-docker, stores, backend: all pass. node
api/runtime/test/vm_placement.mjs settings, flow, two, bundle, delete,
hosts, stores, apple-container, ssh, cleanup: all pass. node
api/agent/test/{calls,exec_output,native_deployment}.mjs: pass;
packaging.mjs surface, lifecycle, copy, sandbox, worker-route,
transport-order: pass; packaging dispatch: FAIL before and after (needs a
built layerfs.node, absent in this worktree). node --test
api/cli/test/cli.test.mjs: pass.

Production LOC: 123216 -> 123214 (delta -2)
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 9a241c0 and the staged tree 16946f5; after: core 41405; engine 77511; api 4298;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…527 platform pass)

What: apple-container.js copy() no longer runs `container copy`. It clones
the folder into a private <home>/.vm-copy-* staging folder (APFS clone where
the volume allows, else a plain copy), the guest copies it from its home bind
mount (virtiofs) onto its own disk with `cp -Rp`, and the staging folder is
removed whatever the outcome. A folder already inside the home is read in
place. index.js and the step order are unchanged.

Why: in the #527 pass every vm-apple-container cell spent 67-78 s in setup
(6 of 12 FAILed for budget), against 9 s on Microsandbox. Measured, one
diagnostic sample each (container 1.6.1, 4 cpus/4G VM, bundle cells-4bedac61
with the 2.7 GB uncompressed runtime image):
  worker.start + Init, old route: 92.6 s, of which `container copy` of
    the bundle 89.5 s (a direct `container copy`: 77.2 s, ~35 MB/s);
    VM create 0.8 s, Store volume 0.06 s, every other step < 0.1 s,
    Init after start 0.5 s
  same, this route: 8.3 s, bundle copy 4.8 s
  6 MB-image bundle (h39): old 4.2 s (copy 1.4 s), new 3.4 s (copy 0.3 s)
  a tar stream through `exec -i` (not chosen): 23.5 s
The image unpack is not in setup (it is sandbox.create, ~3 s in the cell).
The contract stays: the bundle identity check and .sealed marker are
index.js's and unchanged, the VM owns its copy on its own disk, no secret is
on a command line (the worker env still goes by `exec -i` stdin), one
attempt per step and no retry. A create-time bind mount of the bundle was not
used: it would tie a VM's restart to that bundle path for the VM's life.

Tests: vm_placement apple-container asserts no `container copy`, the guest
`cp -Rp` from /layerfs/home/.vm-copy-*/c, the VM-owned copy, no staging
folder left (also after a failed copy), and an in-home folder read in place;
the stand-in maps /layerfs/home to the host home. Docs: api/docs/runtime.md
step 2.

Checks (each <= 120 s wall):
vm_placement settings: PASS vm_placement/settings (0 s)
vm_placement flow: PASS vm_placement/flow (2 s)
vm_placement two: PASS vm_placement/two (2 s)
vm_placement bundle: PASS vm_placement/bundle (3 s)
vm_placement delete: PASS vm_placement/delete (1 s)
vm_placement hosts: PASS vm_placement/hosts (2 s)
vm_placement stores: PASS vm_placement/stores (0 s)
vm_placement apple-container: PASS vm_placement/apple-container (2 s)
vm_placement ssh: PASS vm_placement/ssh (3 s)
vm_placement cleanup: PASS vm_placement/cleanup (1 s)
runtime surface: PASS runtime/surface (0 s)
runtime home-mode: PASS runtime/home-mode (0 s)
runtime projects: PASS runtime/projects (0 s)
runtime init: PASS runtime/init (2 s)
runtime state: PASS runtime/state (0 s)
runtime events: PASS runtime/events (0 s)
runtime leftovers: PASS runtime/leftovers (0 s)
runtime sandboxes: PASS runtime/sandboxes (0 s)
runtime volumes: PASS runtime/volumes (1 s)
runtime transport-order: PASS runtime/transport-order (0 s)
runtime macos-worker: PASS runtime/macos-worker (0 s)
runtime worker-frames: PASS runtime/worker-frames (5 s)
runtime docker-default: PASS runtime/docker-default (0 s)
runtime worker-docker: PASS runtime/worker-docker (1 s)
runtime stores: PASS runtime/stores (0 s)
runtime backend: PASS runtime/backend (0 s)

Production LOC: 123214 -> 123233 (delta +19)
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 72bd04c and the staged tree 7ce514c; after: core 41405; engine 77511; api 4317;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ive|namespaces (#527 step 11)

Owner decision D3. LAYERFS_SANDBOX names the backend; namespaces is no
longer native with LAYERFS_SANDBOX_BACKEND=namespaces.
- layerfs-node runtime.rs is the one validator: `backend(deploy)` answers
  Docker (None), Native or Namespaces from deploy.sandbox and maps the
  former pair for one release (native + sandboxBackend namespaces is
  namespaces; native + unset or native is native). Any other combination
  with sandboxBackend set (namespaces + native, docker + either, unset +
  either) is refused InvalidInput naming both settings; an unknown value of
  either setting is refused naming the values it takes; namespaces off
  Linux is Unsupported. All before any effect. `local()` and `native()`
  go: sandbox.rs passes the selected backend to local::start, and
  discovery's native and namespaces record checks read the same function
  (step 4's rule unchanged).
- @layerfs/agent `deployment` only checks the fields each value needs:
  native needs daemon and localDir, namespaces (or the former pair) also
  image and imageArchive, Docker its Docker settings. It passes
  sandboxBackend on (beside Docker too) and passes an unknown
  LAYERFS_SANDBOX on alone, so the binding refuses them. 34d49eb's host
  rule is kept: the Windows and off-Linux refusals key on sandboxHost.
- @layerfs/runtime names the backend: local still sets
  LAYERFS_SANDBOX=docker with the former setting unset; vm: now sets
  LAYERFS_SANDBOX=namespaces and unsets LAYERFS_SANDBOX_BACKEND, from its
  own settings, so neither an ambient LAYERFS_SANDBOX nor the former one
  reaches its clients (F4).
- The CLI help lists every LAYERFS_* setting the SDK reads, grouped
  (Store, Sandbox per backend, the former setting, binding, trace): seven
  were missing.
- Docs: agent.md's settings table and the native and namespaces sections,
  runtime.md's local and vm: deployments, layerfs-api PROVIDERS.md and
  AGENTS.md's VM paragraph name LAYERFS_SANDBOX=namespaces. §7.1's names
  (record runtime values, capabilities, helper, on-disk names, variants)
  do not change.

No tests deleted. Adapted: native_deployment's typo loop (an unknown value
is now passed on for the binding to refuse, not refused in JS);
vm_placement's settings case expects SANDBOX namespaces with the former
setting unset, under an ambient LAYERFS_SANDBOX=docker too. Added:
native_local::one_sandbox_setting_maps_the_former_pair_and_refuses_conflicts
(five refusals through a namespaces record, then the former native pair
reclaims a native record); native_deployment cases for namespaces on
Linux, its fields, the passed-on conflicts, a client's sandbox.create, and
the darwin and win32 refusals by the new name.

Checks: cargo +1.85.1 fmt -p layerfs-node --check (pass); clippy -p
layerfs-node --all-targets -D warnings on macOS and Linux (pass);
tools/check_product_boundary.py (PASS). Linux, in the runtime image:
native_local 9, native_discovery 6, layerfs-api sandbox_operations 7 (all
pass). JS: runtime.mjs all 16 modes, vm_placement.mjs all 10 modes,
agent native_deployment, calls (39), exec_output, node --test
cli.test.mjs (all pass).

Production LOC: 123233 -> 123262 (delta +29)
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 915522d and the staged tree 49daba8; after: core 41405; engine 77533; api 4324;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…overy reads them (#527 step 12, discovery)

The discovery half of step 12 (F6); the sweep half is 72bd04c. Since
step 3 a local client always runs a Docker deployment, and under it
project.list_sandboxes, runtime.sandbox.reclaim (and so sweep) and
sandbox.create's reclaim refused InvalidInput on any native or namespaces
record, so one such record failed them for the whole project.
- layerfs-node: a new backend call runtime.sandbox.records lists every
  record of the project with its runtime (docker, native, namespaces, or
  local for the former runtime) and state (running, stopped, gone,
  unknown with a message) without consulting the deployment: a Docker
  record's container is inspected once at its own Engine as before, a
  native or namespaces record's process is read from this kernel by id,
  start time and boot (LocalProcess::alive), a local record is never read.
  Nothing is reopened, dialed or changed. discovery's record-process
  parsing is one function (recorded_process) shared with local_record_of;
  runtime::recorded names a record's backend.
- project.list_sandboxes, runtime.sandbox.reclaim and sandbox.create's
  reclaim leave out a record the deployment's backend may not reopen (a
  native or namespaces record of another backend, a local record) instead
  of failing; Docker records are every deployment's, as before. Step 4's
  rule in local_record and namespaces_record is unchanged: reopening such a
  record by its number (mount, close) is still refused, never mixed.
- @layerfs/runtime: sandbox/records.js reads runtime.sandbox.records with
  one backend call on the project's binding (no client, no Store open). On
  Linux (new platform capability localSandboxes: an SDK run as root can
  start native or namespaces Sandboxes on this home's Stores),
  runtime.sandbox.list adds each SQLite project's native and namespaces
  Sandboxes beside the containers (every row now carries runtime), and the
  Docker Store hook storeHolders adds the running ones (and unknown) of the
  Store's projects; releaseStoreHolders refuses Busy, naming them, before
  any docker rm, since only their own backend closes them. The vm:
  placement's storeHolders uses the same read instead of a client's
  list_sandboxes, so it sees every backend's record too. removeVolume's
  Busy says Sandbox(es), not container(s).
- Docs: agent.md (list_sandboxes and reclaim leave other backends' records
  out; reopen still refuses), runtime.md (sandbox.list rows and runtime,
  Store removal holders, sweep, localSandboxes), tests/README.md.

No tests deleted. Adapted (native_local): a_record_of_the_former_local_
runtime_is_never_used_nor_deleted and a_native_record_is_not_reopened_by_
a_namespaces_deployment now expect reclaim to leave the record out
(reclaimed []) and check the never-mixed refusal through sandbox.close.
Added: native_local::every_backend_s_record_is_listed_with_its_runtime_
and_state (stopped native, running namespaces with this test's own
process, local unknown; list_sandboxes and reclaim of a Docker deployment
leave them out and remove nothing); runtime.mjs sandboxes mode: list with
records of three backends, one records call per project with no
deployment, Store removal Busy with and without stopSandboxes naming the
native Sandbox and removing nothing, a stopped one not a holder. The
stand-in binding answers runtime.sandbox.records from recordList.

Checks: cargo +1.85.1 fmt -p layerfs-node --check (pass); clippy -p
layerfs-node --all-targets -D warnings on macOS and Linux (pass);
tools/check_product_boundary.py (PASS; discovery.rs 997 lines, under the
999 ceiling). Linux, in the runtime image: native_local 10,
native_discovery 6, layerfs-api sandbox_operations 7 (all pass). JS:
runtime.mjs all 16 modes, vm_placement.mjs all 10 modes, agent
native_deployment, calls, exec_output, node --test cli.test.mjs (all
pass). Not run: a real Linux local runtime with a native Sandbox beside
Docker ones; the vm: storeHolders change ran only against vm_placement's
stand-ins.

Production LOC: 123262 -> 123405 (delta +143)
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 89226d7 and the staged tree 2b99241; after: core 41405; engine 77639; api 4361;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Windows platform's releaseStore read the worker endpoint file with
readFileSync and threw when none existed, so removing a Store
(project.delete, store.remove, a failed Init's cleanup) failed on a home
whose WSL worker had never started. It now does what macOS's does (and
both share worker/call.js since step 10c): readEndpoint, and return
without a call when there is no endpoint or the worker does not answer a
ping, since then no worker has the Store cached. A running worker is still
asked once to release it; an invalid volume name is still refused
InvalidInput first.

No tests deleted. Added: runtime.mjs mode windows-worker (in the usage
line): no endpoint resolves with no call; a stand-in worker gets one
releaseStore with the WSL home and the volume's Store path; an invalid
volume is refused; the endpoint of a worker that has ended sends nothing.

Checks: node api/runtime/test/runtime.mjs, all 17 modes (pass);
vm_placement.mjs all 10 modes (pass); agent native_deployment, calls,
exec_output, node --test cli.test.mjs (pass);
tools/check_product_boundary.py (PASS). No Rust changed. Not run on a
Windows host.

Production LOC: 123405 -> 123406 (delta +1)
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 d82d6a3 and the staged tree 0c157d7; after: core 41405; engine 77639; api 4362;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In the 50e2c2b pass, vm-apple-container C01 and C02 spent more than
115 s in setup (VM create plus a 2.6 GB bundle copy); run.mjs checked its
deadline only after setup returned, so the outer 120 s bound killed it
with no receipt and no teardown. Setup now races a timer 15 s before the
deadline; when it loses, the receipt is written as FAIL (setup) and names
what was left for cleanup: the run's work folder (recorded as soon as it
is made) and anything named with the run's prefix.

Checks: node --check; macos-native C01 with --budget-ms 15050 wrote a FAIL
(setup) receipt both when the timer won and when setup finished first
(then the existing "only N ms left" refusal); the one leftover work folder
of the first check was removed by hand.

Production LOC: 123406 -> 123406 (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 a1f91f0 and the staged tree e2780c5; after: core 41405; engine 77639; api 4362;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner chose D5's recommended option (2026-11-01): kernel_guard keeps
admitting a local apfs or hfs Store directory on macOS (#427), and
AGENTS.md, which said direct Store access is Linux only, gains the
exception: with LAYERFS_SANDBOX=native the SDK opens the Store in its own
process on this one Mac, so the one-kernel, local-filesystem rule holds.

Production LOC: 123406 -> 123406 (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 a1bf3fd and the staged tree b916aa0; after: core 41405; engine 77639; api 4362;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two rows for the new providers (owner 2026-11-01: all four #516 VMs as
real vm: placements). vm-lima is vmRow("lima"). vm-firecracker sets the
provider's settings on this process's environment, where the runtime
reads them: LAYERFS_FIRECRACKER_LIMA (default lfs527-vzkvm, the Lima vz
host with nested virtualization), LAYERFS_FIRECRACKER_DIR (default
/var/lib/lfs527-firecracker) and a per-run LAYERFS_FIRECRACKER_STATE_DIR,
removed at teardown. Its microVM gets 2G unless LFS527_VM_MEMORY says
otherwise, since the host has 4 GiB. Firecracker cells are pass/fail
only (#516): their timings describe the nesting.

Checks: node --check; setting names match the provider's source.

Production LOC: 123406 -> 123406 (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 09bf9d1 and the staged tree cba36ad; after: core 41405; engine 77639; api 4362;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
yifanxuaaa and others added 7 commits November 1, 2026 05:29
What: a fourth real VM provider for @layerfs/runtime's VM placement
(owner requirement 2026-11-01: all four VMs of #516 as real vm:<provider>
adapters). platform/vm/lima.js makes one Lima vmType: vz instance per
project with limactl and implements the five adapter steps:
- Store disk: a raw Lima disk (limactl disk create --format raw, which vz
  needs) of storeSize, made before the instance. Lima formats it ext4 on first
  attach. The adapter bind-mounts /mnt/lima-<disk> at /layerfs/store after
  every boot and checks that it is ext4.
- Instance: a template written to "limactl create --name <vm> -" on stdin:
  Ubuntu 24.04 cloud image (or vm.image when it is an https URL or an absolute
  path), cpus/memory from settings.vm, the home mounted writable at
  /layerfs/home (virtiofs), and Node.js v22.23.4 installed by a system
  provision script, checked against nodejs.org's SHASUMS256.txt.
- Bundle and Init sources: staged in a private <home>/.vm-copy-* folder and
  copied by the guest with cp -Rp onto its own disk, the same route Apple
  Container takes since 24681576cb. The staging folder is removed whatever the
  outcome.
- Guest commands: root (sudo -n) over the instance's own ssh.config, with
  each argument quoted by ssh.js's shellEscape. The worker's secret goes in a
  0600 file from stdin and never in argv.
- Private stream: the instance's portForwards rule 127.0.0.1:<host> -> 7070;
  a last rule ignores every other guest port.
- Stop/delete: limactl stop keeps everything. limactl delete --force keeps
  the disk, which only discardStore (limactl disk delete) removes.
settings.js lists lima as macOS only (#527 step 8; vz is
Virtualization.framework). index.js registers the provider. vm_real.mjs
gains lima in its provider tables and VM_REAL_PREFIX (default lfs-) for its
names. Docs: api/docs/runtime.md "The Lima provider (vz)".

Why the disk name is short: the first real run showed a VM restart
emptying the Store. Lima's boot script finds a formatted disk by the ext4
label lima-<disk>, mkfs.ext4 cuts labels at 16 bytes, so a long disk name
(lfs527-lima-<id>-shop-vm-store) is never found and the disk is formatted
again at every boot. The Lima disk is therefore lfs + 8 hex of the Store
volume's name. The volume's name and the home are kept as the instance's
LAYERFS_STORE/LAYERFS_HOME params, which inspect answers from.

Lima 1.0.4 cannot start vz on this Mac at all (gvisor-tap-vsock's
resolv.conf watcher opens Docker Desktop's /var/run/docker.sock symlink:
"adding watcher failed ... operation not supported on socket"). brew's
Lima is now 2.2.1, upgraded by the lead's vz helper.

Checks (macOS 26.4.1, M3 Max, Lima 2.2.1; each <= 120 s wall):
vm_placement settings|flow|two|bundle|delete|hosts|stores|apple-container|
ssh|lima|cleanup: all PASS (0-4 s each)
runtime surface|home-mode|projects|init|state|events|leftovers|sandboxes|
volumes|transport-order|macos-worker|worker-frames|docker-default|
worker-docker|stores|backend: all PASS (0-5 s each)
Real proof, one sample each,
VM_REAL_PREFIX=lfs527- node api/runtime/test/verify/vm_real.mjs <h39-6e1774bc bundle copy> lima:
- run 1 (base tree): FAIL at the first agent sandbox.create,
  "namespaces is Linux only" from cb7dc81; reported, fixed by the lead
  in 34d49eb (cherry-picked here as 065824f44b).
- run 2 (cb7dc81's check reverted locally, uncommitted): FAIL after the VM
  restart: Lima reformatted the Store disk (the label finding above).
- run 3 (short disk names, check reverted locally): PASS, 65.1 s.
- run 4 (this tree, on 065824f44b): PASS, "vm real: ok in 65.2 s", 1-min
  load 3.6. That run covered two projects in two vz VMs, Init through each
  worker, Sandboxes on the h39 image unpacked on the ext4 Store disk, user
  namespaces blocked, Commit, stop/start on the same port, worker restart,
  the leftover Sandbox reclaimed, V8 refusal, and one project removed while
  the other runs. Afterwards limactl list and limactl disk list held none of
  its VMs or disks.

Production LOC: 123406 -> 123560 (delta +154)
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 d610a82 and the staged tree f3af0ef; after: core 41405; engine 77639; api 4516;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What: the fifth real VM provider of the VM placement (owner requirement
2026-11-01, #516's Firecracker). platform/vm/firecracker.js runs one
Firecracker microVM per project inside a long-lived Lima vz instance with
nested virtualization (macOS has no KVM). The Lima instance belongs to the
user, like an SSH host:
- LAYERFS_FIRECRACKER_LIMA names it (default layerfs-firecracker).
- LAYERFS_FIRECRACKER_DIR (default /var/lib/layerfs-firecracker) holds
  bin/firecracker, vmlinux, rootfs.ext4 (node and an sshd) and the guest key
  id_ed25519.
- Before any disk or VM, the adapter checks that the instance runs, that it
  has /dev/kvm, those files, mkfs.ext4, ip and flock. Anything missing is
  named.
The five steps:
- Store disk: <dir>/disks/<store>.ext4, sparse and made ext4 by mkfs.ext4.
  It is the second drive and is mounted at /layerfs/store after boot.
- VM: <dir>/vms/<vm> holds its own rootfs copy and a config for
  "firecracker --no-api --config-file" (no PMU: nested KVM here has none).
  The guest gets 2 MiB huge pages when the host has enough free; #516 found
  4 KiB first-touch faults through both hypervisors made boot take 28-55 s.
  Each VM has a tap lfsfc<n> on 172.30.<n>.0/30, with the number taken under
  flock. The guest address comes from the kernel's ip= argument and the CI
  rootfs's MAC rule. The boot waits up to 60 s for the guest sshd (a
  readiness wait).
- Guest commands: root over two SSH hops (Lima's, then the host's into the
  microVM). The bundle goes in as one tar stream (no shared folder). The
  worker's secret goes from stdin into a 0600 file.
- Private stream: ssh -N -L 127.0.0.1:<host>:172.30.<n>.2:7070 from this
  machine, with ExitOnForwardFailure and ControlPath=none. A VM runs when its
  Firecracker process and the forward both run; when only the forward ended,
  start reopens it without a reboot.
- Stop and delete: stop syncs and resets the guest (Firecracker exits),
  SIGKILLs after 5 s and removes the tap. Delete removes the VM folder and
  keeps the disk file unless discardStore.
lima.js exports its ssh target, instance folder and tar stream for this
adapter. settings.js lists firecracker as macOS only and index.js registers
it. vm_real.mjs adds firecracker; there it reads the leftover Sandbox record
in the guest, because Firecracker shares no folder with this machine.
Docs: api/docs/runtime.md "The Firecracker provider (nested in Lima)".

Found on the real host: Lima's ssh.config keeps a persistent control master.
A multiplexed "ssh -N -L" handed its forward to that master and exited 0 at
once, so the adapter could neither see the forward nor end it (smoke run 1;
the stray forward was cancelled with ssh -O cancel). The forward now opens
its own connection, and the stand-in test checks that.

Host used (Lima 2.2.1 instance lfs527-vzkvm: vz, nestedVirtualization, 4 CPUs,
4 GiB, Ubuntu 24.04.5, vm.nr_hugepages=1100 set at runtime), with
LAYERFS_FIRECRACKER_DIR=/var/lib/lfs527-firecracker. All downloads were
checksum-verified:
- Firecracker v1.17.0 aarch64 tgz, sha256 e351ebe4...2256; the binary is OK
  against the release SHA256SUMS.
- CI kernel vmlinux-6.18.56 (ba2081f7...883f) and ubuntu-24.04.squashfs
  (7aeec171...fde5), from
  s3.amazonaws.com/spec.ccfc.min/firecracker-ci/20261028-84df49062576-0/aarch64/.
- Node.js v22.23.4 linux-arm64, OK against SHASUMS256.
The root filesystem is the squashfs plus Node.js plus the key, as a 6 GiB
sparse ext4 image.

Checks (macOS 26.4.1, M3 Max; each <= 120 s wall):
vm_placement settings|flow|two|bundle|delete|hosts|stores|apple-container|
ssh|lima|firecracker|cleanup: all PASS (0-8 s each)
runtime surface|home-mode|projects|init|state|events|leftovers|sandboxes|
volumes|transport-order|macos-worker|worker-frames|docker-default|
worker-docker|stores|backend: all PASS (0-5 s each)
Real, one sample each (pass/fail only per #516: these timings describe the
nesting):
- adapter smoke (one VM, then create/exec/remove): boot to SSH 6.6 s; the
  guest showed kernel 6.18.56, /dev/vdb ext4 at /layerfs/store, /dev/fuse,
  node v22.23.4 and eth0 172.30.1.2/30.
- VM_REAL_PREFIX=lfs527- LAYERFS_VM_MEMORY=1G vm_real.mjs <h39-6e1774bc
  bundle copy> firecracker:
  - run 1: FAIL at the leftover-record check. It read this machine's home,
    which a Firecracker guest does not share; the test was fixed.
  - run 2: PASS, "vm real: ok in 87.3 s", 1-min load 3.5.
  Both runs tore down cleanly: no VM folders, disks, taps, firecracker
  processes or forwards left, and HugePages_Free back to 1100.

Production LOC: 123560 -> 123808 (delta +248)
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 b39550b and the staged tree da35d7f; after: core 41405; engine 77639; api 4764;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 11 made LAYERFS_SANDBOX=docker|native|namespaces the one setting,
with the former pair (native + LAYERFS_SANDBOX_BACKEND=namespaces) kept
for one release. The linux-namespaces row now sets
LAYERFS_SANDBOX=namespaces, and the macos-namespaces refusal expects
Unsupported three times: the new name without and with the image
settings, and the former pair.

Checks: node --check.

Production LOC: 123808 -> 123808 (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 a8cb674 and the staged tree 577c7f7; after: core 41405; engine 77639; api 4764;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The final pass's refusals vm-docker cell (5431c7e) sent
LAYERFS_SANDBOX=docker to a vm:apple-container home: sandbox.create
reached the VM's worker, whose Linux has no Docker Engine, and ended
Failed at Create/Volume with Io NotFound (attempted: false, nothing
made). Design §3.1 lists Docker on vm:* as impossible; it now has a
clean, documented answer. The worker client reads the endpoint's vm
field (written by the vm placement at launch) and refuses any call
whose deployment is docker, or unset, with Unsupported naming the
provider and VM, before anything is sent. The macOS and Windows worker
routes (no vm field) still send Docker deployments.

Deleted tests: none. native_deployment.mjs adds the vm endpoint cases.

Checks (each ≤100 s): native_deployment, calls (39), exec_output ok;
vm_placement flow, two, stores, lima, firecracker, apple-container, ssh
PASS; runtime macos-worker, windows-worker, worker-frames, worker-docker
PASS.

Production LOC: 123808 -> 123811 (delta +3)
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 5431c7e and the staged tree 4907337; after: core 41405; engine 77639; api 4767;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With e9b9646, Docker on a vm: home has a documented answer, so the
refusals vm-docker cell gives PASS or FAIL instead of OBSERVED: placement
vm:docker Unsupported, LAYERFS_SANDBOX=docker on the VM home Unsupported,
and LAYERFS_SANDBOX_BACKEND=docker InvalidInput (step 11).

Checks: node --check.

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 e9b9646 and the staged tree c5b85a5; after: core 41405; engine 77639; api 4767;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLATFORM-PASS.md (status: dated planning checkpoint) and its receipts.
At the final product commit 97d408c, all 120 cells PASS on ten rows:
macOS docker and native; Linux docker, native and namespaces;
vm:apple-container, vm:microsandbox, vm:lima (vz), vm:firecracker
(nested in Lima vz, pass/fail only) and vm:ssh. Both refusals (macOS
namespaces, Docker on a vm: home) PASS. Windows rows are NOT_RUN (no
Windows host). The document lists the receipt identities and why the
5431c7e receipts count at 97d408c, which rows overlapped, the §10
checks, the decisions taken during the pass, the open findings, every
commit's LOC line and the cleanup. receipts/ holds the first pass
(50e2c2b, kept with its failures), the final pass, the vm-docker
rerun and the Apple Container fix's proof cell.

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 97d408c and the staged tree a18da8a; after: core 41405; engine 77639; api 4767;

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The design that the #527 platform pass implemented, written against
layerfs_v2_0_0 at 81f8758: support matrix, findings F1-F11, the
target Rust and JS layouts, one LAYERFS_SANDBOX setting, the step plan
and decisions D1-D7. The status banner records the owner's answers
(all recommended, D1 platform/namespaces/) and points to PLATFORM-PASS.md
for results.

Production LOC: 123811 -> 123811 (delta 0). Docs only. Scope: core,
engine and api production source; counted with tools/production_loc.py
(blob 686d269) at the parent and at this tree. Reference absent
(retired R9c; last counted 65,417 at tree 498dd19).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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