console: a static platform host is steered by default
Static is the one serving class every platform box materializes
unconditionally — no unit, no process, content read from the bucket
channel pointer, so a far edge serves it with no home box and no
backhaul. That half was already true and measured on flagship: every
armed edge served every static host, with a valid cert, including hosts
nobody had opted in.
DNS was the missing half. Without an opt-in a static host resolves to
one edge, so four healthy boxes held the site and every visitor was sent
to Ireland. Of 26 records on flagship exactly two were steered, both by
hand.
So the create door defaults kind:static records to steering "latency".
Server-side rather than in the CLI, so a console-created record gets it
too. Three conditions, each of them a rail that already exists
elsewhere:
- static only. A home-boxed route reaches a far edge by backhaul — a
different trade with a different cost, not one to make silently for
every app on a deployment.
- the platform namespace only. steeredHosts never builds a latency
record for a host outside the apps domain, so defaulting a custom
domain would write an opt-in no pass will honour (#306's shape). A
custom domain reaches the edges by its owner's DNS, and the attach
guidance already tells them to CNAME the steering hostname.
- the shared pool only, the same rail the PATCH door answers 409 for.
A default that wrote what the update door refuses would put an
opt-in in the record nobody could have asked for through the front
door.
A value at mint, never a converge: `carlos steering --app x --host y
off` is the escape hatch and nothing re-stamps an existing record. The
opt-in is inert until a deployment sets CARLOS_STEERING_ZONE_ID, so a
single-box deployment states the intent harmlessly and becomes HA the
day it arms a second edge.
Existing static records are deliberately untouched — converging them
would stamp over anyone's explicit "off". The three on flagship get a
one-off backfill by hand.
Docs say so, in the section a member would read to find out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3 files changed,
+177
−1
docs/site/cli.md+8 −0internal/console/apiinstances.go+44 −1internal/console/apiinstances_test.go+125 −0
diff --git a/docs/site/cli.md b/docs/site/cli.md| index 6749533fe..2e8ee9ec5 100644 |
| --- a/docs/site/cli.md |
| +++ b/docs/site/cli.md |
| @@ -321,6 +321,14 @@ has armed its Route53 steering converger. In v1 only instances on the shared |
| pool can be steered; a pool-scoped route is refused, and the refusal names |
| that as the reason. |
| +A **static site on a platform hostname is steered from the moment it is |
| +created** — you do not have to ask. Static is the one kind every edge can |
| +serve on its own, straight from the channel pointer, so there is no reason |
| +for one edge to answer for all of them. `off` turns it back off if you want |
| +a single answer, and nothing re-applies the default to a host that already |
| +exists. Custom domains are not steered for you, because their DNS is yours: |
| +point them at the steering hostname the app's Settings page names. |
| + |
| ### carlos routes |
| The app's routes on this deployment: where each one sends traffic, which |
diff --git a/internal/console/apiinstances.go b/internal/console/apiinstances.go| index b18c33f07..1eeadc26e 100644 |
| --- a/internal/console/apiinstances.go |
| +++ b/internal/console/apiinstances.go |
| @@ -558,7 +558,13 @@ func (s *Server) handleAPIInstanceCreate(w http.ResponseWriter, r *http.Request) |
| // membership: a label naming another claimed app in the account belongs |
| // to that app. Custom-domain manifests never enter apex space (ValidHost |
| // pinned the suffix above), so this check cannot touch them. |
| - if s.cfg.AppsDomain != "" && m.Domain == acct.Sqid+"."+s.cfg.AppsDomain { |
| + // |
| + // The same question answers a second one further down (the steering |
| + // default), so it is asked once, here, and named: is this manifest |
| + // pinned to the account's platform subdomain, or to a domain of the |
| + // app's own? |
| + platformNamespace := s.cfg.AppsDomain != "" && m.Domain == acct.Sqid+"."+s.cfg.AppsDomain |
| + if platformNamespace { |
| if label := strings.TrimSuffix(host, "."+m.Domain); label != app { |
| claimed, err := s.store.HasAppIn(acct.ID, label) |
| if err != nil { |
| @@ -652,6 +658,43 @@ func (s *Server) handleAPIInstanceCreate(w http.ResponseWriter, r *http.Request) |
| Addrs: req.Addrs, |
| Health: req.Health, |
| } |
| + // A static platform host is HA by default. Static is the one serving |
| + // class every platform box materializes unconditionally |
| + // (provision.everywhereClass): no unit, no process, content read from |
| + // the bucket channel pointer, so a far edge serves it with no home box |
| + // and no backhaul. The content half of that is already true — every |
| + // armed edge holds the site and its account wildcard cert. What was |
| + // missing is DNS: with no opt-in the host resolves to one edge, so |
| + // several healthy boxes hold the site and every visitor is sent to one |
| + // of them. |
| + // |
| + // Scoped to the platform namespace deliberately. internal/steering's |
| + // steeredHosts only ever builds latency records for hosts under the |
| + // apps domain, so defaulting a custom domain's record to "latency" |
| + // would write an opt-in no pass will ever honour (#306's shape); a |
| + // custom domain reaches the edges by its owner's own DNS, and the |
| + // console's attach guidance already tells them to CNAME the steering |
| + // hostname. |
| + // |
| + // Scoped to static deliberately too: a home-boxed route reaches a far |
| + // edge by backhaul, a different trade with a different cost, not one to |
| + // make silently for every app on the deployment. |
| + // |
| + // And it honours v1's pool rail, the same one the PATCH door enforces |
| + // with a 409 (steering needs the shared pool, because a pool's own |
| + // edges are not the deployment's). A default that quietly wrote what |
| + // the update door refuses would be the worse kind of inconsistency: the |
| + // record would carry an opt-in nobody could have asked for through the |
| + // front door, and the converger would skip it anyway. |
| + // |
| + // This is a value at MINT, never a converge: `carlos steering --app x |
| + // --host y off` is the escape hatch, and nothing re-stamps a record |
| + // that already exists. The opt-in is inert on a deployment that has not |
| + // set CARLOS_STEERING_ZONE_ID, so a single-box deployment states the |
| + // intent harmlessly and becomes HA the day it arms a second edge. |
| + if rec.Kind == "static" && platformNamespace && rec.Pool == "" { |
| + rec.Steering = "latency" |
| + } |
| // The shape rails, at the door — kind vocabulary, one upstream shape per |
| // record, addr syntax, and the combinations that would wedge a host (a |
| // static route with an upstream, a hibernating addr-backed one). The |
diff --git a/internal/console/apiinstances_test.go b/internal/console/apiinstances_test.go| index b9a5bd7ff..3edc08607 100644 |
| --- a/internal/console/apiinstances_test.go |
| +++ b/internal/console/apiinstances_test.go |
| @@ -2184,3 +2184,128 @@ func TestAPIInstanceCreateArmedPlacementGate(t *testing.T) { |
| t.Fatalf("create region se (no EIP) = %d %s, want 400", code, out) |
| } |
| } |
| + |
| +// A static site is HA by default. Static is the one serving class every |
| +// platform box materializes unconditionally (provision.everywhereClass): |
| +// no unit, no process, content read from the bucket channel pointer, so a |
| +// far edge serves it with no home box and no backhaul. The content half of |
| +// that was already true and measured — every flagship edge served every |
| +// static host with a valid cert. What was missing was DNS: without an |
| +// opt-in the host resolves to one edge, so four healthy boxes held the site |
| +// and every visitor went to one of them. |
| +// |
| +// The opt-in is inert until a deployment sets CARLOS_STEERING_ZONE_ID, so a |
| +// single-box deployment states the intent harmlessly and becomes HA the day |
| +// it arms a second edge. `carlos steering --app x --host y off` is the |
| +// escape hatch, and nothing here ever converges an existing record. |
| +func TestAPIInstanceCreateStaticSteersByDefault(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-inst-static-steer") |
| + fx.srv.cfg.AppsDomain = "oncarlos.test" |
| + if status, _, out := rawReq(t, http.MethodPut, enableURL(fx, "bab", "titogo"), member, `{}`); status != http.StatusOK { |
| + t.Fatalf("enable = %d %s", status, out) |
| + } |
| + host := "titogo." + fx.sqid + ".oncarlos.test" |
| + |
| + status, _, out := rawReq(t, http.MethodPost, instancesURL(fx, "bab", "titogo"), member, |
| + `{"host":"`+host+`","kind":"static","channel":"edge"}`) |
| + if status != http.StatusCreated { |
| + t.Fatalf("static apex create = %d %s, want 201", status, out) |
| + } |
| + stored, err := provision.GetRecord(context.Background(), fx.rel, fx.sqid, "titogo", host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if stored.Steering != "latency" { |
| + t.Fatalf("stored Steering = %q, want latency — a static platform host is HA by default", stored.Steering) |
| + } |
| + // Whatever the door writes has to survive ValidateShape, or the record |
| + // it just stored is one no box will ever materialize. |
| + if err := provision.ValidateShape(*stored); err != nil { |
| + t.Fatalf("the defaulted record does not validate: %v", err) |
| + } |
| +} |
| + |
| +// The default is scoped to the platform namespace, and deliberately so. The |
| +// steering converger only ever writes latency records for hosts under the |
| +// apps domain (internal/steering's steeredHosts), so defaulting a custom |
| +// domain's record to "latency" would write an opt-in that does nothing — |
| +// a claim in the record that no pass will honour, which is #306's shape. |
| +// A custom domain reaches the edges by its owner's own DNS, and the console |
| +// already tells them to CNAME the steering hostname. |
| +func TestAPIInstanceCreateStaticCustomDomainDoesNotSteer(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-inst-static-custom") |
| + fx.srv.cfg.AppsDomain = "oncarlos.test" |
| + // The operator-written manifest, pinned to the app's OWN domain rather |
| + // than the account's platform subdomain — the custom-domain shape. |
| + enableInstances(t, fx, "titogo", titoManifest()) |
| + host := "site.go.tito.io" |
| + |
| + status, _, out := rawReq(t, http.MethodPost, instancesURL(fx, "bab", "titogo"), member, |
| + `{"host":"`+host+`","region":"ie","kind":"static","channel":"edge"}`) |
| + if status != http.StatusCreated { |
| + t.Fatalf("custom-domain static create = %d %s, want 201", status, out) |
| + } |
| + stored, err := provision.GetRecord(context.Background(), fx.rel, fx.sqid, "titogo", host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if stored.Steering != "" { |
| + t.Fatalf("stored Steering = %q, want empty — the converger never steers a host outside the apps domain", stored.Steering) |
| + } |
| +} |
| + |
| +// Only static. A home-boxed instance route reaches a far edge by backhaul, |
| +// which is a different trade with a different cost, and widening this |
| +// default to every kind would make that trade silently for every app on the |
| +// deployment. `carlos steering` remains the door for those, per host. |
| +func TestAPIInstanceCreateNonStaticDoesNotSteer(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-inst-nonstatic-steer") |
| + fx.srv.cfg.AppsDomain = "oncarlos.test" |
| + if status, _, out := rawReq(t, http.MethodPut, enableURL(fx, "bab", "titogo"), member, `{}`); status != http.StatusOK { |
| + t.Fatalf("enable = %d %s", status, out) |
| + } |
| + host := "titogo." + fx.sqid + ".oncarlos.test" |
| + |
| + status, _, out := rawReq(t, http.MethodPost, instancesURL(fx, "bab", "titogo"), member, |
| + `{"host":"`+host+`","channel":"edge"}`) // no kind: the instance default |
| + if status != http.StatusCreated { |
| + t.Fatalf("instance create = %d %s, want 201", status, out) |
| + } |
| + stored, err := provision.GetRecord(context.Background(), fx.rel, fx.sqid, "titogo", host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if stored.Steering != "" { |
| + t.Fatalf("stored Steering = %q, want empty — only static is steered by default", stored.Steering) |
| + } |
| +} |
| + |
| +// v1's pool rail applies to the default exactly as it applies to the PATCH |
| +// door, which answers 409 for the same request. A default that wrote what |
| +// the update door refuses would put an opt-in in the record that nobody |
| +// could have asked for through the front door — and that the converger |
| +// skips regardless, since a pool's own edges are not the deployment's. |
| +func TestAPIInstanceCreateStaticInAPoolDoesNotSteer(t *testing.T) { |
| + fx, member := instanceFixture(t, "code-inst-static-pooled") |
| + fx.srv.cfg.AppsDomain = "oncarlos.test" |
| + // An operator-written manifest can pin BOTH the account's platform |
| + // subdomain and a pool; the member-facing enable door cannot. |
| + enableInstances(t, fx, "titogo", provision.Manifest{ |
| + Schema: 1, App: "titogo", Domain: fx.sqid + ".oncarlos.test", |
| + Pool: "tito", Channel: "stable", Seed: []string{"seed"}, |
| + }) |
| + host := "titogo." + fx.sqid + ".oncarlos.test" |
| + |
| + status, _, out := rawReq(t, http.MethodPost, instancesURL(fx, "bab", "titogo"), member, |
| + `{"host":"`+host+`","kind":"static","channel":"edge"}`) |
| + if status != http.StatusCreated { |
| + t.Fatalf("pooled static create = %d %s, want 201", status, out) |
| + } |
| + stored, err := provision.GetRecord(context.Background(), fx.rel, fx.sqid, "titogo", host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if stored.Steering != "" { |
| + t.Fatalf("stored Steering = %q, want empty — steering requires the shared pool", stored.Steering) |
| + } |
| +} |