package hostops import ( "context" "crypto/sha256" "encoding/hex" "errors" "os" "slices" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostfence" ) // completionFence is ruling 86: the box a serving claim's undischarged // completed relocation names, when that box's termination fence verifies, // for a delete or purge to pass to the completion barrier as its exempt // box (hostclaim.StartGuard's fencedCompletion), as a failover passes its // fenced source. A dead box's participation slot binds nothing on another // box; without the exemption a purge that has closed the app's admission // waits on a failover that admission refuses (the two-box model, seed // 2224). // // The fence is verified as the failover's start verifies it: the record // at the box's current roster enrollment, by its exact digest, through // hostclaim.VerifyFence under the claim fence authority, and bound to the // box (VerifyFenceForBox). Only that evidence exempts. "" with no error // when there is nothing to exempt (no completion, one already discharged, // or its box already on lost, which the barrier exempts itself) or no // fence to verify (a box not in the roster, or no fence record): the // barrier then refuses as before, since absence is never evidence. A fence // that cannot be read, or does not verify, is its error and the start is // refused. A completion record that cannot be read is left to the // barrier, which refuses over it. func (a ProductionAuthority) completionFence(ctx context.Context, c *hostclaim.Claim) (string, error) { done, ok, err := hostclaim.ReadCompletedRelocation(c) if err != nil || !ok || done.Discharged || slices.Contains(c.Lost, done.Stop.Box) { return "", nil } if a.Store == nil || a.Now == nil { return "", ErrContract } o, err := a.Directory.Read(ctx) if err != nil { return "", err } b, ok := o.Roster.Boxes[done.Stop.Box] if !ok { return "", nil } key := hostfence.FenceKey(b.Enrollment) raw, _, err := a.Store.Get(ctx, key) if errors.Is(err, os.ErrNotExist) { return "", nil } if err != nil { return "", err } sum := sha256.Sum256(raw) ref := hostclaim.FenceRef{Enrollment: b.Enrollment, Key: key, SHA256: hex.EncodeToString(sum[:])} if err := hostclaim.VerifyFenceForBox(ctx, a.Store, hostfence.ClaimFenceAuthority{Directory: a.Directory, Now: a.Now}, ref, done.Stop.Box); err != nil { return "", err } return done.Stop.Box, nil }