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/ed25519"
6 "strconv"
7
8 "github.com/carlosframework/platform/internal/hostactors"
9 "github.com/carlosframework/platform/internal/hostbox"
10 "github.com/carlosframework/platform/internal/hostclaim"
11 "github.com/carlosframework/platform/internal/hostguard"
12 "github.com/carlosframework/platform/internal/moves"
13 "github.com/carlosframework/platform/internal/registry"
14 "github.com/carlosframework/platform/internal/release"
15)
16
17// claimAdmissionHandover is the wake's AfterAdmission hook. A landing
18// target's first admission, whose predecessor writer is the move's source on
19// another box, goes to crossBox when the moves C1 integration bound one. Every
20// other admission keeps main's local cold handover, which needs this box's own
21// completed fence receipt for the predecessor session.
22func claimAdmissionHandover(box string, local, crossBox func(context.Context, hostclaim.Observed, hostclaim.Observed) error) func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
23 return func(ctx context.Context, prior, admitted hostclaim.Observed) error {
24 if crossBox != nil && claimRemotePredecessor(prior, box) {
25 return crossBox(ctx, prior, admitted)
26 }
27 return local(ctx, prior, admitted)
28 }
29}
30
31// claimCrossBoxHandover authorizes a landing target's first cold admission
32// from the sealed cut and the source's stop evidence instead of a local fence
33// receipt (claim Plan 9 D2, moves ruling H2). The target never ran the
34// source's session, so it can never hold that session's fence receipt.
35//
36// Authority: op.exec moves only by a CAS citing stop evidence (claim §4,
37// §6.4a "AdmitWriter"; moves K3, K4). The flip handed op.exec to this box
38// citing stopped-source.json (moves §7.5); the cut journalled the stopped
39// session and the seal (moves §7.4, K13). The admission's coverage is sealed
40// exactly when the seal was built from the stopped database (claim §6.4a
41// "Admissions, coverage and retirement" item 2). The first probe keeps the
42// installation stamp (item 4), so the local record this box initialized for
43// the landing installation is the one the session runs under.
44type claimCrossBoxHandover struct {
45 RecoveryRebind func(context.Context, recoveryRebindRequest) error
46 Launches *hostbox.LaunchJournal
47 afterRecoveryRows func() error
48 PublicKey ed25519.PublicKey
49 FenceAuthority hostclaim.FenceAuthority
50 Directory hostactors.Directory
51 Guard hostguard.Guard
52 Evidence release.Store
53 Inventory *hostbox.StopInventoryDB
54 Registry *registry.Registry
55 Identity hostbox.IdentityProvider
56 Box string
57}
58
59func (h claimCrossBoxHandover) After(ctx context.Context, prior, admitted hostclaim.Observed) error {
60 if claimRecoveryObservation(prior) {
61 return h.afterRecovery(ctx, prior, admitted)
62 }
63 if h.Evidence == nil || h.Box == "" || prior.Claim == nil || prior.Claim.Holder == nil || admitted.Claim == nil || admitted.Claim.Holder == nil || prior.Host != admitted.Host || prior.Absent || admitted.Absent {
64 return hostbox.ErrChanged
65 }
66 seal, err := h.sourceEvidence(ctx, prior)
67 if err != nil {
68 return err
69 }
70 op, old := prior.Claim.Op, prior.Claim.Holder
71 source := op.Intent["source"]
72 next, ac := admitted.Claim.Holder, admitted.Claim
73 if ac.Op.ID != op.ID || ac.Op.Kind != op.Kind || ac.Op.Phase != op.Phase || ac.Op.Exec != h.Box ||
74 next.Incarnation != old.Incarnation || next.Stamp != old.Stamp || next.Session <= old.Session || next.WriterBox != h.Box || len(next.Admissions) == 0 {
75 return hostbox.ErrChanged
76 }
77 predecessor := hostclaim.AdmissionRecord{Session: old.Session, Box: old.WriterBox, Generation: old.Generation}
78 if len(old.Admissions) > 0 {
79 predecessor = old.Admissions[len(old.Admissions)-1]
80 }
81 entry := next.Admissions[len(next.Admissions)-1]
82 if predecessor.Session != old.Session || predecessor.Box != source || entry.Session != next.Session || entry.Box != h.Box ||
83 entry.Mode != "cold" || entry.Generation != next.Generation || entry.Predecessor != predecessor.Generation {
84 return hostbox.ErrChanged
85 }
86 if (entry.Coverage != "sealed" && entry.Coverage != "") || (entry.Coverage == "sealed") != seal.FromDB {
87 return hostbox.ErrChanged
88 }
89 // The kept stamp is this box's active landing installation, and the
90 // admitted session is still the claim's under the guard.
91 return h.Guard.With(ctx, admitted.Host, hostguard.Captured{Incarnation: next.Incarnation, Stamp: next.Stamp}, func(c *hostclaim.Claim) error {
92 if c.Holder.Session != next.Session || c.Holder.Generation != next.Generation || c.Holder.WriterBox != h.Box || c.Op.ID != op.ID || c.Op.Kind != op.Kind || c.Op.Exec != h.Box {
93 return hostbox.ErrChanged
94 }
95 return nil
96 }, func() error { return nil })
97}
98
99func (h claimCrossBoxHandover) sourceEvidence(ctx context.Context, prior hostclaim.Observed) (moves.Seal, error) {
100 if h.Evidence == nil || h.Box == "" || prior.Absent || prior.Claim == nil || prior.Claim.Holder == nil {
101 return moves.Seal{}, hostbox.ErrChanged
102 }
103 op, old := prior.Claim.Op, prior.Claim.Holder
104 source := op.Intent["source"]
105 if op.Kind != hostclaim.KindLanding || op.ID == "" || op.Phase != hostclaim.PhaseLandingProbing || op.Exec != h.Box ||
106 len(op.Participants) != 2 || op.Participants[0] != source || op.Participants[1] != h.Box || op.Intent["target"] != h.Box ||
107 source == "" || source == h.Box || old.WriterBox != source {
108 return moves.Seal{}, hostbox.ErrChanged
109 }
110 cut, err := strconv.ParseUint(op.Journal[hostclaim.JournalCutSession], 10, 64)
111 if err != nil || cut != old.Session {
112 return moves.Seal{}, hostbox.ErrChanged
113 }
114 seal, err := moves.DecodeSeal(op.Journal[hostclaim.JournalSeal])
115 if err != nil || !seal.HasFiles() {
116 return moves.Seal{}, hostbox.ErrChanged
117 }
118 key := op.Journal[hostclaim.JournalEvidenceSource]
119 if key == "" {
120 return moves.Seal{}, hostbox.ErrChanged
121 }
122 report, err := readStoppedReport(ctx, h.Evidence, key)
123 if err != nil {
124 return moves.Seal{}, err
125 }
126 if report.Role != "source" || report.Box != source || report.Host != prior.Host || report.OpID != op.ID || report.Incarnation != old.Incarnation || report.FinalTXID != seal.N {
127 return moves.Seal{}, hostbox.ErrChanged
128 }
129 return seal, nil
130}
131
132func (h claimCrossBoxHandover) Before(ctx context.Context, prior hostclaim.Observed) error {
133 if claimRecoveryObservation(prior) {
134 return h.beforeRecovery(ctx, prior)
135 }
136 if _, err := h.sourceEvidence(ctx, prior); err != nil {
137 return err
138 }
139 old := prior.Claim.Holder
140 return h.Guard.With(ctx, prior.Host, hostguard.Captured{Incarnation: old.Incarnation, Stamp: old.Stamp}, func(c *hostclaim.Claim) error {
141 if c.Op.ID != prior.Claim.Op.ID || c.Op.Kind != hostclaim.KindLanding || c.Op.Phase != hostclaim.PhaseLandingProbing || c.Op.Exec != h.Box || c.Holder.WriterBox != old.WriterBox || c.Holder.Session != old.Session || c.Holder.Generation != old.Generation {
142 return hostbox.ErrChanged
143 }
144 return nil
145 }, func() error { return nil })
146}
147
148// Cross-box source evidence never substitutes this target's controller tuple
149// for a predecessor that ran on another box. All other shapes keep Stop.
150func claimPredecessorStop(w *ClaimWakeAssembly, box string, crossBox func(context.Context, hostclaim.Observed) error) func(context.Context, hostclaim.Observed) error {
151 return func(ctx context.Context, prior hostclaim.Observed) error {
152 if crossBox != nil && claimRemotePredecessor(prior, box) {
153 return crossBox(ctx, prior)
154 }
155 return w.Stop(ctx, prior)
156 }
157}
158
159// wireClaimAdmission routes the source proof to its operation owner before
160// lease acquisition and checks the target installation after admission.
161// With completed set, region HA's N6 handover goes in front of it
162// (wireCompletedHandover).
163func wireClaimAdmission(w *ClaimWakeAssembly, box string, local func(context.Context, hostclaim.Observed, hostclaim.Observed) error, landing claimCrossBoxHandover, relocation *ClaimRelocationOwners, completed *relocationCompletedHandover) {
164 before := landing.Before
165 after := landing.After
166 if relocation != nil {
167 before, after = relocationBefore(landing, relocation, box), relocationAfter(landing, relocation, box)
168 }
169 w.recoveryIntent = landing.prepareRecovery
170 w.recoveryPrior = landing.pendingRecovery
171 w.recoveryPrelaunched = landing.completeRecovery
172 w.BeforeAdmission = claimPredecessorStop(w, box, before)
173 w.AfterAdmission = claimAdmissionHandover(box, func(ctx context.Context, prior, admitted hostclaim.Observed) error {
174 if claimInitialSourceRecovery(prior, box) {
175 return landing.afterRecovery(ctx, prior, admitted)
176 }
177 return local(ctx, prior, admitted)
178 }, after)
179 if completed != nil {
180 wireCompletedHandover(w, completed)
181 }
182}
183
184// wireCompletedHandover is region HA N6 in front of whatever admission
185// hooks w already has: the first serving wake after a relocation completed
186// on this box hands over from the probe's existing receipt; every other
187// prior keeps those hooks unchanged (with no BeforeAdmission, main's
188// Wake.Stop). It is wired whenever claim mode is on, not only with region
189// HA on (ruling 82's reasoning, Task 9 review): it serves a relocation
190// that has already completed, and without a completion record it does
191// nothing. A serving prior on this box whose completion record cannot be
192// read is refused, never routed to main's stop (absence is never
193// evidence): that stop would replace the probe's fence.
194func wireCompletedHandover(w *ClaimWakeAssembly, completed *relocationCompletedHandover) {
195 before, after := w.BeforeAdmission, w.AfterAdmission
196 completedAfter := completed.After()
197 w.BeforeAdmission = func(ctx context.Context, prior hostclaim.Observed) error {
198 if _, ok, err := completed.completedPrior(prior); err != nil {
199 return err
200 } else if ok {
201 return completed.Before(ctx, prior)
202 }
203 if before != nil {
204 return before(ctx, prior)
205 }
206 return w.Stop(ctx, prior)
207 }
208 w.AfterAdmission = func(ctx context.Context, prior, admitted hostclaim.Observed) error {
209 if _, ok, err := completed.completedPrior(prior); err != nil {
210 return err
211 } else if ok {
212 return completedAfter(ctx, prior, admitted)
213 }
214 if after == nil {
215 return hostbox.ErrChanged
216 }
217 return after(ctx, prior, admitted)
218 }
219}
220
221// wireProductionAdmission is OpenProductionClaimAssembly's admission
222// wiring: with moves C1 on, wireClaimAdmission over the cross-box landing
223// handover and, with region HA's owners bound, the relocating routing;
224// with it off, main's hooks as they are. N6 goes in front in both, so a
225// relocation completed while region HA was on is handed over from its
226// probe's receipt at the first serving wake after region HA is turned off.
227func wireProductionAdmission(w *ClaimWakeAssembly, box string, movesC1 bool, landing claimCrossBoxHandover, relocation *ClaimRelocationOwners, completed *relocationCompletedHandover) {
228 if movesC1 {
229 wireClaimAdmission(w, box, w.AfterAdmission, landing, relocation, completed)
230 return
231 }
232 wireCompletedHandover(w, completed)
233}
234
235func claimRemotePredecessor(prior hostclaim.Observed, box string) bool {
236 if prior.Claim == nil || prior.Claim.Holder == nil || prior.Claim.Holder.WriterBox == box {
237 return false
238 }
239 return (prior.Claim.Op.Kind == hostclaim.KindRestoring && prior.Claim.Holder.WriterBox != "" && prior.Claim.Holder.WriterBox == prior.Claim.Op.Intent["target"]) || prior.Claim.Op.Kind == hostclaim.KindRelocating || (prior.Claim.Op.Kind == hostclaim.KindLanding && prior.Claim.Holder.WriterBox != "")
240}
241
242// relocationBefore and relocationAfter are wireClaimAdmission's relocating
243// routing (Plan 11), unchanged, named so the completed-relocation branch
244// (N6) can fall through to them.
245func relocationBefore(landing claimCrossBoxHandover, relocation *ClaimRelocationOwners, box string) func(context.Context, hostclaim.Observed) error {
246 return func(ctx context.Context, prior hostclaim.Observed) error {
247 if prior.Claim.Op.Kind != hostclaim.KindRelocating {
248 return landing.Before(ctx, prior)
249 }
250 if relocation.BeforeAdmission == nil || prior.Claim.Op.Exec != box || prior.Claim.Op.Phase != "probing" {
251 return hostbox.ErrChanged
252 }
253 return relocation.BeforeAdmission(ctx, prior)
254 }
255}
256
257func relocationAfter(landing claimCrossBoxHandover, relocation *ClaimRelocationOwners, box string) func(context.Context, hostclaim.Observed, hostclaim.Observed) error {
258 return func(ctx context.Context, prior, admitted hostclaim.Observed) error {
259 if prior.Claim.Op.Kind != hostclaim.KindRelocating {
260 return landing.After(ctx, prior, admitted)
261 }
262 if relocation.AfterAdmission == nil || prior.Claim.Op.Exec != box || prior.Claim.Op.Phase != "probing" {
263 return hostbox.ErrChanged
264 }
265 return relocation.AfterAdmission(ctx, prior, admitted)
266 }
267}
268