carlos / platform Public

provision: serving classes — static and bake adopt on every edge

The multi-region ingress design's Part 3 table, stated in one place and
asked by both halves of the pass:

  Everywhere          static (bake-served) — the edge resolves the app's
                      channel pointer out of the bucket every box reads
  Everywhere, probed  addr routes whose upstreams are ALL publicly
                      dialable — each edge probes and dials them itself
  Home-boxed          exec, unit-owned, socket-activated, tunnel- and
                      fleet-backed — the process exists on ONE box, and a
                      request landing elsewhere backhauls to it

Only the REGION half of the scope gate widens, and only between platform
regions. Pool stays a hard tenancy boundary (it names the boxes a tenant
is allowed on, not a geography), and a fleet placement ("fleet/<name>")
is refused from both sides: the platform's boxes do not pick up a
customer fleet's routes, and a customer's box does not pick up the
platform's. Unit-owned routes stay home-boxed because adoption CYCLES
the named unit — that exclusion is what keeps a far box from ever
running systemctl for a unit name that means something else there.

The removal phase widens with it, through the same inScope with the
class read off the row: a far box that mints a static route under a
widened gate and cannot then see it as one would serve a retired site
forever. Anything the row cannot be positively classified from —
a name-based upstream, a private or loopback address, a row that has
grown Backing/Tunnel/Caps since it was minted — reads as home-boxed,
which is the direction a wrong answer has to fail in.

Single-box inertness: 'every box' is the one box, every record is in
scope for exactly the reason it was before, and the widened half of the
gate never fires. Nothing writes control/serial.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 15c36731370c934b81e03e1690c0a6fb43bb4aa8 parent 0193912
2 files changed, +382 −3
  • internal/provision/reconcile.go +169 −3
  • internal/provision/reconcile_test.go +213 −0
