carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostlife
2
3import (
4 "crypto/sha256"
5 "fmt"
6 "hash/fnv"
7 "os"
8)
9
10var _ = fnv.New32a // rev 1's stand-in hash, kept importable
11
12// Two boxes per region (region HA spec 1 §9 L18; claim rev 6.30 §11).
13//
14// boxesPerRegion is the world's shape: one box per region, today's model,
15// where every seed is byte-identical to the one-box baseline (spec §4: a
16// region with one box behaves exactly as today); or two, where every
17// region starts with a second enrolled box and the placement, relocation
18// and termination rules below apply. HOSTLIFE_BOXES=2 selects the two-box
19// shape for a sweep (mutate.sh inherits it); TestHostLifecycleTwoBox sets
20// it for its own run. A two-box world draws its extra random values only
21// after the one-box draws of the same step, so the one-box stream is
22// untouched.
23var boxesPerRegion = 1
24
25func init() {
26 if os.Getenv("HOSTLIFE_BOXES") == "2" {
27 boxesPerRegion = 2
28 }
29}
30
31// regionBoxes is every live box of region r, by id.
32func (w *world) regionBoxes(r int) []*box {
33 var out []*box
34 for _, b := range w.boxes {
35 if b.Region == r && !b.Destroyed {
36 out = append(out, b)
37 }
38 }
39 return out
40}
41
42// addBox enrolls a second box in region r of a witness world: the secrets
43// key held, no faults, id nRegions+r (the sweep's second boxes carry the
44// same ids; replacements are 100 and up).
45func (w *world) addBox(r int) *box {
46 b := &box{ID: nRegions + r, Region: r, HasKey: true, Enrolled: true, DeleteOnTermination: true}
47 b.forget()
48 w.boxes = append(w.boxes, b)
49 w.logf("witness: box %d enrolled in region %d", b.ID, r)
50 return b
51}
52
53// tieHash is the model's stand-in for claim P5-D2's SHA-256 of
54// host + "\x00" + box reference: a fixed function of the reference, never
55// a draw, so the choice is the same on every replay.
56func tieHash(id int) uint32 { return tieHashFor(host, id) }
57
58// tieHashFor is claim P5-D2's tie-break for the named host (rev 4, Astra
59// R1-8 and 1b-next R18-2): the first four bytes of SHA-256(host + "\x00" +
60// box reference), so two hostnames on equal loads may choose different
61// boxes. The mutation of tie-hash-host hashes a constant name, as the
62// round-18 chooser bug did.
63func tieHashFor(hostName string, id int) uint32 {
64 if !on("tie-hash-host") {
65 hostName = host
66 }
67 sum := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%d", hostName, id)))
68 return uint32(sum[0])<<24 | uint32(sum[1])<<16 | uint32(sum[2])<<8 | uint32(sum[3])
69}
70
71// hostsOn counts the instances of app placed on b (app "" counts every
72// app): the claimed host on its executor, or on the box its create or
73// move named.
74func (w *world) hostsOn(b *box, app string) int {
75 n := 0
76 if w.h2.Kind != "" && w.h2.Box == b.ID && (app == "" || w.h2.App == app) {
77 n = 1 // the second host, placed by its create (Task 5)
78 }
79 c := w.claim
80 if c == nil || c.Holder == nil || (app != "" && c.Holder.App != app) {
81 return n
82 }
83 switch {
84 case c.Op.Kind == "creating" && c.Op.NewBox == b.ID:
85 return n + 1
86 case c.Op.Exec == b.ID:
87 return n + 1
88 }
89 return n
90}
91
92// owesLocal: an open park-local chore names b.
93func (w *world) owesLocal(c *claim, b *box) bool {
94 if c == nil {
95 return false
96 }
97 for _, o := range c.Obl {
98 if o.Kind == "park-local" && o.Box == b.ID {
99 return true
100 }
101 }
102 return false
103}
104
105// chooseBox is region HA §5.2's choose() (H12, L8; a move's target under
106// L15) with nothing excluded.
107func (w *world) chooseBox(r int, app string) *box { return w.chooseBoxExcept(r, app) }
108
109// chooseBoxExcept is choose() over the region's eligible boxes minus
110// except (a relocation's source, its replaced targets): among the
111// eligible boxes of region r (§2: live, running the claim's executable,
112// holding the secrets key, not on lost, not owing a park-local for the
113// host, enrolled: an unenrolled reference is never eligible, HA7; rule
114// reloc-enrolled-only, Task 3), prefer one running no instance of app,
115// then the fewest hosts, then the greatest tieHash. With one box in the
116// region it is that box, as today (spec §4). It is a pure function of the
117// world; the caller records the choice in the claim's intent and never
118// substitutes (rule create-choice-once).
119func (w *world) chooseBoxExcept(r int, app string, except ...int) *box {
120 return w.chooseBoxFor(host, r, app, except...)
121}
122
123// chooseBoxFor is choose() for the named host (its tie-break hashes that
124// name). The sole-box shortcut, today's behaviour, is the one-box shape's
125// alone: with region HA on, one remaining candidate is checked like any
126// other (rev 4, Astra R1-3; rule ha-sole-box-eligibility).
127func (w *world) chooseBoxFor(hostName string, r int, app string, except ...int) *box {
128 bs := w.regionBoxes(r)
129 if len(bs) <= 1 && (w.perRegion <= 1 || !on("ha-sole-box-eligibility")) {
130 if len(bs) == 0 || contains(except, bs[0].ID) {
131 return nil
132 }
133 return bs[0]
134 }
135 c := w.claim
136 var best *box
137 var bestKey [3]int
138 for _, b := range bs {
139 if contains(except, b.ID) || b.Old || !b.HasKey || (c != nil && contains(c.Lost, b.ID)) {
140 continue
141 }
142 if w.owesLocal(c, b) && (len(except) == 0 || on("reloc-replace-not-owing")) {
143 continue // §2: a box owing a park-local for the host is never chosen (the mutation of reloc-replace-not-owing lifts it for a relocation's target)
144 }
145 if on("reloc-enrolled-only") && !b.Enrolled {
146 continue
147 }
148 key := [3]int{w.hostsOn(b, app), w.hostsOn(b, ""), -int(tieHashFor(hostName, b.ID))}
149 if best == nil || key[0] < bestKey[0] || key[0] == bestKey[0] && (key[1] < bestKey[1] || key[1] == bestKey[1] && key[2] < bestKey[2]) {
150 best, bestKey = b, key
151 }
152 }
153 return best
154}
155
156// adoptionOwner is the box adoption runs on (claim P5-D2, region HA §9 K5):
157// in the one-box shape it is the region's box, as today; in the two-box
158// shape exactly one live box must hold the legacy host's local state (its
159// row or database), and zero or several refuse adoption, even when only
160// one box of the region is left (placement does not prove ownership).
161func (w *world) adoptionOwner(r int) *box {
162 bs := w.regionBoxes(r)
163 if w.perRegion <= 1 {
164 if len(bs) == 0 {
165 return nil
166 }
167 return bs[0]
168 }
169 var owner *box
170 for _, b := range bs {
171 holds := b.Row != nil && b.Row.Inc == w.rec.Inc || b.DB != nil && b.DB.Inc == w.rec.Inc
172 if !holds {
173 continue
174 }
175 if owner != nil {
176 return nil // several proven owners: refused
177 }
178 owner = b
179 }
180 return owner
181}
182
183// ownsObligation: b may run chore o. A park-local is its box's alone; a
184// regional chore is any live box of its region's (claim P6-D5: regional
185// workers), as it was the region's one box's.
186func (w *world) ownsObligation(o obligation, b *box) bool {
187 if o.Kind == "park-local" {
188 return w.ownerOf(o) == b
189 }
190 if o.Owner != "" {
191 return o.Owner == boxOwner(b.ID) && !b.Destroyed // box:<b>, the legacy owner form: that box alone, until a fence or a declared loss passes it to the region
192 }
193 return b.Region == o.Region && !b.Destroyed
194}
195