docs: fleet demo path — placement at create, registry-backed channels, customer root
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5 files changed,
+43
−50
infra/dev/region-pi.md+22 −30infra/modules/README.md+3 −4infra/modules/region-remote-host/README.md+3 −4infra/remote-host.md+9 −0scripts/seed-pi-demo.sh+6 −12
diff --git a/infra/dev/region-pi.md b/infra/dev/region-pi.md| index e32af707e..e33d00c83 100644 |
| --- a/infra/dev/region-pi.md |
| +++ b/infra/dev/region-pi.md |
| @@ -10,29 +10,27 @@ Graviton fleet runs. 32-bit resolves nothing. |
| Follow infra/remote-host.md with these dev-platform values: |
| -- Tofu: instantiate infra/modules/region-remote-host in the dev root |
| - (platform-infrastructure repo) as: |
| - module "region_pi" { |
| - source = "git::https://github.com/carlosframework/platform.git//infra/modules/region-remote-host" |
| - region_code = "pi" |
| - data_bucket_name = "carlos-dev-pi-640012953448" |
| - deployment_bucket_arn = "arn:aws:s3:::carlos-dev-640012953448" |
| - } |
| - Apply from the platform-infrastructure checkout (operator step — the |
| - root module is not in this repo). |
| -- /etc/carlos/credentials: the module's access key output (0600). |
| +- Tofu: the customer root lives in `paulca/pi-fleet` (private), consuming |
| + `infra/modules/region-remote-host` by git URL. Apply from that repo; secret |
| + via `tofu output -raw pi_secret_access_key` there. |
| +- /etc/carlos/credentials: the customer root's access key output (0600). |
| - /etc/carlos/host.env additions beyond the standard agent env: |
| AWS_SHARED_CREDENTIALS_FILE=/etc/carlos/credentials |
| - CARLOS_HIBERNATE_BUCKET=carlos-dev-pi-640012953448 |
| + CARLOS_HIBERNATE_BUCKET=pi-fleet-640012953448 |
| + CARLOS_FLEET=pi |
| + CARLOS_REGION=fleet/pi |
| + CARLOS_STATUS_PREFIX=fleets/pi/ |
| STATUS_BOX_REGION="Pi · demo" |
| - CARLOS_TUNNEL_URL=https://<dev tunnel hostname> |
| - CARLOS_TUNNEL_TOKEN=<from the edge's CARLOS_TUNNEL_CLIENTS entry> |
| - CARLOS_TUNNEL_NAME=pi-box |
| -- Dev edge host.env additions: |
| - CARLOS_TUNNEL_HOST=<dev tunnel hostname, DNS → dev edge> |
| - CARLOS_TUNNEL_CLIENTS=pi-box=<token> |
| - (Restart the dev edge to pick these up. Mint the token with |
| - `openssl rand -hex 32`; it lives only in these two env files.) |
| + CARLOS_TUNNEL_URL=https://tunnel.dev.carlosframework.com |
| + CARLOS_TUNNEL_TOKEN=<from carlos fleets add-box pi pi-box — shown once> |
| + CARLOS_TUNNEL_NAME=pi/pi-box |
| + `CARLOS_FLEET_BOX` is optional and defaults from `CARLOS_TUNNEL_NAME`'s |
| + label half. |
| +- Fleet registration: `carlos fleets create pi -bucket pi-fleet-640012953448` |
| + (idempotent on the console) and `carlos fleets add-box pi pi-box` mint the |
| + fleet and box token (token shown once; rotation is NOT revocation — a |
| + compromised box needs detach). The dev edge picks up channels within ~2s, |
| + no restart required. |
| - No live fallback: no other host gets IAM on the pi bucket. A dark Pi |
| shows UNREACHABLE in the fleet view and `carlos status -live`; instances |
| resume when it re-dials. This is the approved demo posture. |
| @@ -40,21 +38,15 @@ Follow infra/remote-host.md with these dev-platform values: |
| Seeding: scripts/seed-pi-demo.sh (batches of ≤10/day — console limiter; |
| re-running an already-seeded batch is safe but not free, since the limiter |
| counts attempts, not successes, so only re-run a batch that actually |
| -failed). Requires: |
| - |
| -- `CARLOS_PI_TUNNEL_NAME=pi-box` — the same channel name set as |
| - `CARLOS_TUNNEL_NAME` on the Pi and matched in the dev edge's |
| - `CARLOS_TUNNEL_CLIENTS=pi-box=<token>` entry above. |
| -- A carlos CLI build carrying PR #52's `apps create` verb — seeding is |
| - blocked until #52 merges and a release ships. |
| -- App hostnames on dev resolve as `<app>.dev.carlosframework.com` — that's |
| - what the script's echoed `<hostname-of-app>` means. |
| +failed). Requires a carlos CLI build carrying PR #52's `apps create` verb — |
| +seeding is blocked until #52 merges and a release ships. Apps are placed at |
| +create via `-place fleet/pi`. |
| Target: ≥5 resident + ≥50 parked. |
| ## Demo acceptance (run these, tick them) |
| -- [ ] Pi appears in `carlos status -live` with metrics + tunnel=connected. |
| +- [ ] Console fleet page `/a/<sqid>/fleets/pi` shows `pi-box` with metrics + tunnel connected. |
| - [ ] A resident instance serves through the dev edge over the channel. |
| - [ ] A parked instance wakes on first request through the channel. |
| - [ ] Pull Wi-Fi, restore it: next request succeeds after re-dial |
diff --git a/infra/modules/README.md b/infra/modules/README.md| index 4fecbcbda..5687b9b81 100644 |
| --- a/infra/modules/README.md |
| +++ b/infra/modules/README.md |
| @@ -80,14 +80,13 @@ wants one. |
| ## region-remote-host |
| -What an off-cloud CARLOS host (a Raspberry Pi today, tito's hq box later) |
| +What an off-cloud CARLOS host (customer fleets and tito's hq) |
| needs from AWS when there is no EC2 instance profile to lean on: a region |
| data bucket plus a scoped IAM user + access key, limited to exactly the |
| deployment bucket and this region's data bucket — the same |
| wrong-region-fails-at-IAM residency property `region-host` gives cloud |
| -hosts. Two consumers: the dev Raspberry Pi (`region_code = "pi"`) and |
| -tito's hq box (`region_code = "hq"`, later); the module is reusable as-is |
| -for both. |
| +hosts. Consumed by customer fleets (paulca/pi-fleet is the live example) and |
| +the platform's tito hq; the module is reusable as-is for both. |
| | Variable | Description | |
| |---|---| |
diff --git a/infra/modules/region-remote-host/README.md b/infra/modules/region-remote-host/README.md| index 4f07c0abc..e68f31059 100644 |
| --- a/infra/modules/region-remote-host/README.md |
| +++ b/infra/modules/region-remote-host/README.md |
| @@ -6,10 +6,9 @@ inline policy and access key, limited to exactly the deployment bucket and |
| this region's data bucket (wrong-region-fails-at-IAM, the same residency |
| property `region-host` gives cloud hosts). |
| -Two consumers: the dev Raspberry Pi (`region_code = "pi"`) landing in this |
| -PR, and tito's hq box (`region_code = "hq"`) in a later step of the |
| -remote-host plan. The module is reusable as-is for both — they differ only |
| -in account and variable values, not shape. |
| +Consumed by customer fleets (paulca/pi-fleet is the live example) and |
| +the platform's tito hq box. The module is reusable as-is for both — they |
| +differ only in account and variable values, not shape. |
| The S3 action list in the inline policy is not independently chosen: it is |
| copied verbatim from `infra/modules/region-host/main.tf`'s |
diff --git a/infra/remote-host.md b/infra/remote-host.md| index 7c02f1dc1..e2319dc43 100644 |
| --- a/infra/remote-host.md |
| +++ b/infra/remote-host.md |
| @@ -69,6 +69,15 @@ route first, then back it with the tunnel channel: |
| carlos add <h> |
| carlos route -host <h> -tunnel <name> |
| +## Fleet boxes (customer fleets) |
| + |
| +Fleet boxes use the same agent with the fleet env contract documented in |
| +`infra/dev/region-pi.md` (the worked example) and specified in |
| +`docs/superpowers/specs/2026-08-05-customer-fleets-design.md`. The `carlos add` |
| +and `carlos route -tunnel` ceremony documented above applies only to platform |
| +remote boxes (tito hq), NOT fleet boxes — fleet instances are placed at app |
| +create time and boxes auto-join fleet registries. |
| + |
| ## Security note |
| In `-dev` (HTTP) edge mode the registration bearer token travels in clear |
diff --git a/scripts/seed-pi-demo.sh b/scripts/seed-pi-demo.sh| index a46503622..47d1e12cb 100755 |
| --- a/scripts/seed-pi-demo.sh |
| +++ b/scripts/seed-pi-demo.sh |
| @@ -15,9 +15,9 @@ |
| # "that name is taken" (HTTP 409), and this script treats that refusal as |
| # success rather than a fresh error. |
| # |
| -# Requires a carlos CLI build carrying PR #52's `apps create` verb (API |
| -# mode ONLY — sign in first with `carlos auth login -console <url>`; direct |
| -# bucket mode has no claims table to write to and this verb refuses it). |
| +# Requires a carlos CLI build carrying PR #52's `apps create` verb with |
| +# `-place` flag (API mode ONLY — sign in first with `carlos auth login -console <url>`; |
| +# direct bucket mode has no claims table to write to and this verb refuses it). |
| # PR #52 is unmerged as of this writing (branch worktree-cli-app-create) — |
| # seeding cannot run until it lands. Note also: neither main nor PR #52 |
| # has an `apps list`/`ls` verb over the console API, so idempotence here is |
| @@ -26,14 +26,13 @@ set -euo pipefail |
| START=${1:?usage: seed-pi-demo.sh <start-index> <count>} |
| COUNT=${2:?usage: seed-pi-demo.sh <start-index> <count>} |
| -TUNNEL_NAME=${CARLOS_PI_TUNNEL_NAME:?set CARLOS_PI_TUNNEL_NAME (the Pi tunnel channel name)} |
| if (( COUNT > 10 )); then |
| echo "warning: console API limits app creation to 10/address/day — run this in batches of <=10" >&2 |
| fi |
| for i in $(seq "$START" $((START + COUNT - 1))); do |
| app=$(printf 'pi-demo-%03d' "$i") |
| - if out=$(carlos apps create -app "$app" 2>&1); then |
| + if out=$(carlos apps create -app "$app" -place fleet/pi 2>&1); then |
| echo "created: $app" |
| elif [[ "$out" == *"that name is taken"* ]]; then |
| echo "exists: $app" |
| @@ -45,10 +44,5 @@ for i in $(seq "$START" $((START + COUNT - 1))); do |
| done |
| echo |
| -echo "Next, on the DEV EDGE box, add the route then back it with the Pi channel" |
| -echo "(carlos route repoints an EXISTING route — it does not create one):" |
| -echo " carlos add <hostname-of-app>" |
| -echo " carlos route -host <hostname-of-app> -tunnel ${TUNNEL_NAME}" |
| -echo "and on the PI, add the matching local routes (carlos add / adopt as usual)." |
| -echo "First 5 apps stay resident (no hibernate flag); the rest get the" |
| -echo "-hibernate flag on their Pi-local routes so they park." |
| +echo "Apps are fleet-placed at create. Instances land on the fleet's boxes" |
| +echo "automatically once the Pi is joined (runbook: infra/dev/region-pi.md)." |