carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# `carlos canary` — filling the reserved verb
2
3**Date:** 2026-09-04
4**Issue:** the reserved `canary` case in `main.go:131` exits `not implemented`.
5
6## Why
7
8CARLOS canaries instance apps today and has all year — `go.tito.io` follows
9`canary/platform-adoption`, pinned as a live production channel in
10`internal/release/laddercheck_test.go:12`. The machinery is all there:
11
12- a route's channel lives on its instance record, per host, so two instances
13 of one app on different channels stage different versions
14 (`internal/host/agent.go:1549`);
15- `canary/*` routes converge on the 2s fast loop, not the 5-minute tick
16 (`internal/host/agent.go:1502`);
17- promotion **into** a canary is unconditional on both the legacy ladder and
18 an app-defined pipeline (`internal/release/ladder.go:84`,
19 `internal/release/pipeline.go:328`);
20- `VersionStage` only scans pipeline channels, so a build that has been on a
21 canary still enters the pipeline as fresh — canarying costs no rung.
22
23What is missing is *sugar*. Today a member types `instances create` +
24`ship` + `promote` and works out the hostname themselves. A session in
25September 2026 read `carlos canary`'s `not implemented` and README's
26"canary hosts are minted for static apps only" and concluded the platform
27could not canary an instance app at all. Both statements are true and
28narrow; together they read as a blanket no. The verb closes that gap.
29
30## Surface
31
32```
33carlos canary [deploy] --app <a> [--name <n>] [--version <sha>]
34 [--kind binary|static] [--target <goos-goarch>] [--label <l>]
35 [--timeout 120s] <files-or-site-dir>
36carlos canary ls --app <a>
37carlos canary rm --app <a> --name <n>
38```
39
40The bare form is `deploy`, the same way `carlos instances` has a bare form.
41Sub-verbs are positional words that never reach `fs.Parse`, matching
42`runInstances` (`cli.go:269`).
43
44API mode only, like `carlos deploy`: an instance record is a
45console-authorized write, and this verb must complete the job with no box
46access (AGENTS.md, "every `carlos` command is designed for a user with no
47access to the underlying infra").
48
49## Name, channel and host
50
51- `--name`, else the current git branch, slugified to a single channel
52 segment (`internal/release`'s `channelSegRe`: `[a-zA-Z0-9._-]+`, ≤64).
53- channel = `canary/<name>`.
54- host = `<name>.<app>.<manifest domain>` — exactly the scheme README
55 already documents: `<canary>.<app>.<account-sqid>.<apps-domain>`.
56
57A branch that slugifies to nothing, or a `--name` that is not a valid
58segment, is refused by name rather than silently repaired.
59
60## `canary deploy`
61
621. Resolve session, account, app (deploy's rules; `requireDeployAPISession`).
632. Resolve the name, build the channel, refuse an invalid one.
643. `instancesEnable` — idempotent, and the only source of the domain suffix.
654. `instanceList`; then exactly one of:
66 - no record on the canary host → `instanceCreate` with the canary channel;
67 - record exists on a different channel → `instanceSetChannel`;
68 - record already on the canary channel → no write at all.
695. `runShipAPI` (banner suppressed; this verb prints its own).
706. `promote` to `canary/<name>` — never refused, on any app, from any rung.
717. `watchVersion` against the canary host only, never the app's stable host.
72
73Steps 3–4 are what make it sugar rather than a new subsystem: every call
74already exists for `carlos instances` and `carlos deploy`.
75
76## `canary ls`
77
78Instance records whose channel starts `canary/`, joined to the channel
79listing for the lease. Columns: name, host, channel, staged version, and
80what is left of the seven-day lease (`release.CanaryTTL`, refreshed on every
81re-promote). An expired canary is marked, because a canary that outlives its
82reason is the exact hazard the TTL exists to surface.
83
84## `canary rm`
85
86Deletes the instance record for `canary/<name>`. It says plainly that the
87channel pointer itself remains and expires on its own — there is no pointer
88delete in the platform, and a command that implied otherwise would be
89lying.
90
91## Explicitly out of scope
92
93**Traffic splitting.** This is a URL per canary, not a percentage of
94production traffic on one hostname. That would be real work in the edge —
95weighted routing, not sugar over existing calls — and nothing here pretends
96otherwise.
97
98## Tests
99
100- name resolution: `--name` wins; git branch is slugified; an empty or
101 invalid result is refused by name.
102- host construction against a manifest domain.
103- the create / re-channel / no-write table for step 4.
104- lease arithmetic in `ls`, including an expired canary and a pointer that
105 cannot be read.
106- `rm` targets the record for the named canary and no other.
107- the docsite gate: `### carlos canary` still exists, its fenced examples
108 run real verbs, and every `/docs` link resolves.
109