carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package engines
2
3import (
4 "fmt"
5 "math/rand"
6 "sort"
7)
8
9// The model of the fleet-internal engines (spec
10// docs/superpowers/specs/2026-10-21-fleet-internal-engines-design.md). One
11// tick is 10 s: a step is a tick. Everything is decided from one seeded
12// source; nothing reads the clock, starts a goroutine or lets a map's
13// iteration order choose.
14
15const (
16 chaosSteps = 400 // faults and every action are eligible
17 calmSteps = 200 // the same walk, then the final oracle
18 nBuilds = 4
19)
20
21// The starting world's names.
22const (
23 eng = "eng" // the engine group (its owner's host; engine names have no dot)
24 engHost = "eng.acct.apps.example"
25 engAlias = "docs.example" // alias row on box 0, to engHost
26 engSite = "www.example" // static row on box 0, path map to engHost
27 engSitePath = "/convert"
28 shopAlias = "shop-alias.example" // alias row on box 1, to shop
29 shopSite = "shop-www.example" // static row on box 1, path map to shop
30 shopSitePath = "/app"
31)
32
33// build is a platform build: whether it declares the internal-engine
34// protocol, and whether the manifest it writes is in place.
35type build struct {
36 Ver int
37 Declares, ManifestOK bool
38}
39
40// proc is a running process. Slice is the cgroup the peer check reads:
41// inst:<group>, exec:<host> or unit:<host>.
42//
43// Code is the account/app whose code the process runs when that is not its
44// group's: code a flat-era tamper planted in the group's instance directory
45// (Task 5). Residual names the stated residual the process belongs to, if
46// any; a breach it causes is recorded as that residual, not a violation.
47type proc struct {
48 ID int
49 Group string
50 Ver int
51 Slice string
52 Unisolated, OwnPort bool
53 Code, Residual string
54}
55
56// row is a host row of a box's table. Kind is instance, service, static or
57// alias; Backend is a socket path or an address, "" for static and alias.
58// An alias row names its target host in Alias and copies its upstream
59// (edge.go:929); PathTo maps a path to the host that serves it (edge.go:1364).
60//
61// A platform-minted row (Platform) names in Source the host it was minted
62// from: the truth the oracle reads. Owner is what the registry has recorded
63// (routes.owner), "" for a row minted before owners were recorded; the edge
64// then finds the owner by App and backend (spec §5.1, "Platform-minted
65// rows"). App is the row's account/app. GrantAt is the registry change that
66// recorded the doc-engine grant.
67type row struct {
68 Host, Kind, Owner, Backend string
69 Platform, Grant bool
70 PathTo map[string]string
71 Alias string
72 App, Source string
73 GrantAt int
74}
75
76// mark is a row's exposure mark.
77type mark int
78
79const (
80 markNull mark = iota
81 markPublic
82 markInternal
83)
84
85func (m mark) String() string {
86 switch m {
87 case markPublic:
88 return "public"
89 case markInternal:
90 return "internal"
91 }
92 return "null"
93}
94
95// caller names who a request is on behalf of.
96type caller struct{ Account, App, Engine string }
97
98// req is a request the box admitted. Row is the row whose mark admitted it;
99// Backend the proxy it holds from then on; Reached the process it was
100// delivered to (0 for none).
101type req struct {
102 ID, Epoch int
103 Row, Backend string
104 Public bool
105 Caller *caller
106 Held bool
107 Box, Reached int
108}
109
110// box is one host of the fleet: box 0 is the engine's home, box 1 a calling
111// box.
112//
113// Rows and Mark are the registry (the intent). EdgeRows and Serving are the
114// edge's in-memory map, swapped in only by a successful reload. Wrote counts
115// registry mark changes, Fenced is the change the edge has fenced, SetAt the
116// change that last closed a group (its fence generation; a group with no key
117// has never been fenced closed, spec §5.1, "Setting the mark").
118type box struct {
119 ID int
120 Rows map[string]*row
121 Mark map[string]mark
122 Wrote, Fenced, Epoch int
123 ReloadFails, Armed bool
124 Locked map[string]bool // the group's lock, held by --public for its whole run
125 Live, Good map[string]int
126 Staged map[string][]int
127 Procs []*proc
128 Admitted []*req
129
130 EdgeRows map[string]row
131 Serving map[string]mark
132 SetAt map[string]int
133
134 // PortGrant is the operator's ports grant by group (the port); Listen the
135 // ListenStream sockets the capability pass has rendered, port to group
136 // (spec §5.1, "The edge is the only ingress").
137 PortGrant, Listen map[string]string
138
139 // Task 5 (calls_test.go). Mirror is the edge's grant mirror (spec §5.3).
140 // Armed is `carlos engines arm`; Sockets is whether the edge's listener
141 // manager has the engine sockets open (§5.4, §6.2). Unisolated is every
142 // route on the box whose tenant code runs outside an instance slice, by
143 // kind (flat, foreign, worker). Tampered is a nested group's instance
144 // directory written by an unisolated tenant: the planter's app.
145 //
146 // ReadRev is the oracle's, not the protocol's: the highest revision the
147 // box's mirror has read since it was last rebuilt (a restart keeps it), so
148 // INV-G can hold a revoke the box has read in force (§6.4).
149 Mirror mirror
150 Sockets bool
151 Unisolated map[string]bool
152 Tampered map[string]string
153 ReadRev int
154}
155
156type world struct {
157 seed int64
158 rng *rand.Rand
159 boxes []*box
160 builds map[int]build
161 step int
162 trace []string
163 violations []string
164 residuals []string
165 stats map[string]int // actions selected, by name
166 done map[string]int // actions whose transition completed, by name
167 events map[string]int // deliveries: public requests and engine calls that reached a process
168 nextID int // process and request ids
169
170 // clearHook, when set, runs inside operatorPublic at each stage
171 // ("locked": step 1 passed; "stopped": step 2 done; "confirmed": step 3
172 // passed, before the write of 0), so a scenario or the walk can
173 // interleave there.
174 clearHook func(b *box, g, stage string)
175 // clearStopMisses, when set, makes operatorPublic's stop leave one
176 // process of the group behind (a unit or generation whose stop the
177 // activator or control service failed to complete).
178 clearStopMisses bool
179
180 // Task 5 (calls_test.go): the console's engines record and the bucket
181 // that carries it; skew is time that passed without a step (a
182 // fast-forward), so now() is step+skew.
183 console engConsole
184 skew int
185
186 // The spec's two stated residuals. Off by default and set only by tests:
187 // with one on, the walk may generate its sequence, and the breach it
188 // causes is recorded in residuals as residual:<name>, never in
189 // violations. (A fresh box's replay was a third until ruling R5 bounded
190 // it by the freshness bound; INV-G now covers it.)
191 unwrittenManifestAcrossUpgrade, flatEraTamper bool
192
193 verbose bool
194}
195
196// action is one thing the random walk can do. Tasks 2 to 5 append theirs
197// from their own files' init; the walk picks among them by weight.
198//
199// run reports whether the transition completed: false when the action found
200// nothing to act on (no group, no held request, a full table) and returned
201// without calling into the model. A refusal by the protocol is a completed
202// transition. The walk counts selections (stats) and completions (done)
203// apart, so coverage can be checked on what really ran.
204type action struct {
205 name string
206 weight int
207 run func(*world) bool
208}
209
210var actions []action
211
212// stepHooks run at the start of every step, before its action: what happens
213// on a timer whatever the walk chooses (Task 5: the mirror's poll).
214var stepHooks []func(*world)
215
216func newWorld(seed int64, verbose bool) *world {
217 w := &world{
218 seed: seed,
219 rng: rand.New(rand.NewSource(seed)),
220 builds: map[int]build{},
221 stats: map[string]int{},
222 done: map[string]int{},
223 events: map[string]int{},
224 verbose: verbose,
225 }
226 newBox := func(id int) *box {
227 return &box{ID: id, Rows: map[string]*row{}, Mark: map[string]mark{}, Live: map[string]int{}, Good: map[string]int{}, Staged: map[string][]int{},
228 EdgeRows: map[string]row{}, Serving: map[string]mark{}, SetAt: map[string]int{},
229 PortGrant: map[string]string{}, Listen: map[string]string{}, Locked: map[string]bool{},
230 Unisolated: map[string]bool{}, Tampered: map[string]string{}}
231 }
232 home, calling := newBox(0), newBox(1)
233 w.boxes = []*box{home, calling}
234
235 // one engine group, eng: its own route and its minted platform row
236 home.Rows[eng] = &row{Host: eng, Kind: "instance", App: "acct/eng", Backend: "/run/carlos/eng.sock", PathTo: map[string]string{}}
237 home.Rows[engHost] = &row{Host: engHost, Kind: "instance", App: "acct/eng", Owner: eng, Source: eng, Backend: "/run/carlos/eng.sock", Platform: true, PathTo: map[string]string{}}
238 home.Mark[eng], home.Mark[engHost] = markInternal, markInternal
239 home.SetAt[eng] = 0 // closed from the start, and loaded below
240 // one public app group, shop, on the calling box
241 calling.Rows["shop"] = &row{Host: "shop", Kind: "instance", App: "acct/shop", Backend: "127.0.0.1:9001", PathTo: map[string]string{}}
242 calling.Mark["shop"] = markPublic
243 // on each box an alias row and a static row with a path map, both
244 // resolving to the box's group: rows of kinds that serve no release, 0
245 // by construction (spec §5.1, "What the edge does with it")
246 for _, x := range []struct {
247 b *box
248 alias, site, path, to string
249 }{{home, engAlias, engSite, engSitePath, engHost}, {calling, shopAlias, shopSite, shopSitePath, "shop"}} {
250 x.b.Rows[x.alias] = &row{Host: x.alias, Kind: "alias", Alias: x.to, PathTo: map[string]string{}}
251 x.b.Rows[x.site] = &row{Host: x.site, Kind: "static", PathTo: map[string]string{x.path: x.to}}
252 x.b.Mark[x.alias], x.b.Mark[x.site] = markPublic, markPublic
253 }
254 // the edges start with the registry loaded
255 for _, b := range w.boxes {
256 b.loadEdge()
257 }
258 w.initEngines() // the console's first record, read by every edge
259
260 for v := 1; v <= nBuilds; v++ {
261 w.builds[v] = build{Ver: v, Declares: w.rng.Intn(2) == 0, ManifestOK: w.rng.Intn(2) == 0}
262 }
263 w.logf("world seed %d: builds %s", seed, w.buildsString())
264 return w
265}
266
267func (w *world) buildsString() string {
268 var out []string
269 for v := 1; v <= nBuilds; v++ {
270 b := w.builds[v]
271 out = append(out, fmt.Sprintf("%d{declares=%v manifest=%v}", v, b.Declares, b.ManifestOK))
272 }
273 return fmt.Sprint(out)
274}
275
276// logf appends a trace line. The trace is always kept: it is what the
277// determinism test compares, and what ENGINESIM_SEED prints.
278func (w *world) logf(format string, args ...any) {
279 w.trace = append(w.trace, fmt.Sprintf("[%d] ", w.step)+fmt.Sprintf(format, args...))
280}
281
282// calm is true once the chaos steps are over. doStep counts the step before
283// it runs the action, so steps 1 to chaosSteps are chaos and the rest calm.
284func (w *world) calm() bool { return w.step > chaosSteps }
285
286// group is a row's group key: its owner for a minted instance row whose
287// owner the registry can find, else its own host (spec §5.1,
288// "Platform-minted rows"; resolveOwner).
289func (w *world) group(b *box, host string) string {
290 if r := b.Rows[host]; r != nil && followsOwner(r) {
291 if o, ok := b.resolveOwner(r); ok {
292 return o
293 }
294 }
295 return host
296}
297
298// pick chooses one registered action by weight. The registry is sorted by
299// name first, so the order files' init functions ran in never matters.
300func (w *world) pick() *action {
301 sorted := make([]action, 0, len(actions))
302 total := 0
303 for _, a := range actions {
304 if a.weight > 0 {
305 sorted = append(sorted, a)
306 total += a.weight
307 }
308 }
309 if total == 0 {
310 return nil
311 }
312 sort.SliceStable(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name })
313 n := w.rng.Intn(total)
314 for i := range sorted {
315 if n < sorted[i].weight {
316 return &sorted[i]
317 }
318 n -= sorted[i].weight
319 }
320 return nil
321}
322
323// doStep advances one tick: it counts the step, runs one chosen action and
324// counts it under its name, in stats when chosen and in done when its
325// transition completed.
326func (w *world) doStep() {
327 w.step++
328 for _, h := range stepHooks {
329 h(w)
330 }
331 a := w.pick()
332 if a == nil {
333 if w.verbose {
334 w.logf("idle")
335 }
336 return
337 }
338 w.stats[a.name]++
339 w.logf("action %s", a.name) // always traced: the determinism test compares the action sequence
340 if a.run(w) {
341 w.done[a.name]++
342 }
343 w.check()
344}
345
346// final is the end-of-run oracle: what must hold once the calm steps are
347// over. Later tasks add their end-state checks here.
348//
349// The faults are over: every edge's reload works again, and each edge polls
350// once, so every row has had its chance to answer. Then INV-AV.
351func (w *world) final() {
352 for _, b := range w.boxes {
353 if b.ReloadFails {
354 b.ReloadFails = false
355 w.logf("box%d: final: the reload fault has cleared", b.ID)
356 }
357 w.reload(b)
358 }
359 w.checkAV()
360 w.logf("final: %d steps, %d violations, %d residuals", w.step, len(w.violations), len(w.residuals))
361}
362
363func runSeed(seed int64, verbose bool) *world {
364 w := newWorld(seed, verbose)
365 for i := 0; i < chaosSteps+calmSteps; i++ {
366 w.doStep()
367 }
368 w.final()
369 return w
370}
371