| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | |
| 6 | "github.com/carlosframework/platform/internal/hostclaim" |
| 7 | execlease "github.com/carlosframework/platform/internal/hostexec" |
| 8 | ) |
| 9 | |
| 10 | // completedRelocationDischarge is claim ask C10's CC9 discharge, rev 4 of |
| 11 | // its text with rev 4.2's sentence (region HA rulings 56, 66 and 70; Astra |
| 12 | // rounds 3 and 4). It is keyed by the persisted participation binding (the |
| 13 | // slot's, or after a restart the .hostexec record's), never by a captured |
| 14 | // observation: when the claim now serves on the binding's box, its holder |
| 15 | // is still the stopped probe session the serving op records as completed |
| 16 | // for exactly the binding's op and incarnation, and root's completed |
| 17 | // writer-session receipt for it is in this box's guard journal and |
| 18 | // verifies, the session is stopped with root's evidence and the |
| 19 | // participation is discharged. Nothing is stopped again. It runs first in |
| 20 | // every cleanup path, the slot's close and clearUntracked alike. done |
| 21 | // false with no error falls through to main's cleanup unchanged. Any |
| 22 | // error, a failed claim read or an unreadable completion record included, |
| 23 | // is (true, err), and the caller also stops on any error: the cleanup |
| 24 | // fails, nothing is stopped, and the next pass retries (rev 4.2). Region |
| 25 | // HA never releases the row's hold or the activator fence before this has |
| 26 | // succeeded (ruling 66), so no serving admission can move the holder off |
| 27 | // the probe session first. |
| 28 | func (a ClaimAssembly) completedRelocationDischarge(ctx context.Context, binding execlease.Binding) (done bool, err error) { |
| 29 | if binding.Host == "" || binding.Op == "" || binding.Box == "" { |
| 30 | return false, nil |
| 31 | } |
| 32 | now, err := hostclaim.Read(ctx, a.Claims, binding.Host) |
| 33 | if err != nil { |
| 34 | // Rev 4.2 (Astra round 4): a failed read never falls through to |
| 35 | // ordinary cleanup, whose serving-shaped stop would replace the |
| 36 | // completed probe's active fence (BeginFence) and make the first |
| 37 | // serving wake's handover refuse for good. The cleanup fails and |
| 38 | // is retried on the next pass, with nothing stopped. |
| 39 | return true, err |
| 40 | } |
| 41 | if now.Absent || now.Claim == nil || now.Claim.Holder == nil { |
| 42 | return false, nil |
| 43 | } |
| 44 | h := relocationCompletedHandover{Guard: a.Guard, Box: binding.Box} |
| 45 | record, ok, err := h.completedPrior(now) |
| 46 | if err != nil { |
| 47 | // A completion record that cannot be read is not "no completion" |
| 48 | // (absence is never evidence): the same rule as a failed read. |
| 49 | return true, err |
| 50 | } |
| 51 | if !ok || record.Op != binding.Op || now.Claim.Holder.Incarnation != binding.Incarnation { |
| 52 | return false, nil |
| 53 | } |
| 54 | r, err := h.receipt(ctx, now) |
| 55 | if err != nil { |
| 56 | return true, err |
| 57 | } |
| 58 | return true, verifyRetirementReceipt(ctx, r) |
| 59 | } |
| 60 | |