package hostrelocate import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "testing" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostfiles" "github.com/carlosframework/platform/internal/instancekey" "github.com/carlosframework/platform/internal/release" ) // O2' (instance-key plan rev 3, replacing stage B's O2 and ruling 55's // KeyEvidence reader): a nonempty database's pinned manifest evidences its // key by (a) instance.key, or (b') an adoption record bound to the pinned // writer for the key the target will deliver. Presence of a key, in the env // or in the configuration, is never evidence (Astra rounds 1 and 2). func TestStorePinnerKeyEvidence(t *testing.T) { ctx := context.Background() at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC) const key = "q+/Zk3vJ0aW1Yx9sR7tUe2oPl4mN8bC6dF5gH0iJ1kA=" writer := instancekey.Binding{Host: host, Incarnation: "inc-1", Session: 3, Generation: "g3"} earlier := instancekey.Binding{Host: host, Incarnation: "inc-1", Session: 2, Generation: "g2"} envWithKey := "CARLOS_ADMIN_TOKEN=t\nCARLOS_INSTANCE_KEY=" + key + "\n" rec := func(b instancekey.Binding, source, k string) string { return string(instancekey.AdoptionRecord(b, source, instancekey.Digest(k))) } for _, tc := range []struct { name string appKey bool // (a) instance.key in the manifest env, adoption string // "" is no item unreadable string // a named item whose object is missing holder func(*hostclaim.Holder) want error pinned *KeyPin }{ {name: "(a) the app's own instance.key", appKey: true, env: envWithKey, want: nil}, {name: "(b') the platform key, adopted by the pinned writer", env: envWithKey, adoption: rec(writer, "platform", key), pinned: &KeyPin{Source: "platform", SHA256: instancekey.Digest(key)}}, {name: "(b') a configured key, adopted by the pinned writer", env: "CARLOS_ADMIN_TOKEN=t\n", adoption: rec(writer, "config", "configured-A"), pinned: &KeyPin{Source: "config", SHA256: instancekey.Digest("configured-A")}}, {name: "(b') a configured key under an empty override", env: "CARLOS_ADMIN_TOKEN=t\nCARLOS_INSTANCE_KEY=\n", adoption: rec(writer, "config", "configured-A"), pinned: &KeyPin{Source: "config", SHA256: instancekey.Digest("configured-A")}}, {name: "a backed-up platform key, the app's own key missing", env: envWithKey, want: ErrFilesUnevidenced}, {name: "configured A, the app's own unpublished B: no report, no record", env: "CARLOS_ADMIN_TOKEN=t\n", want: ErrFilesUnevidenced}, {name: "a stale record from a previous release", env: envWithKey, adoption: rec(earlier, "platform", key), want: ErrFilesUnevidenced}, {name: "a rollback: a new session, the old session's record", env: envWithKey, adoption: rec(writer, "platform", key), holder: func(h *hostclaim.Holder) { h.Session, h.Generation = 4, "g4" h.Admissions = append(h.Admissions, hostclaim.AdmissionRecord{Generation: "g4", Predecessor: "g3", Session: 4}) }, want: ErrFilesUnevidenced}, {name: "a record from another installation", env: envWithKey, adoption: rec(instancekey.Binding{Host: host, Incarnation: "inc-0", Session: 3, Generation: "g3"}, "platform", key), want: ErrFilesUnevidenced}, {name: "a platform record for another key", env: envWithKey, adoption: rec(writer, "platform", "another-key"), want: ErrFilesUnevidenced}, {name: "a config record while the env's own key overrides it", env: envWithKey, adoption: rec(writer, "config", key), want: ErrFilesUnevidenced}, {name: "a platform record without the platform key", env: "CARLOS_ADMIN_TOKEN=t\n", adoption: rec(writer, "platform", key), want: ErrFilesUnevidenced}, {name: "an exported key line is not what the app receives", env: "export CARLOS_INSTANCE_KEY=" + key + "\n", adoption: rec(writer, "platform", key), want: ErrFilesUnevidenced}, {name: "a malformed record", env: envWithKey, adoption: `{"v":1}`, want: ErrFilesUnevidenced}, {name: "no admitted writer for the pinned generation", env: envWithKey, adoption: rec(writer, "platform", key), holder: func(h *hostclaim.Holder) { h.Incarnation = "" }, want: ErrFilesUnevidenced}, {name: "a named adoption item that cannot be read", env: envWithKey, unreadable: instancekey.AdoptionItem, want: errUnreadable}, {name: "a named env item that cannot be read", adoption: rec(writer, "config", "configured-A"), unreadable: hostfiles.EnvItem, want: errUnreadable}, } { t.Run(tc.name, func(t *testing.T) { claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir()) obs := pinClaim() obs.Claim.Holder.Incarnation = "inc-1" gen := "g3" if tc.holder != nil { tc.holder(obs.Claim.Holder) gen = obs.Claim.Holder.Generation } m := withItem(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 4, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}, hostfiles.WakeItem, "d") if tc.appKey { m = keyed(m) } item := func(name, body string, put bool) { sum := sha256.Sum256([]byte(body)) sha := hex.EncodeToString(sum[:]) obj := pinLocation + "/files/g-" + gen + "/objects/" + fmt.Sprintf("%016d-%s", 4, sha) if put { if _, err := data.Put(ctx, obj, []byte(body), ""); err != nil { t.Fatal(err) } } m.Platform[name] = hostfiles.Entry{Object: obj, SHA256: sha, Size: int64(len(body)), Mode: 0o600, Seq: 4} } if tc.env != "" { item(hostfiles.EnvItem, tc.env, true) } if tc.adoption != "" { item(instancekey.AdoptionItem, tc.adoption, true) } if tc.unreadable != "" { item(tc.unreadable, "never stored\n", false) } putManifest(t, data, gen, m) p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)} ref, err := p.Pin(ctx, obs, at) switch { case tc.want == errUnreadable: if err == nil || errors.Is(err, ErrFilesUnevidenced) { t.Fatalf("got %v, want a read error, never a refusal for no evidence", err) } return case !errors.Is(err, tc.want): t.Fatalf("got %v, want %v", err, tc.want) } if err != nil { return } raw, _, err := claims.Get(ctx, ref.Key) if err != nil { t.Fatal(err) } var obj PinObject if err := json.Unmarshal(raw, &obj); err != nil { t.Fatal(err) } if (obj.InstanceKey == nil) != (tc.pinned == nil) || (tc.pinned != nil && *obj.InstanceKey != *tc.pinned) { t.Fatalf("pinned key %+v, want %+v", obj.InstanceKey, tc.pinned) } }) } } var errUnreadable = errors.New("sentinel: any read error") // A configured key's record vouches only beside the env item that shows // the instance's own file supplies no key. A manifest without host.env is // not evidence that nothing overrides the configuration at launch (absence // is never evidence): the pin refuses, and the same record beside an env // item with no key of its own pins. func TestStorePinnerConfigRecordNeedsTheEnvItem(t *testing.T) { ctx := context.Background() at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC) writer := instancekey.Binding{Host: host, Incarnation: "inc-1", Session: 3, Generation: "g3"} record := string(instancekey.AdoptionRecord(writer, instancekey.SourceConfig, instancekey.Digest("configured-A"))) for _, tc := range []struct { name string env string // "" or "item" want error }{ {"an env item never published", "", ErrFilesUnevidenced}, {"an env item with no key of its own", "item", nil}, } { t.Run(tc.name, func(t *testing.T) { claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir()) obs := pinClaim() obs.Claim.Holder.Incarnation = "inc-1" m := withItem(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 4, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}, hostfiles.WakeItem, "d") item := func(name, body string) { sum := sha256.Sum256([]byte(body)) sha := hex.EncodeToString(sum[:]) obj := pinLocation + "/files/g-g3/objects/" + fmt.Sprintf("%016d-%s", 4, sha) if _, err := data.Put(ctx, obj, []byte(body), ""); err != nil { t.Fatal(err) } m.Platform[name] = hostfiles.Entry{Object: obj, SHA256: sha, Size: int64(len(body)), Mode: 0o600, Seq: 4} } item(instancekey.AdoptionItem, record) if tc.env == "item" { item(hostfiles.EnvItem, "CARLOS_ADMIN_TOKEN=t\n") } putManifest(t, data, "g3", m) p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)} ref, err := p.Pin(ctx, obs, at) if tc.want != nil { if !errors.Is(err, tc.want) { t.Fatalf("got %v, want %v", err, tc.want) } return } if err != nil { t.Fatal(err) } obj, err := ReadPin(ctx, claims, ref) if err != nil { t.Fatal(err) } want := KeyPin{Source: instancekey.SourceConfig, SHA256: instancekey.Digest("configured-A")} if obj.InstanceKey == nil || *obj.InstanceKey != want { t.Fatalf("pinned key %+v, want %+v", obj.InstanceKey, want) } }) } }