carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "fmt"
8
9 "github.com/carlosframework/platform/internal/hostbox"
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostguard"
12)
13
14// relocationCompletedHandover is the first serving cold wake after a
15// relocation completed on this box (rulings 49 and 56; Astra round 2
16// finding 6). The predecessor is the probe session S that the target
17// stopped at relocating/probing with root's writer-session receipt, and
18// the serving op journals that stop (CompletedRelocation). Nothing is
19// stopped again and nothing is relabelled: before admission the receipt
20// is found and verified in this box's guard journal (in place of main's
21// Wake.Stop, which would ask a serving-shaped re-stop of S); after
22// admission it is handed to root's ColdHandover.Run as the exact prior,
23// which Run accepts beside the separately validated successor without
24// matching their operation ids.
25type relocationCompletedHandover struct {
26 Guard hostguard.Guard
27 Identity hostbox.IdentityProvider
28 Socket string
29 Box string
30}
31
32// completedPrior reports whether prior is a serving claim whose writer is
33// the stopped probe session this box completed a relocation with. A
34// serving claim on this box whose completion record is present but cannot
35// be read is an error, never "not the completed prior" (absence is never
36// evidence): routed to main's stop it would re-stop S with a serving-shaped
37// fence that replaces the probe's.
38func (h relocationCompletedHandover) completedPrior(prior hostclaim.Observed) (hostclaim.CompletedRelocation, bool, error) {
39 c := prior.Claim
40 if c == nil || c.Holder == nil || c.Op.Kind != hostclaim.KindServing || c.Op.Exec != h.Box || c.Holder.WriterBox != h.Box {
41 return hostclaim.CompletedRelocation{}, false, nil
42 }
43 done, ok, err := hostclaim.ReadCompletedRelocation(c)
44 if err != nil {
45 return hostclaim.CompletedRelocation{}, false, err
46 }
47 if !ok || done.Stop.Box != h.Box || done.Stop.Session != c.Holder.Session || done.Stamp != c.Holder.Stamp || done.Generation != c.Holder.Generation {
48 return hostclaim.CompletedRelocation{}, false, nil
49 }
50 return done, true, nil
51}
52
53// receipt is root's completed writer-session stop of S, exactly the one
54// the serving op journals: fence id, phase and the receipt bytes' digest.
55func (h relocationCompletedHandover) receipt(ctx context.Context, prior hostclaim.Observed) (hostguard.FenceReceipt, error) {
56 done, ok, err := h.completedPrior(prior)
57 if err != nil {
58 return hostguard.FenceReceipt{}, err
59 }
60 if !ok {
61 return hostguard.FenceReceipt{}, hostbox.ErrChanged
62 }
63 r, err := writerSessionReceipt(ctx, h.Guard, prior.Host, hostguard.Captured{Incarnation: prior.Claim.Holder.Incarnation, Stamp: done.Stamp}, done.Op, h.Box, done.Stop.Session)
64 if err != nil {
65 return hostguard.FenceReceipt{}, err
66 }
67 sum := sha256.Sum256(r.Bytes)
68 if r.Target.FenceID != done.Stop.Receipt || r.Target.Phase != done.Phase || hex.EncodeToString(sum[:]) != done.Stop.ReceiptSHA256 {
69 return hostguard.FenceReceipt{}, fmt.Errorf("%w: session %d's stop under %s is not the one the serving op records", ErrClaimStopReceipt, done.Stop.Session, done.Op)
70 }
71 return r, nil
72}
73
74// writerSessionReceipt is root's completed writer-session stop of session
75// (hostguard.WriterStopObligation) for op on box, from this box's guard
76// journal: the one reader of a probe's stop, for the probe stop and for
77// the serving handover after it. Two different completed stops of the
78// same session under the same op are a defect of the journal, refused as
79// main's CompletedColdPredecessor refuses them (ErrFenceTarget): neither
80// is "the" receipt, and the stop never guesses which to record.
81func writerSessionReceipt(ctx context.Context, guard hostguard.Guard, host string, captured hostguard.Captured, op, box string, session uint64) (hostguard.FenceReceipt, error) {
82 receipts, err := guard.CompletedWriterStops(ctx, host, captured, op, box)
83 if err != nil {
84 return hostguard.FenceReceipt{}, err
85 }
86 want := hostguard.WriterStopObligation(session)
87 var found hostguard.FenceReceipt
88 for _, r := range receipts {
89 if r.Target.Session != session || r.Target.ObligationID != want {
90 continue
91 }
92 if found.Target.FenceID != "" && found.Target != r.Target {
93 return hostguard.FenceReceipt{}, fmt.Errorf("%w: root completed more than one stop of session %d under %s", hostguard.ErrFenceTarget, session, op)
94 }
95 found = r
96 }
97 if found.Target.FenceID == "" {
98 return hostguard.FenceReceipt{}, fmt.Errorf("%w: root has no completed stop of session %d under %s", ErrClaimStopReceipt, session, op)
99 }
100 return found, nil
101}
102
103// Before replaces the predecessor stop for exactly this prior: the stop
104// already happened, so its receipt is found and verified, nothing more.
105func (h relocationCompletedHandover) Before(ctx context.Context, prior hostclaim.Observed) error {
106 r, err := h.receipt(ctx, prior)
107 if err != nil {
108 return err
109 }
110 return verifyRetirementReceipt(ctx, r)
111}
112
113// After is main's cold handover with that receipt as the prior.
114func (h relocationCompletedHandover) After() func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
115 return coldHandoverWith(h.Identity, h.Socket, h.receipt)
116}
117