package hostresolve import ( "context" "encoding/json" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostfence" ) // OperationOwner supplies operation policy. The service owns challenge signing, // acceptance, quarantine and the durable prepared/committed loss sequence. // InspectLoss and LossWriteSources are read-only. PrepareLoss journals the // operation's decision before claim revocation; FinishLoss releases only what // the committed declaration permits. Both hooks must be idempotent. Retry // dispatch uses the accepted loss's kind, even after the current kind changes. // Callbacks receive detached values and must guard every external write. // PrepareLoss can arrive after a concurrent FinishLoss or after the loss is // conflicted. Its conditional record write must never regress a later state; // it must not write the claim or treat its journal as execution authority. // A prepared record is inert until both the claim loss reference and committed // loss record exist. FinishLoss can observe a successor holder or operation: // it derives effects from the signed loss, refuses mismatched identities, and // treats its own durable completion as success without touching the successor. type OperationOwner interface { InspectLoss(context.Context, hostclaim.Observed, hostfence.RecordObservation, string) (hostfence.Consequences, hostclaim.LossConsequences, error) LossWriteSources(context.Context, hostclaim.Observed, hostclaim.LossConsequences) ([]hostclaim.WriteSource, error) hostclaim.LossOperationOwner PrepareLoss(context.Context, hostclaim.Observed, hostclaim.LossRecord) error FinishLoss(context.Context, hostclaim.Observed, hostclaim.LossRecord) error } func (s Service) operationOwner(kind string) (OperationOwner, error) { switch kind { case hostclaim.KindServing, hostclaim.KindParking, hostclaim.KindDeleting: return builtinOwner{}, nil case hostclaim.KindMoving, hostclaim.KindLanding, hostclaim.KindReverting, hostclaim.KindRestoring: if owner := s.Owners[kind]; owner != nil { return owner, nil } } return nil, ErrResolve } type builtinOwner struct{ owner } func (builtinOwner) InspectLoss(_ context.Context, o hostclaim.Observed, _ hostfence.RecordObservation, box string) (hostfence.Consequences, hostclaim.LossConsequences, error) { return effects(o.Claim, box) } func (builtinOwner) LossWriteSources(context.Context, hostclaim.Observed, hostclaim.LossConsequences) ([]hostclaim.WriteSource, error) { return nil, nil } func (builtinOwner) PrepareLoss(context.Context, hostclaim.Observed, hostclaim.LossRecord) error { return nil } func (builtinOwner) FinishLoss(context.Context, hostclaim.Observed, hostclaim.LossRecord) error { return nil } func detached[T any](v T) (T, error) { var out T wire, err := json.Marshal(v) if err == nil { err = json.Unmarshal(wire, &out) } return out, err } func inspectOwner(ctx context.Context, owner OperationOwner, o hostclaim.Observed, record hostfence.RecordObservation, box string) (hostfence.Consequences, hostclaim.LossConsequences, error) { copy, err := detached(o) if err != nil { return hostfence.Consequences{}, hostclaim.LossConsequences{}, err } shown, actual, err := owner.InspectLoss(ctx, copy, record, box) if err != nil { return shown, actual, err } shown, err = detached(shown) if err != nil { return shown, actual, err } actual, err = detached(actual) return shown, actual, err } func ownerSources(ctx context.Context, owner OperationOwner, o hostclaim.Observed, actual hostclaim.LossConsequences) ([]hostclaim.WriteSource, error) { copy, err := detached(o) if err != nil { return nil, err } effects, err := detached(actual) if err != nil { return nil, err } sources, err := owner.LossWriteSources(ctx, copy, effects) if err != nil { return nil, err } return detached(sources) } func ownerHook(ctx context.Context, owner OperationOwner, o hostclaim.Observed, loss hostclaim.LossRecord, finish bool) error { copy, err := detached(o) if err != nil { return err } record, err := detached(loss) if err != nil { return err } if finish { return owner.FinishLoss(ctx, copy, record) } return owner.PrepareLoss(ctx, copy, record) }