carlos / platform Public

rootfs apps: plan index and shared contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 3680013352a17a733c44932c623b7b178ab0dd1f parent bc45f56
1 file changed, +180 −0
  • docs/superpowers/plans/2026-10-24-rootfs-apps-0-index.md +180 −0

CI passed — run details

CI log
=== gate ===
.amadan/test-gate.sh
=== launchctx (0 s)
ok  	amadan.net/carlos/platform.git/launchctx	0.013s
?   	amadan.net/carlos/platform.git/launchctx/controlname	[no test files]
?   	amadan.net/carlos/platform.git/launchctx/wireid	[no test files]
=== vet (1 s)
=== gofmt (0 s)
gofmt -l .: clean
=== rest (154 s)
?   	github.com/carlosframework/platform/cmd/changelog	[no test files]
ok  	github.com/carlosframework/platform/cmd/console	4.047s
ok  	github.com/carlosframework/platform/cmd/moveprobe	11.968s
ok  	github.com/carlosframework/platform/cmd/zddprobe	0.716s
ok  	github.com/carlosframework/platform/internal/activator	62.037s
ok  	github.com/carlosframework/platform/internal/agentsock	0.036s
ok  	github.com/carlosframework/platform/internal/alerting	0.007s
ok  	github.com/carlosframework/platform/internal/analytics	0.033s
ok  	github.com/carlosframework/platform/internal/appconfig	3.193s
ok  	github.com/carlosframework/platform/internal/apperr	0.200s
ok  	github.com/carlosframework/platform/internal/applog	0.304s
ok  	github.com/carlosframework/platform/internal/awsinv	0.007s
ok  	github.com/carlosframework/platform/internal/boxchoice	0.009s
ok  	github.com/carlosframework/platform/internal/boxid	0.908s
?   	github.com/carlosframework/platform/internal/boxid/boxidtest	[no test files]
?   	github.com/carlosframework/platform/internal/boxid/internal/signtest	[no test files]
ok  	github.com/carlosframework/platform/internal/boxvolumes	0.015s
ok  	github.com/carlosframework/platform/internal/caps	0.036s
ok  	github.com/carlosframework/platform/internal/certwarm	0.005s
ok  	github.com/carlosframework/platform/internal/cgroup	0.822s
ok  	github.com/carlosframework/platform/internal/changelog	0.613s
ok  	github.com/carlosframework/platform/internal/claimactivation	1.385s
ok  	github.com/carlosframework/platform/internal/clamd	0.069s
?   	github.com/carlosframework/platform/internal/cliflag	[no test files]
?   	github.com/carlosframework/platform/internal/controlname	[no test files]
?   	github.com/carlosframework/platform/internal/controlsock	[no test files]
ok  	github.com/carlosframework/platform/internal/dbdoor	7.592s
ok  	github.com/carlosframework/platform/internal/dbdoor/dbdoortest	0.014s
ok  	github.com/carlosframework/platform/internal/dbquery	0.009s
ok  	github.com/carlosframework/platform/internal/dbreads	2.185s
?   	github.com/carlosframework/platform/internal/dbreads/dbreadstest	[no test files]
ok  	github.com/carlosframework/platform/internal/dbverify	0.005s
ok  	github.com/carlosframework/platform/internal/dnsrec	0.003s
ok  	github.com/carlosframework/platform/internal/docsite	0.019s
ok  	github.com/carlosframework/platform/internal/domains	2.964s
ok  	github.com/carlosframework/platform/internal/doorkey	0.013s
ok  	github.com/carlosframework/platform/internal/economics	0.011s
ok  	github.com/carlosframework/platform/internal/edge	46.011s
?   	github.com/carlosframework/platform/internal/edgeproof	[no test files]
ok  	github.com/carlosframework/platform/internal/edgesfile	0.014s
ok  	github.com/carlosframework/platform/internal/fleetapi	0.201s
ok  	github.com/carlosframework/platform/internal/gate	1.884s
ok  	github.com/carlosframework/platform/internal/genselect	0.022s
ok  	github.com/carlosframework/platform/internal/geo	0.016s
ok  	github.com/carlosframework/platform/internal/geoip	0.035s
ok  	github.com/carlosframework/platform/internal/historyreadback	0.271s
ok  	github.com/carlosframework/platform/internal/host	151.062s
ok  	github.com/carlosframework/platform/internal/hostactors	6.447s
ok  	github.com/carlosframework/platform/internal/hostactors/awsmembership	0.009s
ok  	github.com/carlosframework/platform/internal/hostbox	3.331s
ok  	github.com/carlosframework/platform/internal/hostclaim	0.857s
ok  	github.com/carlosframework/platform/internal/hostenv	0.006s
ok  	github.com/carlosframework/platform/internal/hostevidence	0.013s
ok  	github.com/carlosframework/platform/internal/hostexec	5.675s
ok  	github.com/carlosframework/platform/internal/hostfence	0.045s
ok  	github.com/carlosframework/platform/internal/hostfiles	10.294s
ok  	github.com/carlosframework/platform/internal/hostfloor	0.033s
ok  	github.com/carlosframework/platform/internal/hostguard	1.101s
ok  	github.com/carlosframework/platform/internal/hostops	2.348s
?   	github.com/carlosframework/platform/internal/hostops/fencerelease	[no test files]
ok  	github.com/carlosframework/platform/internal/hostops/internal/fencerelease	0.007s
ok  	github.com/carlosframework/platform/internal/hostquarantine	0.006s
ok  	github.com/carlosframework/platform/internal/hostrelocate	0.650s
ok  	github.com/carlosframework/platform/internal/hostresolve	0.256s
ok  	github.com/carlosframework/platform/internal/hostwrite	0.003s
ok  	github.com/carlosframework/platform/internal/infraview	0.006s
ok  	github.com/carlosframework/platform/internal/instancectl	5.263s
ok  	github.com/carlosframework/platform/internal/instanceexec	2.821s
ok  	github.com/carlosframework/platform/internal/instancefs	0.008s
ok  	github.com/carlosframework/platform/internal/instancekey	0.006s
ok  	github.com/carlosframework/platform/internal/instanceupdates	0.012s
ok  	github.com/carlosframework/platform/internal/layout	1.927s
ok  	github.com/carlosframework/platform/internal/lbhealth	0.004s
ok  	github.com/carlosframework/platform/internal/lease	10.256s
ok  	github.com/carlosframework/platform/internal/lease/leasetest	0.616s
ok  	github.com/carlosframework/platform/internal/ledger	0.021s
ok  	github.com/carlosframework/platform/internal/mail	0.242s
ok  	github.com/carlosframework/platform/internal/maintenance	0.097s
ok  	github.com/carlosframework/platform/internal/maintenanceexec	5.625s
ok  	github.com/carlosframework/platform/internal/maintenanceguard	0.068s
ok  	github.com/carlosframework/platform/internal/metrics	0.006s
ok  	github.com/carlosframework/platform/internal/moves	27.760s
ok  	github.com/carlosframework/platform/internal/moves/archive	1.154s
ok  	github.com/carlosframework/platform/internal/objstore	0.274s
ok  	github.com/carlosframework/platform/internal/ops	0.194s
ok  	github.com/carlosframework/platform/internal/park	0.295s
ok  	github.com/carlosframework/platform/internal/plans	0.006s
ok  	github.com/carlosframework/platform/internal/privatefile	0.007s
ok  	github.com/carlosframework/platform/internal/provision	47.547s
ok  	github.com/carlosframework/platform/internal/publicrelease	0.008s
?   	github.com/carlosframework/platform/internal/regionname	[no test files]
ok  	github.com/carlosframework/platform/internal/registry	5.654s
ok  	github.com/carlosframework/platform/internal/relay	7.201s
ok  	github.com/carlosframework/platform/internal/release	30.339s
ok  	github.com/carlosframework/platform/internal/rollout	3.807s
ok  	github.com/carlosframework/platform/internal/rolloutwords	0.004s
ok  	github.com/carlosframework/platform/internal/rootaws	0.007s
ok  	github.com/carlosframework/platform/internal/rootparticipation	0.062s
ok  	github.com/carlosframework/platform/internal/rootstate	0.028s
ok  	github.com/carlosframework/platform/internal/runs	0.528s
ok  	github.com/carlosframework/platform/internal/s3compat	0.005s
ok  	github.com/carlosframework/platform/internal/safefile	0.004s
ok  	github.com/carlosframework/platform/internal/schedule	2.773s
ok  	github.com/carlosframework/platform/internal/seeds	0.013s
ok  	github.com/carlosframework/platform/internal/sim/engines	2.849s
ok  	github.com/carlosframework/platform/internal/sim/hostlife	14.112s
ok  	github.com/carlosframework/platform/internal/sim/litestreamfacts	4.481s
ok  	github.com/carlosframework/platform/internal/status	0.119s
ok  	github.com/carlosframework/platform/internal/steering	0.006s
ok  	github.com/carlosframework/platform/internal/stopevidence	0.246s
ok  	github.com/carlosframework/platform/internal/svcrole	0.004s
ok  	github.com/carlosframework/platform/internal/telemetry	0.005s
ok  	github.com/carlosframework/platform/internal/termfence	0.004s
ok  	github.com/carlosframework/platform/internal/tunnelclient	0.005s
ok  	github.com/carlosframework/platform/internal/unixdial	0.003s
ok  	github.com/carlosframework/platform/internal/updatewords	0.006s
ok  	github.com/carlosframework/platform/internal/usage	0.006s
ok  	github.com/carlosframework/platform/internal/version	0.005s
ok  	github.com/carlosframework/platform/internal/vet	1.653s
=== internal_console@0 (87 s)
ok  	github.com/carlosframework/platform/internal/console	84.524s
=== internal_console@1 (50 s)
ok  	github.com/carlosframework/platform/internal/console	47.644s
=== internal_console@2 (48 s)
ok  	github.com/carlosframework/platform/internal/console	45.374s
=== internal_console@3 (47 s)
ok  	github.com/carlosframework/platform/internal/console	44.178s
=== internal_console@4 (50 s)
ok  	github.com/carlosframework/platform/internal/console	47.178s
=== internal_console@5 (49 s)
ok  	github.com/carlosframework/platform/internal/console	46.450s
=== internal_console@6 (45 s)
ok  	github.com/carlosframework/platform/internal/console	42.223s
=== internal_console@7 (52 s)
ok  	github.com/carlosframework/platform/internal/console	49.374s
=== root@0 (117 s)
ok  	github.com/carlosframework/platform	115.343s
=== root@1 (89 s)
ok  	github.com/carlosframework/platform	87.659s
=== root@2 (100 s)
ok  	github.com/carlosframework/platform	98.292s
=== cross ===
--- shard 0 of 5 ---
cross-compiling darwin/arm64
--- shard 1 of 5 ---
cross-compiling darwin/amd64
--- shard 2 of 5 ---
cross-compiling linux/arm64
linux/arm64 carlos binary: 50528418 bytes (ceiling 52000000)
--- shard 3 of 5 ---
cross-compiling windows/amd64
--- shard 4 of 5 ---
cross-compiling windows/arm64
=== minio-contract ===
minio contract: arm64, minio RELEASE.2025-09-07T16-13-09Z, mc RELEASE.2025-08-13T08-35-41Z
  minio: downloading https://github.com/minio/minio/releases/download/RELEASE.2025-09-07T16-13-09Z/minio.linux-arm64.RELEASE.2025-09-07T16-13-09Z
  minio: downloaded, digest verified
  mc: downloading https://github.com/minio/mc/releases/download/RELEASE.2025-08-13T08-35-41Z/mc.linux-arm64.RELEASE.2025-08-13T08-35-41Z
  mc: downloaded, digest verified
  minio healthy on 127.0.0.1:20999
