carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostrelocate
2
3import (
4 "context"
5 "crypto/sha256"
6 "encoding/hex"
7 "encoding/json"
8 "errors"
9 "fmt"
10 "os"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/carlosframework/platform/internal/genselect"
16 "github.com/carlosframework/platform/internal/hostclaim"
17 "github.com/carlosframework/platform/internal/hostfiles"
18 "github.com/carlosframework/platform/internal/moves"
19 "github.com/carlosframework/platform/internal/park"
20 "github.com/carlosframework/platform/internal/release"
21)
22
23const pinLocation = "instances/" + host + "/inc"
24
25func pinClaim() hostclaim.Observed {
26 return hostclaim.Observed{Host: host, Claim: &hostclaim.Claim{Host: host, Gen: 12, Op: hostclaim.Op{Kind: hostclaim.KindRelocating, ID: "rel-1", Phase: hostclaim.PhasePinning, Journal: map[string]string{}},
27 Holder: &hostclaim.Holder{Location: pinLocation, Generation: "g3", Session: 3,
28 Admissions: []hostclaim.AdmissionRecord{{Generation: "g2", Predecessor: "g1"}, {Generation: "g3", Predecessor: "g2"}}}}}
29}
30
31// keyed adds the host's instance.key (H6), as every manifest of a host
32// whose database is nonempty must carry (rev 18), and the platform items
33// every pinned manifest carries (withPlatformItems).
34func keyed(m hostfiles.Manifest) hostfiles.Manifest {
35 sha := strings.Repeat("b", 64)
36 m.Entries["instance.key"] = hostfiles.Entry{Object: pinLocation + "/files/g-g1/objects/0000000000000001-" + sha, SHA256: sha, Size: 32, Mode: 0o600, Seq: 1}
37 return withPlatformItems(m)
38}
39
40// withPlatformItems adds the env file and the wake state, as a release
41// flush publishes them, where m has none (Task 8c: a pinned manifest
42// without either refuses). The pinner reads neither unless the key
43// evidence asks for the env.
44func withPlatformItems(m hostfiles.Manifest) hostfiles.Manifest {
45 return withItem(withItem(m, hostfiles.EnvItem, "c"), hostfiles.WakeItem, "d")
46}
47
48func withItem(m hostfiles.Manifest, name, digit string) hostfiles.Manifest {
49 if _, ok := m.Platform[name]; !ok {
50 sha := strings.Repeat(digit, 64)
51 m.Platform[name] = hostfiles.Entry{Object: pinLocation + "/files/g-g1/objects/0000000000000001-" + sha, SHA256: sha, Size: 10, Mode: 0o600, Seq: 1}
52 }
53 return m
54}
55
56// stagePlatformItems stages the env file and the wake state, as a release
57// flush does.
58func stagePlatformItems(t *testing.T, p *hostfiles.Publisher) {
59 t.Helper()
60 if err := p.StagePlatform(context.Background(), hostfiles.EnvItem, []byte("CARLOS_ADMIN_TOKEN=t\n")); err != nil {
61 t.Fatal(err)
62 }
63 if err := p.StagePlatform(context.Background(), hostfiles.WakeItem, []byte(`{"v":1,"host":"`+host+`","timers":[]}`)); err != nil {
64 t.Fatal(err)
65 }
66}
67
68func putManifest(t *testing.T, data release.Store, gen string, m hostfiles.Manifest) (string, []byte) {
69 t.Helper()
70 raw, _ := json.Marshal(m)
71 key := hostfiles.ManifestKey(moves.GenerationFilesPrefix(pinLocation, gen))
72 if _, err := data.Put(context.Background(), key, raw, ""); err != nil {
73 t.Fatal(err)
74 }
75 return key, raw
76}
77
78func selectAt(txid uint64) func(context.Context, genselect.Request) (*genselect.Plan, error) {
79 return func(_ context.Context, req genselect.Request) (*genselect.Plan, error) {
80 return &genselect.Plan{Generation: req.Holder.Generation, MaxTXID: txid}, nil
81 }
82}
83
84func TestStorePinnerPinsTheNewestFilesManifestInTheChain(t *testing.T) {
85 ctx := context.Background()
86 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
87 // g3 (holder.generation) never published files; g2 did, then g1 earlier.
88 for gen, seq := range map[string]int64{"g2": 5, "g1": 9} {
89 putManifest(t, data, gen, keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: seq, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}))
90 }
91 selects := 0
92 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie",
93 SelectDB: func(ctx context.Context, req genselect.Request) (*genselect.Plan, error) {
94 selects++
95 return selectAt(41)(ctx, req)
96 }}
97 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
98 pin, err := p.Pin(ctx, pinClaim(), at)
99 if err != nil {
100 t.Fatal(err)
101 }
102 if pin.FilesManifest != hostfiles.ManifestKey(pinLocation+"/files/g-g2/") || pin.FilesSeq != 5 || pin.MaxTXID != 41 || pin.Key != PinKey(host, "rel-1", 12) || pin.Empty {
103 t.Fatalf("pin: %+v", pin)
104 }
105 obj, err := ReadPin(ctx, claims, pin)
106 if err != nil {
107 t.Fatal(err)
108 }
109 if want := []string{pinLocation + "/files/g-g3/", pinLocation + "/files/g-g2/", pinLocation + "/files/g-g1/"}; !equalStrings(obj.Files.Origins, want) || obj.ClaimGen != 12 {
110 t.Fatalf("origins %v gen %d", obj.Files.Origins, obj.ClaimGen)
111 }
112 // The exact bytes are retained in the control bucket before the pin.
113 kept, _, kerr := claims.Get(ctx, obj.Files.Retained)
114 if kerr != nil || obj.Files.Retained != RetainedManifestKey(host, "rel-1", obj.Files.SHA256) {
115 t.Fatalf("retained copy: %s %v", obj.Files.Retained, kerr)
116 }
117 if sum := sha256.Sum256(kept); hex.EncodeToString(sum[:]) != obj.Files.SHA256 {
118 t.Fatal("the retained copy is not the pinned bytes")
119 }
120 // A retry at the same observation reuses the object unchanged.
121 again, err := p.Pin(ctx, pinClaim(), at.Add(time.Minute))
122 if err != nil || again != pin || selects != 1 {
123 t.Fatalf("replay: %+v %v selects=%d", again, err, selects)
124 }
125 // The claim moved (the journal CAS lost): the next pin is a fresh
126 // selection at the new generation; the old object is never reused.
127 moved := pinClaim()
128 moved.Claim.Gen = 13
129 next, err := p.Pin(ctx, moved, at.Add(2*time.Minute))
130 if err != nil || next.Key != PinKey(host, "rel-1", 13) || selects != 2 || next.PinnedAt.Equal(pin.PinnedAt) {
131 t.Fatalf("after the claim moved: %+v %v selects=%d", next, err, selects)
132 }
133 // An object at the key that is not this operation's is refused.
134 if _, err := claims.Put(ctx, PinKey(host, "rel-2", 12), []byte(`{"v":1,"host":"other","op":"rel-2","claim_gen":12,"db":{}}`), ""); err != nil {
135 t.Fatal(err)
136 }
137 other := pinClaim()
138 other.Claim.Op.ID = "rel-2"
139 if _, err := p.Pin(ctx, other, at); !errors.Is(err, ErrRefused) {
140 t.Fatalf("foreign pin object: %v", err)
141 }
142 // A rewritten object is caught by the journaled digest.
143 tampered := pin
144 tampered.SHA256 = strings.Repeat("0", 64)
145 if _, err := ReadPin(ctx, claims, tampered); !errors.Is(err, ErrRefused) {
146 t.Fatalf("a pin that does not hash to its digest was read: %v", err)
147 }
148}
149
150func equalStrings(a, b []string) bool {
151 if len(a) != len(b) {
152 return false
153 }
154 for i := range a {
155 if a[i] != b[i] {
156 return false
157 }
158 }
159 return true
160}
161
162func TestStorePinnerChecksTheBoundsBeforeItWrites(t *testing.T) {
163 ctx := context.Background()
164 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
165 release := func(txid uint64, seq int64) string {
166 raw, _ := json.Marshal(hostclaim.Release{ReleasedBy: "src", Enrollment: "e", Generation: "g3", Session: 3, FinalTXID: txid, ManifestSeq: seq, Receipt: "r", ReceiptSHA256: strings.Repeat("d", 64)})
167 return string(raw)
168 }
169 for _, tc := range []struct {
170 name string
171 journal map[string]string
172 txid uint64
173 seq int64
174 now time.Time
175 }{
176 {"the listing is short of the release", map[string]string{"release": release(45, 5)}, 41, 5, at},
177 {"the files manifest is short of the release", map[string]string{"release": release(41, 6)}, 41, 5, at},
178 {"inside the failover window", map[string]string{"fence.src.observed_at": at.Format(time.RFC3339Nano)}, 41, 5, at.Add(29 * time.Second)},
179 } {
180 t.Run(tc.name, func(t *testing.T) {
181 claims, data := release_NewDirStore(t), release_NewDirStore(t)
182 putManifest(t, data, "g3", keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: tc.seq, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}))
183 o := pinClaim()
184 o.Claim.Op.Journal = tc.journal
185 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(tc.txid)}
186 if _, err := p.Pin(ctx, o, tc.now); !errors.Is(err, ErrNotYet) {
187 t.Fatalf("got %v, want ErrNotYet", err)
188 }
189 if keys, _ := claims.List(ctx, "control/relocations/"); len(keys) != 0 {
190 t.Fatalf("a pin that cannot be journaled was written: %v", keys)
191 }
192 })
193 }
194}
195
196func release_NewDirStore(t *testing.T) release.Store { return release.NewDirStore(t.TempDir()) }
197
198func TestStorePinnerPinsEmptyOnlyWithEvidence(t *testing.T) {
199 ctx := context.Background()
200 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
201 empty := func(context.Context, genselect.Request) (*genselect.Plan, error) { return nil, genselect.ErrEmpty }
202 released := func(txid uint64) string {
203 raw, _ := json.Marshal(hostclaim.Release{ReleasedBy: "src", Enrollment: "e", Generation: "g3", Session: 3, FinalTXID: txid, Receipt: "r", ReceiptSHA256: strings.Repeat("d", 64)})
204 return string(raw)
205 }
206 for _, tc := range []struct {
207 name string
208 session uint64
209 journal map[string]string
210 want string
211 }{
212 {"a release at txid 0", 3, map[string]string{"release": released(0)}, "release"},
213 {"a never-woken host", 0, map[string]string{}, "session-0"},
214 {"a release at txid 40 and an empty listing", 3, map[string]string{"release": released(40)}, ""},
215 {"a failover of a host that had a writer", 3, map[string]string{}, ""},
216 } {
217 t.Run(tc.name, func(t *testing.T) {
218 claims, data := release_NewDirStore(t), release_NewDirStore(t)
219 o := pinClaim()
220 o.Claim.Holder.Session, o.Claim.Op.Journal = tc.session, tc.journal
221 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: empty}
222 pin, err := p.Pin(ctx, o, at)
223 if tc.want == "" {
224 if !errors.Is(err, genselect.ErrEmpty) {
225 t.Fatalf("an unevidenced empty listing was pinned: %+v %v", pin, err)
226 }
227 return
228 }
229 if err != nil || !pin.Empty || pin.MaxTXID != 0 || pin.Generation != "" {
230 t.Fatalf("empty pin: %+v %v", pin, err)
231 }
232 obj, err := ReadPin(ctx, claims, pin)
233 if err != nil || obj.Empty != tc.want || obj.DB != nil {
234 t.Fatalf("object: %+v %v", obj, err)
235 }
236 })
237 }
238}
239
240func TestStorePinnerRefusesAnUnreadableManifest(t *testing.T) {
241 ctx := context.Background()
242 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
243 if _, err := data.Put(ctx, hostfiles.ManifestKey(pinLocation+"/files/g-g3/"), []byte(`{"v":1}`), ""); err != nil {
244 t.Fatal(err)
245 }
246 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}
247 if _, err := p.Pin(ctx, pinClaim(), time.Now()); !errors.Is(err, ErrRefused) {
248 t.Fatalf("a version 1 manifest was pinned: %v", err)
249 }
250}
251
252// pinnedManifest publishes gen's manifest whose one entry names an object
253// under origin (an earlier generation's prefix), and returns its pin.
254func pinnedManifest(t *testing.T, claims, data release.Store, gen, origin string, seq int64) (FilesPin, hostfiles.Manifest) {
255 t.Helper()
256 sum := sha256.Sum256([]byte("hello"))
257 sha := hex.EncodeToString(sum[:])
258 object := origin + "objects/" + strings.Repeat("0", 15) + "1-" + sha
259 if _, err := data.Put(context.Background(), object, []byte("hello"), ""); err != nil {
260 t.Fatal(err)
261 }
262 m := hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: seq, Entries: map[string]hostfiles.Entry{"a.txt": {Object: object, SHA256: sha, Size: 5, Mode: 0o644, Seq: 1}}, Platform: map[string]hostfiles.Entry{}}
263 _, raw := putManifest(t, data, gen, m)
264 rawSum := sha256.Sum256(raw)
265 manifestSHA := hex.EncodeToString(rawSum[:])
266 retained := RetainedManifestKey(host, "rel-1", manifestSHA)
267 if _, err := claims.Put(context.Background(), retained, raw, ""); err != nil {
268 t.Fatal(err)
269 }
270 prefix := moves.GenerationFilesPrefix(pinLocation, gen)
271 return FilesPin{Prefix: prefix, Seq: seq, SHA256: manifestSHA, Origins: []string{prefix, origin}, Retained: retained}, m
272}
273
274func TestCopyPinnedManifestEnforcesThePin(t *testing.T) {
275 ctx := context.Background()
276 t.Run("a second relocation keeps objects of an earlier generation", func(t *testing.T) {
277 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
278 pin, want := pinnedManifest(t, claims, data, "P1", pinLocation+"/files/g-g1/", 3)
279 got, err := CopyPinnedManifest(ctx, claims, data, pin, pinLocation+"/files/g-P2/")
280 if err != nil || got.Seq != 0 || want.Seq != 3 || got.Entries["a.txt"] != want.Entries["a.txt"] {
281 t.Fatalf("copy: %+v %v", got, err)
282 }
283 // A replay is a success; the copy's entries still name g1's object.
284 if _, err := CopyPinnedManifest(ctx, claims, data, pin, pinLocation+"/files/g-P2/"); err != nil {
285 t.Fatalf("replay: %v", err)
286 }
287 // Without the earlier generation among the origins the copy refuses.
288 narrow := pin
289 narrow.Origins = []string{pin.Prefix}
290 if _, err := CopyPinnedManifest(ctx, claims, data, narrow, pinLocation+"/files/g-P3/"); !errors.Is(err, hostfiles.ErrOrigin) {
291 t.Fatalf("an inherited object outside the origins: %v", err)
292 }
293 })
294 t.Run("a source manifest that moved after the pin is not read", func(t *testing.T) {
295 // A late PUT to the source changes nothing: P is written from the
296 // retained bytes, so an admitted pin stays usable (B2-4).
297 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
298 pin, m := pinnedManifest(t, claims, data, "P1", pinLocation+"/files/g-g1/", 3)
299 m.Seq = 4
300 m.Entries["late.txt"] = m.Entries["a.txt"]
301 raw, _ := json.Marshal(m)
302 _, etag, _ := data.Get(ctx, hostfiles.ManifestKey(pin.Prefix))
303 if _, err := data.Put(ctx, hostfiles.ManifestKey(pin.Prefix), raw, etag); err != nil {
304 t.Fatal(err)
305 }
306 got, err := CopyPinnedManifest(ctx, claims, data, pin, pinLocation+"/files/g-P2/")
307 if _, late := got.Entries["late.txt"]; err != nil || got.Seq != 0 || late || len(got.Entries) != 1 {
308 t.Fatalf("the pinned revision: %+v %v", got, err)
309 }
310 })
311 t.Run("a retained copy that is not the pinned bytes", func(t *testing.T) {
312 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
313 pin, m := pinnedManifest(t, claims, data, "P1", pinLocation+"/files/g-g1/", 3)
314 e := m.Entries["a.txt"]
315 e.Size = 6
316 m.Entries["a.txt"] = e
317 raw, _ := json.Marshal(m)
318 tampered := release.NewDirStore(t.TempDir())
319 if _, err := tampered.Put(ctx, pin.Retained, raw, ""); err != nil {
320 t.Fatal(err)
321 }
322 if _, err := CopyPinnedManifest(ctx, tampered, data, pin, pinLocation+"/files/g-P2/"); !errors.Is(err, ErrRefused) {
323 t.Fatalf("bytes the pin did not vouch for were copied: %v", err)
324 }
325 if _, _, err := data.Get(ctx, hostfiles.ManifestKey(pinLocation+"/files/g-P2/")); err == nil {
326 t.Fatal("P's manifest was written from bytes the pin did not admit")
327 }
328 })
329 t.Run("a host that never published files", func(t *testing.T) {
330 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
331 pin := FilesPin{Prefix: pinLocation + "/files/g-g3/", Origins: []string{pinLocation + "/files/g-g3/"}}
332 m, err := CopyPinnedManifest(ctx, claims, data, pin, pinLocation+"/files/g-P1/")
333 if err != nil || m.Seq != 0 || len(m.Entries) != 0 {
334 t.Fatalf("empty manifest: %+v %v", m, err)
335 }
336 if _, err := CopyPinnedManifest(ctx, claims, data, pin, pinLocation+"/files/g-P1/"); err != nil {
337 t.Fatalf("replay: %v", err)
338 }
339 })
340}
341
342// Round 11: holder.admissions keeps eight entries, but an unchanged file
343// published at g1 is still referenced by every later generation's
344// manifest (each copies the last, and unchanged content is never
345// rewritten). Ten real admissions and publications later, with g1 trimmed
346// from the admission list, the pin still admits g1's object and the copy
347// succeeds; a reference outside this incarnation's files refuses.
348func TestPinKeepsAFileOriginTheAdmissionListTrimmed(t *testing.T) {
349 ctx := context.Background()
350 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
351 created := time.Date(2026, 10, 4, 1, 0, 0, 0, time.UTC)
352 nh := hostclaim.NewHolder{Family: "instance", Key: "apps/bab/jam/instances/" + host + ".json", Account: "bab", App: "jam", Host: host, Region: "ie", RecordRegion: "ie", CreatedAt: created}
353 body := []byte(`{"host":"` + host + `","account":"bab","app":"jam","region":"ie","created_at":"2026-10-04T01:00:00Z"}`)
354 step := func(o hostclaim.Observed, tr func(*hostclaim.Claim) error) hostclaim.Observed {
355 t.Helper()
356 next, err := hostclaim.Update(ctx, claims, o, tr)
357 if err != nil {
358 t.Fatal(err)
359 }
360 return next
361 }
362 o, err := hostclaim.Read(ctx, claims, host)
363 if err != nil {
364 t.Fatal(err)
365 }
366 o = step(o, hostclaim.BeginCreate(nh, body, "c", "console", created))
367 o = step(o, hostclaim.CompleteCreate("c", "src"))
368 loc := o.Claim.Holder.Location
369 n := 0
370 newID := func() string { n++; return fmt.Sprintf("id%d", n) }
371 var gens []string
372 prev, prevSeq := "", int64(0)
373 for i := 1; i <= 10; i++ {
374 gen := fmt.Sprintf("g%d", i)
375 o = step(o, hostclaim.AdmitWriter(hostclaim.Admission{Region: "ie", PredecessorRegion: "ie", ObservedSession: o.Claim.Holder.Session, ObservedGeneration: o.Claim.Holder.Generation,
376 Box: "src", Mode: "cold", NewGeneration: gen, PredecessorCertified: true}))
377 prefix := moves.GenerationFilesPrefix(loc, gen)
378 gens = append(gens, prefix)
379 if prev != "" {
380 if _, err := hostfiles.CopyManifest(ctx, data, prev, prefix, prevSeq, gens); err != nil {
381 t.Fatal(err)
382 }
383 }
384 p, err := hostfiles.Open(ctx, data, prefix)
385 if err != nil {
386 t.Fatal(err)
387 }
388 // kept.txt and instance.key never change after g1; changed.txt
389 // changes every time.
390 if err := p.Stage(ctx, "kept.txt", []byte("written once"), 0o644, false); err != nil {
391 t.Fatal(err)
392 }
393 if err := p.Stage(ctx, "instance.key", []byte("the host's key"), 0o600, false); err != nil {
394 t.Fatal(err)
395 }
396 stagePlatformItems(t, p)
397 if err := p.Stage(ctx, "changed.txt", []byte(gen), 0o644, false); err != nil {
398 t.Fatal(err)
399 }
400 if prevSeq, err = p.Commit(ctx); err != nil {
401 t.Fatal(err)
402 }
403 prev = prefix
404 o = step(o, hostclaim.PublishGeneration("", "", o.Claim.Holder.Session, gen, "ie", newID))
405 }
406 h := o.Claim.Holder
407 if len(h.Admissions) != 8 {
408 t.Fatalf("admissions not trimmed to eight: %d", len(h.Admissions))
409 }
410 for _, a := range h.Admissions {
411 if a.Generation == "g1" || a.Predecessor == "g1" {
412 t.Fatalf("g1 is still in the admission list: %+v", h.Admissions)
413 }
414 }
415 m, _, err := hostfiles.Load(ctx, data, loc+"/files/g-g10/")
416 if err != nil || !strings.HasPrefix(m.Entries["kept.txt"].Object, loc+"/files/g-g1/objects/") {
417 t.Fatalf("the unchanged file is not g1's object: %+v %v", m.Entries["kept.txt"], err)
418 }
419 obs := o
420 obs.Claim.Op.ID = "rel-1"
421 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}
422 pin, err := p.Pin(ctx, obs, time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC))
423 if err != nil {
424 t.Fatal(err)
425 }
426 obj, err := ReadPin(ctx, claims, pin)
427 if err != nil {
428 t.Fatal(err)
429 }
430 got, err := CopyPinnedManifest(ctx, claims, data, obj.Files, loc+"/files/g-P1/")
431 if err != nil || got.Entries["kept.txt"] != m.Entries["kept.txt"] {
432 t.Fatalf("copy with g1 trimmed from the admissions: %+v %v", got.Entries, err)
433 }
434 // A reference outside this incarnation's files is never admitted.
435 foreign := m
436 foreign.Entries = map[string]hostfiles.Entry{"instance.key": m.Entries["instance.key"], "x.txt": {Object: "instances/" + host + "/other-inc/files/g-g1/objects/" + strings.Repeat("a", 64), Seq: 1, SHA256: strings.Repeat("a", 64)}}
437 raw, _ := json.Marshal(foreign)
438 _, etag, _ := data.Get(ctx, hostfiles.ManifestKey(loc+"/files/g-g10/"))
439 if _, err := data.Put(ctx, hostfiles.ManifestKey(loc+"/files/g-g10/"), raw, etag); err != nil {
440 t.Fatal(err)
441 }
442 obs.Claim.Gen++
443 if _, err := p.Pin(ctx, obs, time.Date(2026, 10, 7, 9, 1, 0, 0, time.UTC)); !errors.Is(err, hostfiles.ErrOrigin) {
444 t.Fatalf("a foreign reference was pinned: %v", err)
445 }
446}
447
448// Round 14 finding 1: the source's files manifest is at sequence 8; P's
449// prepared manifest is a create-only copy of its entries at sequence 0
450// (claim rev 6.30 R18-1, moves rev 7.22 §7.6); the session publisher that
451// starts after the first admission takes it as its baseline and publishes
452// sequence 1, the inherited entries intact.
453func TestPreparedManifestIsSequenceZeroAndTheSessionPublishesOne(t *testing.T) {
454 ctx := context.Background()
455 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
456 src := pinLocation + "/files/g-g3/"
457 p, err := hostfiles.Open(ctx, data, src)
458 if err != nil {
459 t.Fatal(err)
460 }
461 for i := 1; i <= 7; i++ {
462 if i == 7 {
463 // The seventh revision also publishes the host's key (H6), and
464 // its env file and wake state, as the release flush does.
465 if err := p.Stage(ctx, "instance.key", []byte("the host's key"), 0o600, false); err != nil {
466 t.Fatal(err)
467 }
468 stagePlatformItems(t, p)
469 }
470 if err := p.Stage(ctx, "a.txt", []byte(fmt.Sprintf("revision %d", i)), 0o644, false); err != nil {
471 t.Fatal(err)
472 }
473 if _, err := p.Commit(ctx); err != nil {
474 t.Fatal(err)
475 }
476 }
477 pinner := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}
478 ref, err := pinner.Pin(ctx, pinClaim(), time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC))
479 if err != nil || ref.FilesSeq != 10 { // seven revisions of a.txt, the key, the env and the wake state: ten objects
480 t.Fatalf("pin: %+v %v", ref, err)
481 }
482 obj, err := ReadPin(ctx, claims, ref)
483 if err != nil {
484 t.Fatal(err)
485 }
486 prepared := pinLocation + "/files/g-P1/"
487 m, err := CopyPinnedManifest(ctx, claims, data, obj.Files, prepared)
488 if err != nil || m.Seq != 0 {
489 t.Fatalf("prepared manifest: %+v %v", m, err)
490 }
491 stored, _, err := hostfiles.Load(ctx, data, prepared)
492 if err != nil || stored.Seq != 0 || stored.Entries["a.txt"].Object == "" || !strings.HasPrefix(stored.Entries["a.txt"].Object, src+"objects/") {
493 t.Fatalf("stored prepared manifest: %+v %v", stored, err)
494 }
495 session, err := hostfiles.Open(ctx, data, prepared)
496 if err != nil {
497 t.Fatal(err)
498 }
499 if err := session.Stage(ctx, "b.txt", []byte("first session write"), 0o644, false); err != nil {
500 t.Fatal(err)
501 }
502 seq, err := session.Commit(ctx)
503 if err != nil || seq != 1 {
504 t.Fatalf("the first session publication: %d %v", seq, err)
505 }
506 after, _, err := hostfiles.Load(ctx, data, prepared)
507 if err != nil || after.Seq != 1 || after.Entries["a.txt"] != stored.Entries["a.txt"] || after.Entries["b.txt"].Object == "" {
508 t.Fatalf("after the first publication: %+v %v", after, err)
509 }
510}
511
512// Two-box model finding F2: no files manifest anywhere in the chain, and
513// a writer was admitted (session 3): the pin refuses rather than pin an
514// empty set an install would mint a fresh instance.key over. A holder
515// that never admitted a writer pins empty.
516func TestStorePinnerRefusesAnEmptyFilesPinAfterAWriter(t *testing.T) {
517 ctx := context.Background()
518 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
519 for _, tc := range []struct {
520 session uint64
521 want error
522 }{{3, ErrFilesUnevidenced}, {0, nil}} {
523 claims, data := release_NewDirStore(t), release_NewDirStore(t)
524 o := pinClaim()
525 o.Claim.Holder.Session = tc.session
526 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}
527 pin, err := p.Pin(ctx, o, at)
528 if !errors.Is(err, tc.want) {
529 t.Fatalf("session %d: %+v %v", tc.session, pin, err)
530 }
531 }
532}
533
534// Round 17 finding 1 (P1): a manifest exists, copied before the app
535// started, without the instance.key the app minted afterwards; the writer
536// was admitted and the database is nonempty. The pin refuses (the worker
537// marks the operation stuck); with the key in the manifest, or an empty
538// database, it pins.
539func TestStorePinnerRefusesAKeylessManifestForANonemptyDatabase(t *testing.T) {
540 ctx := context.Background()
541 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
542 empty := func(context.Context, genselect.Request) (*genselect.Plan, error) { return nil, genselect.ErrEmpty }
543 released0 := func() string {
544 raw, _ := json.Marshal(hostclaim.Release{ReleasedBy: "src", Enrollment: "e", Generation: "g3", Session: 3, FinalTXID: 0, ManifestSeq: 4, Receipt: "r", ReceiptSHA256: strings.Repeat("d", 64)})
545 return string(raw)
546 }
547 for _, tc := range []struct {
548 name string
549 key bool
550 db func(context.Context, genselect.Request) (*genselect.Plan, error)
551 journal map[string]string
552 want error
553 }{
554 {"keyless, nonempty database", false, selectAt(41), map[string]string{}, ErrFilesUnevidenced},
555 {"keyed, nonempty database", true, selectAt(41), map[string]string{}, nil},
556 {"keyless, empty database at a release of 0", false, empty, map[string]string{"release": released0()}, nil},
557 } {
558 t.Run(tc.name, func(t *testing.T) {
559 claims, data := release_NewDirStore(t), release_NewDirStore(t)
560 m := withPlatformItems(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 4, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}})
561 if tc.key {
562 m = keyed(m)
563 }
564 putManifest(t, data, "g3", m)
565 o := pinClaim()
566 o.Claim.Op.Journal = tc.journal
567 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: tc.db}
568 if _, err := p.Pin(ctx, o, at); !errors.Is(err, tc.want) {
569 t.Fatalf("got %v, want %v", err, tc.want)
570 }
571 })
572 }
573}
574
575// routeRelease stands in for the region controller's registry route
576// (registry.Route.Version), the pinner's only source for a pin's release.
577func routeRelease(_ context.Context, h string) (string, error) {
578 if h != host {
579 return "", fmt.Errorf("asked for %s's route", h)
580 }
581 return "v1.4.2", nil
582}
583
584// R9: the pin's release is the registry route's, read at pinning and
585// nowhere else; a reuse carries the stored value; no release refuses.
586func TestStorePinnerPinsTheRouteRelease(t *testing.T) {
587 ctx := context.Background()
588 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
589 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
590 putManifest(t, data, "g3", keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 5, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}))
591 var asked []string
592 route := "v1.4.2"
593 p := StorePinner{Claims: claims, Data: data, Bucket: "data", Region: "ie", SelectDB: selectAt(41),
594 Release: func(_ context.Context, h string) (string, error) { asked = append(asked, h); return route, nil }}
595 // Everything a box or a request could have written says otherwise; the
596 // pinner never reads any of it.
597 o := pinClaim()
598 o.Claim.Op.Journal["install_release"] = "v-box"
599 o.Claim.Op.Journal["local_install"] = `{"box":"tgt","release":"v-box"}`
600 o.Claim.Op.Intent = map[string]string{"release": "v-request", "version": "v-request"}
601 pin, err := p.Pin(ctx, o, at)
602 if err != nil {
603 t.Fatal(err)
604 }
605 if pin.Release != "v1.4.2" || len(asked) != 1 || asked[0] != host {
606 t.Fatalf("release %q, route asked for %v", pin.Release, asked)
607 }
608 if obj, err := ReadPin(ctx, claims, pin); err != nil || obj.Release != "v1.4.2" {
609 t.Fatalf("object: %+v %v", obj, err)
610 }
611 // A journaled copy whose release is not the object's is refused, though
612 // the bytes still hash to the digest.
613 for _, rel := range []string{"v1.5.0", ""} {
614 forged := pin
615 forged.Release = rel
616 if _, err := ReadPin(ctx, claims, forged); !errors.Is(err, ErrRefused) {
617 t.Fatalf("read a pin whose journaled release %q is not the object's: %v", rel, err)
618 }
619 }
620 // The route moves on; a retry at the same generation reuses the stored
621 // object, its release included, and does not ask the route again.
622 route = "v1.5.0"
623 again, err := p.Pin(ctx, o, at.Add(time.Minute))
624 if err != nil || again != pin || again.Release != "v1.4.2" || len(asked) != 1 {
625 t.Fatalf("reuse: %+v %v asked %v", again, err, asked)
626 }
627 // A route with no version, or an unreadable route, refuses before
628 // anything is written.
629 for name, rel := range map[string]func(context.Context, string) (string, error){
630 "empty": func(context.Context, string) (string, error) { return "", nil },
631 "unreadable": func(context.Context, string) (string, error) { return "", errors.New("registry unavailable") },
632 } {
633 t.Run(name, func(t *testing.T) {
634 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
635 putManifest(t, data, "g3", keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 5, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}))
636 p := StorePinner{Claims: claims, Data: data, Bucket: "data", Region: "ie", SelectDB: selectAt(41), Release: rel}
637 _, err := p.Pin(ctx, pinClaim(), at)
638 if err == nil || (name == "empty" && !errors.Is(err, ErrRefused)) {
639 t.Fatalf("pinned without a release: %v", err)
640 }
641 if keys, lerr := claims.List(ctx, "control/"); lerr != nil || len(keys) != 0 {
642 t.Fatalf("a refused pin wrote %v (%v)", keys, lerr)
643 }
644 })
645 }
646 // No route at all is unsupported, as every other missing provider is.
647 p.Release = nil
648 if _, err := p.Pin(ctx, o, at); !errors.Is(err, ErrUnsupported) {
649 t.Fatalf("no release provider: %v", err)
650 }
651 // A stored object without a release is not this pinner's: refused.
652 bare := pinClaim()
653 bare.Claim.Gen = 20
654 if _, err := claims.Put(ctx, PinKey(host, "rel-1", 20), []byte(`{"v":1,"host":"`+host+`","op":"rel-1","claim_gen":20,"empty":"session-0","files":{}}`), ""); err != nil {
655 t.Fatal(err)
656 }
657 p.Release = routeRelease
658 if _, err := p.Pin(ctx, bare, at); !errors.Is(err, ErrRefused) {
659 t.Fatalf("a pin object with no release was reused: %v", err)
660 }
661}
662
663// Absence is never evidence: a pin with no database is an empty bootstrap
664// only when the object names its evidence and the journal agrees, and a
665// files pin is a host that published no files only when it names neither
666// a digest nor a retained copy. An object with neither a database nor
667// evidence, one the journal disagrees with about emptiness, or a files pin
668// with one of digest and retained copy but not the other, is refused,
669// never read as empty.
670func TestReadPinNeedsTheEmptyEvidence(t *testing.T) {
671 ctx := context.Background()
672 plan := &genselect.Plan{Generation: "g3", MaxTXID: 41}
673 for _, tc := range []struct {
674 name string
675 obj PinObject
676 empty bool // the journal's copy
677 ok bool
678 }{
679 {"an evidenced empty bootstrap", PinObject{Empty: "session-0"}, true, true},
680 {"a database", PinObject{DB: plan}, false, true},
681 {"no database and no evidence", PinObject{}, true, false},
682 {"a database and empty evidence", PinObject{DB: plan, Empty: "release"}, false, false},
683 {"an empty pin the journal calls a database", PinObject{Empty: "session-0"}, false, false},
684 {"a database the journal calls empty", PinObject{DB: plan}, true, false},
685 {"a files pin with its digest and retained copy", PinObject{Empty: "session-0", Files: FilesPin{SHA256: strings.Repeat("c", 64), Retained: "control/retained.json"}}, true, true},
686 {"a files digest without its retained copy", PinObject{Empty: "session-0", Files: FilesPin{SHA256: strings.Repeat("c", 64)}}, true, false},
687 {"a retained copy without its digest", PinObject{Empty: "session-0", Files: FilesPin{Retained: "control/retained.json"}}, true, false},
688 } {
689 t.Run(tc.name, func(t *testing.T) {
690 claims := release.NewDirStore(t.TempDir())
691 obj := tc.obj
692 obj.V, obj.Host, obj.Op, obj.ClaimGen, obj.Release = 1, host, "rel-1", 12, "v1"
693 raw, err := json.Marshal(obj)
694 if err != nil {
695 t.Fatal(err)
696 }
697 key := PinKey(host, "rel-1", 12)
698 if _, err := claims.Put(ctx, key, raw, ""); err != nil {
699 t.Fatal(err)
700 }
701 ref := pinRef(key, raw, obj)
702 ref.Empty = tc.empty
703 _, err = ReadPin(ctx, claims, ref)
704 if tc.ok && err != nil {
705 t.Fatalf("an evidenced pin was refused: %v", err)
706 }
707 if !tc.ok && !errors.Is(err, ErrRefused) {
708 t.Fatalf("got %v, want ErrRefused", err)
709 }
710 })
711 }
712}
713
714// Task 8c fix round 1, M6: the install's rule at pin time. A pinned
715// manifest without the env file or without the wake state is no evidence
716// that the host has neither (platform items have no tombstone, and a
717// failover's manifest carries no wake state), so the pin refuses before
718// any build: no pin object, no retained copy. A host that never published
719// files still pins empty (TestStorePinnerRefusesAnEmptyFilesPinAfterAWriter).
720func TestStorePinnerRefusesAManifestWithoutTheEnvOrWakeState(t *testing.T) {
721 ctx := context.Background()
722 at := time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)
723 for _, tc := range []struct {
724 name string
725 missing string
726 want error
727 }{
728 {"both", "", nil},
729 {"no env file", hostfiles.EnvItem, ErrFilesUnevidenced},
730 {"no wake state", hostfiles.WakeItem, ErrFilesUnevidenced},
731 } {
732 t.Run(tc.name, func(t *testing.T) {
733 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
734 m := keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 5, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}})
735 delete(m.Platform, tc.missing)
736 putManifest(t, data, "g3", m)
737 o := pinClaim()
738 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", SelectDB: selectAt(41)}
739 _, err := p.Pin(ctx, o, at)
740 if !errors.Is(err, tc.want) {
741 t.Fatalf("got %v, want %v", err, tc.want)
742 }
743 if tc.want == nil {
744 return
745 }
746 if _, _, err := claims.Get(ctx, PinKey(host, "rel-1", o.Claim.Gen)); !errors.Is(err, os.ErrNotExist) {
747 t.Fatalf("a pin object was written: %v", err)
748 }
749 if keys, err := claims.List(ctx, ""); err != nil || len(keys) != 0 {
750 t.Fatalf("the refused pin wrote %v %v", keys, err)
751 }
752 })
753 }
754}
755
756// Main's home-region parks: an admission moves recorded in the home names
757// it park.HomeRegion. The pinner's selection sees the holder through the
758// box's home binding (park.HolderRegionView), as main's serving baseline
759// does, so that admission is this region's; with no binding the holder is
760// read exactly as stored.
761func TestStorePinnerSelectsThroughTheHomeBinding(t *testing.T) {
762 ctx := context.Background()
763 for _, home := range []string{"ie", ""} {
764 t.Run("home "+home, func(t *testing.T) {
765 claims, data := release.NewDirStore(t.TempDir()), release.NewDirStore(t.TempDir())
766 putManifest(t, data, "g1", keyed(hostfiles.Manifest{V: hostfiles.ManifestVersion, Seq: 9, Entries: map[string]hostfiles.Entry{}, Platform: map[string]hostfiles.Entry{}}))
767 var seen []string
768 p := StorePinner{Claims: claims, Data: data, Release: routeRelease, Bucket: "data", Region: "ie", HomeRegion: home,
769 SelectDB: func(ctx context.Context, req genselect.Request) (*genselect.Plan, error) {
770 for _, a := range req.Holder.Admissions {
771 seen = append(seen, a.Region+"/"+a.PredecessorRegion)
772 }
773 return selectAt(41)(ctx, req)
774 }}
775 obs := pinClaim()
776 obs.Claim.Holder.Admissions[1].Region, obs.Claim.Holder.Admissions[1].PredecessorRegion = park.HomeRegion, "uk"
777 if _, err := p.Pin(ctx, obs, time.Date(2026, 10, 7, 9, 0, 0, 0, time.UTC)); err != nil {
778 t.Fatal(err)
779 }
780 want := "@home/uk"
781 if home != "" {
782 want = "ie/uk"
783 }
784 if len(seen) != 2 || seen[1] != want {
785 t.Fatalf("selection saw %v, want second %s", seen, want)
786 }
787 if obs.Claim.Holder.Admissions[1].Region != park.HomeRegion {
788 t.Fatal("the view changed the claim's own holder")
789 }
790 })
791 }
792}
793