carlos / platform Public

docs: CI promotes through the console with a deploy credential (#229)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev e84365be739df797884ed9357d4ab043989c7cac parent 8ecd088
2 files changed, +30 −8
  • README.md +22 −6
  • docs/site/cli.md +8 −2
diff --git a/README.md b/README.md
index 22a5c3dca..4704ca9b9 100644
--- a/README.md
+++ b/README.md
@@ -167,12 +167,28 @@ refused** — the console names itself in the refusal, because moving `stable`
needs a passkey step-up only a browser can do. Finish it at the URL the
refusal prints.
-CI and operators keep the existing path unchanged: set
-`CARLOS_DEPLOYMENT_BUCKET` (or `_DIR`) and every verb goes straight at the
-bucket, exactly as before — logging in is for a human at a laptop, never a box
-or a pipeline. Note the modes carry different authority: a scoped
-`CARLOS_RELEASE_KEY` constrains the direct path only — in API mode it is
-ignored and your console membership (plus the rung gates) is what decides.
+**CI uses the console too.** Mint a deploy credential for the app, limited
+to the rungs CI may move, and give CI the secret and the console's address:
+
+ carlos services create --role deploy --app blog --rungs canary,edge ci
+ # in CI:
+ export CARLOS_SERVICE_TOKEN=... CARLOS_CONSOLE=https://console.example.com
+ carlos deploy --app blog --version "$GITHUB_SHA" --channel edge blog-linux-arm64
+
+`carlos deploy` ships, promotes and watches the app's hosts, the same as
+from a laptop. The first deploy of a new app still needs a person: a deploy
+credential cannot create the app or set up its first host. Every promote is
+audited in the console and reaches the boxes within seconds. A deploy
+credential can ship, and promote or roll back only its rungs. It cannot
+reach stable, hotfix, delete the app, or change retention or domains.
+
+**Direct bucket mode is for operators.** With `CARLOS_DEPLOYMENT_BUCKET`
+(or `_DIR`) set, every verb goes straight to the bucket and the console
+never sees it. Use it to set up a deployment before its console exists, to
+run one that has no console, to recover when the console is down, and for
+the platform's own releases. A scoped `CARLOS_RELEASE_KEY` limits that path
+only. The CLI refuses to run with both `CARLOS_SERVICE_TOKEN` and a bucket
+variable set.
**On a box, or in CI, don't log in.** There is no browser and no human, and a
long-lived token sitting on a box is a standing credential nobody is watching.
diff --git a/docs/site/cli.md b/docs/site/cli.md
index 115129a74..276b3b338 100644
--- a/docs/site/cli.md
+++ b/docs/site/cli.md
@@ -1416,16 +1416,22 @@ signup. Reach is fixed at mint, so adding you to another account later does
not widen it, and it has its own rate-limit budget.
```sh
-carlos services create --role publish --app hello ci-shipper
+carlos services create --role deploy --app hello --rungs canary,edge ci-shipper
```
-Pick the narrowest role that works. `publish` ships releases; `instance`
+Pick the narrowest role that works. `publish` ships releases and manages the app; `instance`
provisions an app's instances — create, delete, upstreams and moves, plus its
domains and logs — which is the role a self-serve signup path wants; `operate`
administers; and `admin` includes `store grant`, which mints a path-scoped IAM
user, so treat that one as a real handover. `--app` binds the credential to
one app, which also means it cannot create apps.
+`deploy` is for a CI pipeline: it ships, and promotes or rolls back only
+the rungs named with `--rungs` (`canary`, `edge`, `beta`; never `stable`).
+It must name one app and cannot hotfix. If the app has a pipeline, it may
+move `canary/<name>` channels only. CI reads it from
+`CARLOS_SERVICE_TOKEN` with `CARLOS_CONSOLE`.
+
One door stays shut to every role: `carlos instances enable`, which writes the
manifest bounding which domain, pool and regions an instance may be created
in — and, with `--private-storage`, whether each instance gets an object store