carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "errors"
5 "fmt"
6 "strings"
7 "time"
8
9 "github.com/carlosframework/platform/internal/hostclaim"
10)
11
12// The park scopes a move's chores carry (claim rev 6.23 §2, INV-22; Plan 5
13// ruling K8). These helpers are the one place the box and the console build
14// them. Each takes its genuine bound as an argument and never reads the
15// clock: the scope is bound once, in the CAS that queues the chore, and a
16// retry or a restart that computes a later bound is refused
17// (hostclaim.QueueStagingPark and friends, park.Engine.Bind), so the
18// horizon is the evidence's, never the cleanup's start. The bound is stored
19// in UTC.
20
21// ParkRequestLimit is every moves park's request limit: the longest a
22// staging request runs (lease's stagingRequestTimeout).
23const ParkRequestLimit = 120 * time.Second
24
25// SourceLocationScope is the source region's location park a commit queues:
26// holder.location, in the holder's form without a trailing slash, and the
27// holder's incarnation. Its horizon starts at the flip (ruling H7): the flip
28// comes after ExecToNone, after which the source never writes its location
29// again.
30func SourceLocationScope(holder hostclaim.Holder, flippedAt time.Time) hostclaim.ParkScope {
31 return locationScope(holder, flippedAt)
32}
33
34// TargetLocationScope is a rolled-back target region's location park, queued
35// when the target releases (hostclaim.TargetReleased): holder.location and
36// the holder's incarnation. Its horizon is the target's stop report's
37// CheckedAt, after which the target writes its location no more.
38func TargetLocationScope(holder hostclaim.Holder, stopCheckedAt time.Time) hostclaim.ParkScope {
39 return locationScope(holder, stopCheckedAt)
40}
41
42// ResidueScope is the location a landing's earlier-stay residue parks
43// (hostclaim.QueueResidueParks) are scoped to: holder.location and the
44// holder's incarnation. Its horizon starts at the flip, like the source's.
45func ResidueScope(holder hostclaim.Holder, flippedAt time.Time) hostclaim.ParkScope {
46 return locationScope(holder, flippedAt)
47}
48
49func locationScope(holder hostclaim.Holder, bound time.Time) hostclaim.ParkScope {
50 return hostclaim.ParkScope{Location: strings.TrimSuffix(holder.Location, "/"), Incarnation: holder.Incarnation, EvidenceAt: bound.UTC(), RequestLimit: ParkRequestLimit}
51}
52
53// StagingScope is the park of a move's own staging lane, moves/<host>/<op>,
54// after a seal: its horizon is the seal's bound (a staged seal's for a
55// rollback's target, the journalled revert bound for a revert).
56func StagingScope(host, op, inc string, sealBound time.Time) hostclaim.ParkScope {
57 return laneScope(host, op, inc, sealBound)
58}
59
60// AbortScope is the park of an aborted move's staging lane. No seal exists
61// before an abort, so its horizon is the journalled abort bound (Plan 5
62// ruling K5): the later of the decision's time plus a signature's life and
63// the skew, and any seal's bound.
64func AbortScope(host, op, inc string, abortBound time.Time) hostclaim.ParkScope {
65 return laneScope(host, op, inc, abortBound)
66}
67
68// ReturnLaneScope is the park of a dirty rollback's return lane,
69// moves/<host>/<rb>: its horizon is the return seal's bound.
70func ReturnLaneScope(host, rbID, inc string, returnSealBound time.Time) hostclaim.ParkScope {
71 return laneScope(host, rbID, inc, returnSealBound)
72}
73
74func laneScope(host, lane, inc string, bound time.Time) hostclaim.ParkScope {
75 return hostclaim.ParkScope{Location: ScopeLocation(host, lane), Incarnation: inc, EvidenceAt: bound.UTC(), RequestLimit: ParkRequestLimit}
76}
77
78// CommitScopes are the two parks a commit queues (hostclaim.Commit), built
79// from the claim and the record's flip time alone (Plan 5 Task 3, ruling
80// K9): the source region's location from the flip (SourceLocationScope,
81// ruling H7) and the move's own lane to the seal the claim journals
82// (StagingScope with the seal's bound). The target box and the console's
83// pass (Task 7a) both call it, so whichever commits first writes the
84// obligations the other's replay finds; a scope built any other way would
85// be refused as "another scope" by the queue helpers. It refuses without
86// the flip's time, the holder or the journalled seal.
87func CommitScopes(c *hostclaim.Claim, flippedAt *time.Time) (source, staging hostclaim.ParkScope, err error) {
88 if c == nil || c.Holder == nil {
89 return source, staging, errors.New("a commit's parks need the claim's holder")
90 }
91 if flippedAt == nil || flippedAt.IsZero() {
92 return source, staging, errors.New("the source's location park needs the flip's time")
93 }
94 raw, ok := c.Op.Journal[hostclaim.JournalSeal]
95 if !ok {
96 return source, staging, fmt.Errorf("%w: the claim carries no seal", ErrBadSeal)
97 }
98 seal, err := DecodeSeal(raw)
99 if err != nil {
100 return source, staging, err
101 }
102 return SourceLocationScope(*c.Holder, *flippedAt), StagingScope(c.Host, c.Op.ID, c.Holder.Incarnation, seal.Bound()), nil
103}
104