| 1 | package hostops |
| 2 | |
| 3 | import ( |
| 4 | "github.com/carlosframework/platform/internal/hostclaim" |
| 5 | "github.com/carlosframework/platform/internal/park" |
| 6 | "strconv" |
| 7 | "strings" |
| 8 | ) |
| 9 | |
| 10 | const MoveLossReplicaBaseline = "replica:" |
| 11 | const MoveLossSealBaseline = "seal:" |
| 12 | |
| 13 | // ParseMoveLossBaseline is shared by the frozen-source writer and validators. |
| 14 | func ParseMoveLossBaseline(value string) (kind, region, identity string, err error) { |
| 15 | if strings.HasPrefix(value, MoveLossReplicaBaseline) { |
| 16 | r, g, ok := strings.Cut(strings.TrimPrefix(value, MoveLossReplicaBaseline), "/") |
| 17 | if ok && r != "" && g != "" && !strings.Contains(g, "/") { |
| 18 | return "replica", r, g, nil |
| 19 | } |
| 20 | } |
| 21 | if strings.HasPrefix(value, MoveLossSealBaseline) { |
| 22 | id := strings.TrimPrefix(value, MoveLossSealBaseline) |
| 23 | if id != "" && !strings.Contains(id, "/") { |
| 24 | return "seal", "", id, nil |
| 25 | } |
| 26 | } |
| 27 | return "", "", "", hostclaim.ErrLossRecord |
| 28 | } |
| 29 | |
| 30 | // MoveLossRecoveryOwner shares the resolve owner's signed-intent policy with boxes. |
| 31 | // This policy is pure; it exposes neither the console store nor mutation grants. |
| 32 | func MoveLossRecoveryOwner() hostclaim.LossRecoveryOwner { return &moveLossOwner{} } |
| 33 | |
| 34 | func (m *moveLossOwner) ValidateLossRecovery(c hostclaim.Claim, loss hostclaim.LossRecord, a hostclaim.Admission) error { |
| 35 | in, _, err := lossIntentOf(loss) |
| 36 | if err != nil { |
| 37 | return err |
| 38 | } |
| 39 | stamp, err := strconv.ParseUint(c.Op.Journal[hostclaim.JournalSourceStamp], 10, 64) |
| 40 | if err != nil || stamp == 0 || c.Holder == nil || c.Holder.Stamp != stamp || |
| 41 | c.Holder.Incarnation != in.Incarnation || c.Holder.Key != in.RecordKey || |
| 42 | c.Op.ID != in.Move || c.Op.Kind != hostclaim.KindRestoring || c.Op.Phase != hostclaim.PhaseProbing || |
| 43 | c.Op.Intent["rollback"] != in.Rollback || c.Op.Exec != in.Source || c.Op.Intent["source"] != in.Source || |
| 44 | c.Op.Intent["target"] != in.Target || c.Op.Intent["to"] != in.TargetRegion || |
| 45 | !hostclaim.LostTargetReleased(&c, loss.ID) || a.Box != in.Source || !park.SameRegion(c.Op.Intent["home_region"], a.Region, in.SourceRegion) || |
| 46 | a.Mode != "cold" || a.Coverage != "" || a.KeepStamp || a.PredecessorCertified { |
| 47 | return hostclaim.ErrLossRecord |
| 48 | } |
| 49 | return nil |
| 50 | } |
| 51 | |