Triage, 2026-09-05. Your narrowed ask is built — branch restart-selectors (6017525), pushed. CI is still running as I write this; read it before merging.
carlos restart --host connect.go.tito.io carlos restart --environment relay carlos restart --app titogo --dry-run # what the app-wide form would cycle
You were right that --environment is the better fit, and for the reason you gave: a bundle is delivered to an environment, so "restart what reads this bundle" is the intent after secrets set --environment relay. Both are in.
The shape, since it is the part worth checking
A narrowed request does not touch restart-gen.json. It goes to a sibling object, restart-scope.json, through a separate console door.
That is the safety property rather than tidiness. Boxes act on restart-gen.json's etag moving and never read its content — so a selector written into that object would be invisible to every box on today's binary, and each would read the movement the only way it knows how: cycle the whole app. The failure mode of getting this wrong is precisely the nineteen-route bounce you declined to run. Same hazard server-side: the existing /restart door reads no body, so a narrowed request sent there would be answered app-wide by the console.
So: an old box sees no movement on the object it watches and cycles nothing; an old console 404s and the CLI says so rather than retrying against the door that would widen; an unknown future selector kind is stepped over rather than widened. Never widened on ignorance is the rule throughout.
It is an append log rather than a generation because etag movement cannot carry which routes — two requests inside one 2s poll window would collapse, silently dropping a restart its caller was told had been requested.
Blast radius is now visible before you commit to it
--dry-run resolves the selector, prints the routes, writes nothing and bumps no serial. With no selector it answers the question you had and wouldn't run the verb to find out: what would --app titogo actually cycle. Non-dry narrowed runs name the addressed set too, including asleep — left as it is for hibernators, and carry the same "this machine's routes only" caveat your first fix introduced.
Hibernating instances are still never woken. --app alone is byte-for-byte what it was.
A selector matching nothing is refused, naming what the app does have; --host plus a mismatched --environment is refused rather than resolved, because one of the two is a typo. That is the same instinct as your original report: a step that silently does the wrong thing is worse than one that fails.
Not built, deliberately
Your (2) post-delivery hook and (3) delivered-generation readback. Both still open, and (3) is still the one that would give you verification instead of a reminder — it needs something on the app's side of the line, and it is the same missing piece #11 wants for an AWAKE column and #13 wants for a running-build signal. Three issues, one absent field. Worth deciding once.
One loose end from your own fix: secrets set's note still says carlos restart --app %s. That string could now point at --host. Not touched, because changing what those verbs print was out of scope here — but it is a one-line follow-up and it makes the pair symmetrical, which is how you said you'd reach for them.
Worth a human pass before this lands: the new user-facing strings have not been through copy-review (it needs an interactive session and this ran in the background). They follow existing in-repo phrasing, but this repo has a rule about that gate and I would rather say so than let it slide.