carlos / platform Public

hostrelocate: a configured key's adoption record is accepted beside the env item with no key of its own or the release flush's deletion entry for it, and an env item never published still refuses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev bd4d9355bd4dad4c4d75483a108bd16ac4326faa parent 4328806
2 files changed, +20 −13
  • internal/hostrelocate/pin.go +7 −4
  • internal/hostrelocate/pin_envkey_test.go +13 −9
diff --git a/internal/hostrelocate/pin.go b/internal/hostrelocate/pin.go
index 4ca47d4a0..272031c63 100644
--- a/internal/hostrelocate/pin.go
+++ b/internal/hostrelocate/pin.go
@@ -488,12 +488,15 @@ func (p StorePinner) adoptedKey(ctx context.Context, writer instancekey.Binding,
return nil, err
}
own, _ := instancekey.Effective(nil, instancekey.ParseEnv(env))
+ // A configured key needs positive evidence that the own file supplies
+ // none: the env item with no key of its own, or the release flush's
+ // deletion entry for it. An env item never published is not evidence
+ // that nothing overrides the configuration (absence is never evidence).
+ envEntry, listed := m.Platform[hostfiles.EnvItem]
+ envDeleted := listed && envEntry.Deleted
switch {
case a.Source == instancekey.SourcePlatform && own != "" && instancekey.Digest(own) == a.SHA256:
- // A configured key needs the env item that shows the own file supplies
- // none: a manifest without host.env is not evidence that nothing
- // overrides the configuration (absence is never evidence).
- case a.Source == instancekey.SourceConfig && haveEnv && own == "":
+ case a.Source == instancekey.SourceConfig && (haveEnv || envDeleted) && own == "":
default:
return refuse("its adoption record does not name the key the env delivers")
}
diff --git a/internal/hostrelocate/pin_envkey_test.go b/internal/hostrelocate/pin_envkey_test.go
index bca47d25f..b6b8ff7f8 100644
--- a/internal/hostrelocate/pin_envkey_test.go
+++ b/internal/hostrelocate/pin_envkey_test.go
@@ -127,11 +127,11 @@ func TestStorePinnerKeyEvidence(t *testing.T) {
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.
+// A configured key's record vouches only beside positive evidence that the
+// instance's own file supplies no key: the env item with no key of its
+// own, or the release flush's deletion entry for it. A manifest that never
+// published host.env is not evidence that nothing overrides the
+// configuration at launch (absence is never evidence): the pin refuses.
func TestStorePinnerConfigRecordNeedsTheEnvItem(t *testing.T) {
ctx := context.Background()
at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
@@ -139,11 +139,12 @@ func TestStorePinnerConfigRecordNeedsTheEnvItem(t *testing.T) {
record := string(instancekey.AdoptionRecord(writer, instancekey.SourceConfig, instancekey.Digest("configured-A")))
for _, tc := range []struct {
name string
- env bool
+ env string // "", "item" or "deleted"
want error
}{
- {"no env item", false, ErrFilesUnevidenced},
- {"an env item with no key of its own", true, nil},
+ {"an env item never published", "", ErrFilesUnevidenced},
+ {"an env item with no key of its own", "item", nil},
+ {"the flush's deletion entry for the env item", "deleted", nil},
} {
t.Run(tc.name, func(t *testing.T) {
claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
@@ -160,8 +161,11 @@ func TestStorePinnerConfigRecordNeedsTheEnvItem(t *testing.T) {
m.Platform[name] = hostfiles.Entry{Object: obj, SHA256: sha, Size: int64(len(body)), Mode: 0o600, Seq: 4}
}
item(instancekey.AdoptionItem, record)
- if tc.env {
+ switch tc.env {
+ case "item":
item(hostfiles.EnvItem, "CARLOS_ADMIN_TOKEN=t\n")
+ case "deleted":
+ m.Platform[hostfiles.EnvItem] = hostfiles.Entry{Seq: 4, Deleted: true}
}
putManifest(t, data, "g3", m)
p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}