package host import ( "context" "sync" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/release" ) // ClaimProductionRelocationSource is region HA's production owner source // (claim Plan 11 R1, R8). Open returns the typed relocation owner set for // the verified box actor, plus any append-only policy refusals. It grants no // local process, roster or policy authority beyond those: the owners see // only what the claim's dispatcher and policy gate hand them, and a refusal // can only refuse. type ClaimProductionRelocationSource interface { Open(context.Context, release.Store, lease.VersionedStore, hostbox.Actor) (ClaimRelocationOwners, []hostbox.PolicyRefusal, error) Close(context.Context) error } var claimRelocationSources struct { sync.Mutex Source ClaimProductionRelocationSource } // RegisterProductionClaimRelocationSource registers region HA's source once // at process startup. A second registration, or a nil one, is refused: it // can neither replace an owner set nor add a permissive fallback. func RegisterProductionClaimRelocationSource(s ClaimProductionRelocationSource) error { claimRelocationSources.Lock() defer claimRelocationSources.Unlock() if s == nil || claimRelocationSources.Source != nil { return hostwrite.ErrUnavailable } claimRelocationSources.Source = s return nil } func productionClaimRelocationSource() ClaimProductionRelocationSource { claimRelocationSources.Lock() defer claimRelocationSources.Unlock() return claimRelocationSources.Source } // openClaimRelocationOwners binds the registered source into out with // CARLOS_REGION_HA on. With it off, or nothing registered, out is unchanged // and relocating stays refused exactly as before Plan 11. The returned close // is the source's, or nil when nothing was opened. func openClaimRelocationOwners(ctx context.Context, getenv func(string) string, source ClaimProductionRelocationSource, store release.Store, data lease.VersionedStore, actor hostbox.Actor, out *ClaimOwnerBindings) (func(context.Context) error, error) { if source == nil || getenv == nil || !regionHAEnabled(getenv) { return nil, nil } // N5: region HA's source gets main's own serving launch, markers and // C8 provider, inside the region HA gate, before it opens. Its // Identity is main's serving owners' identity check (carry 5): the // owners' own IdentityProvider.Verify, as claimconstruction.go binds // it before every data-bucket request main's serving owners make. if s, ok := source.(*RelocationSource); ok { serving := out.Serving identity := out.Identity serving.Identity = func(ctx context.Context) error { _, err := identity.Verify(ctx); return err } s.SetServingSources(serving) } owners, refusals, err := source.Open(ctx, store, data, actor) if err != nil { return nil, err } out.Relocation = owners out.PolicyRefusals = append(append([]hostbox.PolicyRefusal(nil), out.PolicyRefusals...), refusals...) return source.Close, nil }