carlos / platform Public

settings-2 task 2: the signed promotion record and AuthorizedManifest

The record lives at releases/<acct>/<app>/promoted/<version>. The hostops
mutation classifier gains a reviewed, app-scoped promotion-record family
for exactly that key, so a claim-enabled console can write it (ruling R1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 024fb554781e1514061219e13410aad122afbb47 parent 75aae42
4 files changed, +395 −0
  • internal/hostops/mutation_classifier.go +16 −0
  • internal/hostops/promotionrecord_classifier_test.go +54 −0
  • internal/release/promotion.go +181 −0
  • internal/release/promotion_test.go +144 −0
diff --git a/internal/hostops/mutation_classifier.go b/internal/hostops/mutation_classifier.go
index e9694df65..69ae1ce2a 100644
--- a/internal/hostops/mutation_classifier.go
+++ b/internal/hostops/mutation_classifier.go
@@ -148,6 +148,8 @@ func ClassifyMutation(ctx context.Context, raw release.Store, w *ControlWriter,
return classifyGeoip(t)
case strings.HasPrefix(t.Key, "store/entitlements/") && len(p) == 3:
return Classification{Family: "store-entitlement", NonApp: "operator-set account entitlement; no app state"}, nil
+ case p[0] == "releases" && len(p) == 5 && p[3] == "promoted":
+ return classifyPromotionRecord(t, p)
case p[0] == "releases":
return classifyPointerDigestMarker(t, p)
}
@@ -179,6 +181,20 @@ func classifyPointerDigestMarker(t MutationTarget, p []string) (Classification,
NonApp: "root-signed account switch for pointer digest emission; no app state"}, nil
}
+// classifyPromotionRecord admits a build's signed promotion record,
+// releases/<acct>/<app>/promoted/<version> (settings plan 2, spec §4.1),
+// written when a version is promoted or authorized and deleted when the app
+// is purged. It is app state, scoped to exactly that app. Anything else
+// under releases/<acct>/<app>/ stays unreviewed.
+func classifyPromotionRecord(t MutationTarget, p []string) (Classification, error) {
+ if len(p) != 5 || p[3] != "promoted" || !controlname.ValidAccount(p[1]) || !controlname.ValidApp(p[2]) ||
+ !release.ValidVersion(p[4]) || p[4] == "dev" || t.Key != release.PromotionRecordKey(p[1], p[2], p[4]) ||
+ (t.Method != "Put" && t.Method != "Delete") {
+ return Classification{}, fmt.Errorf("%w: %s", ErrUnclassified, t.Key)
+ }
+ return appScoped("promotion-record", p[1], p[2])
+}
+
func appScoped(family, account, app string) (Classification, error) {
if !controlname.ValidAccount(account) || !controlname.ValidApp(app) {
return Classification{}, ErrIdentity
diff --git a/internal/hostops/promotionrecord_classifier_test.go b/internal/hostops/promotionrecord_classifier_test.go
new file mode 100644
index 000000000..adda9c3dc
--- /dev/null
+++ b/internal/hostops/promotionrecord_classifier_test.go
@@ -0,0 +1,54 @@
+package hostops
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/carlosframework/platform/internal/release"
+)
+
+// TestClassifyPromotionRecordIsExact: a build's signed promotion record,
+// releases/<acct>/<app>/promoted/<version> (settings plan 2, spec §4.1), is
+// a reviewed family scoped to exactly its app, put and deleted; nothing
+// else under releases/<acct>/<app>/ is.
+func TestClassifyPromotionRecordIsExact(t *testing.T) {
+ ctx := context.Background()
+ key := release.PromotionRecordKey("bab", "km", "v1")
+ for _, c := range []struct {
+ name string
+ target MutationTarget
+ }{
+ {"record put", MutationTarget{Surface: "bucket", Method: "Put", Key: key, After: []byte(`{}`)}},
+ {"record put over an existing one", MutationTarget{Surface: "bucket", Method: "Put", Key: key, ETag: "x", Before: []byte(`{}`), After: []byte(`{}`)}},
+ {"record delete", MutationTarget{Surface: "bucket", Method: "Delete", Key: key}},
+ } {
+ got, err := ClassifyMutation(ctx, nil, nil, c.target)
+ if err != nil {
+ t.Errorf("%s: %v", c.name, err)
+ continue
+ }
+ if got.Family != "promotion-record" || got.NonApp != "" || got.MailGate ||
+ len(got.Scopes) != 1 || got.Scopes[0] != (AppScope{"bab", "km"}) {
+ t.Errorf("%s: classified %+v, want family promotion-record scoped to exactly {bab km}", c.name, got)
+ }
+ }
+ for _, c := range []struct {
+ name string
+ target MutationTarget
+ }{
+ {"an invalid account", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/BAB!/km/promoted/v1", After: []byte(`{}`)}},
+ {"an invalid app", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/bab/KM!/promoted/v1", After: []byte(`{}`)}},
+ {"an empty version", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/bab/km/promoted/", After: []byte(`{}`)}},
+ {"a sixth segment", MutationTarget{Surface: "bucket", Method: "Put", Key: key + "/x", After: []byte(`{}`)}},
+ {"no version", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/bab/km/promoted", After: []byte(`{}`)}},
+ {"another name under the app", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/bab/km/other/v1", After: []byte(`{}`)}},
+ {"the dev version", MutationTarget{Surface: "bucket", Method: "Put", Key: "releases/bab/km/promoted/dev", After: []byte(`{}`)}},
+ {"a delete-if-match", MutationTarget{Surface: "bucket", Method: "DeleteIfMatch", Key: key, ETag: "x"}},
+ } {
+ got, err := ClassifyMutation(ctx, nil, nil, c.target)
+ if !errors.Is(err, ErrUnclassified) && !errors.Is(err, ErrIdentity) {
+ t.Errorf("%s: classified %+v, err %v; want ErrUnclassified or ErrIdentity", c.name, got, err)
+ }
+ }
+}
diff --git a/internal/release/promotion.go b/internal/release/promotion.go
new file mode 100644
index 000000000..ed73eef25
--- /dev/null
+++ b/internal/release/promotion.go
@@ -0,0 +1,181 @@
+package release
+
+import (
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "time"
+)
+
+// PromotionRecordKey is the durable record of one promoted build (spec §4.1).
+// It sits outside AppPrefix, beside the account's pointer digest marker, so
+// PurgeApp sweeps it explicitly.
+func PromotionRecordKey(acct, app, version string) string {
+ return "releases/" + acct + "/" + app + "/promoted/" + version
+}
+
+// PromotionRecord binds a version name to the exact manifest that was
+// promoted (or explicitly authorized). It keeps a build authorized after it
+// falls out of the pointer's history, and a reused name cannot inherit it:
+// the new name's manifest hashes to another digest.
+type PromotionRecord struct {
+ Schema int `json:"schema"` // 1
+ Account string `json:"account"`
+ App string `json:"app"`
+ Version string `json:"version"`
+ ManifestDigest string `json:"manifest_digest"`
+ Channel string `json:"channel,omitempty"`
+ Authorized bool `json:"authorized,omitempty"`
+ Actor string `json:"actor,omitempty"`
+ At time.Time `json:"at"`
+}
+
+// ErrNoPromotionRecord is a build with no record: promoted before records
+// existed, or never promoted. It wraps os.ErrNotExist.
+var ErrNoPromotionRecord = fmt.Errorf("release: no promotion record: %w", os.ErrNotExist)
+
+func validateRecord(r *PromotionRecord, acct, app, version string) error {
+ if r.Schema != 1 {
+ return fmt.Errorf("unsupported promotion record schema %d", r.Schema)
+ }
+ if r.Account != acct || r.App != app || r.Version != version {
+ return fmt.Errorf("promotion record names %s/%s/%s, expected %s/%s/%s", r.Account, r.App, r.Version, acct, app, version)
+ }
+ if !ValidAccount(acct) || !ValidApp(app) || !ValidVersion(version) || version == "dev" {
+ return fmt.Errorf("promotion record for invalid %s/%s/%s", acct, app, version)
+ }
+ if !validDigest(r.ManifestDigest) {
+ return fmt.Errorf("promotion record has an invalid manifest digest")
+ }
+ if r.Channel != "" && !ValidChannel(r.Channel) {
+ return fmt.Errorf("promotion record has an invalid channel %q", r.Channel)
+ }
+ if r.Channel == "" && !r.Authorized {
+ return fmt.Errorf("promotion record names no channel and is not an authorization")
+ }
+ if r.At.IsZero() {
+ return fmt.Errorf("promotion record has no time")
+ }
+ return nil
+}
+
+// WritePromotionRecord signs rec, stamped now, and writes it unless the key
+// already holds a verified record for the same digest: one record per
+// version, kept from its first promotion. A record for other bytes (a name
+// pruned and reshipped) is replaced. A scoped signer may write only a record
+// whose channel its grant covers, and never an authorization.
+func (sg Signer) WritePromotionRecord(ctx context.Context, s Store, rec PromotionRecord) error {
+ if err := sg.usable(); err != nil {
+ return err
+ }
+ rec.Schema = 1
+ rec.At = time.Now().UTC().Truncate(time.Second)
+ if err := validateRecord(&rec, rec.Account, rec.App, rec.Version); err != nil {
+ return err
+ }
+ if rec.Channel == "" || rec.Authorized {
+ if sg.scope != nil {
+ return fmt.Errorf("promotion record: an authorization needs the deployment's root key")
+ }
+ } else if err := sg.allows(rec.Channel, rec.At); err != nil {
+ return fmt.Errorf("promotion record: %w", err)
+ }
+ key := PromotionRecordKey(rec.Account, rec.App, rec.Version)
+ _, etag, err := s.Get(ctx, key)
+ switch {
+ case errors.Is(err, os.ErrNotExist):
+ etag = ""
+ case err != nil:
+ return err
+ default:
+ if cur, rerr := ReadPromotionRecord(ctx, s, sg.verifyPub(), rec.Account, rec.App, rec.Version); rerr == nil && cur.ManifestDigest == rec.ManifestDigest {
+ return nil
+ }
+ }
+ payload, err := json.MarshalIndent(rec, "", " ")
+ if err != nil {
+ return err
+ }
+ b64 := base64.StdEncoding.EncodeToString(payload)
+ wire, err := json.MarshalIndent(signedWrapper{
+ Payload: b64,
+ Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(sg.key, []byte(b64))),
+ Grant: sg.grant,
+ }, "", " ")
+ if err != nil {
+ return err
+ }
+ _, err = s.Put(ctx, key, wire, etag)
+ return err
+}
+
+// ReadPromotionRecord verifies the record at version's key: root -> grant ->
+// record, the chain verifyPointer checks, and the signed payload must name
+// this account, app and version. (nil, ErrNoPromotionRecord) when absent; any
+// other failure is an error that is never read as "absent".
+func ReadPromotionRecord(ctx context.Context, s Store, pub ed25519.PublicKey, acct, app, version string) (*PromotionRecord, error) {
+ raw, _, err := s.Get(ctx, PromotionRecordKey(acct, app, version))
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, fmt.Errorf("%s/%s: %w", app, version, ErrNoPromotionRecord)
+ }
+ if err != nil {
+ return nil, err
+ }
+ var sw signedWrapper
+ if err := json.Unmarshal(raw, &sw); err != nil {
+ return nil, fmt.Errorf("promotion record wrapper: %v", err)
+ }
+ signer, scope := pub, (*Grant)(nil)
+ if sw.Grant != nil {
+ g, err := verifyGrant(pub, sw.Grant)
+ if err != nil {
+ return nil, err
+ }
+ gpub, err := ParsePublicKey(g.Pub, "key grant pub")
+ if err != nil {
+ return nil, err
+ }
+ signer, scope = gpub, g
+ }
+ sig, err := base64.StdEncoding.DecodeString(sw.Sig)
+ if err != nil || len(signer) != ed25519.PublicKeySize || !ed25519.Verify(signer, []byte(sw.Payload), sig) {
+ return nil, fmt.Errorf("promotion record %s/%s: signature verification failed", app, version)
+ }
+ payload, err := base64.StdEncoding.DecodeString(sw.Payload)
+ if err != nil {
+ return nil, fmt.Errorf("promotion record payload: %v", err)
+ }
+ var rec PromotionRecord
+ if err := json.Unmarshal(payload, &rec); err != nil {
+ return nil, fmt.Errorf("promotion record payload: %v", err)
+ }
+ if err := validateRecord(&rec, acct, app, version); err != nil {
+ return nil, err
+ }
+ if scope != nil {
+ if rec.Channel == "" || rec.Authorized {
+ return nil, fmt.Errorf("promotion record %s/%s: an authorization signed by a scoped key", app, version)
+ }
+ if err := scope.allows(rec.Channel, rec.At); err != nil {
+ return nil, fmt.Errorf("promotion record %s/%s: %w", app, version, err)
+ }
+ }
+ return &rec, nil
+}
+
+// AuthorizedManifest is §4.1's evidence for every install the settings add:
+// the build's signed record, and its canonical manifest hashing to the
+// record's digest. The manifest it returns is the one it checked; the caller
+// stages from it and never re-reads it.
+func AuthorizedManifest(ctx context.Context, s Store, pub ed25519.PublicKey, acct, app, version string) (*Manifest, error) {
+ rec, err := ReadPromotionRecord(ctx, s, pub, acct, app, version)
+ if err != nil {
+ return nil, err
+ }
+ m, _, err := CheckManifestDigest(ctx, s, acct, app, version, rec.ManifestDigest)
+ return m, err
+}
diff --git a/internal/release/promotion_test.go b/internal/release/promotion_test.go
new file mode 100644
index 000000000..99444b8de
--- /dev/null
+++ b/internal/release/promotion_test.go
@@ -0,0 +1,144 @@
+package release
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+ "time"
+)
+
+func recordFor(t *testing.T, s Store, sg Signer, acct, app, ver, channel string) PromotionRecord {
+ t.Helper()
+ raw, _, err := s.Get(context.Background(), ManifestKey(acct, app, ver))
+ if err != nil {
+ t.Fatal(err)
+ }
+ rec := PromotionRecord{Account: acct, App: app, Version: ver, ManifestDigest: ManifestDigest(raw), Channel: channel, Actor: "test"}
+ if err := sg.WritePromotionRecord(context.Background(), s, rec); err != nil {
+ t.Fatalf("WritePromotionRecord: %v", err)
+ }
+ return rec
+}
+
+func TestPromotionRecordRoundTrip(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ want := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
+ got, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
+ if err != nil || got.ManifestDigest != want.ManifestDigest || got.Channel != "stable" || got.At.IsZero() {
+ t.Fatalf("ReadPromotionRecord = %+v, %v", got, err)
+ }
+ if m, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); err != nil || m.Version != "v1" {
+ t.Fatalf("AuthorizedManifest = %+v, %v", m, err)
+ }
+}
+
+// Spec §8: a fresh, never-promoted version with a matching manifest is
+// refused for a pin.
+func TestANeverPromotedVersionHasNoRecord(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ _, pub := testKeys(t)
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1")
+ if !errors.Is(err, ErrNoPromotionRecord) || !errors.Is(err, os.ErrNotExist) {
+ t.Fatalf("AuthorizedManifest = %v; want ErrNoPromotionRecord wrapping os.ErrNotExist", err)
+ }
+}
+
+// Spec §8 "Promotion binding": a reused version name with a replayed record
+// and a different manifest is refused.
+func TestAReplayedRecordDoesNotAuthorizeAReusedName(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("promoted bytes"))
+ recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
+ saved, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
+ if err := DeleteVersion(ctx, s, "bab", "km", "v1"); err != nil {
+ t.Fatal(err)
+ }
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("nobody promoted these"))
+ _, etag, gerr := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
+ if errors.Is(gerr, os.ErrNotExist) {
+ etag = ""
+ }
+ if _, err := s.Put(ctx, PromotionRecordKey("bab", "km", "v1"), saved, etag); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); !errors.Is(err, ErrManifestDigest) {
+ t.Fatalf("AuthorizedManifest = %v; want ErrManifestDigest", err)
+ }
+}
+
+// Review Focus 2.
+func TestARecordCopiedToAnotherKeyIsRefused(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
+ raw, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1"))
+ if _, err := s.Put(ctx, PromotionRecordKey("bab", "blog", "v1"), raw, ""); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := ReadPromotionRecord(ctx, s, pub, "bab", "blog", "v1"); err == nil || errors.Is(err, ErrNoPromotionRecord) {
+ t.Fatalf("a copied record = %v; want a refusal that is not 'absent'", err)
+ }
+}
+
+func TestScopedRecordsFollowTheGrant(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ root, pub := testKeys(t)
+ seed, grant, err := MintKeyGrant(root, "ci", []string{"canary"}, time.Hour)
+ if err != nil {
+ t.Fatal(err)
+ }
+ key, _ := ParseSigningKey(seed, "test")
+ sg, err := ScopedSigner(key, grant, pub)
+ if err != nil {
+ t.Fatal(err)
+ }
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ raw, _, _ := s.Get(ctx, ManifestKey("bab", "km", "v1"))
+ d := ManifestDigest(raw)
+ if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "canary/x"}); err != nil {
+ t.Fatalf("in scope: %v", err)
+ }
+ if rec, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); err != nil || rec.Channel != "canary/x" {
+ t.Fatalf("scoped record = %+v, %v", rec, err)
+ }
+ if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "stable"}); err == nil {
+ t.Fatal("a canary-scoped key signed a stable record")
+ }
+ if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Authorized: true}); err == nil {
+ t.Fatal("a scoped key signed an authorize record")
+ }
+}
+
+func TestWriteKeepsAnIdenticalRecordAndReplacesAnother(t *testing.T) {
+ s := NewDirStore(t.TempDir())
+ ctx := context.Background()
+ priv, pub := testKeys(t)
+ shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one"))
+ first := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "canary/x")
+ got1, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
+ time.Sleep(1100 * time.Millisecond)
+ recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable")
+ got2, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1")
+ if !got2.At.Equal(got1.At) || got2.Channel != "canary/x" {
+ t.Fatalf("an identical digest replaced the record: %+v -> %+v", got1, got2)
+ }
+ other := first
+ other.ManifestDigest = ManifestDigest([]byte("another manifest"))
+ if err := RootSigner(priv).WritePromotionRecord(ctx, s, other); err != nil {
+ t.Fatal(err)
+ }
+ if got3, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); got3.ManifestDigest != other.ManifestDigest {
+ t.Fatal("a record for different bytes did not replace the old one")
+ }
+}