Delivery to Tito is tracked in #201 (console back onto main: store boundary in Tito's own tofu, env incl. CARLOSSYSTEMOPERATORS=bill@keymail.dev, roll order). Code for the role is on branch system-operators-176.
A system operator role for rolling platform releases Feature Request open
What we want
A role that lets a person run carlos system update, status and rollback on a deployment, and nothing else. If updating from an uploaded build belongs with it, the role also covers PUT /api/cli/system/upload.
Tito wants bill@keymail.dev to roll releases onto its six edge boxes (IE, UK, SE, DE, US and IN) through the console at carlos.tito.io. Paul (paul@keymail.dev) owns Tito's ops account and should stay the owner.
The design is yours. We'd lean to a list or role of its own, shaped like CARLOS_STORE_OPERATORS but separate from it: that list lets its members read tenant bytes through store grants and antivirus scanning, and someone rolling releases shouldn't get that.
Why the current primitive doesn't fit (as of origin/main 0d2fdd44)
apiSystemOwner in internal/console/apisystemupdate.go admits only a person who owns the ops account. Everyone else gets a bare 404. The routes in internal/console/authcli.go (lines 851 to 855) register with an empty svcrole.Set{}, and that should stay: this is for people.
So Bill would have to own the ops account, and he can't without Paul giving it up. An invite only makes a member, and TransferOwnership in internal/console/ownership.go promotes the new owner and demotes the old one in one transaction. The schema allows several owners, but no door adds a second. Ownership would also give Bill member removal, ownership transfer and account deletion, none of which rolling needs.
Audit already copes with more than one roller. auditSystem records the actor against the ops account, and PromoteSystem signs the actor into the channel pointer's Audit list.
What Tito does meanwhile
Tito hand-rolls with its runbook (RUNBOOK-platform-roll.md): verify the signed release, upload it to the deployment bucket, then install it box by box over AWS Systems Manager. That needs AWS access to Tito's account, which we'd rather not hand out just to roll releases. The last hand roll put v0.21.0 on all six boxes on 2026-09-24, and every box has had carlos-system-update.path and carlos-system-recover.service armed since v0.20.0.
We haven't yet checked from the CLI that Tito's console serves carlos system.
Related
platform#333: boxes updating themselves from a channel subscription. With that and this role, Tito's runbook can go.
Built and merged to main as a7a2aaa (squash of branch system-operators-176).
What landed
CARLOS_SYSTEM_OPERATORS, a comma-separated address list on the console. A listed person can runcarlos system status,updatefrom the signed release feed, watch the job, androllbackwithout owning the ops account. Ops-account owners keep everything.- Ruling: local builds stay owner-only.
PUT /api/cli/system/upload, and an update naming an upload handle, answer a 403 to an operator: "shipping a local build needs an owner of the ops account. as a system operator you can update to a signed release". The reason is that the console signs an uploaded build with the deployment key and every box runs it, so an upload is arbitrary code on the fleet. An operator chooses which upstream-signed release runs. The handle check runs before a job starts, so an operator cannot consume an owner's parked build. - Everyone else still gets the bare 404. Service credentials stay shut out by
svcrole.Set{}/ closedDoors. The list match is relayOperator's (entry trimmed and lowercased, address compared exactly). ARole()store error admits nobody. - Audit: the operator's address is the actor in both the pointer's Audit list and the ops-account audit rows.
- Docs:
docs/site/cli.md"carlos system" andcarlos systemusage. Copy approved unchanged in copy review. - Accepted edge: an operator's rollback can point a channel back at an owner-uploaded build that was on it before.
Not done yet: getting it to Bill. Tito's console runs cf02a4d5-tito, 314 commits behind main. Moving it is #201, which needs a store boundary in Tito's own tofu first. That is production IAM in Tito's account and waits for Paul's approval there. Once the console runs a7a2aaa or later, set CARLOS_SYSTEM_OPERATORS=bill@keymail.dev and check carlos system status against carlos.tito.io as Bill.