package host import ( "context" "errors" "os" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" execlease "github.com/carlosframework/platform/internal/hostexec" ) // Acknowledged is region HA's read of main's participation for a // relocating operation (rev 2, ruling 42). It adds no authority: it is // Prepare, exactly as WorkSource.Prepare and the participation loop call // it, then the slot's own Ready. Whichever caller comes first opens the // box's one controller for {host, op}; every later caller gets that same // controller's state. A lost or failed controller is main's retained // denial (CC9), returned as its error and never replaced here. func (p *productionClaimLocal) Acknowledged(ctx context.Context, o hostclaim.Observed) (execlease.State, error) { if o.Claim == nil || o.Claim.Holder == nil || o.Claim.Op.Kind != hostclaim.KindRelocating { return execlease.State{}, hostbox.ErrChanged } if err := p.Prepare(ctx, o); err != nil { return execlease.State{}, err } p.mu.Lock() slot := p.slots[o.Host] p.mu.Unlock() if slot == nil { return execlease.State{}, execlease.ErrInactive } select { case <-slot.Ready: case <-ctx.Done(): return execlease.State{}, ctx.Err() } if slot.Err != nil { return execlease.State{}, slot.Err } if slot.Controller == nil { return execlease.State{}, execlease.ErrInactive } return slot.Controller.Ready(ctx) } // boundAssembly is the assembly Build bound this owner to: the copy whose // reconcile adapter, census and guard reader Build configured. Nil before // Build. N1's second accessor. func (p *productionClaimLocal) boundAssembly() *ClaimAssembly { p.mu.Lock() defer p.mu.Unlock() return p.assembly } // DischargeCompleted is ruling 66's gate before a completed relocation's // hold is released: main's own Prepare for the serving observation, which // closes the participation slot (its cleanup is C10's discharge, first) // and clears the .hostexec record, or, after a restart, clears the // untracked record through clearUntracked (the same discharge, keyed by // the persisted binding). It succeeds only once neither a slot nor a // record is left for the host. N1's third accessor; no new authority. func (p *productionClaimLocal) DischargeCompleted(ctx context.Context, o hostclaim.Observed) error { if o.Claim == nil || o.Claim.Holder == nil || o.Claim.Op.Kind != hostclaim.KindServing { return hostbox.ErrChanged } actor, err := p.Identity.Verify(ctx) if err != nil { return err } return p.dischargeCompletedAs(ctx, actor, o) } // dischargeCompletedAs is DischargeCompleted for a verified actor: main's // prepareWith, then nothing of the participation may be left. func (p *productionClaimLocal) dischargeCompletedAs(ctx context.Context, actor hostbox.Actor, o hostclaim.Observed) error { if err := p.prepareWith(ctx, actor, o); err != nil { return err } p.mu.Lock() slot := p.slots[o.Host] p.mu.Unlock() if slot != nil { return execlease.ErrInactive } if !p.legacyParticipation { return nil } if _, err := execlease.InspectLocal(p.Resources.DataDir, o.Host); !errors.Is(err, os.ErrNotExist) { return errors.Join(execlease.ErrInactive, err) } return nil }