package provision import ( "context" "encoding/json" "errors" "fmt" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) // A schema-1 record exactly as the flagship and tito buckets hold it today: // none of the shape fields exist. Every one must default to what reconcile // hardcoded before they were introduced — a non-nil Hibernate here, or a // Kind that does not fall back to "instance", rewrites the fleet on the // first pass after deploy. This is the additive wire-shape rule stated as a // test; do not relax it. func TestRecordWireShapeIsAdditive(t *testing.T) { const legacy = `{"schema":1,"host":"acme.go.tito.io","slug":"acme","app":"tito",` + `"account":"bab","pool":"tito","region":"ie","channel":"stable",` + `"created_at":"2026-08-04T09:00:00Z"}` var rec Record if err := json.Unmarshal([]byte(legacy), &rec); err != nil { t.Fatal(err) } if rec.Kind != "" { t.Errorf("Kind = %q, want empty", rec.Kind) } if rec.Addr != "" { t.Errorf("Addr = %q, want empty", rec.Addr) } if rec.Unit != "" { t.Errorf("Unit = %q, want empty", rec.Unit) } if rec.Environment != "" { t.Errorf("Environment = %q, want empty", rec.Environment) } if rec.StatusNamed != nil { t.Errorf("StatusNamed = %v, want nil — absent and false are different facts here too", rec.StatusNamed) } if rec.StatusNamedOrDefault() { t.Error("StatusNamedOrDefault() = true, want false — reconcile minted StatusNamed:false for these") } if rec.Hibernate != nil { t.Errorf("Hibernate = %v, want nil — absent and false are different facts here", rec.Hibernate) } if got := rec.KindOrDefault(); got != "instance" { t.Errorf("KindOrDefault() = %q, want instance", got) } if !rec.DerivesPaths() { t.Error("DerivesPaths() = false, want true — a legacy record is a socket-backed instance") } if !rec.HibernateOrDefault() { t.Error("HibernateOrDefault() = false, want true — reconcile mints Hibernate:true for these today") } } // The console rewrites records (plan 2's PUT). A rewrite must not sprout // keys the reader before it did not know, so every added field is // omitempty and a record carrying none of them marshals to exactly what it // marshalled to before. func TestRecordMarshalOmitsAbsentShapeFields(t *testing.T) { body, err := json.Marshal(Record{ Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bab", Pool: "tito", Region: "ie", Channel: "stable", }) if err != nil { t.Fatal(err) } for _, key := range []string{"kind", "addr", "unit", "hibernate", "environment", "status_named", "steering"} { if strings.Contains(string(body), `"`+key+`"`) { t.Errorf("marshalled record carries %q when it was never set: %s", key, body) } } } // TestRecordSteeringPrePlanFixtureParses is the plan's own wire-additivity // demand: a record HAND-TYPED as a binary that predates the steering field // would have written it — a current production-shaped record (schema-1 plus // the shape and multi-upstream fields, the vocabulary provision's own test // fixtures already exercise), verbatim bytes, never round-tripped through // this build's Record first. It must still parse, with Steering == "" — the // only value that reproduces what routeFor already mints for every record // that predates this plan: unsteered, today's static per-host answer. func TestRecordSteeringPrePlanFixtureParses(t *testing.T) { const preplan = `{"schema":1,"host":"ha.go.tito.io","slug":"ha","app":"tito",` + `"account":"bab","pool":"tito","region":"ie","channel":"stable",` + `"created_at":"2026-08-12T09:00:00Z","kind":"service",` + `"addrs":["127.0.0.1:7001","127.0.0.1:7002"],` + `"probe":{"path":"/up","status":200},"environment":"demo",` + `"status_named":true}` var rec Record if err := json.Unmarshal([]byte(preplan), &rec); err != nil { t.Fatal(err) } if rec.Steering != "" { t.Errorf("Steering = %q, want empty — a pre-plan record names no opinion", rec.Steering) } if !ValidSteering(rec.Steering) { t.Error("the zero value must itself be valid — every record ever written must pass this door unchanged") } // Confirm nothing ELSE about the record's own established shape was // disturbed by adding the new field. if rec.Host != "ha.go.tito.io" || rec.Pool != "tito" || len(rec.Addrs) != 2 || rec.Environment != "demo" { t.Fatalf("pre-plan fixture's own fields mis-parsed: %+v", rec) } // Round trip: re-marshaling must not sprout a "steering" key for a // record that never carried one. body, err := json.Marshal(rec) if err != nil { t.Fatal(err) } if strings.Contains(string(body), `"steering"`) { t.Errorf("re-marshaled pre-plan record carries steering: %s", body) } } func TestValidHost(t *testing.T) { cases := []struct { host, domain string want bool }{ {"acme.go.tito.io", "go.tito.io", true}, {"a.go.tito.io", "go.tito.io", true}, {"acme.go.tito.io", "other.io", false}, // wrong suffix {"go.tito.io", "go.tito.io", false}, // no label {"a.b.go.tito.io", "go.tito.io", false}, // two labels {"Acme.go.tito.io", "go.tito.io", false}, // uppercase refused, not folded {"-a.go.tito.io", "go.tito.io", false}, // leading hyphen {"a-.go.tito.io", "go.tito.io", false}, // trailing hyphen {"../x.go.tito.io", "go.tito.io", false}, // traversal shapes {"a/b.go.tito.io", "go.tito.io", false}, {"acme.go.tito.io", "", false}, // empty domain never validates {strings.Repeat("a", 64) + ".go.tito.io", "go.tito.io", false}, // label > 63 {strings.Repeat("a", 250) + ".go.tito.io", "go.tito.io", false}, // total > 253 } for _, c := range cases { if got := ValidHost(c.host, c.domain); got != c.want { t.Errorf("ValidHost(%q, %q) = %v, want %v", c.host, c.domain, got, c.want) } } } func TestValidHostShape(t *testing.T) { cases := []struct { host string want bool }{ {"acme.go.tito.io", true}, {"a", true}, {"a..b", false}, {"a/b", false}, {".a", false}, {strings.Repeat("a", 254), false}, {"Acme", false}, } for _, c := range cases { if got := ValidHostShape(c.host); got != c.want { t.Errorf("ValidHostShape(%q) = %v, want %v", c.host, got, c.want) } } } func TestValidRegion(t *testing.T) { cases := []struct { region string want bool }{ {"ie", true}, {"", true}, {"IE", false}, {"x/y", false}, {strings.Repeat("a", 17), false}, } for _, c := range cases { if got := ValidRegion(c.region); got != c.want { t.Errorf("ValidRegion(%q) = %v, want %v", c.region, got, c.want) } } } func TestValidPool(t *testing.T) { cases := []struct { pool string want bool }{ {"ie", true}, {"", true}, {"IE", false}, {"x/y", false}, {strings.Repeat("a", 17), false}, } for _, c := range cases { if got := ValidPool(c.pool); got != c.want { t.Errorf("ValidPool(%q) = %v, want %v", c.pool, got, c.want) } } } func TestRecordKeyRoundTrip(t *testing.T) { key := RecordKey("bad", "tito", "acme.go.tito.io") want := "apps/bad/tito/instances/acme.go.tito.io.json" if key != want { t.Fatalf("RecordKey = %q, want %q", key, want) } known := map[string]bool{"bad": true} acct, app, host, ok := ParseRecordKey(key, known) if !ok || acct != "bad" || app != "tito" || host != "acme.go.tito.io" { t.Fatalf("ParseRecordKey(%q) = (%q, %q, %q, %v), want (bad, tito, acme.go.tito.io, true)", key, acct, app, host, ok) } if _, _, _, ok := ParseRecordKey(key, map[string]bool{}); ok { t.Errorf("ParseRecordKey with unknown account should refuse, got ok=true") } if _, _, _, ok := ParseRecordKey(ManifestKey("bad", "tito"), known); ok { t.Errorf("ParseRecordKey on the manifest key should refuse, got ok=true") } if _, _, _, ok := ParseRecordKey("apps/bad/tito/channels/stable.json", known); ok { t.Errorf("ParseRecordKey on a channel pointer should refuse, got ok=true") } if _, _, _, ok := ParseRecordKey("apps/bad/tito/instances/sub/host.json", known); ok { t.Errorf("ParseRecordKey with a nested slash after instances/ should refuse, got ok=true") } } func TestCreateRecordCreateOnly(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{ Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bad", Channel: "stable", CreatedAt: time.Now().UTC(), } if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("first CreateRecord: %v", err) } err := CreateRecord(ctx, s, rec) if err == nil { t.Fatal("second CreateRecord: want error, got nil") } if !errors.Is(err, release.ErrCASConflict) { t.Errorf("second CreateRecord error = %v, want wrapping release.ErrCASConflict", err) } } // TestSetRecordRegion: an existing record's Region repoints in place — the // rest of the record (identity, CreatedAt/CreatedBy, Pool, Channel) is // untouched — and it can be called again to repoint a second time, unlike // CreateRecord's create-only contract. A host with no record answers // os.ErrNotExist verbatim. func TestSetRecordRegion(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{ Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bad", Pool: "tito", Region: "ie", Channel: "stable", CreatedAt: time.Now().UTC(), CreatedBy: "alice", } if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord: %v", err) } if err := SetRecordRegion(ctx, s, "bad", "tito", "acme.go.tito.io", "fleet/pi"); err != nil { t.Fatalf("SetRecordRegion: %v", err) } got, err := GetRecord(ctx, s, "bad", "tito", "acme.go.tito.io") if err != nil { t.Fatalf("GetRecord: %v", err) } if got.Region != "fleet/pi" { t.Fatalf("Region = %q, want fleet/pi", got.Region) } if got.Pool != rec.Pool || got.Channel != rec.Channel || got.CreatedBy != rec.CreatedBy || !got.CreatedAt.Equal(rec.CreatedAt) { t.Fatalf("SetRecordRegion touched more than Region: got %+v, want the rest unchanged from %+v", got, rec) } // A second repoint (e.g. clearing placement back to unconstrained) is not // a create-only conflict. if err := SetRecordRegion(ctx, s, "bad", "tito", "acme.go.tito.io", ""); err != nil { t.Fatalf("second SetRecordRegion: %v", err) } got, err = GetRecord(ctx, s, "bad", "tito", "acme.go.tito.io") if err != nil { t.Fatal(err) } if got.Region != "" { t.Fatalf("Region after clearing = %q, want empty", got.Region) } if err := SetRecordRegion(ctx, s, "bad", "tito", "missing.go.tito.io", "fleet/pi"); !errors.Is(err, os.ErrNotExist) { t.Fatalf("SetRecordRegion on a missing record: err = %v, want os.ErrNotExist", err) } } // TestUpdateRecordGuardedByETag: the write-back half of the console's PATCH // door. Unlike CreateRecord (create-only, a lost race is a permanent 409) // and unlike SetRecordRegion (an internal read-modify-write that retries // once), this write is guarded by the etag the CALLER read the record under // and a lost race SURFACES — the member's request read a record, decided // what the new upstream set should be from it, and a silent retry would // re-apply that decision on top of a body it never saw. func TestUpdateRecordGuardedByETag(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{ Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bad", Kind: "service", Addr: "127.0.0.1:7000", Channel: "stable", CreatedAt: time.Now().UTC(), CreatedBy: "alice", } if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord: %v", err) } got, etag, err := GetRecordETag(ctx, s, "bad", "tito", "acme.go.tito.io") if err != nil { t.Fatalf("GetRecordETag: %v", err) } if etag == "" { t.Fatal("GetRecordETag returned no etag — a guarded write-back has nothing to guard on") } if got.Addr != "127.0.0.1:7000" { t.Fatalf("GetRecordETag body = %+v", got) } got.Addr = "" got.Addrs = []string{"127.0.0.1:7001", "127.0.0.1:7002"} if err := UpdateRecord(ctx, s, *got, etag); err != nil { t.Fatalf("UpdateRecord: %v", err) } after, err := GetRecord(ctx, s, "bad", "tito", "acme.go.tito.io") if err != nil { t.Fatal(err) } if len(after.Addrs) != 2 || after.Addrs[0] != "127.0.0.1:7001" || after.Addrs[1] != "127.0.0.1:7002" { t.Fatalf("Addrs = %v, want the pair in order", after.Addrs) } if after.CreatedBy != rec.CreatedBy || !after.CreatedAt.Equal(rec.CreatedAt) || after.Channel != rec.Channel { t.Fatalf("UpdateRecord touched the record's identity/provenance: %+v", after) } // The same (now stale) etag again is a lost race, surfaced — never a // silent overwrite of whatever landed in between. if err := UpdateRecord(ctx, s, *got, etag); !errors.Is(err, release.ErrCASConflict) { t.Fatalf("UpdateRecord with a stale etag = %v, want release.ErrCASConflict", err) } } func TestManifestRoundTrip(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) m := Manifest{ Schema: 1, App: "tito", Domain: "go.tito.io", Pool: "tito", Channel: "stable", Regions: []string{"ie"}, Seed: []string{"titogo", "seed"}, } if err := PutManifest(ctx, s, "bad", "tito", m); err != nil { t.Fatalf("PutManifest: %v", err) } got, err := GetManifest(ctx, s, "bad", "tito") if err != nil { t.Fatalf("GetManifest: %v", err) } if got.App != m.App || got.Domain != m.Domain || got.Pool != m.Pool || got.Channel != m.Channel || len(got.Regions) != 1 || got.Regions[0] != "ie" || len(got.Seed) != 2 { t.Errorf("GetManifest round trip mismatch: got %+v, want %+v", got, m) } // Update: PutManifest twice must succeed (CAS upsert, not create-only). m.Channel = "canary/x" if err := PutManifest(ctx, s, "bad", "tito", m); err != nil { t.Fatalf("second PutManifest (update): %v", err) } got, err = GetManifest(ctx, s, "bad", "tito") if err != nil { t.Fatalf("GetManifest after update: %v", err) } if got.Channel != "canary/x" { t.Errorf("GetManifest after update: Channel = %q, want canary/x", got.Channel) } // Absence. if _, err := GetManifest(ctx, s, "bad", "missing"); !errors.Is(err, os.ErrNotExist) { t.Errorf("GetManifest on absence: err = %v, want os.ErrNotExist", err) } // A newer-schema body is refused on Get. newer := []byte(`{"schema":2,"app":"tito","domain":"go.tito.io"}`) if _, err := s.Put(ctx, ManifestKey("bad", "future"), newer, ""); err != nil { t.Fatalf("seeding schema-2 manifest: %v", err) } if _, err := GetManifest(ctx, s, "bad", "future"); err == nil { t.Error("GetManifest on schema 2: want error, got nil") } } // Astra plan 6 R3: the deploy watch must know when the list it was handed is // not the whole app, so a record that cannot be read is named, not dropped // silently. func TestListRecordsCompleteNamesTheSkippedHost(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{Schema: 1, Host: "a.example.com", Slug: "a", App: "app", Account: "acct", Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord: %v", err) } if _, err := s.Put(ctx, RecordKey("acct", "app", "b.example.com"), []byte("not json"), ""); err != nil { t.Fatal(err) } recs, truncated, skipped, err := ListRecordsComplete(ctx, s, "acct", "app", 10) if err != nil || truncated || len(recs) != 1 || len(skipped) != 1 || skipped[0] != "b.example.com" { t.Fatalf("recs %d truncated %v skipped %v err %v", len(recs), truncated, skipped, err) } // ListRecords is the same list without the skipped names. recs, truncated, err = ListRecords(ctx, s, "acct", "app", 10) if err != nil || truncated || len(recs) != 1 { t.Fatalf("ListRecords: recs %d truncated %v err %v", len(recs), truncated, err) } } func TestListRecords(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) hosts := []string{"c.go.tito.io", "a.go.tito.io", "b.go.tito.io"} for _, h := range hosts { rec := Record{Schema: 1, Host: h, Slug: h[:1], App: "tito", Account: "bad", Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord(%s): %v", h, err) } } // The manifest sits at the app prefix, not under instances/ — must never // be mistaken for a record. m := Manifest{Schema: 1, App: "tito", Domain: "go.tito.io"} if err := PutManifest(ctx, s, "bad", "tito", m); err != nil { t.Fatalf("PutManifest: %v", err) } recs, truncated, err := ListRecords(ctx, s, "bad", "tito", 10) if err != nil { t.Fatalf("ListRecords: %v", err) } if truncated { t.Error("ListRecords: truncated = true, want false") } if len(recs) != 3 { t.Fatalf("ListRecords: got %d records, want 3", len(recs)) } for i, want := range []string{"a.go.tito.io", "b.go.tito.io", "c.go.tito.io"} { if recs[i].Host != want { t.Errorf("ListRecords[%d].Host = %q, want %q (not sorted)", i, recs[i].Host, want) } } recs, truncated, err = ListRecords(ctx, s, "bad", "tito", 2) if err != nil { t.Fatalf("ListRecords with max=2: %v", err) } if !truncated { t.Error("ListRecords with max=2: truncated = false, want true") } if len(recs) != 2 { t.Fatalf("ListRecords with max=2: got %d records, want 2", len(recs)) } // A malformed record body is skipped, not fatal. badKey := RecordKey("bad", "tito", "z.go.tito.io") if _, err := s.Put(ctx, badKey, []byte("not json"), ""); err != nil { t.Fatalf("seeding malformed record: %v", err) } recs, _, err = ListRecords(ctx, s, "bad", "tito", 10) if err != nil { t.Fatalf("ListRecords with a malformed body: %v", err) } if len(recs) != 3 { t.Fatalf("ListRecords with a malformed body: got %d records, want 3 (malformed skipped)", len(recs)) } // max=0 and a negative max must both degrade to "list nothing", never // panic on the slice bound — a console handler hands this an // unvalidated int. for _, max := range []int{0, -1} { recs, truncated, err := ListRecords(ctx, s, "bad", "tito", max) if err != nil { t.Fatalf("ListRecords with max=%d: %v", max, err) } if len(recs) != 0 { t.Errorf("ListRecords with max=%d: got %d records, want 0", max, len(recs)) } if !truncated { t.Errorf("ListRecords with max=%d: truncated = false, want true (keys exist)", max) } } } func TestGetRecord(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bad", Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord: %v", err) } got, err := GetRecord(ctx, s, "bad", "tito", "acme.go.tito.io") if err != nil { t.Fatalf("GetRecord: %v", err) } if got.Host != rec.Host || got.App != rec.App || got.Account != rec.Account { t.Errorf("GetRecord = %+v, want %+v", got, rec) } if _, err := GetRecord(ctx, s, "bad", "tito", "missing.go.tito.io"); !errors.Is(err, os.ErrNotExist) { t.Errorf("GetRecord on absence: err = %v, want os.ErrNotExist", err) } // A body that disagrees with its own key path is refused, not guessed at. body, _ := json.Marshal(Record{Schema: 1, Host: "other.go.tito.io", App: "tito", Account: "bad"}) if _, err := s.Put(ctx, RecordKey("bad", "tito", "liar.go.tito.io"), body, ""); err != nil { t.Fatalf("seeding disagreeing record: %v", err) } if _, err := GetRecord(ctx, s, "bad", "tito", "liar.go.tito.io"); err == nil { t.Error("GetRecord on a disagreeing body: want error, got nil") } } func TestDeleteRecordIdempotent(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{Schema: 1, Host: "acme.go.tito.io", App: "tito", Account: "bad", Channel: "stable"} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatalf("CreateRecord: %v", err) } if err := DeleteRecord(ctx, s, "bad", "tito", "acme.go.tito.io"); err != nil { t.Fatalf("DeleteRecord: %v", err) } if _, err := GetRecord(ctx, s, "bad", "tito", "acme.go.tito.io"); !errors.Is(err, os.ErrNotExist) { t.Errorf("GetRecord after delete: err = %v, want os.ErrNotExist", err) } // Deleting again is not an error (Store's idempotent-delete contract). if err := DeleteRecord(ctx, s, "bad", "tito", "acme.go.tito.io"); err != nil { t.Errorf("second DeleteRecord: %v, want nil (idempotent)", err) } } func TestDefaults(t *testing.T) { if got := DefaultSocket("h"); got != "/run/carlos/h.sock" { t.Errorf("DefaultSocket(%q) = %q, want /run/carlos/h.sock", "h", got) } if got := DefaultDBPath("/data", "h"); got != "/data/h.db" { t.Errorf("DefaultDBPath(%q, %q) = %q, want /data/h.db", "/data", "h", got) } // CARLOS_DATA_DIR comes from an env var an operator types; a trailing // slash is the ordinary way to type a directory and must not produce // "/data//h.db" — a second path for the same database, which is how a // route and litestream's box-wide config come to disagree about which // file they are replicating. if got := DefaultDBPath("/data/", "h"); got != "/data/h.db" { t.Errorf("DefaultDBPath(%q, %q) = %q, want /data/h.db", "/data/", "h", got) } } // TestPutManifestIfMatch pins the guarded write's two properties: etag "" // is CREATE-ONLY (a manifest landing between a caller's read-nothing and // its write surfaces as the conflict it is, never an overwrite), and a // stale etag — the caller's read racing somebody else's update — is refused // the same way. This is what makes the console enable door's domain check // honest: PutManifest's upsert re-reads inside and the last writer wins, // which is exactly the wrong property when the write was authorized by // what the FIRST read said. func TestPutManifestIfMatch(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) m := Manifest{Schema: 1, App: "tito", Domain: "go.tito.io"} if err := PutManifestIfMatch(ctx, s, "bad", "tito", m, ""); err != nil { t.Fatalf("create-only put: %v", err) } if err := PutManifestIfMatch(ctx, s, "bad", "tito", m, ""); !errors.Is(err, release.ErrCASConflict) { t.Fatalf("second create-only put: err = %v, want ErrCASConflict", err) } got, etag, err := GetManifestETag(ctx, s, "bad", "tito") if err != nil || etag == "" { t.Fatalf("GetManifestETag: %+v %q %v, want a manifest with an etag", got, etag, err) } got.Channel = "beta" if err := PutManifestIfMatch(ctx, s, "bad", "tito", *got, etag); err != nil { t.Fatalf("guarded update: %v", err) } // The first read's etag is now stale; a write still guarded by it must // lose, not overwrite the update that beat it. got.Channel = "edge" if err := PutManifestIfMatch(ctx, s, "bad", "tito", *got, etag); !errors.Is(err, release.ErrCASConflict) { t.Fatalf("stale-etag put: err = %v, want ErrCASConflict", err) } final, err := GetManifest(ctx, s, "bad", "tito") if err != nil || final.Channel != "beta" { t.Fatalf("manifest after stale write = %+v %v, want the guarded update's beta", final, err) } } func TestValidKind(t *testing.T) { for _, k := range []string{"", "instance", "service", "static"} { if !ValidKind(k) { t.Errorf("ValidKind(%q) = false, want true", k) } } // "status" is the platform's own status-page class, not an app's. for _, k := range []string{"status", "Instance", "unti", "instance "} { if ValidKind(k) { t.Errorf("ValidKind(%q) = true, want false", k) } } } func TestValidSteering(t *testing.T) { for _, s := range []string{"", "latency"} { if !ValidSteering(s) { t.Errorf("ValidSteering(%q) = false, want true", s) } } for _, s := range []string{"Latency", "geo", "LATENCY", " latency", "latency "} { if ValidSteering(s) { t.Errorf("ValidSteering(%q) = true, want false", s) } } } func TestValidateShape(t *testing.T) { yes := true base := Record{Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "tito", Account: "bab", Channel: "stable"} with := func(f func(*Record)) Record { r := base f(&r) return r } ok := []struct { name string rec Record }{ {"legacy instance", base}, {"addr tenant", with(func(r *Record) { r.Addr = "127.0.0.1:8090" })}, {"unit-owned tenant", with(func(r *Record) { r.Addr = "127.0.0.1:8090"; r.Unit = "console.service" })}, {"service with addr", with(func(r *Record) { r.Kind = "service"; r.Addr = "127.0.0.1:8090" })}, {"static", with(func(r *Record) { r.Kind = "static" })}, {"explicitly non-hibernating instance", with(func(r *Record) { r.Hibernate = new(bool) })}, {"steered instance", with(func(r *Record) { r.Steering = "latency" })}, } for _, c := range ok { if err := ValidateShape(c.rec); err != nil { t.Errorf("%s: ValidateShape = %v, want nil", c.name, err) } } bad := []struct { name string rec Record }{ {"bogus kind", with(func(r *Record) { r.Kind = "unti" })}, {"status kind", with(func(r *Record) { r.Kind = "status" })}, {"addr without port", with(func(r *Record) { r.Addr = "127.0.0.1" })}, {"addr with garbage port", with(func(r *Record) { r.Addr = "127.0.0.1:abc" })}, {"unit typo", with(func(r *Record) { r.Unit = "console" })}, {"bad environment name", with(func(r *Record) { r.Environment = "Demo" })}, {"bad steering value", with(func(r *Record) { r.Steering = "geo" })}, {"service with no addr", with(func(r *Record) { r.Kind = "service" })}, {"static with addr", with(func(r *Record) { r.Kind = "static"; r.Addr = "127.0.0.1:8090" })}, {"static with unit", with(func(r *Record) { r.Kind = "static"; r.Unit = "console.service" })}, {"static hibernating", with(func(r *Record) { r.Kind = "static"; r.Hibernate = &yes })}, // The same wedge from the other side: edge's activator gate is // `Kind == "instance" && Hibernate`, which Addr does not exclude, so // this route would be armed for a wake it has no socket for. {"addr hibernating", with(func(r *Record) { r.Addr = "127.0.0.1:8090"; r.Hibernate = &yes })}, {"addr service hibernating", with(func(r *Record) { r.Kind = "service" r.Addr = "127.0.0.1:8090" r.Hibernate = &yes })}, } for _, c := range bad { if err := ValidateShape(c.rec); err == nil { t.Errorf("%s: ValidateShape = nil, want a refusal", c.name) } } } // Design §3.2: the suffix pin stays exactly where the traversal danger is, // and nowhere else. func TestValidRecordHostFollowsPathDerivation(t *testing.T) { socketBacked := Record{Host: "acme.go.tito.io"} if !ValidRecordHost(socketBacked, "go.tito.io") { t.Error("a socket-backed instance under its own domain was refused") } offDomain := Record{Host: "carloku.com"} if ValidRecordHost(offDomain, "go.tito.io") { t.Error("a socket-backed instance escaped its domain suffix — that is the traversal rail") } static := Record{Host: "carloku.com", Kind: "static"} if !ValidRecordHost(static, "go.tito.io") { t.Error("a static apex was refused; it derives no path and needs shape validation only") } addrBacked := Record{Host: "console.platform.tito.io", Addr: "127.0.0.1:8090"} if !ValidRecordHost(addrBacked, "go.tito.io") { t.Error("an addr-backed tenant off-domain was refused; it derives no path") } hostile := Record{Host: "../../etc/passwd", Kind: "static"} if ValidRecordHost(hostile, "go.tito.io") { t.Error("a hostile host passed the shape check") } } func TestAddrsOrDefaultFoldsLegacyAddr(t *testing.T) { one := Record{Addr: "10.0.0.1:3000"} if got := one.AddrsOrDefault(); len(got) != 1 || got[0] != "10.0.0.1:3000" { t.Fatalf("legacy fold = %v", got) } many := Record{Addrs: []string{"10.0.0.1:3000", "10.0.0.2:3000"}} if got := many.AddrsOrDefault(); len(got) != 2 { t.Fatalf("addrs = %v", got) } if got := (Record{}).AddrsOrDefault(); got != nil { t.Fatalf("empty = %v, want nil", got) } } func TestHealthDefaults(t *testing.T) { h := Health{}.WithDefaults() if h.Path != "/" || h.Status != 200 || h.IntervalMS != 2000 || h.FailAfter != 3 || h.RiseAfter != 2 { t.Fatalf("defaults = %+v", h) } // Multi-addr with no explicit health implies default probing. if (Record{Addrs: []string{"a:1", "b:1"}}).HealthOrDefault() == nil { t.Fatal("multi-addr record must default to probed") } // Single legacy addr with no health stays unprobed (today's behavior). if (Record{Addr: "a:1"}).HealthOrDefault() != nil { t.Fatal("legacy single-addr record must stay unprobed") } } func TestValidateShapeMultiAddr(t *testing.T) { base := Record{Host: "x", Kind: "service"} ok := base ok.Addrs = []string{"10.0.0.1:3000", "10.0.0.2:3000"} if err := ValidateShape(ok); err != nil { t.Fatalf("valid multi-addr rejected: %v", err) } bad := base bad.Addrs = []string{"10.0.0.1:3000", "not-an-addr"} if err := ValidateShape(bad); err == nil { t.Fatal("bad element accepted") } both := base both.Addr, both.Addrs = "10.0.0.1:3000", []string{"10.0.0.2:3000"} if err := ValidateShape(both); err == nil { t.Fatal("addr AND addrs together must be rejected — one shape per record") } hib := true sleepy := base sleepy.Addrs, sleepy.Hibernate = []string{"10.0.0.1:3000"}, &hib if err := ValidateShape(sleepy); err == nil { t.Fatal("addrs with hibernate must be rejected (wake-safety invariant)") } dup := base dup.Addrs = []string{"10.0.0.1:3000", "10.0.0.1:3000"} if err := ValidateShape(dup); err == nil { t.Fatal("duplicate upstream must be rejected") } } // TestValidateShapeRefusesLinkLocalUpstream (ruling 2026-08-12): an upstream // is an address the EDGE connects to on a caller's behalf, so 169.254.0.0/16 // — the cloud instance metadata service and its neighbours — is refused at // the same door the port range is, in both the legacy Addr and the Addrs // spelling. // // The rail is deliberately MINIMAL: loopback and RFC1918 stay allowed, // because that is where every legitimate addr-backed route on the fleet // points today (127.0.0.1:8090 consoles, private-subnet tenants). Widening it // is a separate ruling, not a tidy-up. func TestValidateShapeRefusesLinkLocalUpstream(t *testing.T) { for _, addr := range []string{"169.254.169.254:80", "169.254.0.1:8080"} { if err := ValidateShape(Record{Host: "x", Kind: "service", Addr: addr}); err == nil { t.Errorf("legacy addr %s accepted, want it refused as link-local", addr) } else if !strings.Contains(err.Error(), "link-local") { t.Errorf("refusal for %s = %v, want it to say link-local", addr, err) } if err := ValidateShape(Record{Host: "x", Kind: "service", Addrs: []string{"10.0.0.1:3000", addr}}); err == nil { t.Errorf("addrs element %s accepted, want it refused as link-local", addr) } } // The allowed neighbours, stated so a later widening has to break a test // rather than slip through: these are live production shapes. for _, addr := range []string{"127.0.0.1:8090", "10.0.0.1:3000", "192.168.1.5:8080", "172.16.0.9:443", "169.254.169.254.example.com:80", "backend.internal:8080"} { if err := ValidateShape(Record{Host: "x", Kind: "service", Addr: addr}); err != nil { t.Errorf("addr %s refused (%v), want it allowed — this rail is metadata-service only", addr, err) } } } // TestAddrsElementsMustBeIPLiterals closes the hole the link-local rail had // while it only ran on a parsed IP: a NAME skipped the check entirely, so // "md.attacker.example:80" resolving to the metadata service walked through // the door the rail exists to guard. The new Addrs set therefore takes IP // literals only — the platform dials these addresses itself, and a name is // free to resolve somewhere else after the door said yes. // // The rule is scoped to Addrs BY DESIGN (wire-compat): no record in any // bucket carries Addrs yet, while legacy Addr records naming a hostname do // exist, and those must keep validating. That asymmetry is pinned below. func TestAddrsElementsMustBeIPLiterals(t *testing.T) { svc := func(addrs ...string) Record { return Record{Host: "x", Kind: "service", Addrs: addrs} } for _, addr := range []string{"md.attacker.example:80", "backend.internal:8080", "localhost:8090"} { if err := ValidateShape(svc("10.0.0.1:3000", addr)); err == nil { t.Errorf("addrs element %s accepted, want it refused as a name", addr) } else if !strings.Contains(err.Error(), "names a host") { t.Errorf("refusal for %s = %v, want it to say the element names a host", addr, err) } } // Link-local in the OTHER family: the same SSRF, IPv6 clothes. v4 // link-local is covered by TestValidateShapeRefusesLinkLocalUpstream. for _, addr := range []string{"[fe80::1]:80", "[fe80::a00:27ff:fe4e:66a1]:8080", "[ff02::1]:80"} { if err := ValidateShape(svc(addr)); err == nil { t.Errorf("addrs element %s accepted, want it refused as link-local", addr) } else if !strings.Contains(err.Error(), "link-local") { t.Errorf("refusal for %s = %v, want it to say link-local", addr, err) } } // Loopback and RFC1918 literals stay allowed in the Addrs spelling too — // the rail closed the name hole, it did not narrow what an upstream may be. for _, addr := range []string{"127.0.0.1:8090", "10.0.0.1:3000", "192.168.1.5:8080", "172.16.0.9:443", "[::1]:8090"} { if err := ValidateShape(svc(addr)); err != nil { t.Errorf("addrs element %s refused (%v), want it allowed", addr, err) } } // The wire-compat half, stated explicitly: a legacy single Addr naming a // hostname keeps validating. Records like this are in production buckets; // tightening this line stops them materializing. if err := ValidateShape(Record{Host: "x", Kind: "service", Addr: "backend.internal:8080"}); err != nil { t.Errorf("legacy addr with a hostname refused (%v) — records in live buckets carry that shape", err) } } // TestValidateShapeCapsUpstreams: the prober walks a host's upstreams // sequentially per tick and the design is sized for a handful of backends per // route, so the set has an upper bound at the door. func TestValidateShapeCapsUpstreams(t *testing.T) { addrs := func(n int) []string { out := make([]string, n) for i := range out { out[i] = fmt.Sprintf("10.0.%d.%d:3000", i/250, i%250+1) } return out } if err := ValidateShape(Record{Host: "x", Kind: "service", Addrs: addrs(MaxUpstreams)}); err != nil { t.Errorf("%d upstreams refused (%v), want the cap itself allowed", MaxUpstreams, err) } err := ValidateShape(Record{Host: "x", Kind: "service", Addrs: addrs(MaxUpstreams + 1)}) if err == nil { t.Fatalf("%d upstreams accepted, want a refusal", MaxUpstreams+1) } if !strings.Contains(err.Error(), fmt.Sprint(MaxUpstreams)) { t.Errorf("refusal = %v, want it to name the limit (%d)", err, MaxUpstreams) } } func TestLegacyRecordStillParses(t *testing.T) { // A record written before addrs/health existed — byte-for-byte old wire shape. raw := []byte(`{"schema":1,"host":"a.example.com","slug":"a","app":"web","account":"bab","channel":"stable","created_at":"2026-08-01T00:00:00Z","kind":"service","addr":"10.0.0.1:3000"}`) var rec Record if err := json.Unmarshal(raw, &rec); err != nil { t.Fatal(err) } if rec.Addrs != nil || rec.Health != nil { t.Fatalf("legacy record grew fields: %+v", rec) } if got := rec.AddrsOrDefault(); len(got) != 1 || got[0] != "10.0.0.1:3000" { t.Fatalf("legacy fold = %v", got) } } // TestHealthWireTagIsProbeNotHealth pins Record.Health's wire key to // "probe": the console's instance-list API already ships "health" for a // computed one-word status string (instanceListRecord.Health, parsed by a // deployed CLI), so this field must never collide with it. A well-meaning // rename back to "health" would silently break that shipped surface. func TestHealthWireTagIsProbeNotHealth(t *testing.T) { rec := Record{Host: "x", App: "web", Account: "bab", Channel: "stable", Health: &Health{Path: "/up"}} b, err := json.Marshal(rec) if err != nil { t.Fatal(err) } if !strings.Contains(string(b), `"probe":`) { t.Fatalf("marshal dropped the probe key: %s", b) } if strings.Contains(string(b), `"health":`) { t.Fatalf("marshal used the collided health key: %s", b) } } // The move transitions (issue #331): SetRecordMove is the one writer of // Record.Move, and each transition validates the state it leaves from. The // terminal MoveComplete flips Region and clears Move in ONE write. func TestSetRecordMoveTransitions(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) rec := Record{Schema: 1, Host: "a.go.example.com", Slug: "a", App: "app", Account: "bab", Region: "ie", Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatal(err) } mv := func(mut func(*Record) error) error { _, err := SetRecordMove(ctx, s, "bab", "app", "a.go.example.com", mut) return err } read := func() Record { t.Helper() got, err := GetRecord(ctx, s, "bab", "app", "a.go.example.com") if err != nil { t.Fatal(err) } return *got } now := time.Now() if err := mv(MoveRequest("ie", now)); !errors.Is(err, ErrMoveConflict) { t.Fatalf("move to the current region = %v, want ErrMoveConflict", err) } if err := mv(MoveMarkCutting()); !errors.Is(err, ErrMoveConflict) { t.Fatalf("cut with no move in flight = %v, want ErrMoveConflict", err) } if err := mv(MoveRequest("uk", now)); err != nil { t.Fatal(err) } if got := read(); got.Move == nil || got.Move.State != MovePreparing || got.Move.To != "uk" || got.Region != "ie" { t.Fatalf("after request: %+v", got.Move) } if err := mv(MoveRequest("se", now)); !errors.Is(err, ErrMoveConflict) { t.Fatalf("second request while in flight = %v, want ErrMoveConflict", err) } if err := mv(MoveMarkCutting()); !errors.Is(err, ErrMoveConflict) { t.Fatalf("cut from preparing = %v, want ErrMoveConflict", err) } if err := mv(MovePromoteReady(now)); err != nil { t.Fatal(err) } if got := read(); got.Move.State != MoveReady || got.Move.ReadyAt == nil { t.Fatalf("after promote: %+v", got.Move) } if err := mv(MoveMarkCutting()); err != nil { t.Fatal(err) } if err := mv(MoveAbort()); !errors.Is(err, ErrMoveConflict) { t.Fatalf("abort from cutting = %v, want ErrMoveConflict (completes forward)", err) } if err := mv(MoveComplete()); err != nil { t.Fatal(err) } if got := read(); got.Region != "uk" || got.Move != nil { t.Fatalf("after complete: region=%q move=%+v, want uk/nil in one write", got.Region, got.Move) } // Abort is two-phase: ready → aborting (cut and promote both refuse // there) → cleared only by MoveAbortComplete. if err := mv(MoveRequest("ie", now)); err != nil { t.Fatal(err) } if err := mv(MovePromoteReady(now)); err != nil { t.Fatal(err) } if err := mv(MoveAbort()); err != nil { t.Fatal(err) } if got := read(); got.Move == nil || got.Move.State != MoveAborting { t.Fatalf("after abort: %+v, want aborting", got.Move) } if err := mv(MoveMarkCutting()); !errors.Is(err, ErrMoveConflict) { t.Fatalf("cut from aborting = %v, want ErrMoveConflict", err) } if err := mv(MoveAbort()); !errors.Is(err, ErrMoveConflict) { t.Fatalf("double abort = %v, want ErrMoveConflict", err) } if err := mv(MoveAbortComplete()); err != nil { t.Fatal(err) } if got := read(); got.Move != nil || got.Region != "uk" { t.Fatalf("after abort-complete: %+v region=%q", got.Move, got.Region) } } // Abort names the states it may leave, so a state a newer box writes is // refused here rather than aborted (G16): "landing" is the target restoring // from the staging an abort would delete. The Phase 1 states past the cut // (committed, rolling_back, reverting, stuck) are refused the same way. This // is the real function, not a copy: v0.22.2's MoveAbort // (internal/provision/provision.go:1022-1039 at that tag) allows exactly the // same cases, preparing and ready, so a v0.22 console refuses all of them too // (§14.3). func TestMoveAbortRefusesStatesItDoesNotKnow(t *testing.T) { for _, state := range []string{MoveLanding, MoveCommitted, MoveRollingBack, MoveReverting, MoveStuck, "freezing", "done", ""} { rec := &Record{Move: &MoveState{To: "uk", State: state}} if err := MoveAbort()(rec); !errors.Is(err, ErrMoveConflict) { t.Errorf("abort from %q = %v, want ErrMoveConflict", state, err) } if rec.Move.State != state { t.Errorf("abort from %q rewrote the state to %q", state, rec.Move.State) } } for _, state := range []string{MovePreparing, MoveReady} { rec := &Record{Move: &MoveState{To: "uk", State: state}} if err := MoveAbort()(rec); err != nil || rec.Move.State != MoveAborting { t.Errorf("abort from %q = %v, state %q; want aborting", state, err, rec.Move.State) } } } // A replica never gets a route on a platform box, so a move of one would // wedge in preparing with no box to drive it (G14). func TestMoveRequestRefusesAReplica(t *testing.T) { rec := &Record{Region: "ie", Role: "replica"} if err := MoveRequest("uk", time.Now())(rec); !errors.Is(err, ErrMoveConflict) || rec.Move != nil { t.Fatalf("move of a replica = %v, move %+v; want ErrMoveConflict and no move", err, rec.Move) } } // A record written before the move field existed round-trips byte-identical // through the CAS helper's read-marshal cycle — the wire-shape rule. func TestMoveFieldAbsentOnPreMoveRecords(t *testing.T) { raw := []byte(`{"schema":1,"host":"a.go.example.com","slug":"a","app":"app","account":"bab","region":"ie","channel":"stable","created_at":"2026-08-01T00:00:00Z"}`) var rec Record if err := json.Unmarshal(raw, &rec); err != nil { t.Fatal(err) } if rec.Move != nil { t.Fatalf("pre-move record decoded a Move: %+v", rec.Move) } out, err := json.Marshal(rec) if err != nil { t.Fatal(err) } if strings.Contains(string(out), "move") { t.Fatalf("re-marshal grew a move key: %s", out) } } func TestValidateShapeGate(t *testing.T) { ok := Record{Kind: "static", Gate: "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"} if err := ValidateShape(ok); err != nil { t.Fatalf("bcrypt gate refused: %v", err) } bad := Record{Kind: "static", Gate: "hunter2"} if err := ValidateShape(bad); err == nil || !strings.Contains(err.Error(), "gate") { t.Fatalf("plaintext gate accepted: %v", err) } } func TestInstanceIDShortHostsAreThemselves(t *testing.T) { for _, h := range []string{"h", "bill.go.tito.io", strings.Repeat("a", 40)} { if got := InstanceID(h); got != h { t.Errorf("InstanceID(%q) = %q, want the host itself", h, got) } } } func TestInstanceIDLongHostsAreBoundedAndPrefixed(t *testing.T) { long := strings.Repeat("a", 60) + ".example.com" got := InstanceID(long) if len(got) != MaxInstanceID { t.Fatalf("InstanceID(long) = %q (%d bytes), want %d", got, len(got), MaxInstanceID) } if got[0] != '_' { t.Errorf("InstanceID(long) = %q, want a leading underscore: no valid host has one", got) } if got[1:23] != long[:22] { t.Errorf("InstanceID(long) = %q, want the host's first 22 bytes after the underscore", got) } if got[23] != '-' { t.Errorf("InstanceID(long) = %q, want a dash before the hash", got) } if got != InstanceID(long) { t.Error("InstanceID is not stable") } // Two long hosts sharing a 22-byte prefix get different identifiers. other := strings.Repeat("a", 60) + ".example.org" if InstanceID(other) == got { t.Errorf("InstanceID(%q) == InstanceID(%q); the hash must separate them", long, other) } } func TestInstanceIDDomainsNeverMeet(t *testing.T) { // codex R6-1: the hashed form of one host must never equal any valid // short host. A leading underscore is not a valid host label. long := strings.Repeat("a", 31) + ".example.com" if ValidHostShape(InstanceID(long)) { t.Errorf("InstanceID(%q) = %q is a valid host shape; the domains overlap", long, InstanceID(long)) } } func TestNestedHelpersAreKeyedByInstanceID(t *testing.T) { long := strings.Repeat("b", 50) + ".example.com" iid := InstanceID(long) if got, want := InstanceDir("/data", long), "/data/instances/"+iid; got != want { t.Errorf("InstanceDir = %q, want %q", got, want) } if got, want := NestedDBPath("/data/", long), "/data/instances/"+iid+"/"+long+".db"; got != want { t.Errorf("NestedDBPath = %q, want %q (file names keep the full host)", got, want) } if got, want := InstanceRunDir(long), "/run/carlos/instances/"+iid; got != want { t.Errorf("InstanceRunDir = %q, want %q", got, want) } if got, want := NestedSocket(long), "/run/carlos/instances/"+iid+"/app.sock"; got != want { t.Errorf("NestedSocket = %q, want %q", got, want) } if got, want := LogSocket(long, "sidecar"), "/run/carlos/logs/"+iid+".sidecar.sock"; got != want { t.Errorf("LogSocket = %q, want %q", got, want) } } // NestedPaths is a path-shape test and nothing more: it must answer true // for a row that left management (unflagged, unit-backed) and for a row // whose paths are nested but not the exact defaults, because the downgrade // guards have to see every row a pre-layout release would mishandle. func TestNestedPathsIsShapeOnly(t *testing.T) { host := "acme.example.com" for _, tc := range []struct { name string rt registry.Route want bool }{ {"flat defaults", registry.Route{Host: host, Hibernate: true, DBPath: DefaultDBPath("/data", host), Socket: DefaultSocket(host)}, false}, {"nested defaults", registry.Route{Host: host, Hibernate: true, DBPath: NestedDBPath("/data", host), Socket: NestedSocket(host)}, true}, {"nested and unflagged", registry.Route{Host: host, DBPath: NestedDBPath("/data", host), Socket: NestedSocket(host)}, true}, {"nested db only, off the default name", registry.Route{Host: host, Backing: registry.BackingUnit, DBPath: "/data/instances/other/x.db"}, true}, {"nested socket only", registry.Route{Host: host, Socket: "/run/carlos/instances/zz/app.sock"}, true}, {"the instances directory itself", registry.Route{Host: host, DBPath: "/data/instances", Socket: "/run/carlos/instances"}, false}, {"a sibling that shares the prefix", registry.Route{Host: host, DBPath: "/data/instances-old/x.db", Socket: "/run/carlos/instancesx/app.sock"}, false}, {"another data dir", registry.Route{Host: host, DBPath: "/srv/instances/a/a.db"}, false}, // A hand-edited row is read as the kernel would resolve it. {"a doubled slash in the db", registry.Route{Host: host, DBPath: "/data//instances/a/a.db"}, true}, {"a doubled slash in the socket", registry.Route{Host: host, Socket: "/run/carlos//instances/zz/app.sock"}, true}, {"a dot segment in the db", registry.Route{Host: host, DBPath: "/data/./instances/a/a.db"}, true}, {"a dot segment in the socket", registry.Route{Host: host, Socket: "/run/carlos/./instances/zz/app.sock"}, true}, {"a dot-dot segment that leaves the tree", registry.Route{Host: host, DBPath: "/data/instances/../a.db"}, false}, } { if got := NestedPaths("/data/", tc.rt); got != tc.want { t.Errorf("%s: NestedPaths = %v, want %v", tc.name, got, tc.want) } } } func TestFlatDefaultsUnchangedThisRelease(t *testing.T) { // Spec §8.1: nothing mints nested in this release. The flat helpers // are what routeFor and `carlos add` still use. if got := DefaultSocket("h"); got != "/run/carlos/h.sock" { t.Errorf("DefaultSocket = %q", got) } if got := DefaultDBPath("/data", "h"); got != "/data/h.db" { t.Errorf("DefaultDBPath = %q", got) } } func TestLongestSocketAddressFitsSockaddrUn(t *testing.T) { // codex R5-3: an 80-byte host overflowed the first draft's paths. The // bound is computed, not assumed, over a 253-byte host. if got := LongestSocketAddress(); got > MaxUnixPath { t.Fatalf("longest socket address is %d bytes, over the %d-byte limit", got, MaxUnixPath) } host := strings.Repeat("c", 253) for _, p := range []string{NestedSocket(host), NestedSocket(host) + ".control", LogSocket(host, "sidecar")} { if len(p) > MaxUnixPath { t.Errorf("%q is %d bytes", p, len(p)) } } } func TestIsControlEndpoint(t *testing.T) { for path, want := range map[string]bool{ ControlSocketPath: true, "/run/carlos-control/anything": true, "/run/carlos-control": true, "/run/carlos/h.sock": false, "/run/carlos/instances/h/app.sock": false, "/run/carlos-controlx/control.sock": false, "/run/titogo/example.sock": false, } { if got := IsControlEndpoint(path); got != want { t.Errorf("IsControlEndpoint(%q) = %v, want %v", path, got, want) } } } func TestLayoutJournalPath(t *testing.T) { if got := LayoutJournalPath("/data/", "h.example.com"); got != "/data/layout/h.example.com.json" { t.Errorf("LayoutJournalPath = %q", got) } } // --- #162: replica role and placement set --------------------------------- // TestRecordReplicaFieldsAreAdditive extends TestRecordWireShapeIsAdditive // to the replica pair: a record written before the fields existed reads as // "the serving home with no replicas anywhere", byte for byte the routing // and placement truth every box in the fleet held before #162. func TestRecordReplicaFieldsAreAdditive(t *testing.T) { const legacy = `{"schema":1,"host":"acme.go.tito.io","slug":"acme","app":"tito",` + `"account":"bab","pool":"tito","region":"ie","channel":"stable",` + `"created_at":"2026-08-04T09:00:00Z"}` var rec Record if err := json.Unmarshal([]byte(legacy), &rec); err != nil { t.Fatal(err) } if rec.Role != "" { t.Errorf("Role = %q, want empty", rec.Role) } if len(rec.Replicas) != 0 { t.Errorf("Replicas = %v, want none", rec.Replicas) } if got := rec.Replicas; got != nil { t.Errorf("Replicas = %v on a legacy record, want nil", got) } } // TestValidRolePinsTheVocabulary: closed and case-sensitive, ValidSteering's // discipline — a value no reader has heard of is refused where it is typed. func TestValidRolePinsTheVocabulary(t *testing.T) { for _, role := range []string{"", "replica"} { if !ValidRole(role) { t.Errorf("ValidRole(%q) = false, want true", role) } } for _, role := range []string{"Replica", "REPLICA", "read", "mirror", "home"} { if ValidRole(role) { t.Errorf("ValidRole(%q) = true, want false", role) } } } // TestValidReplicasCanonicalizesAndRejects: the set is canonical (sorted, // deduplicated) so the wire column and the reconciler's converge compare // equal regardless of the order the writer typed; a placement outside the // Region vocabulary is refused at the door rather than discovered as a // placement no box ever announces. func TestValidReplicasCanonicalizesAndRejects(t *testing.T) { got, err := ValidReplicas([]string{"uk", " ie ", "", "uk", "us"}) if err != nil { t.Fatalf("ValidReplicas: %v", err) } if strings.Join(got, ",") != "ie,uk,us" { t.Errorf("ValidReplicas = %v, want [ie uk us] — sorted and deduplicated", got) } if _, err := ValidReplicas([]string{"fleet/pi"}); err == nil { t.Error("ValidReplicas(fleet/pi) accepted — a fleet is a tenancy boundary, not a replica placement") } if _, err := ValidReplicas([]string{"not a place"}); err == nil { t.Error("ValidReplicas(garbage) accepted") } var many []string for i := 0; i < MaxReplicas+1; i++ { many = append(many, fmt.Sprintf("r%d", i)) } if _, err := ValidReplicas(many); err == nil { t.Errorf("ValidReplicas(%d placements) accepted, want the residence cap refusal", MaxReplicas+1) } } // TestValidateShapeReplicaRails pins the shape rules the door enforces for // the replica role: instance-only, and the placement set's vocabulary. func TestValidateShapeReplicaRails(t *testing.T) { replica := Record{Role: "replica", Region: "uk"} if err := ValidateShape(replica); err != nil { t.Fatalf("ValidateShape(bare replica): %v", err) } if err := ValidateShape(Record{Role: "replica", Kind: "static"}); err == nil { t.Error("ValidateShape(replica static) accepted — a replica is a process holding a mirror, never a file tree") } if err := ValidateShape(Record{Role: "replica", Kind: "service", Addr: "10.0.0.1:8090"}); err == nil { t.Error("ValidateShape(replica service) accepted — the platform spawns the mirror's process") } if err := ValidateShape(Record{Replicas: []string{"fleet/pi"}}); err == nil { t.Error("ValidateShape(fleet replica placement) accepted") } if err := ValidateShape(Record{Replicas: []string{"ie", "uk", "ie"}}); err != nil { t.Errorf("ValidateShape(duplicate placement): %v — deduplication is the door's job, not a refusal", err) } } // PrivateStorage is additive and its ZERO VALUE is the legacy behaviour: a // manifest written before the field existed — every manifest in a // production bucket — must read as an app whose instances share the app's // storage, which is what those apps do (issue #412, spec §5). func TestManifestPrivateStorageIsAdditive(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) // The bytes an older console wrote: no such key at all. legacy := []byte(`{"schema":1,"app":"tito","domain":"go.tito.io","channel":"stable"}`) if _, err := s.Put(ctx, ManifestKey("bad", "tito"), legacy, ""); err != nil { t.Fatal(err) } got, err := GetManifest(ctx, s, "bad", "tito") if err != nil { t.Fatal(err) } if got.PrivateStorage { t.Fatal("a manifest written before the field reads as private-storage — every app in production would start minting buckets") } got.PrivateStorage = true if err := PutManifest(ctx, s, "bad", "tito", *got); err != nil { t.Fatal(err) } back, err := GetManifest(ctx, s, "bad", "tito") if err != nil { t.Fatal(err) } if !back.PrivateStorage { t.Fatalf("policy did not survive a round trip: %+v", back) } // omitempty: an app without the policy writes no key, so nothing an old // reader parses changes shape. back.PrivateStorage = false if err := PutManifest(ctx, s, "bad", "tito", *back); err != nil { t.Fatal(err) } raw, _, err := s.Get(ctx, ManifestKey("bad", "tito")) if err != nil { t.Fatal(err) } if strings.Contains(string(raw), "private_storage") { t.Errorf("an app without the policy still writes the key: %s", raw) } } func TestListRecordsStrict(t *testing.T) { s, sqid := shopSetup(t) for _, h := range []string{"c.example.com", "a.example.com", "b.example.com"} { rec := Record{Schema: 1, Host: h, Slug: h[:1], App: "shop", Account: sqid, Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, s, rec); err != nil { t.Fatal(err) } } recs, unreadable, err := ListRecordsStrict(ctx, s, sqid, "shop", 3) if err != nil || len(unreadable) != 0 || unreadable == nil || len(recs) != 3 { t.Fatalf("clean listing = %+v, %#v, %v", recs, unreadable, err) } for i, want := range []string{"a.example.com", "b.example.com", "c.example.com"} { if recs[i].Host != want { t.Errorf("recs[%d].Host = %q, want %q", i, recs[i].Host, want) } } // The cap is an error, not a flag: one key over, and at 0 or below. for _, max := range []int{2, 0, -1} { recs, unreadable, err := ListRecordsStrict(ctx, s, sqid, "shop", max) if !errors.Is(err, ErrRecordsTruncated) || recs != nil || unreadable != nil { t.Fatalf("max %d = %+v, %v, %v; want ErrRecordsTruncated and nothing else", max, recs, unreadable, err) } } // An app with no records is complete at any cap. if recs, unreadable, err := ListRecordsStrict(ctx, s, sqid, "empty", 0); err != nil || len(recs) != 0 || len(unreadable) != 0 { t.Fatalf("empty app = %+v, %v, %v", recs, unreadable, err) } // A body that does not decode, and one that names another host, are // reported by the host in their key. ListRecords skips them. if _, err := s.Put(ctx, RecordKey(sqid, "shop", "z.example.com"), []byte("not json"), ""); err != nil { t.Fatal(err) } liar := Record{Schema: 1, Host: "elsewhere.example.com", Slug: "y", App: "shop", Account: sqid, Channel: "stable", CreatedAt: time.Now().UTC()} raw, err := json.Marshal(liar) if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, RecordKey(sqid, "shop", "y.example.com"), raw, ""); err != nil { t.Fatal(err) } recs, unreadable, err = ListRecordsStrict(ctx, s, sqid, "shop", 10) if err != nil || len(recs) != 3 || len(unreadable) != 2 || !unreadable["z.example.com"] || !unreadable["y.example.com"] { t.Fatalf("with two bad bodies = %d recs, %v, %v", len(recs), unreadable, err) } if plain, _, err := ListRecords(ctx, s, sqid, "shop", 10); err != nil || len(plain) != 3 { t.Fatalf("ListRecords must keep skipping them: %d, %v", len(plain), err) } } func TestListRecordsStrictFailsOnAnyReadFailure(t *testing.T) { inner, sqid := shopSetup(t) for _, h := range []string{"a.example.com", "b.example.com"} { rec := Record{Schema: 1, Host: h, Slug: h[:1], App: "shop", Account: sqid, Channel: "stable", CreatedAt: time.Now().UTC()} if err := CreateRecord(ctx, inner, rec); err != nil { t.Fatal(err) } } // ListRecords drops the record it cannot Get; the strict listing fails. fs := &failStore{Store: inner} fs.failReadsFor(RecordKey(sqid, "shop", "b.example.com")) if plain, _, err := ListRecords(ctx, fs, sqid, "shop", 10); err != nil || len(plain) != 1 { t.Fatalf("ListRecords over an unreadable record = %d, %v", len(plain), err) } if recs, unreadable, err := ListRecordsStrict(ctx, fs, sqid, "shop", 10); err == nil || recs != nil || unreadable != nil { t.Fatalf("ListRecordsStrict over an unreadable record = %+v, %v, %v; want an error", recs, unreadable, err) } // A listing that fails is an error too. ls := &bucketShaped{Store: inner, failList: RecordsPrefix(sqid, "shop")} if recs, unreadable, err := ListRecordsStrict(ctx, ls, sqid, "shop", 10); err == nil || recs != nil || unreadable != nil { t.Fatalf("ListRecordsStrict over a failed listing = %+v, %v, %v; want an error", recs, unreadable, err) } }