Added `local` successfully.
Bucket created successfully `local/contract-bucket`.
Bucket created successfully `local/denied-bucket`.
Created policy `contract-only` successfully.
Added user `scopeduser` successfully.
Attached Policies: [contract-only]
To User: scopeduser
  buckets and scoped user ready
=== RUN   TestDeliveredContractMinio
=== RUN   TestDeliveredContractMinio/put
=== RUN   TestDeliveredContractMinio/get_round-trips_the_body
=== RUN   TestDeliveredContractMinio/list_shows_the_key
=== RUN   TestDeliveredContractMinio/head_and_list_agree_on_the_version
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict
=== RUN   TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed
=== RUN   TestDeliveredContractMinio/delete_removes_it
=== RUN   TestDeliveredContractMinio/cross-bucket_denial
--- PASS: TestDeliveredContractMinio (0.02s)
    --- PASS: TestDeliveredContractMinio/put (0.00s)
    --- PASS: TestDeliveredContractMinio/get_round-trips_the_body (0.00s)
    --- PASS: TestDeliveredContractMinio/list_shows_the_key (0.00s)
    --- PASS: TestDeliveredContractMinio/head_and_list_agree_on_the_version (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/not_by_PutObjectTagging (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_its_own_object/and_not_by_x-amz-tagging_on_upload (0.00s)
    --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route (0.01s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CreateMultipartUpload (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/not_by_CopyObject_with_a_REPLACE_tagging_directive (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/but_multipart_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_copy_itself_is_allowed (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/a_POST-policy_forged_tag_with_a_matching_version_is_still_not_a_verdict (0.00s)
        --- PASS: TestDeliveredContractMinio/the_app_cannot_tag_by_any_other_route/and_an_ordinary_tagged-with-something-else_upload_is_allowed (0.00s)
    --- PASS: TestDeliveredContractMinio/delete_removes_it (0.00s)
    --- PASS: TestDeliveredContractMinio/cross-bucket_denial (0.00s)
PASS
ok  	github.com/carlosframework/platform/internal/objstore	0.026s
=== RUN   TestT4StorageProbeMinio
2026/10/24 16:27:59 moves: T4: STORE: list: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: 18E18403ED8C86D4, HostID: 7c420053e0fbcd7f41653fdd92ffa2802c903d05303b1aa15adc2e8bb65d183f, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
--- PASS: TestT4StorageProbeMinio (0.01s)
PASS
ok  	github.com/carlosframework/platform/internal/host	0.024s
=== RUN   TestS3StoreMinioContract
=== RUN   TestS3StoreMinioContract/conditional_get_and_etag_listing
=== RUN   TestS3StoreMinioContract/bump_through_every_mutation_path
--- PASS: TestS3StoreMinioContract (0.03s)
    --- PASS: TestS3StoreMinioContract/conditional_get_and_etag_listing (0.01s)
    --- PASS: TestS3StoreMinioContract/bump_through_every_mutation_path (0.02s)
PASS
ok  	github.com/carlosframework/platform/internal/release	0.035s
Bucket created successfully `local/dbf-deploy`.
Bucket created successfully `local/dbf-data-ie`.
Bucket created successfully `local/dbf-data-uk`.
local/dbf-deploy versioning is enabled
Created policy `dbf-console` successfully.
Created policy `dbf-ukbox` successfully.
Added user `dbfconsole` successfully.
Added user `dbfukbox` successfully.
Attached Policies: [dbf-console]
To User: dbfconsole
Attached Policies: [dbf-ukbox]
To User: dbfukbox
=== RUN   TestDBForwardMinio
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 16:28:01 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000002
2026/10/24 16:28:01 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000002 maxTXID=0000000000000002 size=370
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000002
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000002 txid.db=0000000000000003
2026/10/24 16:28:01 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000003 maxTXID=0000000000000003 size=454
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000003 txid.db=0000000000000003
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000000 txid.db=0000000000000001
2026/10/24 16:28:01 INFO ltx file uploaded system=store db=source.db replica=file level=0 minTXID=0000000000000001 maxTXID=0000000000000001 size=720
2026/10/24 16:28:01 INFO replica sync system=store db=source.db replica=file txid.replica=0000000000000001 txid.db=0000000000000001
2026/10/24 16:28:01 console: CARLOS_SECRETS_PUBKEY is unset, so no region outside the home can be chosen automatically
2026/10/24 16:28:01 console: could not notify member@example.com of a CLI token issue: keymail: lookup member@example.com: keymail: GET /api/federation/lookup?addr=member%40example.com&from=console%40127.0.0.1%3A40307: could not determine target mailbox
    agent.go:135: dbreads: dbr_jdiej4fxvnjdlh24s2l2asfqae: planned 1 files (720 bytes) and restored to 1 in 9ms
    agent.go:135: dbreads: dbr_jdiej4fxvnjdlh24s2l2asfqae: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 16:28:02 console: db read dbr_jdiej4fxvnjdlh24s2l2asfqae: relayed 1 chunks (102 bytes) in 1.004s, ended done
    agent.go:135: dbreads: dbr_gxpxlyfurma4vlw3qoslicu3ke: planned 3 files (1544 bytes) and restored to 3 in 10ms
    agent.go:135: dbreads: dbr_gxpxlyfurma4vlw3qoslicu3ke: output 5 bytes in 1 confirmed chunks in 1ms
2026/10/24 16:28:03 console: db read dbr_gxpxlyfurma4vlw3qoslicu3ke: relayed 1 chunks (102 bytes) in 1.004s, ended done
    agent.go:135: dbreads: dbr_tyegrnne3x32bw2zornwblyhuu: planned 3 files (1544 bytes) and held through 3 in 9ms
    agent.go:135: dbreads: dbr_tyegrnne3x32bw2zornwblyhuu: output 1544 bytes in 1 confirmed chunks in 1ms
2026/10/24 16:28:04 console: db read dbr_tyegrnne3x32bw2zornwblyhuu: relayed 1 chunks (1641 bytes) in 1.003s, ended done
=== RUN   TestDBForwardMinio/by_disconnect
=== NAME  TestDBForwardMinio
    agent.go:135: dbreads: dbr_golwee4m4diqmshk6viltvyriq: planned 3 files (1544 bytes) and restored to 3 in 9ms
2026/10/24 16:28:04 console: db read dbr_golwee4m4diqmshk6viltvyriq: relayed 0 chunks (0 bytes) in 64ms, ended client gone
    agent.go:135: dbreads: dbr_golwee4m4diqmshk6viltvyriq: output 5 bytes in 1 confirmed chunks in 103ms
=== RUN   TestDBForwardMinio/by_timeout
=== NAME  TestDBForwardMinio
    agent.go:135: dbreads: dbr_bp5ld6ge6vfla6qqjyiobjfthy: planned 3 files (1544 bytes) and restored to 3 in 10ms
2026/10/24 16:28:24 console: db read dbr_bp5ld6ge6vfla6qqjyiobjfthy: relayed 0 chunks (0 bytes) in 19.114s, ended no-result
    agent.go:135: dbreads: dbr_bp5ld6ge6vfla6qqjyiobjfthy: output 5 bytes in 1 confirmed chunks in 19.088s
--- PASS: TestDBForwardMinio (22.55s)
    --- PASS: TestDBForwardMinio/by_disconnect (0.22s)
    --- PASS: TestDBForwardMinio/by_timeout (19.22s)
PASS
ok  	github.com/carlosframework/platform/internal/console	22.569s
minio contract: PASS
diff --git a/docs/superpowers/plans/2026-10-24-rootfs-apps-0-index.md b/docs/superpowers/plans/2026-10-24-rootfs-apps-0-index.md
new file mode 100644
index 000000000..4db87412a
--- /dev/null
+++ b/docs/superpowers/plans/2026-10-24-rootfs-apps-0-index.md
@@ -0,0 +1,180 @@
+# Rootfs apps Implementation Plans: Index
+
+> **For agentic workers:** this file is the map. Each plan it names is a
+> self-contained implementation plan with the superpowers header; execute
+> them in the order below with superpowers:subagent-driven-development or
+> superpowers:executing-plans. Do not start a plan whose dependencies are
+> not merged on `main`.
+
+**Goal:** Build rootfs apps (amadan carlos/platform #366): a release may
+carry a read-only root filesystem built locally from an OCI image or a
+directory, run as a nested instance under systemd on the customer's own
+boxes, always-on by default, with declared `web`, worker, `release` and
+`console` processes, an operator-granted egress allow, and interactive
+sessions.
+
+**Spec:** `docs/superpowers/specs/2026-10-24-rootfs-apps-design.md` rev 4
+(bc45f56b), approved by Paul 2026-10-24. Rulings R1 to R6 (§2.1) and the
+calls in §11 are requirements; plans cite the section beside the task that
+answers it.
+
+**Built against:** `main` at 64a002c8.
+
+**Copy:** every user-facing string of the series goes in one deck,
+`2026-10-24-rootfs-apps-copy.md`, assembled from the string tables each plan
+carries (IDs `RFS-<plan>-C<nn>`). Plan 1 Task 1 puts the gate in place and
+gets Paul's review before any task writes a string. Copy voice: no em
+dashes; neutral names only (`shop`, `example.com`), never a tenant's.
+
+## The series
+
+| # | plan | produces | depends on |
+|---|---|---|---|
+| 1 | `2026-10-24-rootfs-apps-1-ship.md` | the copy gate; manifest `kind: "rootfs"`, `processes`, `hibernate`; validation; `carlos ship --rootfs` (OCI or directory to squashfs, locally); the ship door's kind-fixed refusal and rootfs upload cap; `carlos ship --check`; the framework `console` fill-in | `main` |
+| 2 | `2026-10-24-rootfs-apps-2-box-isolation.md` | staging the image; sweep coverage; the rootfs unit templates and the rootfs probe; the trampoline's rootfs launch shape; the control service's rootfs start path; the box's `rootfs_ready` report; the box-side placement refusal | Plan 1 merged |
+| 3 | `2026-10-24-rootfs-apps-3-lifecycle.md` | `Record.Runtime` and the route column; hibernation fixed at mint; always-on nested instances; workers; the `release` process at swap; `carlos run` for rootfs and `--version` | Plans 1 and 2 merged |
+| 4 | `2026-10-24-rootfs-apps-4-console-and-docs.md` | the console's rootfs placement checks and instance creation; the single-tenant deployment setting; the guide and contract docs; the sample Rails app; the dev-fleet end-to-end checklist (spec §6.1) | Plans 1 to 3 merged |
+| 5 | `2026-10-24-rootfs-apps-5-egress-allow.md` | the egress grant (console, signed, delivered), its rendering as `IPAddressAllow=` on the instance slice, hostname re-resolution | Plan 2 merged |
+| 6 | `2026-10-24-rootfs-apps-6-interactive.md` | `carlos run -it` and `carlos console`; the per-app allow; the CLI-console WebSocket, the tunnel leg, the box's pty cmd unit; audit and limits | Plans 2 and 3 merged |
+
+```
+main ─► Plan 1 ─► Plan 2 ─► Plan 3 ─► Plan 4 (dev-fleet e2e = spec §6 step 1)
+ ├─────► Plan 5
+ └─────────────► Plan 6
+```
+
+Plans 5 and 6 are needed for spec §6 step 4, not step 1. They can be built
+beside Plan 4.
+
+## The shared contract
+
+Every plan uses these names. A plan that finds one cannot work against the
+code stops and records the problem at the top of its own file under
+"Contract deviations" instead of choosing a different name silently.
+
+### Manifest (`internal/release/release.go`, `Manifest`)
+
+- `Kind` gains the value `"rootfs"`. `validateManifest` (release.go:332)
+ accepts it. An old binary's `validateManifest` rejects it, which is what
+ keeps an old box from staging a rootfs release (spec C2); Plan 1 pins
+ that with a test against the old rule.
+- Artifact key `"rootfs-linux-arm64"`, file `"rootfs.squashfs"`. Never
+ `"linux-arm64"` on a rootfs manifest.
+- New fields, additive to schema 1, `omitempty`, valid only when
+ `Kind == "rootfs"`:
+ ```go
+ Processes map[string]Process `json:"processes,omitempty"`
+ Hibernate bool `json:"hibernate,omitempty"` // opt-in; default always-on (R5)
+
+ type Process struct {
+ Command []string `json:"command"`
+ Env map[string]string `json:"env,omitempty"`
+ WorkingDir string `json:"working_dir,omitempty"`
+ TimeoutSec int `json:"timeout_sec,omitempty"` // release only
+ }
+ ```
+- Process names: `web` (exactly one), `release` (at most one), `console`
+ (at most one), workers matching `^[a-z][a-z0-9-]{0,15}$` (at most 4).
+ `release.ProcessWeb`, `ProcessRelease`, `ProcessConsole` are the
+ constants; `release.ValidWorkerName(string) bool` the predicate.
+- `release` timeout: default 600 s, maximum the `carlos run` maximum.
+ Health bound for rootfs `web`: 120 s.
+- Rootfs upload cap: 2 GiB for the rootfs part (`apiShipMaxBytesRootfs`).
+
+### Image
+
+- Format: squashfs, written by `carlos ship` in pure Go (no mksquashfs, no
+ Docker needed to ship). Plan 1 picks the writer and proves a round trip:
+ ownership, modes, symlinks, hardlinks, device-free, readable by the
+ kernel's squashfs on the box.
+- Input: an OCI layout directory, a `docker save`/`podman save` tarball,
+ or a plain directory. OCI is flattened locally (whiteouts honoured).
+- Mount points `carlos ship` adds if absent: `/data`, `/run/carlos`,
+ `/opt/carlos`, `/tmp`, `/dev`, `/proc`, `/sys`.
+- Architecture: arm64 only; anything else refuses at ship.
+
+### Instance record and route
+
+- `provision.Record` gains `Runtime string \`json:"runtime,omitempty"\``.
+ Values: `""` (binary, today) and `"rootfs-v1"`. Any other value refuses
+ before materialization or start.
+- A rootfs record always carries an explicit `Hibernate` (`*bool`), set at
+ mint from the creating release's manifest and never changed by ordinary
+ writers (spec §5.0).
+- `registry.Route` gains `Runtime string`, column `runtime TEXT NOT NULL
+ DEFAULT ''`, materialized from the record in the same transaction as
+ `hibernate`.
+
+### Environment the processes get
+
+- `CARLOS_SOCKET`: the socket `web` binds (the path today's binary gets as
+ `--socket`).
+- `CARLOS_DB`: the database path (today's `--db`).
+- Every rootfs process (web, workers, release, console, `carlos run`) gets
+ the same environment as `web`; workers no longer read
+ `<host>-sidecar.env`.
+
+### Units and roles (`internal/host/units/`, `internal/instancectl/names.go`)
+
+- Templates: `carlos-instance-rootfs@.service`,
+ `carlos-instance-rootfs-gen@.service`,
+ `carlos-instance-rootfs-worker@.service`,
+ `carlos-instance-rootfs-cmd@.service`,
+ `carlos-instance-rootfs-probe.service`.
+- They share one restated namespace block, byte for byte, the way the
+ binary templates do today (`TestInstanceTemplatesShareOneNamespace`).
+- New role: `instancectl.RoleWorker` (`"worker"`). The worker unit's
+ instance string encodes (instance ID, worker name); Plan 2 fixes the
+ encoding and its parser, `instancectl.WorkerInstance(iid, name string)
+ string` and `ParseWorkerInstance(s string) (iid, name string, ok bool)`.
+- The rootfs templates are chosen by `Route.Runtime`, never by what is
+ staged.
+
+### Box facts
+
+- `rootfs_ready` (bool) joins the box status snapshot beside the nested
+ layout readiness; true only after the rootfs probe passed on this boot.
+- Placement predicate, box side:
+ `host.RootfsPlacementAllowed(f RootfsBoxFacts, rt registry.Route) error`
+ (Plan 2). Allowed only when: the box is nested-ready and `rootfs_ready`;
+ and either `CARLOS_DEPLOYMENT_SINGLE_TENANT=1` or the box is a fleet box
+ (`CARLOS_REGION=fleet/<name>`) whose fleet's owning account is the row's
+ account. Pools never qualify in v1.
+- `CARLOS_DEPLOYMENT_SINGLE_TENANT` joins the host.env allowlist
+ (`internal/hostenv`) and the console's env; operator-only.
+
+### Egress (Plan 5)
+
+- Capability `egress`, a list of destinations (`10.0.0.0/16`,
+ `db.example.com`). The build declares it; the operator grants it per app;
+ the box renders the intersection. Valid only where
+ `RootfsPlacementAllowed`'s ownership half holds (the customer's own
+ boxes), for binary and rootfs nested rows alike (spec §5.9).
+
+## Repo rules every plan carries
+
+- Read AGENTS.md. Delegation tiers, the census gates and the claim
+ machinery are in force: new console tables go in `claimSQLTables`; new
+ workers and routes need `claim-routes.txt`, a claim fixture and a probe;
+ new bucket keys need a hostops classifier family; operator-only doors
+ join the svcroutes empty-role list; changes under console/plans/root
+ re-pin the plan14 overlay; never name a function `RecordKey`.
+- CI builds darwin and windows: linux-only syscalls need `//go:build linux`
+ and a `!linux` stub.
+- Tests run locally only for touched packages, with `-run`; CI runs the
+ full suite. New `internal/host` tests call `t.Parallel()` unless they set
+ env, cwd, umask, log output or package vars.
+- Production-wired witnesses: tests for refusals and launch go through
+ real wiring, not fixtures.
+- A box that has armed host lifecycle claim control refuses legacy launches
+ (`internal/instancectl/server.go:840`); each launch path states what it
+ does on such a box, as `carlos run` did.
+
+## Where the code differs from the spec
+
+Collected from the plans as they are written. Each is decided once, here.
+
+1. **#122 is fixed.** The spec's rev 1 to 3 said old versions are never
+ reaped; `sweepStagedVersions` (`internal/host/versionsweep.go:52`) runs
+ as step 0 of the root adopt pass. Plan 2 extends its keep set to rootfs
+ images.