package host import ( "context" "errors" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/lease/leasetest" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) // fakeFilesSource is a C8 files source that records its opens and closes. type fakeFilesSource struct { opens, closes int actor hostbox.Actor err error files ClaimFilesManifest } func (s *fakeFilesSource) Open(_ context.Context, _ release.Store, _ lease.VersionedStore, actor hostbox.Actor) (ClaimFilesManifest, error) { s.opens++ s.actor = actor return s.files, s.err } func (s *fakeFilesSource) Close(context.Context) error { s.closes++; return nil } func withClaimFilesSource(t *testing.T, s ClaimProductionFilesSource) { t.Helper() claimFilesSources.Lock() prev := claimFilesSources.Source claimFilesSources.Source = s claimFilesSources.Unlock() t.Cleanup(func() { claimFilesSources.Lock() claimFilesSources.Source = prev claimFilesSources.Unlock() }) } // The C8 provider registers once at process start, under // CARLOS_HOST_CLAIM alone (platform-1e, 2026-10-12): a nil source and a // second registration are refused, and neither can replace the first. func TestRegisterProductionClaimFilesIsOneShot(t *testing.T) { withClaimFilesSource(t, nil) if err := RegisterProductionClaimFiles(nil); !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatalf("nil source: %v", err) } first := &fakeFilesSource{files: &movesFiles{}} if err := RegisterProductionClaimFiles(first); err != nil { t.Fatal(err) } if err := RegisterProductionClaimFiles(&fakeFilesSource{}); !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatalf("second registration: %v", err) } if productionClaimFilesSource() != ClaimProductionFilesSource(first) { t.Fatal("the registered source is not the first") } } // openClaimFilesManifest binds the registered source's provider into the // serving sources with the claim on and moves off or on; with the claim // off, or nothing registered, the bindings are untouched and nothing is // opened. A provider the moves source already bound is a wiring error, // never silently replaced; an Open error or a nil provider refuses. func TestOpenClaimFilesManifestBindsUnderTheClaimAlone(t *testing.T) { store := release.NewDirStore(t.TempDir()) data := leasetest.NewVersionedBucket() actor := hostbox.Actor{Box: "box-1", Region: "ie", Bucket: "data", HomeRegion: "ie", Enrollment: "enr-1"} claimOn := func(k string) string { if k == "CARLOS_HOST_CLAIM" { return "1" } return "" } bothOn := func(k string) string { if k == "CARLOS_HOST_CLAIM" || k == "CARLOS_MOVES_PHASE1" { return "1" } return "" } off := func(string) string { return "" } pass := func(context.Context) error { return nil } provider := &movesFiles{} for name, getenv := range map[string]func(string) string{"claim alone": claimOn, "both": bothOn} { src := &fakeFilesSource{files: provider} var out ClaimOwnerBindings closeFn, err := openClaimFilesManifest(context.Background(), getenv, src, store, data, actor, &out, pass) if err != nil || closeFn == nil { t.Fatalf("%s: %v %v", name, err, closeFn != nil) } if out.Serving.Files != ClaimFilesManifest(provider) || src.opens != 1 || src.actor != actor { t.Fatalf("%s: bound %v, opens %d, actor %+v", name, out.Serving.Files, src.opens, src.actor) } if err := closeFn(context.Background()); err != nil || src.closes != 1 { t.Fatalf("%s: close %v %d", name, err, src.closes) } } // Claim off: untouched, not opened. src := &fakeFilesSource{files: provider} var out ClaimOwnerBindings closeFn, err := openClaimFilesManifest(context.Background(), off, src, store, data, actor, &out, pass) if err != nil || closeFn != nil || out.Serving.Files != nil || src.opens != 0 { t.Fatalf("claim off: %v %v %v %d", err, closeFn != nil, out.Serving.Files, src.opens) } // Nothing registered: untouched. if closeFn, err := openClaimFilesManifest(context.Background(), claimOn, nil, store, data, actor, &out, pass); err != nil || closeFn != nil || out.Serving.Files != nil { t.Fatalf("nothing registered: %v %v %v", err, closeFn != nil, out.Serving.Files) } // Already bound by the moves source: refused, not replaced. already := &movesFiles{Box: "other"} out = ClaimOwnerBindings{Serving: ClaimServingSources{Files: already}} src = &fakeFilesSource{files: provider} if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); !errors.Is(err, hostwrite.ErrUnavailable) || out.Serving.Files != ClaimFilesManifest(already) || src.opens != 0 { t.Fatalf("two providers: %v %v %d", err, out.Serving.Files, src.opens) } // An Open error refuses; a nil provider refuses. out = ClaimOwnerBindings{} src = &fakeFilesSource{err: errors.New("no bucket")} if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); err == nil || out.Serving.Files != nil { t.Fatalf("open error: %v %v", err, out.Serving.Files) } src = &fakeFilesSource{} if _, err := openClaimFilesManifest(context.Background(), claimOn, src, store, data, actor, &out, pass); !errors.Is(err, hostwrite.ErrUnavailable) || out.Serving.Files != nil { t.Fatalf("nil provider: %v %v", err, out.Serving.Files) } } // C8 reads the claim's data bucket as the serving and moves owners do // (whole-branch fixes, item 5): through hostbox.RequestStore, so every // version listing and exact-version read it makes is bounded by the // claim's request limit and preceded by the box's identity check, a // refused identity issuing no bucket call. An establishment under a // refused identity refuses with hostbox.ErrChanged and writes nothing. func TestClaimFilesReadTheBucketThroughTheRequestStore(t *testing.T) { inner := &mwBucket{VersionedBucket: leasetest.NewVersionedBucket()} inner.AddBucket("data") var refuse error checks := 0 check := func(context.Context) error { checks++; return refuse } src := &movesFilesSource{DataDir: t.TempDir(), Routes: testClaimRoutes{}, newFiles: func(context.Context, string, string) (release.Store, error) { return casBucketStore{b: inner.VersionedBucket, bucket: "data"}, nil }} actor := hostbox.Actor{Box: "uk-box", Region: "uk", Bucket: "data", HomeRegion: "ie", Cloud: hostbox.CloudIdentity{Region: "eu-west-2"}} var out ClaimOwnerBindings out.Data = inner closeFn, err := openClaimFilesManifest(t.Context(), func(k string) string { return "1" }, src, release.NewDirStore(t.TempDir()), inner, actor, &out, check) if err != nil || closeFn == nil { t.Fatalf("open: %v", err) } f, ok := out.Serving.Files.(*movesFiles) if !ok { t.Fatalf("%T", out.Serving.Files) } if _, ok := f.Data.(hostbox.RequestStore); !ok { t.Fatalf("C8's bucket is %T, not the claim's request store", f.Data) } before := time.Now() if _, err := f.Data.ListVersions(t.Context(), "data", "instances/"); err != nil { t.Fatal(err) } if inner.lists != 1 || checks != 1 || inner.deadlines[0].IsZero() || inner.deadlines[0].After(time.Now().Add(hostbox.RequestLimit)) || inner.deadlines[0].Before(before.Add(hostbox.RequestLimit-time.Minute)) { t.Fatalf("the request's deadline %v (lists %d, checks %d), want the request limit", inner.deadlines, inner.lists, checks) } refuse = hostbox.ErrChanged if _, err := f.Data.ListVersions(t.Context(), "data", "instances/"); !errors.Is(err, hostbox.ErrChanged) || inner.lists != 1 { t.Fatalf("a refused identity: %v, %d bucket calls", err, inner.lists) } if _, err := f.Data.GetVersion(t.Context(), "data", "k", "v"); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("a refused identity's read: %v", err) } // An establishment under a refused identity: the chain's first listing // refuses, nothing is written. x := &filesFixture{t: t, bucket: inner.VersionedBucket, files: casBucketStore{b: inner.VersionedBucket, bucket: "data"}, dir: src.DataDir} x.f = f f.EnvFile = flatClaimEnvFile(x.dir) x.writeEnv("K=1\n") o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") if err := f.Establish(t.Context(), o, launchInputs("g2"), x); !errors.Is(err, hostbox.ErrChanged) { t.Fatalf("establish under a refused identity: %v", err) } if _, found := x.manifest("g2"); found { t.Fatal("a manifest was written under a refused identity") } if inner.lists != 1 { t.Fatalf("%d bucket calls, want none from the refused establishment", inner.lists) } } // The production source opens a movesFiles over the verified actor: the // data bucket by exact version, the files store for that bucket in its // cloud region, this box's engine region and the home alias, and the // host's files directory and env file under the data directory. No pin // reader is wired on this branch, so Evidence is nil. func TestProductionFilesSourceOpensOverTheActor(t *testing.T) { data := leasetest.NewVersionedBucket() var gotBucket, gotRegion string src := &movesFilesSource{DataDir: "/data", Routes: testClaimRoutes{}, newFiles: func(_ context.Context, bucket, region string) (release.Store, error) { gotBucket, gotRegion = bucket, region return release.NewDirStore(t.TempDir()), nil }} actor := hostbox.Actor{Box: "box-1", Region: "", HomeRegion: "ie", Bucket: "data-ie", Cloud: hostbox.CloudIdentity{Region: "eu-west-1"}} got, err := src.Open(context.Background(), release.NewDirStore(t.TempDir()), data, actor) if err != nil { t.Fatal(err) } f, ok := got.(*movesFiles) if !ok { t.Fatalf("%T", got) } if gotBucket != "data-ie" || gotRegion != "eu-west-1" || f.Bucket != "data-ie" || f.Box != "box-1" || f.HomeRegion != "ie" || f.Region != "@home" || f.Data == nil || f.Files == nil || f.Evidence != nil { t.Fatalf("provider: %+v (%s %s)", f, gotBucket, gotRegion) } if f.EnvFile == nil || f.DataDir != "/data" { t.Fatalf("env file resolver set %v, data %s", f.EnvFile != nil, f.DataDir) } // An actor without a bucket, box or region cannot be opened, nor a // source without the box's registry to find env files by. if _, err := src.Open(context.Background(), release.NewDirStore(t.TempDir()), data, hostbox.Actor{Box: "box-1"}); !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatalf("bare actor: %v", err) } noRoutes := *src noRoutes.Routes = nil if _, err := noRoutes.Open(context.Background(), release.NewDirStore(t.TempDir()), data, actor); !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatalf("no registry: %v", err) } // Registration happens only with the claim on, and needs the registry. reg, err := registry.Open(filepath.Join(t.TempDir(), "registry.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { reg.Close() }) withClaimFilesSource(t, nil) if err := registerProductionClaimFiles(func(string) string { return "" }, "/data", reg); err != nil || productionClaimFilesSource() != nil { t.Fatalf("claim off registered: %v %v", err, productionClaimFilesSource()) } if err := registerProductionClaimFiles(func(k string) string { return "1" }, "/data", nil); !errors.Is(err, hostwrite.ErrUnavailable) || productionClaimFilesSource() != nil { t.Fatalf("claim on without a registry: %v", err) } if err := registerProductionClaimFiles(func(k string) string { return "1" }, "/data", reg); err != nil || productionClaimFilesSource() == nil { t.Fatalf("claim on: %v", err) } }