carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "strconv"
8 "strings"
9 "testing"
10
11 "github.com/carlosframework/platform/internal/domains"
12 "github.com/carlosframework/platform/internal/provision"
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17// ---- fakes -----------------------------------------------------------------
18
19// failingListStore breaks the LISTING of one prefix — the control-bucket
20// blip that must never reach the converger as "no edges" or "no records".
21// Prefix-scoped because the pass now lists three of them (control/edges/,
22// accounts/, apps/) and each is a separate fail-closed direction.
23type failingListStore struct {
24 release.Store
25 prefix string
26 err error
27}
28
29func (f failingListStore) List(ctx context.Context, prefix string) ([]string, error) {
30 if prefix == f.prefix {
31 return nil, f.err
32 }
33 return f.Store.List(ctx, prefix)
34}
35
36// ListDirs fails the same prefix: the record readers walk apps/ by its
37// directories now (provision.listAppSubtree), so "the apps listing fails"
38// is a ListDirs of apps/ failing as much as a List of it.
39func (f failingListStore) ListDirs(ctx context.Context, prefix string) ([]string, error) {
40 if prefix == f.prefix {
41 return nil, f.err
42 }
43 return f.Store.ListDirs(ctx, prefix)
44}
45
46// failingGetStore lists fine but refuses ONE named key — the half-read that
47// would otherwise fold to a short desired set and delete the missing box's
48// records.
49type failingGetStore struct {
50 release.Store
51 bad string
52 err error
53}
54
55func (f failingGetStore) Get(ctx context.Context, key string) ([]byte, string, error) {
56 if key == f.bad {
57 return nil, "", f.err
58 }
59 return f.Store.Get(ctx, key)
60}
61
62// convergeRecorder captures what the pass handed the converger, and whether
63// it was called at all — the assertion every fail-closed test makes.
64type convergeRecorder struct {
65 calls int
66 routes []registry.Route
67 edges [][]byte
68 err error
69}
70
71func (c *convergeRecorder) converge(_ context.Context, routes []registry.Route, edges [][]byte) error {
72 c.calls++
73 c.routes = routes
74 c.edges = edges
75 return c.err
76}
77
78func steeringTestStore(t *testing.T, files map[string][]byte) release.Store {
79 t.Helper()
80 s := release.NewDirStore(t.TempDir())
81 for key, body := range files {
82 if _, err := s.Put(context.Background(), key, body, ""); err != nil {
83 t.Fatal(err)
84 }
85 }
86 return s
87}
88
89// seedAccount writes the account manifest ListAllRecords' known-account
90// guard (provision.ParseRecordKey) requires, and returns its sqid — the
91// frozen encoding of the id, via domains.Sqid, never a literal.
92func seedAccount(t *testing.T, s release.Store, id int64, name string) string {
93 t.Helper()
94 sqid := domains.Sqid(id)
95 body := []byte(`{"id":` + strconv.FormatInt(id, 10) + `,"sqid":"` + sqid +
96 `","name":"` + name + `","created_at":"2026-08-02T09:00:00Z"}`)
97 if _, err := s.Put(context.Background(), "accounts/"+sqid+"/account.json", body, ""); err != nil {
98 t.Fatal(err)
99 }
100 return sqid
101}
102
103// seedRecord writes one instance record at its canonical key.
104func seedRecord(t *testing.T, s release.Store, sqid, app, host, steering, pool string) {
105 t.Helper()
106 rec := provision.Record{
107 Schema: 1, Host: host, Slug: app, App: app, Account: sqid,
108 Pool: pool, Channel: "canary/main", Steering: steering,
109 }
110 body, err := json.Marshal(rec)
111 if err != nil {
112 t.Fatal(err)
113 }
114 if _, err := s.Put(context.Background(), provision.RecordKey(sqid, app, host), body, ""); err != nil {
115 t.Fatal(err)
116 }
117}
118
119// ---- the pass: what it reads, and failing closed (H2) ----------------------
120
121// steeringWorld seeds a whole small deployment: two boxes' edges files, one
122// account, and three records — one steered, one unsteered, one steered under
123// a pool.
124func steeringWorld(t *testing.T) (release.Store, string) {
125 t.Helper()
126 s := steeringTestStore(t, map[string][]byte{
127 "control/edges/dublin.json": []byte(`{"v":1,"box":"dublin"}`),
128 "control/edges/oregon.json": []byte(`{"v":1,"box":"oregon"}`),
129 })
130 sqid := seedAccount(t, s, 12, "Tito")
131 seedRecord(t, s, sqid, "app", "app.example.com", "latency", "")
132 seedRecord(t, s, sqid, "app", "plain.example.com", "", "")
133 return s, sqid
134}
135
136// The healthy pass: edges bodies in sorted listing order, and routes derived
137// from the BUCKET RECORDS — not from any registry.
138func TestSteeringPassReadsEdgesFilesAndRecords(t *testing.T) {
139 quietLog(t)
140 s, _ := steeringWorld(t)
141 rec := &convergeRecorder{}
142
143 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
144 t.Fatalf("steeringPass: %v", err)
145 }
146 if rec.calls != 1 {
147 t.Fatalf("converge calls = %d, want 1", rec.calls)
148 }
149 if len(rec.edges) != 2 {
150 t.Fatalf("edges = %d bodies, want 2", len(rec.edges))
151 }
152 // Listing order is sorted, so the fold is deterministic tick to tick.
153 if !strings.Contains(string(rec.edges[0]), "dublin") || !strings.Contains(string(rec.edges[1]), "oregon") {
154 t.Fatalf("edges = %q, want dublin then oregon (sorted)", rec.edges)
155 }
156 // Only the steered record becomes a route; the unsteered one is dropped
157 // here because the fold would skip it silently anyway.
158 if len(rec.routes) != 1 {
159 t.Fatalf("routes = %+v, want only the steered record", rec.routes)
160 }
161 got := rec.routes[0]
162 if got.Host != "app.example.com" || got.Steering != "latency" || got.Pool != "" {
163 t.Fatalf("route = %+v, want the three fields the fold reads", got)
164 }
165}
166
167// FINDING 1's pin (review, 2026-08-23). The steered host here is the
168// HOME-BOXED class — an instance record with no Addr, which everywhereClass
169// materializes on ONE box only, so it is absent from every other box's
170// registry. It must still produce a record from whichever box holds the
171// housekeeper lease, because the lease moves and DNS must not.
172//
173// The proof is structural as much as behavioural: steeringPass takes no
174// registry at all, so there is no local view for the answer to depend on.
175// Before the fix this test could not even have been written — the pass would
176// have needed a registry, and the host would have been missing from it.
177func TestSteeringPassSteersHomeBoxedRecordWithNoLocalRegistry(t *testing.T) {
178 quietLog(t)
179 s := steeringTestStore(t, map[string][]byte{
180 "control/edges/dublin.json": []byte(`{"v":1,"box":"dublin"}`),
181 })
182 sqid := seedAccount(t, s, 12, "Tito")
183 // No Addr, no Unit, default hibernation: home-boxed, exactly the class
184 // the rollout's stage-5 checklist steers.
185 seedRecord(t, s, sqid, "exec-app", "home.example.com", "latency", "")
186 rec := &convergeRecorder{}
187
188 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
189 t.Fatalf("steeringPass: %v", err)
190 }
191 if len(rec.routes) != 1 || rec.routes[0].Host != "home.example.com" {
192 t.Fatalf("routes = %+v, want the home-boxed steered host — a lease migration must not withdraw it", rec.routes)
193 }
194}
195
196// The loader drops Steering=="" (the fold skips those silently anyway) but
197// must NOT become a second, quieter policy gate: an invalid value, a
198// wildcard host and a pool-scoped row all go DOWN to the fold, whose loud
199// documented refusals are the operator's only signal that they exist.
200func TestSteeringPassPassesNonLatencySteeringValuesDown(t *testing.T) {
201 quietLog(t)
202 s := steeringTestStore(t, map[string][]byte{
203 "control/edges/dublin.json": []byte(`{"v":1,"box":"dublin"}`),
204 })
205 sqid := seedAccount(t, s, 12, "Tito")
206 seedRecord(t, s, sqid, "app", "weird.example.com", "weighted", "")
207 seedRecord(t, s, sqid, "app", "pooled.example.com", "latency", "eu")
208 rec := &convergeRecorder{}
209
210 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
211 t.Fatalf("steeringPass: %v", err)
212 }
213 if len(rec.routes) != 2 {
214 t.Fatalf("routes = %+v, want both rows delivered for the fold to refuse loudly", rec.routes)
215 }
216}
217
218// H2, direction one: a broken edges LISTING must not converge.
219func TestSteeringPassFailsClosedOnEdgesListError(t *testing.T) {
220 quietLog(t)
221 boom := errors.New("bucket down")
222 base, _ := steeringWorld(t)
223 s := failingListStore{Store: base, prefix: "control/edges/", err: boom}
224 rec := &convergeRecorder{}
225
226 err := steeringPass(context.Background(), s, rec.converge)
227 if err == nil {
228 t.Fatal("steeringPass returned nil on a failed edges listing")
229 }
230 if !errors.Is(err, boom) {
231 t.Fatalf("err = %v, want it to wrap the store error", err)
232 }
233 if rec.calls != 0 {
234 t.Fatalf("converge ran %d times after a failed listing, want 0 — never converge on a partial fold", rec.calls)
235 }
236}
237
238// H2, direction two, and the one the shrink guard was written for: the
239// listing succeeded, so the pass KNOWS how many boxes there are — and one of
240// their files would not come back.
241func TestSteeringPassFailsClosedOnEdgesGetError(t *testing.T) {
242 quietLog(t)
243 boom := errors.New("object unreadable")
244 base, _ := steeringWorld(t)
245 s := failingGetStore{Store: base, bad: "control/edges/oregon.json", err: boom}
246 rec := &convergeRecorder{}
247
248 err := steeringPass(context.Background(), s, rec.converge)
249 if err == nil {
250 t.Fatal("steeringPass returned nil with one edges file unreadable")
251 }
252 if !errors.Is(err, boom) {
253 t.Fatalf("err = %v, want it to wrap the store error", err)
254 }
255 if !strings.Contains(err.Error(), "control/edges/oregon.json") {
256 t.Fatalf("err = %v, want the unreadable key named", err)
257 }
258 if rec.calls != 0 {
259 t.Fatalf("converge ran %d times after a failed get, want 0", rec.calls)
260 }
261}
262
263// H2, direction three, re-pointed at the RECORDS read (FINDING 1): the
264// records are now the routes half of the fold, so every way that read can
265// come back short is a way a live steered host gets deleted. Four of them,
266// each fatal.
267func TestSteeringPassFailsClosedOnRecordsRead(t *testing.T) {
268 boom := errors.New("bucket down")
269 cases := []struct {
270 name string
271 wrap func(release.Store) release.Store
272 named string
273 }{
274 {
275 // Without accounts/, ParseRecordKey's known-account guard rejects
276 // EVERY record key — the fold would see no steered hosts at all.
277 name: "accounts listing fails",
278 wrap: func(s release.Store) release.Store {
279 return failingListStore{Store: s, prefix: "accounts/", err: boom}
280 },
281 },
282 {
283 // The subtle one: ONE account's manifest is unreadable, so only
284 // THAT account's records vanish. Silent, partial, and exactly the
285 // shape that deletes one tenant's DNS.
286 name: "an account manifest is unreadable",
287 wrap: func(s release.Store) release.Store {
288 return failingGetStore{Store: s, bad: "accounts/" + domains.Sqid(12) + "/account.json", err: boom}
289 },
290 },
291 {
292 name: "apps listing fails",
293 wrap: func(s release.Store) release.Store {
294 return failingListStore{Store: s, prefix: "apps/", err: boom}
295 },
296 },
297 {
298 name: "a record object will not Get",
299 wrap: func(s release.Store) release.Store {
300 return failingGetStore{Store: s, bad: provision.RecordKey(domains.Sqid(12), "app", "app.example.com"), err: boom}
301 },
302 named: "app.example.com",
303 },
304 }
305 for _, c := range cases {
306 t.Run(c.name, func(t *testing.T) {
307 quietLog(t)
308 base, _ := steeringWorld(t)
309 rec := &convergeRecorder{}
310
311 err := steeringPass(context.Background(), c.wrap(base), rec.converge)
312 if err == nil {
313 t.Fatalf("steeringPass returned nil when %s", c.name)
314 }
315 if rec.calls != 0 {
316 t.Fatalf("converge ran %d times, want 0 — a short records read must never converge", rec.calls)
317 }
318 if c.named != "" && !strings.Contains(err.Error(), c.named) {
319 t.Fatalf("err = %v, want the unreadable record named", err)
320 }
321 })
322 }
323}
324
325// The content-vs-visibility split, records half: a record that READS but does
326// not decode is skipped and logged, and the pass converges on the rest. One
327// malformed body must not blind the deployment to every other steered host.
328func TestSteeringPassSkipsUnparseableRecord(t *testing.T) {
329 logs := captureLog(t)
330 s, sqid := steeringWorld(t)
331 if _, err := s.Put(context.Background(), provision.RecordKey(sqid, "app", "broken.example.com"), []byte("{not json"), ""); err != nil {
332 t.Fatal(err)
333 }
334 rec := &convergeRecorder{}
335
336 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
337 t.Fatalf("steeringPass: %v — an unparseable record is not a read failure", err)
338 }
339 if rec.calls != 1 {
340 t.Fatalf("converge calls = %d, want 1", rec.calls)
341 }
342 if len(rec.routes) != 1 || rec.routes[0].Host != "app.example.com" {
343 t.Fatalf("routes = %+v, want the healthy steered record still folded", rec.routes)
344 }
345 if !strings.Contains(logs(), "broken.example.com") {
346 t.Fatalf("log = %q, want the malformed record named", logs())
347 }
348}
349
350// The same split, edges half: the host loader must NOT pre-judge content —
351// a body that will not parse is delivered verbatim and the fold decides.
352func TestSteeringPassPassesUnparseableBodiesDown(t *testing.T) {
353 quietLog(t)
354 s := steeringTestStore(t, map[string][]byte{
355 "control/edges/dublin.json": []byte("{not json"),
356 })
357 seedAccount(t, s, 12, "Tito")
358 rec := &convergeRecorder{}
359
360 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
361 t.Fatalf("steeringPass: %v — an unparseable body is not a read failure", err)
362 }
363 if rec.calls != 1 {
364 t.Fatalf("converge calls = %d, want 1 — parse failures are the fold's business", rec.calls)
365 }
366 if len(rec.edges) != 1 {
367 t.Fatalf("edges = %d bodies, want the unparseable one delivered verbatim", len(rec.edges))
368 }
369}
370
371// Objects under the edges prefix that this deployment's edges loop did not
372// write are not read at all — the same key-shape filter internal/edge's
373// homeMap applies. A stray object must not be able to fail the pass forever.
374func TestSteeringPassIgnoresForeignKeysUnderThePrefix(t *testing.T) {
375 quietLog(t)
376 s := steeringTestStore(t, map[string][]byte{
377 "control/edges/dublin.json": []byte(`{"v":1,"box":"dublin"}`),
378 "control/edges/README.txt": []byte("hello"),
379 "control/edges/nested/inner.json": []byte(`{"v":1,"box":"nested"}`),
380 })
381 seedAccount(t, s, 12, "Tito")
382 rec := &convergeRecorder{}
383
384 if err := steeringPass(context.Background(), s, rec.converge); err != nil {
385 t.Fatalf("steeringPass: %v", err)
386 }
387 if len(rec.edges) != 1 {
388 t.Fatalf("edges = %d bodies, want only control/edges/<box>.json", len(rec.edges))
389 }
390}
391
392// ---- the duty: dormancy and the housekeeper gate ---------------------------
393
394// The dormancy proof: with no zone id the duty returns nil having called
395// NOTHING — the pass closure holds every read and every AWS call, so "the
396// pass never ran" is "zero reads, zero API calls" structurally.
397func TestSteeringDutyDormantDoesNoWork(t *testing.T) {
398 quietLog(t)
399 ran := 0
400 duty := steeringDuty("", func(context.Context) error { ran++; return nil })
401
402 if err := duty.run(context.Background()); err != nil {
403 t.Fatalf("dormant duty returned %v, want nil", err)
404 }
405 if ran != 0 {
406 t.Fatalf("dormant duty ran the pass %d times, want 0", ran)
407 }
408}
409
410// FINDING 3 (review, 2026-08-23): totality on a production DNS write path.
411// A zone id with no pass is unreachable today — steeringLoop leaves pass nil
412// in exactly the branch that leaves zoneID empty — but if that ever stopped
413// being true, the duty must decline rather than panic the agent's goroutine
414// on a nil call.
415func TestSteeringDutyWithNoPassDeclines(t *testing.T) {
416 quietLog(t)
417 duty := steeringDuty("Z0ABCDEF1234", nil)
418 if err := duty.run(context.Background()); err != nil {
419 t.Fatalf("duty with a nil pass returned %v, want nil (and no panic)", err)
420 }
421}
422
423func TestSteeringDutyRunsThePassWhenZoneSet(t *testing.T) {
424 quietLog(t)
425 ran := 0
426 boom := errors.New("converge failed")
427 duty := steeringDuty("Z0ABCDEF1234", func(context.Context) error { ran++; return boom })
428
429 if err := duty.run(context.Background()); !errors.Is(err, boom) {
430 t.Fatalf("duty error = %v, want the pass's error propagated to the housekeeper log", err)
431 }
432 if ran != 1 {
433 t.Fatalf("pass ran %d times, want 1", ran)
434 }
435}
436
437// The housekeeper contract: one box converges the deployment's zone, not
438// every box. A non-holder does nothing and says nothing.
439func TestSteeringDutyRunsOnlyOnTheHousekeeper(t *testing.T) {
440 quietLog(t)
441 ran := 0
442 duty := steeringDuty("Z0ABCDEF1234", func(context.Context) error { ran++; return nil })
443
444 runHousekeeperDuties(noGate, duty)
445 if ran != 0 {
446 t.Fatalf("pass ran %d times on a non-holder, want 0", ran)
447 }
448 runHousekeeperDuties(heldGate, duty)
449 if ran != 1 {
450 t.Fatalf("pass ran %d times on the holder, want 1", ran)
451 }
452}
453
454// ---- the loop: arming ------------------------------------------------------
455
456// Mirrors TestMintMeshKeyLoopIsAPlatformBoxJob: a fleet box must skip before
457// ever opening a store.
458func TestSteeringLoopIsAPlatformBoxJob(t *testing.T) {
459 const skipLine = "agent: steering loop is a platform-box job — skipped on a fleet box"
460
461 logs := captureLog(t)
462 door := newDoorFake(t)
463 for k, v := range fleetDoorEnv(door.url) {
464 t.Setenv(k, v)
465 }
466 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
467 t.Setenv("CARLOS_DEPLOYMENT_DIR", "")
468
469 steeringLoop() // must return; a fleet box never reaches the loop
470
471 if got := logs(); !strings.Contains(got, skipLine) {
472 t.Fatalf("log = %q, want the skip line verbatim", got)
473 }
474 if hits := door.seen(); len(hits) != 0 {
475 t.Fatalf("door saw %+v, want zero — the skip must land BEFORE the store opens", hits)
476 }
477}
478
479// Arming independence, the mesh-key loop's own regression guard applied here:
480// a box missing CARLOS_RELEASE_PUBKEY (retentionLoop's gate, unrelated to
481// steering) still WINS the housekeeper election, so a steering duty gated on
482// that var would silently never converge fleet-wide.
483func TestSteeringLoopArmsWithoutReleasePubkey(t *testing.T) {
484 t.Setenv("CARLOS_FLEET", "")
485 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
486 t.Setenv("CARLOS_DEPLOYMENT_DIR", t.TempDir())
487 t.Setenv("CARLOS_RELEASE_PUBKEY", "") // deliberately unset — the whole point
488 t.Setenv("CARLOS_STEERING_ZONE_ID", "")
489
490 buf := captureRacyLog(t)
491 go steeringLoop()
492
493 waitFor(t, "the steering loop to arm despite no CARLOS_RELEASE_PUBKEY", func() bool {
494 return strings.Contains(buf.String(), "agent: steering loop armed")
495 })
496}
497
498// Today's boxes: the env is unset, so the loop arms and says so ONCE, and its
499// duty is the dormant one — no store reads, no registry reads, no AWS client
500// ever constructed.
501func TestSteeringLoopDormantWhenZoneUnset(t *testing.T) {
502 t.Setenv("CARLOS_FLEET", "")
503 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
504 t.Setenv("CARLOS_DEPLOYMENT_DIR", t.TempDir())
505 t.Setenv("CARLOS_STEERING_ZONE_ID", "")
506
507 buf := captureRacyLog(t)
508 go steeringLoop()
509
510 waitFor(t, "the dormant line", func() bool {
511 return strings.Contains(buf.String(), "steering: zone unset — dormant")
512 })
513 if n := strings.Count(buf.String(), "steering: zone unset — dormant"); n != 1 {
514 t.Fatalf("dormant line logged %d times, want exactly 1 (at arm)", n)
515 }
516}
517
518// A malformed zone id is dormancy too, not a crash loop: SteeringZoneID logs
519// it loudly and returns "", and the loop arms dormant on that.
520func TestSteeringLoopDormantWhenZoneMalformed(t *testing.T) {
521 t.Setenv("CARLOS_FLEET", "")
522 t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "")
523 t.Setenv("CARLOS_DEPLOYMENT_DIR", t.TempDir())
524 t.Setenv("CARLOS_STEERING_ZONE_ID", "/hostedzone/Z0ABCDEF1234")
525
526 buf := captureRacyLog(t)
527 go steeringLoop()
528
529 waitFor(t, "the dormant line for a malformed zone id", func() bool {
530 return strings.Contains(buf.String(), "steering: zone unset — dormant")
531 })
532 if !strings.Contains(buf.String(), "is not a bare hosted-zone id") {
533 t.Fatalf("log = %q, want the malformed id named loudly", buf.String())
534 }
535}
536