package park import ( "context" "encoding/json" "time" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/lease" ) // MoveSweepVerifier verifies the newest retired move's durable fence or // release, covering every moves/ version in the complete census. // A missing history, unresolved writer, or newer unretired move must refuse. // The caller supplies the current claim and census; a timestamp alone is not // evidence. Moves Plan 5 supplies this provider from its retained journals. type MoveSweepVerifier interface { VerifyMovesSweep(context.Context, hostclaim.Observed, []lease.ObjectVersion) (MoveSweepProof, error) } type MoveSweepProof struct { Host, ClaimETag, MoveID, Incarnation string RetiredAt time.Time } // Check permits listing-time evidence only after the newest retired move's // verified fence or release. Requiring it before listing starts also excludes // a retirement racing a paginated listing. The queued EvidenceAt is the end // of that listing and is retained by every retry. func (p MoveSweepProof) Check(o hostclaim.Observed, incarnation string, listingStarted time.Time) error { if o.Absent || o.Claim == nil || o.ETag == "" || p.Host != o.Host || p.ClaimETag != o.ETag || !token(p.MoveID) || p.Incarnation != incarnation || p.RetiredAt.IsZero() || !p.RetiredAt.Before(listingStarted) { return ErrUnsafe } c := o.Claim if c.Free() || c.Op.Kind == hostclaim.KindServing { return nil } if c.Holder == nil && c.Op.Kind == hostclaim.KindParking && c.Op.ID != "" && c.Op.Exec == "" && c.Op.Intent["park_sweep"] == "v1" { return nil } return ErrUnsafe } // moveSweepObservation keeps a provider's read-only input detached from the CAS. func moveSweepObservation(o hostclaim.Observed) (hostclaim.Observed, error) { raw, err := json.Marshal(o.Claim) if err != nil { return hostclaim.Observed{}, err } var c *hostclaim.Claim if err := json.Unmarshal(raw, &c); err != nil { return hostclaim.Observed{}, err } o.Claim = c return o, nil }