carlos / platform Public

help + two review notes: the 409 overlap, and why an empty channel is free

Written down because both are places a later reader would suspect a bug:

- enableUnavailable takes 409 for both the operator-pinned instance domain
  and a lost CAS race. Telling them apart needs the message, and reading
  English for control flow is exactly what the typed error exists to avoid.
  The door's own sentence is printed either way, and it says try again.

- moveGate leaves an EMPTY channel ungated. That is not a hole: the
  provisioning reconciler refuses to mint a route from a channel-less
  record, so nothing is serving — and naming one is the repair this door is
  for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 0f850e1fb8a10161d083b67824e8b146e423b659 parent 37ad475
3 files changed, +18 −1
  • apimode.go +9 −0
  • internal/console/channelmove.go +7 −0
  • main.go +2 −1
diff --git a/apimode.go b/apimode.go
index 87c1322ad..71ba7adfa 100644
--- a/apimode.go
+++ b/apimode.go
@@ -2074,6 +2074,15 @@ func (e *enableError) Error() string { return e.msg }
// instance domain an operator pinned elsewhere in store mode (409), which
// this door refuses to speak for. Both mean "ask the member for a hostname
// instead"; every other status is a real failure and must not be swallowed.
+//
+// The 409 is deliberately imprecise: the door also answers 409 for a lost
+// CAS race ("instance settings changed while this request was in flight"),
+// which is transient and would be better retried than fallen back from.
+// Telling the two apart needs the message, and reading English to make a
+// control-flow decision is the mistake this type exists to avoid — so both
+// take the fallback, and the caller PRINTS the door's own sentence, which
+// says "try again" in the transient case. A wrong branch here costs one
+// re-run, never a wrong record.
func enableUnavailable(err error) bool {
var ee *enableError
return errors.As(err, &ee) &&
diff --git a/internal/console/channelmove.go b/internal/console/channelmove.go
index e3fa90d60..d5cbffd15 100644
--- a/internal/console/channelmove.go
+++ b/internal/console/channelmove.go
@@ -76,6 +76,13 @@ func moveGate(p *release.Pipeline, from, to string) (passkey bool, approvals int
// unconditional passkey on the stable rung — in both directions,
// since moving OFF stable changes what stable's URL serves just as
// surely as moving onto it.
+ //
+ // An EMPTY channel reaches here and is free, which is right rather
+ // than an oversight: the provisioning reconciler refuses to mint a
+ // route from a record with no channel at all (reconcile.go's
+ // "skipping record: empty channel"), so such a record serves no
+ // traffic to protect — and naming one is precisely the repair this
+ // door exists for.
if rung, ok := release.Rung(ch); ok && rung == release.RungStable {
passkey = true
}
diff --git a/main.go b/main.go
index d6b3600c8..7d7cd5dbf 100644
--- a/main.go
+++ b/main.go
@@ -173,7 +173,8 @@ Your apps — ship and run software on a CARLOS deployment
env plain per-app config vars: set | unset | list
secrets sealed per-app secrets: set | unset | list | genkey
instances an app's instances: enable | create | list | delete |
- set-upstreams
+ set-upstreams | set-channel — set-channel is the only
+ door onto which channel a route follows
steering opt an instance into DNS latency steering, or back off —
-app <a> -host <h> latency|off (shared pool only, v1)
routes an app's routes and what each is bound to: upstream,