carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostops
2
3import (
4 "context"
5 "encoding/json"
6 "reflect"
7
8 "github.com/carlosframework/platform/internal/hostclaim"
9 "github.com/carlosframework/platform/internal/hostfence"
10 "github.com/carlosframework/platform/internal/hostresolve"
11)
12
13// Local destruction is a separate declaration. An active target still takes
14// the data-loss path; its newly queued local chore needs a fresh inspection.
15// A committed move can discharge its retired source's local installation.
16func moveLocalLossEligible(c *hostclaim.Claim, box string) bool {
17 if c == nil || c.Holder == nil || box == "" || c.Op.Exec == box {
18 return false
19 }
20 inactive := slicesContain(c.Lost, box) || (c.Op.Kind == hostclaim.KindLanding && c.Op.Phase == hostclaim.PhaseCommitted) || !slicesContain(c.Op.Participants, box)
21 if !inactive {
22 return false
23 }
24 for _, o := range c.Obligations {
25 if o.Kind == "park-local" && o.Box == box {
26 return true
27 }
28 }
29 return false
30}
31
32func isMoveLocalLoss(actual hostclaim.LossConsequences) bool {
33 return hostclaim.IsDestroyedLocalOnly(actual)
34}
35
36// The signed claim ETag binds each original operation, chore and retirement
37// identity to its inventory. The operator attests exactly this displayed set.
38func moveLocalLossConsequences(c *hostclaim.Claim, box string) (hostfence.Consequences, hostclaim.LossConsequences, error) {
39 if !moveLocalLossEligible(c, box) {
40 return hostfence.Consequences{}, hostclaim.LossConsequences{}, hostresolve.ErrResolve
41 }
42 shown := hostfence.Consequences{Transition: "destroyed-local-only"}
43 actual := hostclaim.LossConsequences{RecoveryIntent: json.RawMessage(`{"kind":"destroyed-local-only"}`)}
44 for _, o := range c.Obligations {
45 if o.Kind != "park-local" || o.Box != box {
46 continue
47 }
48 ref := hostclaim.ObligationRef{ID: o.ID, Op: o.Op, Retirement: o.Retirement}
49 key := o.Op + "/" + o.ID + "/" + o.Retirement
50 shown.DischargeLocal = append(shown.DischargeLocal, ref)
51 shown.DestroyedStorageAssertion = append(shown.DestroyedStorageAssertion, key)
52 actual.DischargeLocal = append(actual.DischargeLocal, ref)
53 actual.DestroyedStorage = append(actual.DestroyedStorage, key)
54 }
55 return shown, actual, nil
56}
57
58// No record rollback, release, execution handover or post-commit effect is
59// authorized by this declaration. The generic claim CAS is its entire effect.
60func (m *moveLossOwner) localLossHook(ctx context.Context, o hostclaim.Observed, loss hostclaim.LossRecord, finish bool) error {
61 durable, err := m.durableLoss(ctx, loss)
62 if err != nil {
63 return err
64 }
65 if durable.Validate() != nil || !reflect.DeepEqual(durable.ConfirmedConsequences, loss.ConfirmedConsequences) || durable.Host != o.Host {
66 return hostresolve.ErrResolve
67 }
68 if finish {
69 if durable.State != "committed" || o.Claim == nil || !slicesContain(o.Claim.LossRefs, loss.ID) {
70 return hostresolve.ErrResolve
71 }
72 return nil
73 }
74 if durable.State == "committed" {
75 return nil
76 }
77 if durable.State != "prepared" || o.Absent || o.ETag != durable.ExpectedClaimETag {
78 return hostresolve.ErrResolve
79 }
80 _, want, err := moveLocalLossConsequences(o.Claim, durable.Box)
81 if err != nil || !reflect.DeepEqual(want, durable.ConfirmedConsequences) {
82 return hostresolve.ErrResolve
83 }
84 return nil
85}
86