package host import ( "context" "errors" "log" "sync" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/lease" "github.com/carlosframework/platform/internal/park" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) type claimMoveParkSources struct { View func(context.Context, hostclaim.Observed) (park.View, error) Evidence hostbox.RegionalEvidence } // RegisterProductionClaimMoveParks installs the regional contracts independently // of the wake owners and C8 files manifest. Registration is atomic and one-shot. func RegisterProductionClaimMoveParks(view func(context.Context, hostclaim.Observed) (park.View, error), retirementEvidence hostbox.RegionalEvidence) error { var missing []string if view == nil { missing = append(missing, "moves Plan 5 exact-version generation and files-manifest view") } if retirementEvidence == nil { missing = append(missing, "moves Plan 5 retired-move regional evidence") } if len(missing) != 0 { return &ClaimDependencyError{Contracts: missing} } claimMoveSources.Lock() defer claimMoveSources.Unlock() if claimMoveSources.Parks != nil { return hostwrite.ErrUnavailable } claimMoveSources.Parks = &claimMoveParkSources{View: view, Evidence: retirementEvidence} return nil } // claimMoveWakeLogf and claimMoveWakeLogOnce log a moves source that did // not open, once per process (per agent start); variables so the tests can // read and reset them. var ( claimMoveWakeLogf = log.Printf claimMoveWakeLogOnce = new(sync.Once) ) // openProductionClaimMoveWake opens the registered moves source over the // claim's data bucket as the serving owners see it (moves Task 8 fix round // 1): through hostbox.RequestStore, so every bucket request the moves // owners make is bounded by the claim's request limit, runs under the // operation's own context (its cancellation still reaches the request), and // is preceded by check, the box's bound identity (claimBoundIdentity), so a // refused identity issues no later bucket call. func openProductionClaimMoveWake(ctx context.Context, store release.Store, actor hostbox.Actor, external ClaimProductionMoveSources, out *ClaimOwnerBindings, check func(context.Context) error) { if external == nil { out.wakeUnavailable = &ClaimDependencyError{Contracts: []string{"moves wake owners and C8 origin-qualified files manifest provider"}} return } var data lease.VersionedStore if out.Data != nil && check != nil { data = hostbox.RequestStore{Store: out.Data, Check: check} } owners, files, err := external.Open(ctx, store, data, actor, out.moveRead) if err != nil { out.wakeUnavailable = errors.Join(&ClaimDependencyError{Contracts: []string{"moves wake owners and C8 origin-qualified files manifest provider"}}, err) // Fail closed (platform-1e): every wake on this box is refused. Say // why once, so an operator can see it (for example, an unusable // CARLOS_SECRETS_KEY with CARLOS_MOVES_PHASE1 on). claimMoveWakeLogOnce.Do(func() { claimMoveWakeLogf("host claims: the moves wake source did not open, so every wake on this box is refused, serving included: %v", err) }) return } out.Moves, out.Serving.Files = owners, files } func refusingClaimMoveWake(box string, store release.Store, reason error) *activator.ClaimRuntime { w := refusingRootWake(box, store) w.Prerequisites = func(context.Context, registry.Route, hostclaim.Observed, activator.StartPolicy) error { return reason } w.Admit = func(context.Context, registry.Route, hostclaim.Observed, activator.StartPolicy) (hostclaim.Observed, error) { return hostclaim.Observed{}, reason } w.Prepare = func(context.Context, registry.Route, hostclaim.Observed, activator.StartPolicy) (activator.ClaimStart, error) { return activator.ClaimStart{}, reason } return w } // claimBoundIdentity pins the entire verified enrollment, including its cloud // instance, buckets, namespace and binding, for a built assembly. The roster // write counter is not pinned: holds and other boxes' rows move it. func claimBoundIdentity(verify func(context.Context) (hostbox.Actor, error), expected hostbox.Actor) func(context.Context) error { return func(ctx context.Context) error { if ctx == nil || verify == nil || expected.Enrollment == "" { return hostwrite.ErrUnavailable } current, err := verify(ctx) if err != nil { return err } if !hostbox.SameBinding(current, expected) { return hostbox.ErrChanged } return nil } } // ClaimLocalParkAuthorizer is the owner's local check before park.Local effects. type ClaimLocalParkAuthorizer func(context.Context, *hostclaim.Claim, hostclaim.Obligation) error type claimMoveParks struct { local ClaimLocalParkAuthorizer engine park.Engine leases hostbox.LeaseSet check func(context.Context) error } // MoveParks returns the regional engine, lease inventory and local authorizer. // Only Build can populate this capability, after Identity.Verify succeeds. func (p ClaimProviders) MoveParks(ctx context.Context) (park.Engine, hostbox.LeaseSet, ClaimLocalParkAuthorizer, error) { if p.moveParks == nil || p.moveParks.check == nil || p.moveParks.leases == nil { return park.Engine{}, nil, nil, hostwrite.ErrUnavailable } if err := p.moveParks.check(ctx); err != nil { return park.Engine{}, nil, nil, err } if p.moveParks.local == nil { return park.Engine{}, nil, nil, hostwrite.ErrUnavailable } return p.moveParks.engine, p.moveParks.leases, p.moveParks.local, nil } func (p productionClaimRegional) VerifyMovesSweep(ctx context.Context, o hostclaim.Observed, versions []lease.ObjectVersion) (park.MoveSweepProof, error) { if _, err := p.Identity.Verify(ctx); err != nil { return park.MoveSweepProof{}, err } if p.Parks == nil { return park.MoveSweepProof{}, park.ErrUnsafe } if o.Claim != nil && o.Claim.Free() { return claimFreeMoveProof(o) } verifier, ok := p.Parks.Evidence.(park.MoveSweepVerifier) if !ok { return park.MoveSweepProof{}, &ClaimDependencyError{Contracts: []string{"moves Plan 5 newest retired move fence or release evidence"}} } if _, err := p.Identity.Verify(ctx); err != nil { return park.MoveSweepProof{}, err } return verifier.VerifyMovesSweep(ctx, o, versions) }