| 1 | package release |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "slices" |
| 6 | "strings" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/rootstate" |
| 9 | ) |
| 10 | |
| 11 | // Recovery uses usable denials without asserting that the inventory is complete. |
| 12 | // Minting continues to require Refusals and its durable completeness checks. |
| 13 | func (h *RootHistory) recoveryDenials(b HistoryBinding) []Refusal { |
| 14 | if h == nil || h.Store == nil || !b.valid() { |
| 15 | return nil |
| 16 | } |
| 17 | var refs []Refusal |
| 18 | raw, err := h.Store.Read(historyKey(b)) |
| 19 | var record historyRecord |
| 20 | if err == nil && len(raw) <= rootstate.MaxRecord && strictCanonicalJSON(raw, &record) == nil && record.Schema == 1 && record.Binding == b { |
| 21 | for _, ref := range record.Refusals.Values { |
| 22 | if ref.Schema == 1 && ref.Host == b.Host && ref.App == b.App && ref.Version != "" && !ref.RefusedAt.IsZero() && ref.MoveOp == "" && |
| 23 | (ref.Kind == RefusalNotUp || ref.Kind == RefusalWrongProgram || ref.Kind == RefusalNotProven) { |
| 24 | refs = append(refs, ref) |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | names, _ := h.Store.Names() |
| 29 | var outcomes []HistoryRecovery |
| 30 | for _, name := range names { |
| 31 | if !strings.HasSuffix(name, ".outcome") { |
| 32 | continue |
| 33 | } |
| 34 | raw, err := h.Store.Read(name) |
| 35 | var r historyRecoveryRecord |
| 36 | if err != nil || len(raw) > rootstate.MaxRecord || strictCanonicalJSON(raw, &r) != nil || r.Schema != 1 || !validHistoryRecovery(r.HistoryRecovery) || historyRecoveryKey(r.HistoryRecovery) != name { |
| 37 | continue |
| 38 | } |
| 39 | outcomes = append(outcomes, r.HistoryRecovery) |
| 40 | } |
| 41 | for _, r := range outcomes { |
| 42 | if r.Binding != b || (!r.Failed && !r.Recovering) { |
| 43 | continue |
| 44 | } |
| 45 | retired, err := h.recoveryRetired(r, outcomes) |
| 46 | if err == nil && retired { |
| 47 | continue |
| 48 | } |
| 49 | refs = append(refs, Refusal{Schema: 1, Host: b.Host, App: b.App, Version: r.Attempt.Version, SHA256: r.Attempt.SHA256, Platform: r.Attempt.Platform, PointerPromotedAt: r.Attempt.PromotedAt, RefusedAt: r.Attempt.Started, Kind: RefusalNotUp}) |
| 50 | } |
| 51 | return refs |
| 52 | } |
| 53 | |
| 54 | // RetireD1 accepts only an independently verified later promotion of this |
| 55 | // version. Program and proof refusals keep their separate authorization rules. |
| 56 | func (h *RootHistory) RetireD1(b HistoryBinding, p *ChannelPointer) error { |
| 57 | if p == nil || p.Version == "" || p.PromotedAt.IsZero() { |
| 58 | return os.ErrPermission |
| 59 | } |
| 60 | r, err := h.read(b) |
| 61 | if err != nil { |
| 62 | return err |
| 63 | } |
| 64 | refs := slices.DeleteFunc(slices.Clone(r.Refusals.Values), func(v Refusal) bool { |
| 65 | return v.Kind == RefusalNotUp && v.Version == p.Version && p.PromotedAt.After(v.PointerPromotedAt) |
| 66 | }) |
| 67 | if len(refs) == len(r.Refusals.Values) { |
| 68 | return nil |
| 69 | } |
| 70 | if r.Revision == ^uint64(0) { |
| 71 | return os.ErrPermission |
| 72 | } |
| 73 | r.Revision++ |
| 74 | r.Refusals.Values, r.Refusals.Revision = refs, r.Revision |
| 75 | return h.write(r) |
| 76 | } |
| 77 | |