diff --git a/internal/provision/reconcile.go b/internal/provision/reconcile.go
index 71ee5a3b7..56b0b32c0 100644
--- a/internal/provision/reconcile.go
+++ b/internal/provision/reconcile.go
@@ -1,7 +1,9 @@
package provision
// Reconcile turns bucket instance records into registry routes, for THIS
-// agent's own region and pool set. Spec:
+// agent's own region and pool set — plus, since the multi-region ingress
+// design's Part 3, the serving classes every platform box materializes
+// wherever they are homed (see "serving classes" below). Spec:
// docs/superpowers/specs/2026-08-04-instance-provisioning-design.md §4.
//
// DNS seam (spec §10): multi-region deployments write the per-slug CNAME at
@@ -14,6 +16,7 @@ import (
"errors"
"fmt"
"log"
+ "net"
"os"
"slices"
"strings"
@@ -52,6 +55,160 @@ type ReconcileConfig struct {
// only within an account, never globally.
type acctApp struct{ acct, app string }
+// ---- serving classes -------------------------------------------------------
+//
+// Which boxes materialize a record. The multi-region ingress design's Part 3
+// table, verbatim, and the ONE place this pass states it:
+//
+// Everywhere static (bake-served): the edge resolves the app's
+// channel pointer and serves the shipped tar out of the
+// deployment store every box already reads, so no box
+// needs the home box to serve it.
+// Everywhere, probed addr routes whose upstreams are ALL publicly dialable:
+// each edge runs its own prober and dials the backends
+// directly, so per-edge live sets are per-edge by design.
+// Home-boxed exec, unit-owned, socket-activated, tunnel- and
+// fleet-backed: the process exists on exactly ONE box,
+// and a request that lands elsewhere backhauls to it
+// (internal/edge's hop). Unchanged invariant.
+//
+// Everything here decides SCOPE only. Nothing about the route a record mints
+// changes with the box that mints it — routeFor stays the one renderer, so a
+// far box's copy and the home box's copy are the same route, column for
+// column, including the record's own Region.
+//
+// Single-box inertness: with one box there is no far box, every record is in
+// scope for the same reason it was before, and the widened half of the gate
+// never fires.
+
+// everywhereClass reports whether rec's serving CLASS is one every platform
+// box materializes. It answers the class question only — the tenancy
+// questions (pool, fleet placement) are inScope's, below.
+//
+// Conservative by construction: anything this function cannot positively
+// classify as servable from a box that holds nothing but the bucket is
+// home-boxed. That is the direction a wrong answer must fail in — a route that
+// stays home-boxed is served exactly as it is today (and backhauled from
+// elsewhere), while one wrongly promoted to everywhere is a box proxying to an
+// upstream it cannot reach.
+func everywhereClass(rec Record) bool {
+ // Unit names the systemd unit that owns this route's process (issue
+ // #110); adoption CYCLES it. That unit exists on the home box, so a far
+ // box adopting the row would be stamping authority over a unit name that
+ // means something else there, or nothing at all.
+ if rec.Unit != "" {
+ return false
+ }
+ // Belt and braces: ValidateShape already refuses hibernation on both
+ // classes below (a static route has no process to wake, an addr-backed one
+ // has no socket), and this pass validates every record before the gate. A
+ // hibernating route needs the activator and the socket only its home box
+ // has, so if that door ever moved, this must not be the hole it opens.
+ if rec.HibernateOrDefault() {
+ return false
+ }
+ if rec.KindOrDefault() == "static" {
+ return true
+ }
+ ups := rec.AddrsOrDefault()
+ if len(ups) == 0 {
+ return false // socket-backed: the exec child lives on one box
+ }
+ for _, a := range ups {
+ if !publiclyDialable(a) {
+ return false
+ }
+ }
+ return true
+}
+
+// everywhereRoute is everywhereClass read off a registry ROW instead of a
+// record — the removal phase's half of the same question, asked when the
+// record is gone and the row is all that is left to ask.
+//
+// The two must agree, or the widened mint gate leaves immortal routes: a far
+// box that mints an everywhere-class route and then cannot see it as one is a
+// box serving a retired site forever. They are separate functions rather than
+// one over a shared shape because the row carries columns no record can state
+// (Backing, Tunnel, Caps) — and a row that has GROWN one of those since it was
+// minted is deliberately read as home-boxed here, which costs a stale row on a
+// far box and never a surprise deletion.
+func everywhereRoute(rt registry.Route) bool {
+ if rt.Unit != "" || rt.Hibernate || rt.Backing != "" || rt.Tunnel != "" || rt.Caps != "" {
+ return false
+ }
+ if rt.Kind == "static" {
+ return true
+ }
+ ups := rt.AddrList()
+ if len(ups) == 0 {
+ return false
+ }
+ for _, a := range ups {
+ if !publiclyDialable(a) {
+ return false
+ }
+ }
+ return true
+}
+
+// publiclyDialable reports whether one upstream is an address every edge in
+// the deployment can reach — the "publicly-reachable upstreams" half of the
+// spec's table.
+//
+// A NAME is not dialable for this purpose even if it resolves to a public
+// address today: validAddrsElement's doc comment already records why a name
+// cannot be judged at all here (it is free to move after the check), and the
+// legacy Addr column is full of names pointing at box-local backends. It stays
+// home-boxed, which is where those routes are serving from today.
+//
+// Loopback and link-local are the spec's own examples (another box's
+// 127.0.0.1 is not this box's). RFC1918 and ULA are refused for exactly the
+// same reason one layer out: a private address is private to ONE network, and
+// two boxes in two regions are two networks. IsGlobalUnicast covers the rest
+// (unspecified, multicast, loopback, link-local).
+func publiclyDialable(addr string) bool {
+ h, _, err := net.SplitHostPort(addr)
+ if err != nil {
+ return false
+ }
+ ip := net.ParseIP(h)
+ if ip == nil {
+ return false
+ }
+ return ip.IsGlobalUnicast() && !ip.IsPrivate()
+}
+
+// inScope answers "is this region/pool pair this pass's business" — the one
+// gate the record scan and the removal phase both ask, so a record this box
+// mints is a record this box can retire.
+//
+// Pool is compared FIRST and never widened. It is a TENANCY boundary, not a
+// geography: a pool names the boxes a tenant's routes are allowed to land on
+// (customer fleets, dedicated boxes), and "everywhere" in the spec's table
+// means every box that already serves this tenant, not every box in the world.
+//
+// Region equality is the unchanged home-box test. It is widened for the
+// everywhere classes, and only between PLATFORM regions: a fleet placement
+// ("fleet/<name>", registry.FleetPlacement) is the other tenancy boundary and
+// is refused from both sides — the platform's boxes do not pick up a
+// customer fleet's routes, and a customer's box does not pick up the
+// platform's.
+func (cfg ReconcileConfig) inScope(region, pool string, everywhere bool) bool {
+ if !poolServed(cfg.Pools, pool) {
+ return false
+ }
+ if region == cfg.Region {
+ return true
+ }
+ if !everywhere {
+ return false
+ }
+ _, recordOnFleet := registry.FleetPlacement(region)
+ _, boxOnFleet := registry.FleetPlacement(cfg.Region)
+ return !recordOnFleet && !boxOnFleet
+}
+
// routeFor is the one answer to "what route does this record describe". The
// mint path builds from it and the adoption match test (design §4.2)
// compares against it — one function, so a shape the reconciler would mint
@@ -418,7 +575,10 @@ func Reconcile(ctx context.Context, s release.Store, reg *registry.Registry, cfg
protect[host] = true
continue
}
- if rec.Region != cfg.Region || !poolServed(cfg.Pools, rec.Pool) {
+ // Scope. For a home-boxed record this is the region/pool test it
+ // always was; for a static or public-addr one the region half widens
+ // to every platform box in the deployment (everywhereClass, above).
+ if !cfg.inScope(rec.Region, rec.Pool, everywhereClass(rec)) {
continue // another agent's jurisdiction, not this pass's business
}
// Two accounts writing the same host is a collision this pass
@@ -640,6 +800,12 @@ func Reconcile(ctx context.Context, s release.Store, reg *registry.Registry, cfg
// absence from the record set this pass just read (the bucket's own
// decision to retire the instance, not a guess this pass made).
//
+ // Scope is asked through the SAME inScope as the record scan above, with
+ // the class read off the row (everywhereRoute) because the record whose
+ // class it was is exactly what is gone. Both halves must widen together:
+ // a far box that mints a static route under the widened mint gate and then
+ // judges it out of scope here would serve a retired site forever.
+ //
// What reaches reg.Delete here may carry Backing, Tunnel or Caps even
// though shapeMismatch refuses to ADOPT a row carrying any of them: those
// refusals cover the adoption instant only. `carlos route -backing/-unit/
@@ -650,7 +816,7 @@ func Reconcile(ctx context.Context, s release.Store, reg *registry.Registry, cfg
// the adoption guard is not mistaken for a standing guarantee.
if !readFailed {
for _, rt := range routes {
- if !rt.Provisioned || rt.Region != cfg.Region || !poolServed(cfg.Pools, rt.Pool) {
+ if !rt.Provisioned || !cfg.inScope(rt.Region, rt.Pool, everywhereRoute(rt)) {
continue
}
if _, ok := recs[rt.Host]; ok {
diff --git a/internal/provision/reconcile_test.go b/internal/provision/reconcile_test.go
index f9193a80d..9048d09d4 100644
--- a/internal/provision/reconcile_test.go
+++ b/internal/provision/reconcile_test.go
@@ -1826,6 +1826,219 @@ func TestReconcileRefusesAdoptInstanceRouteForStaticRecord(t *testing.T) {
}
}
+// ---- serving classes (multi-region ingress design, Part 3's table) ---------
+//
+// The same deployment seen from two PLATFORM boxes: the record's home region
+// ("ie") and another one ("us"), both serving the tito pool. Everything below
+// asks the same question of one record — "does the far box materialize it?" —
+// and the answer is the record's serving class.
+
+func homeBoxCfg() ReconcileConfig {
+ return ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data"}
+}
+
+func farBoxCfg() ReconcileConfig {
+ return ReconcileConfig{Region: "us", Pools: []string{"tito"}, DataDir: "/data"}
+}
+
+// titoRecord is the tito-app fixture every serving-class case below mutates:
+// homed in "ie", in the "tito" pool, on the stable channel.
+func titoRecord(sqid, host string) Record {
+ return Record{Schema: 1, Host: host + ".go.tito.io", Slug: host, App: "tito",
+ Account: sqid, Pool: "tito", Region: "ie", Channel: "stable", CreatedAt: time.Now()}
+}
+
+// A static (bake-served) record is materialized by EVERY platform box in the
+// deployment, not only its home: the edge resolves the app's channel pointer
+// out of the same bucket every box already reads, so no box needs the home box
+// to serve it. Spec Part 3, class "Everywhere".
+func TestReconcileStaticIsMaterializedOnEveryPlatformBox(t *testing.T) {
+ s, sqid := setup(t)
+ rec := titoRecord(sqid, "site")
+ rec.Kind = "static"
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ reg := regWith(t)
+ if err := Reconcile(ctx, s, reg, farBoxCfg()); err != nil {
+ t.Fatal(err)
+ }
+ got := mustGet(t, reg, "site.go.tito.io")
+ if !got.Provisioned || got.Kind != "static" {
+ t.Fatalf("far box did not materialize the static record: %+v", *got)
+ }
+ // The row still carries the RECORD's region — the record is the desired
+ // state and routeFor is its one renderer, so a far box's copy and the home
+ // box's copy are the same route, byte for byte.
+ if got.Region != "ie" {
+ t.Errorf("Region = %q, want the record's own %q", got.Region, "ie")
+ }
+ if got.Socket != "" || got.DBPath != "" || got.Hibernate {
+ t.Errorf("a static route grew a process shape on the far box: %+v", *got)
+ }
+}
+
+// An addr route whose upstreams are all publicly dialable is materialized
+// everywhere too — every edge probes and dials the same public backends. Spec
+// Part 3, class "Everywhere, probed".
+func TestReconcilePublicAddrIsMaterializedOnEveryPlatformBox(t *testing.T) {
+ s, sqid := setup(t)
+ one := titoRecord(sqid, "pub")
+ one.Kind = "service"
+ one.Addr = "203.0.113.10:443"
+ many := titoRecord(sqid, "pubs")
+ many.Kind = "service"
+ many.Addrs = []string{"203.0.113.11:443", "198.51.100.7:443"}
+ for _, rec := range []Record{one, many} {
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ }
+ reg := regWith(t)
+ if err := Reconcile(ctx, s, reg, farBoxCfg()); err != nil {
+ t.Fatal(err)
+ }
+ for _, host := range []string{"pub.go.tito.io", "pubs.go.tito.io"} {
+ if got := mustGet(t, reg, host); !got.Provisioned {
+ t.Errorf("%s: far box did not materialize a public-upstream record: %+v", host, *got)
+ }
+ }
+}
+
+// The home-boxed classes, unchanged: the process backing them exists on
+// exactly ONE box, so a far box must still refuse every one of them. This is
+// the refusal test the class change is only safe with.
+func TestReconcileHomeBoxedClassesStayHomeBoxed(t *testing.T) {
+ s, sqid := setup(t)
+ socketed := titoRecord(sqid, "exec") // no kind, no addr: the socket/exec shape
+ loopback := titoRecord(sqid, "loop")
+ loopback.Kind = "service"
+ loopback.Addr = "127.0.0.1:8090"
+ private := titoRecord(sqid, "priv")
+ private.Kind = "service"
+ private.Addrs = []string{"10.0.0.5:3000"}
+ unitOwned := titoRecord(sqid, "unit")
+ unitOwned.Addr = "203.0.113.20:8090"
+ unitOwned.Unit = "console.service"
+ named := titoRecord(sqid, "named")
+ named.Kind = "service"
+ named.Addr = "backend.example.com:443"
+ for _, rec := range []Record{socketed, loopback, private, unitOwned, named} {
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ }
+ reg := regWith(t)
+ if err := Reconcile(ctx, s, reg, farBoxCfg()); err != nil {
+ t.Fatal(err)
+ }
+ for _, host := range []string{"exec.go.tito.io", "loop.go.tito.io", "priv.go.tito.io",
+ "unit.go.tito.io", "named.go.tito.io"} {
+ if rt, err := reg.Get(host); err != nil || rt != nil {
+ t.Errorf("%s: a home-boxed record materialized on a foreign box: %+v %v", host, rt, err)
+ }
+ }
+ // ...and every one of them still materializes on its OWN box.
+ if err := Reconcile(ctx, s, reg, homeBoxCfg()); err != nil {
+ t.Fatal(err)
+ }
+ for _, host := range []string{"exec.go.tito.io", "loop.go.tito.io", "priv.go.tito.io",
+ "unit.go.tito.io", "named.go.tito.io"} {
+ mustGet(t, reg, host)
+ }
+}
+
+// Pool is a TENANCY boundary, not a geography: widening the region half of the
+// gate must not widen the pool half. A static record in the tito pool stays
+// off a box that does not serve that pool, however far it travels.
+func TestReconcileEverywhereClassStillRespectsThePool(t *testing.T) {
+ s, sqid := setup(t)
+ rec := titoRecord(sqid, "site")
+ rec.Kind = "static"
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ reg := regWith(t)
+ // A far box serving only the shared "" pool (nil Pools), and one serving
+ // somebody else's pool.
+ for _, cfg := range []ReconcileConfig{
+ {Region: "us", DataDir: "/data"},
+ {Region: "us", Pools: []string{"eleven"}, DataDir: "/data"},
+ } {
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if rt, err := reg.Get("site.go.tito.io"); err != nil || rt != nil {
+ t.Fatalf("pools %v: a static record crossed a pool boundary: %+v %v", cfg.Pools, rt, err)
+ }
+ }
+}
+
+// Fleet placement is the other tenancy boundary, and it is refused from BOTH
+// sides: a customer fleet's static site is not served by the platform's boxes,
+// and a customer's fleet box does not pick up the platform's static records.
+// (registry.FleetPlacement is the grammar — "fleet/<name>" in the Region.)
+func TestReconcileFleetPlacementIsNeverEverywhere(t *testing.T) {
+ s, sqid := setup(t)
+ onFleet := titoRecord(sqid, "pisite")
+ onFleet.Kind = "static"
+ onFleet.Region = "fleet/pi"
+ platform := titoRecord(sqid, "site")
+ platform.Kind = "static"
+ for _, rec := range []Record{onFleet, platform} {
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ }
+ reg := regWith(t)
+
+ // A platform box must not serve the fleet's site...
+ if err := Reconcile(ctx, s, reg, farBoxCfg()); err != nil {
+ t.Fatal(err)
+ }
+ if rt, err := reg.Get("pisite.go.tito.io"); err != nil || rt != nil {
+ t.Errorf("a fleet-placed static record materialized on a platform box: %+v %v", rt, err)
+ }
+ // ...and the fleet's own box must not pick up the platform's.
+ fleetReg := regWith(t)
+ if err := Reconcile(ctx, s, fleetReg, ReconcileConfig{Region: "fleet/pi", Pools: []string{"tito"}, DataDir: "/data"}); err != nil {
+ t.Fatal(err)
+ }
+ if rt, err := fleetReg.Get("site.go.tito.io"); err != nil || rt != nil {
+ t.Errorf("a platform static record materialized on a customer fleet box: %+v %v", rt, err)
+ }
+ mustGet(t, fleetReg, "pisite.go.tito.io") // its own placement, unchanged
+}
+
+// The other half of adopting everywhere: retiring everywhere. A far box that
+// mints an everywhere-class route from a record must remove it when that
+// record goes, or the widened mint gate leaves an immortal route serving a
+// retired site on every box but its home.
+func TestReconcileRemovesEverywhereRouteWhenItsRecordGoes(t *testing.T) {
+ s, sqid := setup(t)
+ rec := titoRecord(sqid, "site")
+ rec.Kind = "static"
+ if err := CreateRecord(ctx, s, rec); err != nil {
+ t.Fatal(err)
+ }
+ reg := regWith(t)
+ cfg := farBoxCfg()
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+ mustGet(t, reg, "site.go.tito.io")
+
+ if err := DeleteRecord(ctx, s, sqid, "tito", "site.go.tito.io"); err != nil {
+ t.Fatal(err)
+ }
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if rt, err := reg.Get("site.go.tito.io"); err != nil || rt != nil {
+ t.Errorf("a far box's everywhere-class route outlived its record: %+v %v", rt, err)
+ }
+}
+
// TestRouteForCarriesAddrsAndHealth is Task 3's mint-side proof: a record
// with an explicit multi-upstream Addrs set mints a route whose Addrs is the
// comma-joined string and whose Health carries a fully-materialized default