carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "errors"
6 "github.com/carlosframework/platform/internal/park"
7 "github.com/carlosframework/platform/internal/registry"
8 "sync"
9 "time"
10
11 "github.com/carlosframework/platform/internal/hostclaim"
12 "github.com/carlosframework/platform/internal/hostguard"
13 "github.com/carlosframework/platform/internal/hostwrite"
14)
15
16var ErrChanged = errors.New("hostbox: selected authority changed")
17var ErrUnsupported = errors.New("hostbox: unresolved lifecycle contract")
18
19type Selection struct {
20 HomeRegion, FleetHomeRegion string
21 Host, Action, Box, Region string
22 Captured hostguard.Captured
23 Kind, Op, Phase, Generation string
24 Session uint64
25 // installation marks the work Reconciler.installWithWork selected for
26 // one installation (Installation). Only this package sets it.
27 installation bool
28}
29
30// Installation reports whether the selection is the work an installation
31// selected for itself (Reconciler.installWithWork, the path to the remote
32// route registration and the local row write), not ordinary work on an
33// installed host. A policy may admit what only an installation can need,
34// such as a registry row the installation has not written yet (harness G10,
35// 2026-10-21); no caller outside this package can set it.
36func (s Selection) Installation() bool { return s.installation }
37
38// installationWork marks w, a selection SelectAt made, as installation
39// work. Any other work is returned unmarked, so it is refused whatever an
40// installation-only admission would allow.
41func installationWork(w hostwrite.Work) hostwrite.Work {
42 selected, ok := w.(selectedWork)
43 if !ok {
44 return w
45 }
46 selected.selection.installation = true
47 return selected
48}
49
50type WorkSource struct {
51 Timer func(context.Context, hostwrite.Work, registry.Timer, string, bool) (*registry.Timer, error)
52 FleetHomeRegion string
53 HomeRegion string
54 Prepare func(context.Context, hostclaim.Observed) error
55 Guard hostguard.Guard
56 Box, Region string
57 Now func() time.Time
58 // Policy checks the action's role/phase and the selected config/release.
59 Policy func(context.Context, *hostclaim.Claim, Selection, hostwrite.Identity) error
60 // Local checks quarantine, execution deadlines and failed cleanup under flock.
61 Local func(context.Context, *hostclaim.Claim, Selection) error
62 // Continue reports whether a selection last checked at one op phase may
63 // keep working at another (hostclaim.SessionObserves). Nil keeps main's rule:
64 // any phase change ends the selection. Only the moves C1 integration sets
65 // it, and only with CARLOS_HOST_CLAIM and CARLOS_MOVES_PHASE1 both on.
66 Continue func(kind, from, to string) bool
67}
68
69type selectedWork struct {
70 source WorkSource
71 selection Selection
72 // checked is the op phase of the last claim a With check accepted. The
73 // selection's own Phase stays the selected one for Policy and Local.
74 checked *checkedPhase
75}
76
77type checkedPhase struct {
78 mu sync.Mutex
79 phase string
80}
81
82func (s WorkSource) Select(ctx context.Context, host, action string) (hostwrite.Work, error) {
83 if ctx == nil || s.Box == "" || s.Region == "" || action == "" || s.Guard.Claims == nil || s.Policy == nil || s.Local == nil || s.Now == nil {
84 return nil, ErrUnsupported
85 }
86 captured, err := s.Guard.Capture(host)
87 if err != nil {
88 return nil, err
89 }
90 obs, err := s.Guard.Claims.Read(ctx, host)
91 if err != nil {
92 return nil, err
93 }
94 if obs.Absent || obs.Host != host || obs.Claim == nil || obs.Claim.Holder == nil || obs.Claim.Host != host {
95 return nil, ErrChanged
96 }
97 if s.Prepare != nil {
98 if err := s.Prepare(ctx, obs); err != nil {
99 return nil, err
100 }
101 }
102 c := obs.Claim
103 if c.Holder.Incarnation != captured.Incarnation || c.Holder.Stamp != captured.Stamp || captured.Stamp == 0 {
104 return nil, ErrChanged
105 }
106 w := selectedWork{source: s, selection: Selection{HomeRegion: s.HomeRegion, FleetHomeRegion: s.FleetHomeRegion, Host: host, Action: action, Box: s.Box, Region: s.Region, Captured: captured, Kind: c.Op.Kind, Op: c.Op.ID, Phase: c.Op.Phase, Generation: c.Holder.Generation, Session: c.Holder.Session}, checked: &checkedPhase{phase: c.Op.Phase}}
107 return w, nil
108}
109
110func (w selectedWork) With(ctx context.Context, identity hostwrite.Identity, fn func() error) error {
111 s, selected := w.source, w.selection
112 return s.Guard.With(ctx, selected.Host, selected.Captured, func(c *hostclaim.Claim) error {
113 if c.Op.Kind != selected.Kind || c.Op.ID != selected.Op || !s.continues(selected.Kind, w.checked.get(), c.Op.Phase) || c.Holder.Stamp != selected.Captured.Stamp || c.Holder.Session != selected.Session || c.Holder.Generation != selected.Generation {
114 return ErrChanged
115 }
116 switch selected.Action {
117 case "install", "restore", "spawn", "seed", "writer", "probe":
118 if until := park.QuietUntil(c.QuietAfter, s.HomeRegion, s.Region); !until.IsZero() && !s.Now().After(until) {
119 return ErrChanged
120 }
121 }
122 if err := s.Local(ctx, c, selected); err != nil {
123 return err
124 }
125 if err := s.Policy(ctx, c, selected, identity); err != nil {
126 return err
127 }
128 return w.checked.advance(func(from string) bool { return s.continues(selected.Kind, from, c.Op.Phase) }, c.Op.Phase)
129 }, fn)
130}
131
132func (p *checkedPhase) get() string {
133 p.mu.Lock()
134 defer p.mu.Unlock()
135 return p.phase
136}
137
138// advance records phase as checked if it still continues from the last
139// checked phase. A concurrent check that already moved on refuses this one.
140func (p *checkedPhase) advance(continues func(from string) bool, phase string) error {
141 p.mu.Lock()
142 defer p.mu.Unlock()
143 if !continues(p.phase) {
144 return ErrChanged
145 }
146 p.phase = phase
147 return nil
148}
149
150// continues binds the selection to op id, kind, session, stamp and generation.
151// Its phase may only take a forward step the configured table allows from the
152// last phase a check accepted, so a skipped or backward step ends it.
153func (s WorkSource) continues(kind, from, to string) bool {
154 if s.Continue == nil {
155 return from == to
156 }
157 return s.Continue(kind, from, to)
158}
159
160// Serving permits ordinary host writes only. Operations need their own policy.
161func Serving(c *hostclaim.Claim, selected Selection) error {
162 if c.Holder == nil || c.Op.Kind != hostclaim.KindServing || c.Op.ID != "" || c.Op.Phase != "" || c.Op.Exec != selected.Box {
163 return ErrChanged
164 }
165 for _, box := range c.Lost {
166 if box == selected.Box {
167 return ErrChanged
168 }
169 }
170 return nil
171}
172
173// SelectAt captures an already chosen installation without relabelling it.
174func (s WorkSource) SelectAt(ctx context.Context, host, action string, captured hostguard.Captured) (hostwrite.Work, error) {
175 work, err := s.Select(ctx, host, action)
176 if err != nil {
177 return nil, err
178 }
179 selected := work.(selectedWork)
180 if selected.selection.Captured != captured {
181 return nil, ErrChanged
182 }
183 return work, nil
184}
185