package actor import ( "context" "io" "net/http" "google.golang.org/protobuf/proto" ) type MutationResult[V any] struct { Result V `json:"result"` Mutations []ApplyResult `json:"mutations,omitempty"` } type GrainPool[V any] interface { Apply(ctx context.Context, id uint64, mutation ...proto.Message) (*MutationResult[V], error) Get(ctx context.Context, id uint64) (*V, error) OwnerHost(id uint64) (Host[V], bool) Hostname() string TakeOwnership(id uint64) // HandleOwnershipChange processes a remote first-touch ownership // claim. lastChanges is the parallel []int64 of UnixNano stamps from // the announcer; -1 entries fall back to the pre-arbitration // "always accept remote" behaviour so mixed-version rollouts // remain safe. HandleOwnershipChange(host string, ids []uint64, lastChanges []int64) error HandleRemoteExpiry(host string, ids []uint64, lastChanges []int64) error Negotiate(otherHosts []string) GetLocalIds() []uint64 IsHealthy() bool Close() IsKnown(string) bool RemoveHost(host string) AddRemoteHost(host string) } // Host abstracts a remote node capable of proxying cart requests. type Host[V any] interface { // AnnounceExpiry broadcasts per-id eviction decisions. lastChanges is // a parallel []int64 of UnixNano stamps taken at the moment the // grain was dropped from the local cache; it is informational on // this path (the receiver just removes the id from its remoteOwners // map) but kept in the wire signature for symmetry with // AnnounceOwnership. AnnounceExpiry(ids []uint64, lastChanges []int64) Negotiate(otherHosts []string) ([]string, error) Name() string Proxy(id uint64, w http.ResponseWriter, r *http.Request, customBody io.Reader) (bool, error) Apply(ctx context.Context, id uint64, mutation ...proto.Message) (*MutationResult[V], error) Get(ctx context.Context, id uint64) (*V, error) GetActorIds() []uint64 Close() error Ping() bool IsHealthy() bool // AnnounceOwnership broadcasts a first-touch ownership claim. // lastChanges is a parallel []int64 of UnixNano stamps of the // announcing pod's local grain's GetLastChange() at the moment of // broadcast; receivers use it as the first-spawn-wins oracle for // concurrent cold-cache first-touch (smaller stamp = older spawn = // owns the grain; on equal stamps the lexicographically smaller // hostname wins). A stamp of -1 means "no local grain to back this // id" — receivers treat that as legacy/no-arbitration and accept // the remote claim as before. AnnounceOwnership(ownerHost string, ids []uint64, lastChanges []int64) }