carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1// The per-app production flag — the console bake controls spec
2// (docs/superpowers/specs/2026-08-04-console-bake-controls-design.md, §2–§3).
3//
4// production: true means the supply-chain bake window (bake.go) applies to
5// this app's held channels; false — and MISSING, the default — means its
6// boxes adopt immediately. The object is root-signed in the pointer's own
7// signedWrapper envelope, and root-ONLY: a wrapper carrying a key grant is
8// refused before its signature is even checked, because a scoped key that
9// could flip the flag could skip the very window it bakes under.
10//
11// What verification here does NOT decide: whether a missing or unreadable
12// flag downgrades a box that once saw production: true. It never does —
13// that memory, the ratchet and the replay floor are the box's own
14// (bake.go's production memory), because anyone with bucket write but no
15// key can delete an object, and deletion must not be a downgrade (spec §4).
16package release
17
18import (
19 "context"
20 "crypto/ed25519"
21 "encoding/base64"
22 "encoding/json"
23 "errors"
24 "fmt"
25 "os"
26 "time"
27)
28
29// ProductionFlag is the signed payload: one app's declaration that it is (or
30// is not) in production, and therefore whether its releases bake.
31type ProductionFlag struct {
32 Schema int `json:"schema"` // 1
33 Account string `json:"account"`
34 App string `json:"app"`
35 Production bool `json:"production"`
36 // Actor is who flipped it — a console member's address. Recorded for the
37 // same reason the pointer's audit trail exists: an invisible bypass is a
38 // broken gate.
39 Actor string `json:"actor,omitempty"`
40 SetAt time.Time `json:"set_at"`
41}
42
43// ProductionKey is where an app's flag lives: beside channels/ and
44// releases/, deliberately NEVER under ChannelsPrefix — those listings are
45// parsed as channel pointers by the console and the CLI.
46//
47// WARNING to anything that copies objects under an app prefix (`carlos
48// accounts migrate` is the one such thing today): this object must be
49// RE-DECLARED at a new account, never copied. Its signed payload names the
50// account, so a byte-identical copy fails validateProductionFlag at the
51// destination forever — which every box reads as "unreadable, keep baking",
52// holding the app's held heads with no expiry and no error anyone sees.
53func ProductionKey(acct, app string) string {
54 return appPrefix(acct, app) + "production.json"
55}
56
57// SetProduction writes the flag, root key only, CAS over whatever is there
58// (a concurrent flip surfaces as ErrCASConflict — re-ask, never overwrite
59// blind). SetAt is stamped here; boxes use their OWN sighting for the
60// ratchet and this stamp only as a replay floor.
61func claimRawSetProduction(ctx context.Context, s Store, priv ed25519.PrivateKey, acct, app string, production bool, actor string) error {
62 if len(priv) != ed25519.PrivateKeySize {
63 return fmt.Errorf("set production: no usable release signing key")
64 }
65 if !ValidAccount(acct) {
66 return fmt.Errorf("set production: invalid account %q", acct)
67 }
68 if !ValidApp(app) {
69 return fmt.Errorf("set production: invalid app %q", app)
70 }
71 // A trashed app cannot be declared production, in EITHER direction: §3
72 // already refuses ship/promote to one, and a production flag appearing
73 // on an app in the trash would contradict the flag's own promise
74 // (production = never deletable). There is nothing legitimate to do to
75 // a trashed app's flag; restore first. Unreadable refuses too — the
76 // usual direction: a caller about to declare production must never act
77 // as though a marker it cannot verify simply isn't there.
78 tomb, err := ReadAppTombstone(ctx, s, acct, app)
79 if err != nil {
80 return fmt.Errorf("set production: cannot check the trash marker for %s/%s: %w", acct, app, err)
81 }
82 if tomb != nil {
83 return fmt.Errorf("set production: %s/%s is in the trash — restore it before flagging it production", acct, app)
84 }
85 key := ProductionKey(acct, app)
86 raw, etag, err := s.Get(ctx, key)
87 if errors.Is(err, os.ErrNotExist) {
88 etag = "" // first write: create-only
89 } else if err != nil {
90 return fmt.Errorf("set production: read %s: %w", key, err)
91 }
92 setAt := time.Now().UTC().Truncate(time.Second)
93 if err == nil {
94 // SetAt is truncated to the second; two flips inside one wall-clock
95 // second would otherwise share a value, and the box-side replay
96 // floor (bake.go's NewestSetAt) treats an equal set_at as nothing
97 // new — silently swallowing the second flip on every box. Bump
98 // strictly past whatever this key's own last signed write recorded.
99 if existing, verr := verifyProductionFlag(priv.Public().(ed25519.PublicKey), raw, acct, app); verr == nil && !existing.SetAt.Before(setAt) {
100 setAt = existing.SetAt.Add(time.Second)
101 }
102 }
103 f := &ProductionFlag{
104 Schema: 1, Account: acct, App: app, Production: production,
105 Actor: actor, SetAt: setAt,
106 }
107 if err := validateProductionFlag(f, acct, app); err != nil {
108 return fmt.Errorf("set production: %w", err)
109 }
110 payload, err := json.MarshalIndent(f, "", " ")
111 if err != nil {
112 return err
113 }
114 b64 := base64.StdEncoding.EncodeToString(payload)
115 wrapper, err := json.MarshalIndent(signedWrapper{
116 Payload: b64,
117 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(priv, []byte(b64))),
118 }, "", " ")
119 if err != nil {
120 return err
121 }
122 _, err = s.Put(ctx, key, wrapper, etag)
123 return err
124}
125
126// GetProduction resolves an app's flag. A MISSING object is (nil, nil): the
127// default, not production — but never let a nil collapse the distinction the
128// spec draws: any OTHER failure (unreadable, unverifiable, misnamed) is the
129// error, and callers on the adopt path treat it as production=true, failing
130// toward baking.
131func GetProduction(ctx context.Context, s Store, pub ed25519.PublicKey, acct, app string) (*ProductionFlag, error) {
132 raw, _, err := s.Get(ctx, ProductionKey(acct, app))
133 if errors.Is(err, os.ErrNotExist) {
134 return nil, nil
135 }
136 if err != nil {
137 return nil, err
138 }
139 return verifyProductionFlag(pub, raw, acct, app)
140}
141
142// verifyProductionFlag is verifyPointer's discipline for the flag: refuse a
143// grant outright, check the signature against the ROOT key alone, decode
144// after, validate the payload names the account/app whose key it sits at.
145func verifyProductionFlag(pub ed25519.PublicKey, wrapper []byte, wantAcct, wantApp string) (*ProductionFlag, error) {
146 var sw signedWrapper
147 if err := json.Unmarshal(wrapper, &sw); err != nil {
148 return nil, fmt.Errorf("production flag wrapper: %v", err)
149 }
150 if sw.Grant != nil {
151 return nil, fmt.Errorf("production flag %s/%s: signed by a scoped key — the flag takes the root key, because a delegate that could flip it could skip the window it bakes under", wantAcct, wantApp)
152 }
153 if len(pub) != ed25519.PublicKeySize {
154 return nil, fmt.Errorf("production flag: no usable release public key")
155 }
156 sig, err := base64.StdEncoding.DecodeString(sw.Sig)
157 if err != nil {
158 return nil, fmt.Errorf("production flag sig: %v", err)
159 }
160 if !ed25519.Verify(pub, []byte(sw.Payload), sig) {
161 return nil, fmt.Errorf("production flag signature verification failed")
162 }
163 payload, err := base64.StdEncoding.DecodeString(sw.Payload)
164 if err != nil {
165 return nil, fmt.Errorf("production flag payload: %v", err)
166 }
167 var f ProductionFlag
168 if err := json.Unmarshal(payload, &f); err != nil {
169 return nil, fmt.Errorf("production flag payload: %v", err)
170 }
171 if err := validateProductionFlag(&f, wantAcct, wantApp); err != nil {
172 return nil, err
173 }
174 return &f, nil
175}
176
177func validateProductionFlag(f *ProductionFlag, wantAcct, wantApp string) error {
178 if f.Schema != 1 {
179 return fmt.Errorf("unsupported production flag schema %d", f.Schema)
180 }
181 if f.Account != wantAcct || f.App != wantApp {
182 return fmt.Errorf("production flag names %s/%s, expected %s/%s", f.Account, f.App, wantAcct, wantApp)
183 }
184 if !ValidAccount(f.Account) || !ValidApp(f.App) {
185 return fmt.Errorf("production flag has invalid account/app %s/%s", f.Account, f.App)
186 }
187 if f.SetAt.IsZero() {
188 return fmt.Errorf("production flag has no set_at")
189 }
190 return nil
191}
192func SetProduction(ctx context.Context, s Store, priv ed25519.PrivateKey, acct, app string, production bool, actor string) (claimResult0 error) {
193 claimResult0 = WithAppMutation(ctx, s, acct, app, "release.SetProduction", func(claimContext context.Context) error {
194 claimResult0 = claimRawSetProduction(claimContext, s, priv, acct, app, production, actor)
195 return claimResult0
196 })
197 return
198}
199