package hostbox import ( "slices" "github.com/carlosframework/platform/internal/hostclaim" ) // RowCase is what reconcile does with one relocatable host's registry row // on this box (region HA spec 1 §5.3, claim C4 as amended by L12). type RowCase int const ( // RowNone: mint nothing, and there is no row to keep. RowNone RowCase = iota // RowReady: mint the row, or keep it, ready. RowReady // RowHeld: mint the row, or keep it, held with HoldOp. RowHeld // RowRetiring: keep an existing row held; mint none. A park-local chore // this box owes retires it under the host guard (claim P6-D2). RowRetiring // RowUnexplained: the claim gives this box no role, yet a row exists. // Keep it held, never serve it, and alert. Never delete it by hostname. RowUnexplained ) // RowDecision is RelocationRow's answer. type RowDecision struct { Case RowCase HoldOp string } // RelocationRow decides one row from a fresh claim read. box is this box's // reference; exists says whether the registry holds a row for the host; // fenceClosed says whether this box's local admission fence for the host is // closed (the source's release has begun, §5.8's release sequence step 1). // // op.exec, serving (Serving's own rule) ready // relocating source in starting, or releasing // until its admission fence closes ready // any other participant of a running operation held, hold_op = op id // owner of an open park-local chore for the host retiring (held, no mint) // none of these none; an existing row is unexplained // // A box on the claim's lost list is never ready. A nil or holderless claim // (an error is the caller's: claim C6, keep rows and mint none) gives none. func RelocationRow(c *hostclaim.Claim, box string, exists, fenceClosed bool) RowDecision { if c == nil || c.Holder == nil || box == "" { return unexplained(exists) } lost := slices.Contains(c.Lost, box) switch { case lost: case Serving(c, Selection{Box: box}) == nil: return RowDecision{Case: RowReady} case c.Op.Kind == hostclaim.KindRelocating && c.Op.Exec == box && c.Op.Intent["source_box"] == box && (c.Op.Phase == hostclaim.PhaseStarting || (c.Op.Phase == hostclaim.PhaseReleasing && !fenceClosed)): return RowDecision{Case: RowReady} case c.Op.Kind != hostclaim.KindServing && c.Op.ID != "" && slices.Contains(c.Op.Participants, box): return RowDecision{Case: RowHeld, HoldOp: c.Op.ID} } for _, o := range c.Obligations { if o.Kind == "park-local" && o.Box == box { if !exists { return RowDecision{Case: RowNone} } return RowDecision{Case: RowRetiring, HoldOp: o.Op} } } return unexplained(exists) } func unexplained(exists bool) RowDecision { if exists { return RowDecision{Case: RowUnexplained} } return RowDecision{Case: RowNone} }