carlos / platform Public

docs: carlos system, the env contract, and the roll script's last two lines

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QCUCpfxbCyEAVQ2m51dH5
Paul Campbell pushed by paul@keymail.dev 0e5640a77d1016eb07dc9c294d9607af675cdfbc parent 3f38513
4 files changed, +54 −0
  • AGENTS.md +7 −0
  • docs/site/cli.md +26 −0
  • docs/superpowers/plans/2026-09-06-carlos-system-update-pinfra.md +17 −0
  • docs/superpowers/specs/2026-09-06-carlos-system-update-design.md +4 −0
diff --git a/AGENTS.md b/AGENTS.md
index f50e5801a..4b85e4bbc 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -278,6 +278,13 @@ anything.
PR #10). Hibernating instances are never woken. Static routes carry
`X-Carlos-Version` from the same roll (#112), so `carlos deploy -kind
static -host <site>` verifies static sites too.
+- `carlos-system-update.path` relays the agent's platform self-update request
+ to `carlos system apply` (root; spec 2026-09-06-carlos-system-update-design.md
+ §6). `carlos-system-recover.service` runs once per boot, before the edge,
+ and on the apply's failure. Both must be armed; the last run of
+ `update-platform-binary.sh` does it, and the hourly `carlos adopt` pass
+ installs and arms them if it did not. A box follows `CARLOS_SYSTEM_CHANNEL`
+ (canary|stable, default stable) in host.env.
- **`carlos restart --host` / `--environment` / `--dry-run`** (discussion
#16): the same verb narrowed to one route, or to everything reading one
config bundle — the shape that follows `carlos secrets set --environment
diff --git a/docs/site/cli.md b/docs/site/cli.md
index 3b017d0d6..9ae89d76a 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -929,6 +929,32 @@ fixture escaped. Run it before you ever point this binary at a real bucket.
Neither mode publishes anything: the public status page is written by each
box's own agent tick.
+### carlos system
+
+The platform's own release channel. `update` promotes a public release (or,
+with `--file`, a binary you built) to `canary` or `stable`; every box on that
+channel fetches it through its normal store, verifies it against the signed
+pointer, installs it, restarts its edge, and rolls back to the previous
+binary if the new one does not come up and stay up. `status` shows each box's
+platform, channel, running build, the channel's target, and the last apply
+outcome with its reason. `rollback` points a channel back at the release
+before this one, and every box on it rolls back the same way it rolled
+forward. All three talk to your console; you need to be an owner of the ops
+account.
+
+```sh
+carlos system update --channel canary
+carlos system status --channel canary
+carlos system update --channel stable --spread 10m
+carlos system rollback --channel stable
+```
+
+A box is put on canary with `CARLOS_SYSTEM_CHANNEL=canary` in its host.env.
+Boxes that have not reported a platform yet count as stable. `carlos system
+apply` is the root oneshot behind `carlos-system-update.service`; it is not
+for operators. The console is an app — update it with `carlos deploy --app
+console`.
+
## On the host
These are what systemd starts. You will rarely type them, but knowing what
diff --git a/docs/superpowers/plans/2026-09-06-carlos-system-update-pinfra.md b/docs/superpowers/plans/2026-09-06-carlos-system-update-pinfra.md
new file mode 100644
index 000000000..25d87c79c
--- /dev/null
+++ b/docs/superpowers/plans/2026-09-06-carlos-system-update-pinfra.md
@@ -0,0 +1,17 @@
+# update-platform-binary.sh: the last two lines it needs
+
+After the `carlos-restart.path` block, guarded the same way:
+
+ if [ -f /etc/systemd/system/carlos-system-update.path ]; then
+ systemctl enable --now carlos-system-update.path
+ systemctl enable carlos-system-recover.service
+ fi
+
+And to the post-conditions at the end:
+
+ systemctl is-active carlos-system-update.path
+ systemctl is-enabled carlos-system-recover.service
+
+Same change in amadan.net/tito/carlos-infrastructure/scripts/update-platform-binary.sh.
+After every deployment has been hand-rolled once onto a binary carrying the
+system-update spec, the script is retired (spec §10).
diff --git a/docs/superpowers/specs/2026-09-06-carlos-system-update-design.md b/docs/superpowers/specs/2026-09-06-carlos-system-update-design.md
index 447bd811b..b44999bc4 100644
--- a/docs/superpowers/specs/2026-09-06-carlos-system-update-design.md
+++ b/docs/superpowers/specs/2026-09-06-carlos-system-update-design.md
@@ -853,6 +853,10 @@ reach GitHub uses `--file`.
- A box that is asleep or dark during the promote rolls when it next
ticks. `status` shows it on the old version until then, which is the
truth.
+- A permanently failing recovery re-runs every minute (the agent re-touches
+ the marker while its request is unanswered) and never writes
+ `result.json`, so the fleet sees a stale result; that is the needs-a-human
+ state, and it is fleet-invisible.
---