| index 1086feb07..067e1cae9 100644 |
| --- a/internal/host/movesfiles_test.go |
| +++ b/internal/host/movesfiles_test.go |
| @@ -10,7 +10,6 @@ import ( |
| "os" |
| "path/filepath" |
| "reflect" |
| - "sort" |
| "strings" |
| "sync" |
| "testing" |
| @@ -95,6 +94,7 @@ func (r *recordingReader) GetVersion(ctx context.Context, bucket, key, version s |
| |
| type filesFixture struct { |
| t *testing.T |
| + claims release.Store |
| bucket *leasetest.VersionedBucket |
| data *recordingReader |
| files casBucketStore |
| @@ -111,9 +111,21 @@ type filesFixture struct { |
| workErrAt int |
| } |
| |
| -const ( |
| - filesHost = "shop.example.com" |
| - filesLoc = "instances/" + filesHost + "/" + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" |
| +const filesHost = "shop.example.com" |
| + |
| +// filesCreated and filesInc are the fixture host's creation and incarnation |
| +// (hostclaim.Incarnation), so its location is a valid origin tuple |
| +// (instances/<host>/<incarnation>) for claim Plan 15's proof. |
| +var ( |
| + filesCreated = time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC) |
| + filesInc = func() string { |
| + inc, err := hostclaim.Incarnation("bab", "jam", filesHost, filesCreated) |
| + if err != nil { |
| + panic(err) |
| + } |
| + return inc |
| + }() |
| + filesLoc = "instances/" + filesHost + "/" + filesInc |
| ) |
| |
| func (x *filesFixture) With(_ context.Context, _ hostwrite.Identity, fn func() error) error { |
| @@ -128,10 +140,14 @@ func (x *filesFixture) With(_ context.Context, _ hostwrite.Identity, fn func() e |
| |
| func newFilesFixture(t *testing.T) *filesFixture { |
| t.Helper() |
| + // Claim Plan 15: the proof reads the claim's recorded admissions from |
| + // the claims store under both switches. |
| + t.Setenv("CARLOS_HOST_CLAIM", "1") |
| + t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "1") |
| b := leasetest.NewVersionedBucket() |
| b.AddBucket("data") |
| - x := &filesFixture{t: t, bucket: b, data: &recordingReader{VersionedStore: b}, files: casBucketStore{b: b, bucket: "data"}, dir: t.TempDir()} |
| - x.f = &movesFiles{Files: x.files, Data: x.data, Bucket: "data", HomeRegion: "ie", Region: "uk", Box: "uk-box", DataDir: x.dir, |
| + x := &filesFixture{t: t, claims: release.NewDirStore(t.TempDir()), bucket: b, data: &recordingReader{VersionedStore: b}, files: casBucketStore{b: b, bucket: "data"}, dir: t.TempDir()} |
| + x.f = &movesFiles{Files: x.files, Data: x.data, Bucket: "data", HomeRegion: "ie", Region: "uk", Box: "uk-box", DataDir: x.dir, Proof: ClaimOriginProof{Claims: x.claims, HomeRegion: "ie", Region: "uk"}, |
| FilesDir: func(host string) (string, string) { |
| return filepath.Join(x.dir, host), filepath.Join(x.dir, host+".env") |
| }} |
| @@ -175,16 +191,123 @@ func (x *filesFixture) manifest(gen string) (hostfiles.Manifest, bool) { |
| return m, found |
| } |
| |
| -// serving is a serving claim on uk-box whose holder is at gen with the |
| -// cold admission chain given, newest last: each entry is |
| -// "<generation>:<region>:<predecessor>:<predecessor region>". |
| +// servingObserved is a serving claim on uk-box whose holder is at gen with |
| +// the cold admission chain given, newest last: each entry is |
| +// "<generation>:<region>:<predecessor>:<predecessor region>". It is a |
| +// hand-built claim: nothing in it is proven until observe stores it with |
| +// recorded admissions (recordOrigins). |
| func servingObserved(gen string, chain ...string) hostclaim.Observed { |
| - h := &hostclaim.Holder{Family: "instance", Key: "k", Account: "bab", App: "jam", Incarnation: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", Location: filesLoc, Generation: gen, Session: 3, WriterBox: "uk-box", Stamp: 7} |
| - for _, c := range chain { |
| + h := &hostclaim.Holder{Family: "instance", Key: "apps/bab/jam/instances/" + filesHost + ".json", Account: "bab", App: "jam", CreatedAt: filesCreated, Incarnation: filesInc, Location: filesLoc, Generation: gen, Session: 3, WriterBox: "uk-box", Stamp: 7} |
| + for i, c := range chain { |
| p := strings.Split(c, ":") |
| - h.Admissions = append(h.Admissions, hostclaim.AdmissionRecord{Generation: p[0], Region: p[1], Predecessor: p[2], PredecessorRegion: p[3], Mode: "cold", Session: 3, Box: "uk-box"}) |
| + h.Admissions = append(h.Admissions, hostclaim.AdmissionRecord{Generation: p[0], Region: p[1], Predecessor: p[2], PredecessorRegion: p[3], Mode: "cold", Session: uint64(3 + i), Box: "uk-box"}) |
| + } |
| + if n := len(h.Admissions); n > 0 { |
| + h.Session = h.Admissions[n-1].Session |
| + } |
| + return hostclaim.Observed{Host: filesHost, ETag: "e1", Claim: &hostclaim.Claim{V: 2, Host: filesHost, Gen: 9, Holder: h, Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: "uk-box"}}} |
| +} |
| + |
| +// recordOrigins records tuples as the claim's admitted origins, as the |
| +// claim's CAS records an admission and the #250 files backfill records a |
| +// live origin into an admitted prefix: one immutable node per tuple under |
| +// control/host-origins/<host>/, chained from the claim's current head, and |
| +// the head written on the stored claim. The claim must already be stored. |
| +func recordOrigins(t *testing.T, claims release.Store, host string, tuples ...hostclaim.OriginTuple) { |
| + t.Helper() |
| + ctx := context.Background() |
| + o, err := hostclaim.Read(ctx, claims, host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if o.Absent { |
| + t.Fatalf("recordOrigins: %s has no stored claim", host) |
| + } |
| + c := o.Claim |
| + head := hostclaim.OriginHead{} |
| + if c.Origins != nil { |
| + head = *c.Origins |
| + } |
| + type node struct { |
| + V int `json:"v"` |
| + Host string `json:"host"` |
| + Count uint64 `json:"count"` |
| + Previous string `json:"previous,omitempty"` |
| + Origin hostclaim.OriginTuple `json:"origin"` |
| + } |
| + for _, tuple := range tuples { |
| + body, err := json.Marshal(node{V: 1, Host: host, Count: head.Count + 1, Previous: head.Digest, Origin: tuple}) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + sum := sha256.New() |
| + sum.Write([]byte("carlos-admitted-origin-v1\x00")) |
| + sum.Write(body) |
| + digest := hex.EncodeToString(sum.Sum(nil)) |
| + key := "control/host-origins/" + host + "/" + digest + ".json" |
| + if err := hostclaim.ValidateOriginObject(key, body); err != nil { |
| + t.Fatalf("recordOrigins: %v", err) |
| + } |
| + if _, err := claims.Put(ctx, key, body, ""); err != nil && !errors.Is(err, release.ErrCASConflict) { |
| + t.Fatal(err) |
| + } |
| + head = hostclaim.OriginHead{Digest: digest, Count: head.Count + 1} |
| + } |
| + c.Origins = &head |
| + raw, err := json.Marshal(c) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + key, err := hostclaim.Key(host) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := claims.Put(ctx, key, raw, o.ETag); err != nil { |
| + t.Fatal(err) |
| } |
| - return hostclaim.Observed{Host: filesHost, ETag: "e1", Claim: &hostclaim.Claim{V: 1, Host: filesHost, Gen: 9, Holder: h, Op: hostclaim.Op{Kind: hostclaim.KindServing, Exec: "uk-box"}}} |
| +} |
| + |
| +// observe stores the hand-built observation's claim in the fixture's claims |
| +// store, records the named generations as admitted origins of the holder's |
| +// incarnation and location in this region (the admitted generation among |
| +// them, as its admission records it), and returns the stored observation, |
| +// which is what Materialize is given and what the proof authenticates. |
| +func (x *filesFixture) observe(o hostclaim.Observed, recorded ...string) hostclaim.Observed { |
| + x.t.Helper() |
| + ctx := context.Background() |
| + c := o.Claim |
| + if err := c.Validate(); err != nil { |
| + x.t.Fatalf("observe: %v", err) |
| + } |
| + key, err := hostclaim.Key(o.Host) |
| + if err != nil { |
| + x.t.Fatal(err) |
| + } |
| + etag := "" |
| + if stored, err := hostclaim.Read(ctx, x.claims, o.Host); err == nil && !stored.Absent { |
| + // A re-observation keeps the chain the store already records: the |
| + // head is the claim's, never a hand-built claim's. |
| + etag, c.Origins = stored.ETag, stored.Claim.Origins |
| + } |
| + raw, err := json.Marshal(c) |
| + if err != nil { |
| + x.t.Fatal(err) |
| + } |
| + if _, err := x.claims.Put(ctx, key, raw, etag); err != nil { |
| + x.t.Fatal(err) |
| + } |
| + tuples := make([]hostclaim.OriginTuple, 0, len(recorded)) |
| + for _, gen := range recorded { |
| + tuples = append(tuples, hostclaim.OriginTuple{Incarnation: c.Holder.Incarnation, Location: c.Holder.Location, Region: "uk", Generation: gen}) |
| + } |
| + if len(tuples) > 0 { |
| + recordOrigins(x.t, x.claims, o.Host, tuples...) |
| + } |
| + stored, err := hostclaim.Read(ctx, x.claims, o.Host) |
| + if err != nil { |
| + x.t.Fatal(err) |
| + } |
| + return stored |
| } |
| |
| func launchInputs(newGen string) ClaimLaunchInputs { |
| @@ -450,7 +573,7 @@ func TestEstablishCopiesTheNewestValidManifestByExactVersion(t *testing.T) { |
| if g1.Seq != 3 { |
| t.Fatalf("g1 at seq %d", g1.Seq) |
| } |
| - o := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| + o := x.observe(servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk"), "g1", "g3") |
| if err := x.f.Establish(ctx, o, launchInputs("g3"), x); err != nil { |
| t.Fatal(err) |
| } |
| @@ -533,23 +656,24 @@ func TestEstablishKeepsOriginsPastAdmissionPruning(t *testing.T) { |
| for i := 4; i <= 11; i++ { |
| chain = append(chain, fmt.Sprintf("g%d:uk:g%d:uk", i, i-1)) |
| } |
| - o := servingObserved("g11", chain...) |
| - if len(o.Claim.Holder.Admissions) != 8 { |
| - t.Fatalf("%d admissions", len(o.Claim.Holder.Admissions)) |
| - } |
| - // Provenance comes from the claim (platform-1e's ruling, fix round 3): |
| - // the carried set is a hint, and every origin must be proven against |
| - // admissions the claim has recorded. Today that is the retained chain, |
| - // which no longer names g1, so the establishment refuses; it passes |
| - // only with a proof that records g1, as claim Plan 15's durable |
| - // per-incarnation record will. |
| + c := servingObserved("g11", chain...) |
| + if len(c.Claim.Holder.Admissions) != 8 { |
| + t.Fatalf("%d admissions", len(c.Claim.Holder.Admissions)) |
| + } |
| + // Provenance comes from the claim (platform-1e's ruling, fix round 3; |
| + // claim Plan 15): the carried set is a hint, retained admissions are |
| + // never evidence, and every origin must be proven by the claim's |
| + // recorded admissions. g1 is retained-only (pruned from the chain and |
| + // never recorded), so the establishment refuses; once the claim records |
| + // g1 (its admission, or the #250 files backfill), it passes. |
| + o := x.observe(c, "g11") |
| if err := x.f.Establish(ctx, o, launchInputs("g11"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| - t.Fatalf("past pruning with the retained chain alone: %v", err) |
| + t.Fatalf("past pruning, g1 retained only: %v", err) |
| } |
| if _, found := x.manifest("g11"); found { |
| t.Fatal("a manifest was written over an unproven ancestor") |
| } |
| - x.f.Proof = recordedOrigins{"g1": true, "g2": true, "g3": true, "g4": true, "g5": true, "g6": true, "g7": true, "g8": true, "g9": true, "g10": true, "g11": true} |
| + o = x.observe(c, "g1") |
| if err := x.f.Establish(ctx, o, launchInputs("g11"), x); err != nil { |
| t.Fatalf("establish past pruning with the claim's record: %v", err) |
| } |
| @@ -623,7 +747,8 @@ func preparedFixture(t *testing.T) (*filesFixture, preparedManifest, hostclaim.O |
| x.journal("mv1", pm) |
| x.writeEnv("K=1\nCARLOS_INSTANCE_KEY=abc\n") |
| o := servingObserved("mv1", "cur:ie:@root:ie", "mv1:uk:cur:ie") |
| - o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindLanding, ID: "mv1", Phase: "probing", Exec: "uk-box"} |
| + o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindLanding, ID: "mv1", Phase: "probing", Exec: "uk-box", Participants: []string{"ie-box", "uk-box"}, Intent: map[string]string{"to": "uk", "source": "ie-box", "target": "uk-box"}} |
| + o = x.observe(o, "mv1") |
| in := launchInputs("mv1") |
| in.Prepared = true |
| return x, pm, o, in |
| @@ -776,7 +901,7 @@ func TestEstablishPublishesOutsideTheStartsWork(t *testing.T) { |
| }) |
| st := &stallingStore{Store: x.files, key: hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g2")), stalled: make(chan struct{}), release: make(chan struct{})} |
| x.f.Files = st |
| - o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := x.observe(servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk"), "g1", "g2") |
| done := make(chan error, 1) |
| go func() { done <- x.f.Establish(ctx, o, launchInputs("g2"), x) }() |
| select { |
| @@ -834,7 +959,7 @@ func TestEstablishRefusesANeverAdmittedOrigin(t *testing.T) { |
| t.Fatal(err) |
| } |
| }) |
| - o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := x.observe(servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk"), "g1", "g2") |
| never := movesFilesPrefix(filesLoc, "never") + "objects/0000000000000001-" + sha([]byte("N")) |
| x.bucket.Put("data", never, []byte("N")) |
| for name, origins := range map[string][]string{"no carried set": nil, "a carried set without it": {movesFilesPrefix(filesLoc, "g1")}} { |
| @@ -880,7 +1005,7 @@ func TestEstablishRefusesANeverAdmittedOrigin(t *testing.T) { |
| hint := m |
| hint.Origins = []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g2"), movesFilesPrefix(filesLoc, "never")} |
| x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g2")), mustJSON(t, hint)) |
| - o3 := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| + o3 := x.observe(servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk"), "g3") |
| if err := x.f.Establish(ctx, o3, launchInputs("g3"), x); err != nil { |
| t.Fatalf("carried only: %v", err) |
| } |
| @@ -917,6 +1042,7 @@ func TestEstablishSeedsALegacyLocationsOriginSet(t *testing.T) { |
| } |
| o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| o.Claim.Holder.Location = legacy |
| + o = x.observe(o, "g1", "g2") |
| if err := x.f.Establish(ctx, o, launchInputs("g2"), x); err != nil { |
| t.Fatal(err) |
| } |
| @@ -933,20 +1059,12 @@ func TestEstablishSeedsALegacyLocationsOriginSet(t *testing.T) { |
| x.bucket.Put("data", hostfiles.ManifestKey(loc("g2")), mustJSON(t, bad)) |
| o3 := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| o3.Claim.Holder.Location = legacy |
| + o3 = x.observe(o3, "g3") |
| if err := x.f.Establish(ctx, o3, launchInputs("g3"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| t.Fatalf("never admitted under a legacy location: %v", err) |
| } |
| } |
| |
| -// recordedOrigins is a claim-backed origin proof that records the named |
| -// generations: what claim Plan 15's durable per-incarnation record will |
| -// answer. |
| -type recordedOrigins map[string]bool |
| - |
| -func (r recordedOrigins) Proven(_ context.Context, _ hostclaim.Observed, _ hostclaim.Admission, generation string) (bool, error) { |
| - return r[generation], nil |
| -} |
| - |
| // answeringProof is a claim-backed origin proof with an answer per |
| // generation: proven, not proven, or an error; a generation it does not |
| // name is not proven. |
| @@ -955,11 +1073,15 @@ type answeringProof struct { |
| errs map[string]error |
| } |
| |
| -func (a answeringProof) Proven(_ context.Context, _ hostclaim.Observed, _ hostclaim.Admission, generation string) (bool, error) { |
| - if err := a.errs[generation]; err != nil { |
| - return false, err |
| +func (a answeringProof) Proven(_ context.Context, _ hostclaim.Observed, _ hostclaim.Admission, generations []string) (map[string]bool, error) { |
| + out := map[string]bool{} |
| + for _, generation := range generations { |
| + if err := a.errs[generation]; err != nil { |
| + return nil, err |
| + } |
| + out[generation] = a.proven[generation] |
| } |
| - return a.proven[generation], nil |
| + return out, nil |
| } |
| |
| // movesOriginProof is the only route to origin proof (fix round 4): the |
| @@ -1025,30 +1147,15 @@ func TestEstablishProvesEveryOriginThroughTheProof(t *testing.T) { |
| } |
| } |
| |
| -// chainOriginProof never accepts a bare Admission.NewGeneration supplied |
| -// on its own (platform-1e's condition, fix round 4): a generation is |
| -// proven only when the bound observation's retained chain records it, as |
| -// the post-admission observation Materialize is given does. A |
| -// free-standing generation, named by the admission but by no observation, |
| -// is refused at the proof and by Establish, and nothing is written. |
| -func TestChainProofRefusesAFreeStandingGeneration(t *testing.T) { |
| +// The proof never accepts a bare Admission.NewGeneration supplied on its |
| +// own (platform-1e's condition, fix round 4; claim Plan 15): a generation |
| +// is proven only when the bound observation's claim records its |
| +// admission, as the post-admission observation Materialize is given does. |
| +// A free-standing generation, named by the admission but recorded by no |
| +// observation, is refused at the proof and by Establish, and nothing is |
| +// written; an observation of no claim is an error. |
| +func TestClaimProofRefusesAFreeStandingGeneration(t *testing.T) { |
| ctx := context.Background() |
| - proof := chainOriginProof{home: "ie", region: "uk"} |
| - before := servingObserved("g1", "g1:uk:@root:uk") // the observation before g2's admission |
| - in := hostclaim.Admission{Box: "uk-box", Mode: "cold", NewGeneration: "g2", Region: "uk"} |
| - if ok, err := proof.Proven(ctx, before, in, "g2"); ok || err != nil { |
| - t.Fatalf("a free-standing generation proven: %v %v", ok, err) |
| - } |
| - if ok, err := proof.Proven(ctx, hostclaim.Observed{Host: filesHost, Absent: true}, in, "g2"); ok || err != nil { |
| - t.Fatalf("proven with no claim: %v %v", ok, err) |
| - } |
| - if ok, _ := proof.Proven(ctx, before, in, "g1"); !ok { |
| - t.Fatal("the observed holder's generation is not proven") |
| - } |
| - after := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") // the CAS result the writer's admission returns |
| - if ok, _ := proof.Proven(ctx, after, in, "g2"); !ok { |
| - t.Fatal("the admitted generation the observation records is not proven") |
| - } |
| x := newFilesFixture(t) |
| x.writeEnv("K=1\n") |
| x.publish("g1", func(p *hostfiles.Publisher) { |
| @@ -1056,27 +1163,41 @@ func TestChainProofRefusesAFreeStandingGeneration(t *testing.T) { |
| t.Fatal(err) |
| } |
| }) |
| + proof := ClaimOriginProof{Claims: x.claims, HomeRegion: "ie", Region: "uk"} |
| + before := x.observe(servingObserved("g1", "g1:uk:@root:uk"), "g1") // the observation before g2's admission |
| + in := hostclaim.Admission{Box: "uk-box", Mode: "cold", NewGeneration: "g2", Region: "uk"} |
| + if got, err := proof.Proven(ctx, before, in, []string{"g1", "g2"}); err != nil || !got["g1"] || got["g2"] { |
| + t.Fatalf("a free-standing generation: %v %v", got, err) |
| + } |
| + if _, err := proof.Proven(ctx, hostclaim.Observed{Host: filesHost, Absent: true}, in, []string{"g2"}); err == nil { |
| + t.Fatal("proven with no claim") |
| + } |
| if err := x.f.Establish(ctx, before, launchInputs("g2"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| t.Fatalf("establish with an observation that does not record the admission: %v", err) |
| } |
| if _, found := x.manifest("g2"); found { |
| t.Fatal("a manifest was written for a free-standing generation") |
| } |
| + after := x.observe(servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk"), "g2") // the CAS result the writer's admission returns |
| if err := x.f.Establish(ctx, after, launchInputs("g2"), x); err != nil { |
| t.Fatalf("establish with the bound observation: %v", err) |
| } |
| } |
| |
| -// countingProof records every generation it is asked about, in order, |
| +// countingProof records every Proven call and the set it was asked about, |
| // answering from proven. |
| type countingProof struct { |
| proven map[string]bool |
| - asked []string |
| + calls [][]string |
| } |
| |
| -func (c *countingProof) Proven(_ context.Context, _ hostclaim.Observed, _ hostclaim.Admission, generation string) (bool, error) { |
| - c.asked = append(c.asked, generation) |
| - return c.proven[generation], nil |
| +func (c *countingProof) Proven(_ context.Context, _ hostclaim.Observed, _ hostclaim.Admission, generations []string) (map[string]bool, error) { |
| + c.calls = append(c.calls, append([]string(nil), generations...)) |
| + out := map[string]bool{} |
| + for _, generation := range generations { |
| + out[generation] = c.proven[generation] |
| + } |
| + return out, nil |
| } |
| |
| // Only referenced origins are proven and carried (Task 6b, the moves side |
| @@ -1117,9 +1238,10 @@ func TestEstablishProvesAndCarriesOnlyReferencedOrigins(t *testing.T) { |
| if !found || !reflect.DeepEqual(m.Origins, want) { |
| t.Fatalf("g2 origins %v, want %v", m.Origins, want) |
| } |
| - sort.Strings(proof.asked) |
| - if !reflect.DeepEqual(proof.asked, []string{"g1", "g2", "g5"}) { |
| - t.Fatalf("Proven asked %v, want each referenced origin and the admitted generation once", proof.asked) |
| + // One Proven(set) call per Establish (Plan 15): the deduplicated |
| + // referenced origins plus the admitted generation, in one batch. |
| + if len(proof.calls) != 1 || !reflect.DeepEqual(proof.calls[0], []string{"g1", "g2", "g5"}) { |
| + t.Fatalf("Proven calls %v, want one call over the referenced origins and the admitted generation", proof.calls) |
| } |
| // A referenced origin the proof refuses still refuses the whole. |
| x.f.Proof = &countingProof{proven: map[string]bool{"g1": true, "g2": true, "g3": true}} |
| @@ -1164,8 +1286,8 @@ func TestRecoveryEstablishesFromTheRecordedSourceGeneration(t *testing.T) { |
| for name, shape := range map[string]func(o *hostclaim.Observed){ |
| "a clean return": func(*hostclaim.Observed) {}, |
| "a declared-loss return": func(o *hostclaim.Observed) { |
| - o.Claim.Op.Journal["loss.l1"] = "1" |
| - o.Claim.LossRefs = []string{"control/loss-records/l1.json"} |
| + o.Claim.Op.Journal["loss.l1"] = `{"kind":"move-target-lost","target":"ie-box"}` |
| + o.Claim.LossRefs, o.Claim.Lost = []string{"l1"}, []string{"ie-box"} |
| }, |
| "a fenced return": func(o *hostclaim.Observed) { |
| o.Claim.Op.Journal["fence.ie-box"] = `{"enrollment":"enr","key":"control/box-fences/enr.json","sha256":"ab"}` |
| @@ -1190,6 +1312,7 @@ func TestRecoveryEstablishesFromTheRecordedSourceGeneration(t *testing.T) { |
| }) |
| o := recoveryObserved(hostclaim.KindRestoring, "src", "mv1", "r1") |
| shape(&o) |
| + o = x.observe(o, "src", "r1") |
| if err := x.f.Establish(ctx, o, launchInputs("r1"), x); err != nil { |
| t.Fatalf("establish from src_gen: %v", err) |
| } |
| @@ -1263,7 +1386,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| publish(x, "src", "A") |
| } |
| rb := publish(x, "rb1", "B") |
| - o := recoveryRestartObserved(hostclaim.KindRestoring, "rb1", "r2") |
| + o := x.observe(recoveryRestartObserved(hostclaim.KindRestoring, "rb1", "r2"), "rb1", "r2") |
| if err := x.f.Establish(ctx, o, launchInputs("r2"), x); err != nil { |
| t.Fatalf("the restart: %v", err) |
| } |
| @@ -1280,7 +1403,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| x.writeEnv("K=1\n") |
| publish(x, "src", "A") |
| newer := publish(x, "r1", "C") // the earlier recovery admission's publication |
| - o := recoveryRestartObserved(kind, "r1", "r2") |
| + o := x.observe(recoveryRestartObserved(kind, "r1", "r2"), "r1", "r2") |
| if err := x.f.Establish(ctx, o, launchInputs("r2"), x); err != nil { |
| t.Fatalf("the restart: %v", err) |
| } |
| @@ -1299,6 +1422,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| rb := publish(x, "rb1", "B") |
| o := recoveryRestartObserved(hostclaim.KindRestoring, "rb1", "r2") |
| o.Claim.Op.Journal[moves.JournalReturnSeal] = "sealed" |
| + o = x.observe(o, "rb1", "r2") |
| if err := x.f.Establish(ctx, o, launchInputs("r2"), x); err != nil { |
| t.Fatalf("the second admission: %v", err) |
| } |
| @@ -1308,6 +1432,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| r2 := publish(x, "r2", "D") // the second admission's own publication |
| o3 := servingObserved("r3", "src:uk:@root:uk", "mv1:ie:src:uk", "rb1:uk:mv1:ie", "r2:uk:rb1:uk", "r3:uk:r2:uk") |
| o3.Claim.Op = o.Claim.Op |
| + o3 = x.observe(o3, "r3") |
| if err := x.f.Establish(ctx, o3, launchInputs("r3"), x); err != nil { |
| t.Fatalf("the third admission: %v", err) |
| } |
| @@ -1324,6 +1449,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| publish(x, "src", "A") |
| o := recoveryRestartObserved(hostclaim.KindRestoring, "rb1", "r2") |
| o.Claim.Op.Journal[moves.JournalReturnSeal] = "sealed" |
| + o = x.observe(o, "src", "r2") |
| if err := x.f.Establish(ctx, o, launchInputs("r2"), x); !errors.Is(err, hostfiles.ErrNoEvidence) { |
| t.Fatalf("a dirty return without its rollback manifest: %v", err) |
| } |
| @@ -1335,7 +1461,7 @@ func TestRecoveryRestartTakesTheNewestRecoveryManifest(t *testing.T) { |
| x := newFilesFixture(t) |
| x.writeEnv("K=1\n") |
| src := publish(x, "src", "A") |
| - o := recoveryObserved(hostclaim.KindRestoring, "src", "mv1", "r1") |
| + o := x.observe(recoveryObserved(hostclaim.KindRestoring, "src", "mv1", "r1"), "src", "r1") |
| if err := x.f.Establish(ctx, o, launchInputs("r1"), x); err != nil { |
| t.Fatalf("the first admission: %v", err) |
| } |
| @@ -1366,7 +1492,7 @@ func TestEstablishCarriesTheEnvBytesAndDropsTheAdoptionItem(t *testing.T) { |
| if err := os.WriteFile(adoption, []byte(`{"v":2}`), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| - o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := x.observe(servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk"), "g1", "g2") |
| x.workErr = hostbox.ErrChanged |
| if err := x.f.Establish(ctx, o, launchInputs("g2"), x); !errors.Is(err, hostbox.ErrChanged) { |
| t.Fatalf("refused work: %v", err) |
| @@ -1483,7 +1609,7 @@ func TestEstablishHostThatNeverPublishedFiles(t *testing.T) { |
| x.f.Evidence = func(context.Context, hostclaim.Observed) (hostfiles.PinnedFiles, bool, error) { |
| return hostfiles.PinnedFiles{}, true, nil |
| } |
| - o := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| + o := x.observe(servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk"), "g3") |
| if err := x.f.Establish(ctx, o, launchInputs("g3"), x); err != nil { |
| t.Fatal(err) |
| } |
| @@ -1494,7 +1620,7 @@ func TestEstablishHostThatNeverPublishedFiles(t *testing.T) { |
| // The claim's own empty bootstrap: a created incarnation before its |
| // first writer, admitted after EmptyAuthority. |
| x.f.Evidence = nil |
| - empty := servingObserved("g1", "g1:uk:@root:uk") // admitted: the holder at g1 after EmptyAuthority |
| + empty := x.observe(servingObserved("g1", "g1:uk:@root:uk"), "g1") // admitted: the holder at g1 after EmptyAuthority |
| in := launchInputs("g1") |
| in.Empty = true |
| if err := x.f.Establish(ctx, empty, in, x); err != nil { |
| @@ -1561,7 +1687,8 @@ func TestEstablishOnThePreparedGenerationContinuesFromSequenceZero(t *testing.T) |
| x.journal("mv1", pm) |
| x.writeEnv("K=1\nCARLOS_INSTANCE_KEY=abc\n") |
| o := servingObserved("mv1", "cur:ie:@root:ie", "mv1:uk:cur:ie") |
| - o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindLanding, ID: "mv1", Phase: "probing", Exec: "uk-box"} |
| + o.Claim.Op = hostclaim.Op{Kind: hostclaim.KindLanding, ID: "mv1", Phase: "probing", Exec: "uk-box", Participants: []string{"ie-box", "uk-box"}, Intent: map[string]string{"to": "uk", "source": "ie-box", "target": "uk-box"}} |
| + o = x.observe(o, "mv1") |
| in := launchInputs("mv1") |
| in.Prepared = true |
| // The first attempt crashes after the commit, before the local effect: |