carlos / platform Public
carlos canary — filling the reserved verb
Date: 2026-09-04 Issue: the reserved canary case in main.go:131 exits not implemented.
Why
CARLOS canaries instance apps today and has all year — go.tito.io follows canary/platform-adoption, pinned as a live production channel in internal/release/laddercheck_test.go:12. The machinery is all there:
- a route's channel lives on its instance record, per host, so two instances of one app on different channels stage different versions (
internal/host/agent.go:1549); canary/*routes converge on the 2s fast loop, not the 5-minute tick (internal/host/agent.go:1502);- promotion into a canary is unconditional on both the legacy ladder and an app-defined pipeline (
internal/release/ladder.go:84,internal/release/pipeline.go:328); VersionStageonly scans pipeline channels, so a build that has been on a canary still enters the pipeline as fresh — canarying costs no rung.
What is missing is sugar. Today a member types instances create + ship + promote and works out the hostname themselves. A session in September 2026 read carlos canary's not implemented and README's "canary hosts are minted for static apps only" and concluded the platform could not canary an instance app at all. Both statements are true and narrow; together they read as a blanket no. The verb closes that gap.
Surface
carlos canary [deploy] --app <a> [--name <n>] [--version <sha>]
[--kind binary|static] [--target <goos-goarch>] [--label <l>]
[--timeout 120s] <files-or-site-dir>
carlos canary ls --app <a>
carlos canary rm --app <a> --name <n>The bare form is deploy, the same way carlos instances has a bare form. Sub-verbs are positional words that never reach fs.Parse, matching runInstances (cli.go:269).
API mode only, like carlos deploy: an instance record is a console-authorized write, and this verb must complete the job with no box access (AGENTS.md, "every carlos command is designed for a user with no access to the underlying infra").
Name, channel and host
--name, else the current git branch, slugified to a single channel segment (internal/release'schannelSegRe:[a-zA-Z0-9._-]+, ≤64).- channel =
canary/<name>. - host =
<name>.<app>.<manifest domain>— exactly the scheme README already documents:<canary>.<app>.<account-sqid>.<apps-domain>.
A branch that slugifies to nothing, or a --name that is not a valid segment, is refused by name rather than silently repaired.
canary deploy
- Resolve session, account, app (deploy's rules;
requireDeployAPISession). - Resolve the name, build the channel, refuse an invalid one.
instancesEnable— idempotent, and the only source of the domain suffix.instanceList; then exactly one of: - no record on the canary host →instanceCreatewith the canary channel; - record exists on a different channel →instanceSetChannel; - record already on the canary channel → no write at all.runShipAPI(banner suppressed; this verb prints its own).promotetocanary/<name>— never refused, on any app, from any rung.watchVersionagainst the canary host only, never the app's stable host.
Steps 3–4 are what make it sugar rather than a new subsystem: every call already exists for carlos instances and carlos deploy.
canary ls
Instance records whose channel starts canary/, joined to the channel listing for the lease. Columns: name, host, channel, staged version, and what is left of the seven-day lease (release.CanaryTTL, refreshed on every re-promote). An expired canary is marked, because a canary that outlives its reason is the exact hazard the TTL exists to surface.
canary rm
Deletes the instance record for canary/<name>. It says plainly that the channel pointer itself remains and expires on its own — there is no pointer delete in the platform, and a command that implied otherwise would be lying.
Explicitly out of scope
Traffic splitting. This is a URL per canary, not a percentage of production traffic on one hostname. That would be real work in the edge — weighted routing, not sugar over existing calls — and nothing here pretends otherwise.
Tests
- name resolution:
--namewins; git branch is slugified; an empty or invalid result is refused by name. - host construction against a manifest domain.
- the create / re-channel / no-write table for step 4.
- lease arithmetic in
ls, including an expired canary and a pointer that cannot be read. rmtargets the record for the named canary and no other.- the docsite gate:
### carlos canarystill exists, its fenced examples run real verbs, and every/docslink resolves.