| 1 | package hostbox |
| 2 | |
| 3 | import ( |
| 4 | "slices" |
| 5 | |
| 6 | "github.com/carlosframework/platform/internal/hostclaim" |
| 7 | ) |
| 8 | |
| 9 | // RowCase is what reconcile does with one relocatable host's registry row |
| 10 | // on this box (region HA spec 1 §5.3, claim C4 as amended by L12). |
| 11 | type RowCase int |
| 12 | |
| 13 | const ( |
| 14 | // RowNone: mint nothing, and there is no row to keep. |
| 15 | RowNone RowCase = iota |
| 16 | // RowReady: mint the row, or keep it, ready. |
| 17 | RowReady |
| 18 | // RowHeld: mint the row, or keep it, held with HoldOp. |
| 19 | RowHeld |
| 20 | // RowRetiring: keep an existing row held; mint none. A park-local chore |
| 21 | // this box owes retires it under the host guard (claim P6-D2). |
| 22 | RowRetiring |
| 23 | // RowUnexplained: the claim gives this box no role, yet a row exists. |
| 24 | // Keep it held, never serve it, and alert. Never delete it by hostname. |
| 25 | RowUnexplained |
| 26 | ) |
| 27 | |
| 28 | // RowDecision is RelocationRow's answer. |
| 29 | type RowDecision struct { |
| 30 | Case RowCase |
| 31 | HoldOp string |
| 32 | } |
| 33 | |
| 34 | // RelocationRow decides one row from a fresh claim read. box is this box's |
| 35 | // reference; exists says whether the registry holds a row for the host; |
| 36 | // fenceClosed says whether this box's local admission fence for the host is |
| 37 | // closed (the source's release has begun, §5.8's release sequence step 1). |
| 38 | // |
| 39 | // op.exec, serving (Serving's own rule) ready |
| 40 | // relocating source in starting, or releasing |
| 41 | // until its admission fence closes ready |
| 42 | // any other participant of a running operation held, hold_op = op id |
| 43 | // owner of an open park-local chore for the host retiring (held, no mint) |
| 44 | // none of these none; an existing row is unexplained |
| 45 | // |
| 46 | // A box on the claim's lost list is never ready. A nil or holderless claim |
| 47 | // (an error is the caller's: claim C6, keep rows and mint none) gives none. |
| 48 | func RelocationRow(c *hostclaim.Claim, box string, exists, fenceClosed bool) RowDecision { |
| 49 | if c == nil || c.Holder == nil || box == "" { |
| 50 | return unexplained(exists) |
| 51 | } |
| 52 | lost := slices.Contains(c.Lost, box) |
| 53 | switch { |
| 54 | case lost: |
| 55 | case Serving(c, Selection{Box: box}) == nil: |
| 56 | return RowDecision{Case: RowReady} |
| 57 | case c.Op.Kind == hostclaim.KindRelocating && c.Op.Exec == box && c.Op.Intent["source_box"] == box && |
| 58 | (c.Op.Phase == hostclaim.PhaseStarting || (c.Op.Phase == hostclaim.PhaseReleasing && !fenceClosed)): |
| 59 | return RowDecision{Case: RowReady} |
| 60 | case c.Op.Kind != hostclaim.KindServing && c.Op.ID != "" && slices.Contains(c.Op.Participants, box): |
| 61 | return RowDecision{Case: RowHeld, HoldOp: c.Op.ID} |
| 62 | } |
| 63 | for _, o := range c.Obligations { |
| 64 | if o.Kind == "park-local" && o.Box == box { |
| 65 | if !exists { |
| 66 | return RowDecision{Case: RowNone} |
| 67 | } |
| 68 | return RowDecision{Case: RowRetiring, HoldOp: o.Op} |
| 69 | } |
| 70 | } |
| 71 | return unexplained(exists) |
| 72 | } |
| 73 | |
| 74 | func unexplained(exists bool) RowDecision { |
| 75 | if exists { |
| 76 | return RowDecision{Case: RowUnexplained} |
| 77 | } |
| 78 | return RowDecision{Case: RowNone} |
| 79 | } |
| 80 | |