| index 1e8f882c8..cf8e51ce6 100644 |
| --- a/internal/host/movesfiles_test.go |
| +++ b/internal/host/movesfiles_test.go |
| @@ -450,7 +450,7 @@ func TestEstablishCopiesTheNewestValidManifestByExactVersion(t *testing.T) { |
| if g1.Seq != 3 { |
| t.Fatalf("g1 at seq %d", g1.Seq) |
| } |
| - o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| if err := x.f.Establish(ctx, o, launchInputs("g3"), x); err != nil { |
| t.Fatal(err) |
| } |
| @@ -527,12 +527,13 @@ func TestEstablishKeepsOriginsPastAdmissionPruning(t *testing.T) { |
| prev, _ = x.manifest(gen) |
| gens = append(gens, gen) |
| } |
| - // The holder at g10 with the eight newest admissions only: g3..g10. |
| + // The observation Materialize is given: g11 admitted, the holder at |
| + // g11, the eight newest admissions only: g4..g11. |
| var chain []string |
| - for i := 3; i <= 10; i++ { |
| + for i := 4; i <= 11; i++ { |
| chain = append(chain, fmt.Sprintf("g%d:uk:g%d:uk", i, i-1)) |
| } |
| - o := servingObserved("g10", chain...) |
| + o := servingObserved("g11", chain...) |
| if len(o.Claim.Holder.Admissions) != 8 { |
| t.Fatalf("%d admissions", len(o.Claim.Holder.Admissions)) |
| } |
| @@ -548,7 +549,7 @@ func TestEstablishKeepsOriginsPastAdmissionPruning(t *testing.T) { |
| 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} |
| + 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} |
| if err := x.f.Establish(ctx, o, launchInputs("g11"), x); err != nil { |
| t.Fatalf("establish past pruning with the claim's record: %v", err) |
| } |
| @@ -624,7 +625,7 @@ func preparedFixture(t *testing.T) (*filesFixture, preparedManifest, hostclaim.O |
| } |
| x.journal("mv1", pm) |
| x.writeEnv("K=1\nCARLOS_INSTANCE_KEY=abc\n") |
| - o := servingObserved("cur", "cur:ie:@root:ie") |
| + 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"} |
| in := launchInputs("mv1") |
| in.Prepared = true |
| @@ -778,7 +779,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("g1", "g1:uk:@root:uk") |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| done := make(chan error, 1) |
| go func() { done <- x.f.Establish(ctx, o, launchInputs("g2"), x) }() |
| select { |
| @@ -836,7 +837,7 @@ func TestEstablishRefusesANeverAdmittedOrigin(t *testing.T) { |
| t.Fatal(err) |
| } |
| }) |
| - o := servingObserved("g1", "g1:uk:@root:uk") |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| 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")}} { |
| @@ -907,7 +908,7 @@ func TestEstablishSeedsALegacyLocationsOriginSet(t *testing.T) { |
| if old.Origins != nil { |
| t.Fatalf("the legacy manifest carries a set: %v", old.Origins) |
| } |
| - o := servingObserved("g1", "g1:uk:@root:uk") |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| o.Claim.Holder.Location = legacy |
| if err := x.f.Establish(ctx, o, launchInputs("g2"), x); err != nil { |
| t.Fatal(err) |
| @@ -921,8 +922,11 @@ func TestEstablishSeedsALegacyLocationsOriginSet(t *testing.T) { |
| } |
| bad := old |
| bad.Entries = map[string]hostfiles.Entry{"n.txt": {Object: loc("never") + "objects/0000000000000001-" + sha([]byte("N")), SHA256: sha([]byte("N")), Size: 1, Seq: 1}} |
| - x.bucket.Put("data", hostfiles.ManifestKey(loc("g1")), mustJSON(t, bad)) |
| - if err := x.f.Establish(ctx, o, launchInputs("g3"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| + // Planted at g2, the newest manifest the chain from g3 reads. |
| + 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 |
| + 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) |
| } |
| } |
| @@ -936,6 +940,126 @@ func (r recordedOrigins) Proven(_ context.Context, _ hostclaim.Observed, _ hostc |
| 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. |
| +type answeringProof struct { |
| + proven map[string]bool |
| + 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 |
| + } |
| + return a.proven[generation], nil |
| +} |
| + |
| +// movesOriginProof is the only route to origin proof (fix round 4): the |
| +// chain's prefixes and the prepared generation go through Proven like |
| +// every other origin, so a different proof (claim Plan 15's) has the final |
| +// say. A proof that answers false, or an error, for a chain-prefix origin |
| +// or for the prepared generation's origin refuses the establishment and |
| +// nothing is written. |
| +func TestEstablishProvesEveryOriginThroughTheProof(t *testing.T) { |
| + ctx := context.Background() |
| + for name, mk := range map[string]func(gen string) answeringProof{ |
| + "not proven": func(gen string) answeringProof { return answeringProof{proven: map[string]bool{}} }, |
| + "an error": func(gen string) answeringProof { |
| + return answeringProof{errs: map[string]error{gen: errors.New("the record is unreadable")}} |
| + }, |
| + } { |
| + t.Run("a chain prefix, "+name, func(t *testing.T) { |
| + x := newFilesFixture(t) |
| + x.writeEnv("K=1\n") |
| + x.publish("g1", func(p *hostfiles.Publisher) { |
| + if err := p.Stage(ctx, "a.txt", []byte("A"), 0o600, false); err != nil { |
| + t.Fatal(err) |
| + } |
| + }) |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + proof := mk("g1") |
| + proof.proven = map[string]bool{"g2": true} |
| + x.f.Proof = proof |
| + err := x.f.Establish(ctx, o, launchInputs("g2"), x) |
| + if err == nil || (name == "not proven" && !errors.Is(err, hostfiles.ErrOrigin)) { |
| + t.Fatalf("a chain prefix the proof does not prove: %v", err) |
| + } |
| + if _, found := x.manifest("g2"); found { |
| + t.Fatal("a manifest was written over an unproven chain prefix") |
| + } |
| + // The new generation itself is proven through the proof too. |
| + x.f.Proof = answeringProof{proven: map[string]bool{"g1": true}} |
| + if err := x.f.Establish(ctx, o, launchInputs("g2"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| + t.Fatalf("the new generation the proof does not prove: %v", err) |
| + } |
| + x.f.Proof = answeringProof{proven: map[string]bool{"g1": true, "g2": true}} |
| + if err := x.f.Establish(ctx, o, launchInputs("g2"), x); err != nil { |
| + t.Fatalf("everything proven: %v", err) |
| + } |
| + }) |
| + t.Run("the prepared generation, "+name, func(t *testing.T) { |
| + x, pm, o, in := preparedFixture(t) |
| + proof := mk("mv1") |
| + proof.proven = map[string]bool{"cur": true} |
| + x.f.Proof = proof |
| + err := x.f.Establish(ctx, o, in, x) |
| + if err == nil || (name == "not proven" && !errors.Is(err, hostfiles.ErrOrigin)) { |
| + t.Fatalf("the prepared generation the proof does not prove: %v", err) |
| + } |
| + if x.bucket.LatestID("data", pm.Key) != pm.Version { |
| + t.Fatal("the prepared manifest was published over") |
| + } |
| + x.f.Proof = answeringProof{proven: map[string]bool{"cur": true, "mv1": true}} |
| + if err := x.f.Establish(ctx, o, in, x); err != nil { |
| + t.Fatalf("everything proven: %v", err) |
| + } |
| + }) |
| + } |
| +} |
| + |
| +// 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) { |
| + 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) { |
| + if err := p.Stage(ctx, "a.txt", []byte("A"), 0o600, false); err != nil { |
| + t.Fatal(err) |
| + } |
| + }) |
| + 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") |
| + } |
| + if err := x.f.Establish(ctx, after, launchInputs("g2"), x); err != nil { |
| + t.Fatalf("establish with the bound observation: %v", err) |
| + } |
| +} |
| + |
| // The instance key rule (O1'): the manifest Establish leaves carries the |
| // env file with exactly the bytes on disk when it runs, staged and |
| // committed before it returns, and no adoption item; the local adoption |
| @@ -957,7 +1081,7 @@ func TestEstablishCarriesTheEnvBytesAndDropsTheAdoptionItem(t *testing.T) { |
| if err := os.WriteFile(adoption, []byte(`{"v":2}`), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| - o := servingObserved("g1", "g1:uk:@root:uk") |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| 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) |
| @@ -998,7 +1122,7 @@ func TestEstablishCarriesTheEnvBytesAndDropsTheAdoptionItem(t *testing.T) { |
| if err := os.Remove(x.envPath()); err != nil { |
| t.Fatal(err) |
| } |
| - if err := x.f.Establish(ctx, servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk"), launchInputs("g3"), x); err == nil { |
| + if err := x.f.Establish(ctx, servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk"), launchInputs("g3"), x); err == nil { |
| t.Fatal("established without an env file") |
| } |
| if _, found := x.manifest("g3"); found { |
| @@ -1016,7 +1140,7 @@ func TestEstablishNeverBootstrapsEmpty(t *testing.T) { |
| x := newFilesFixture(t) |
| ctx := context.Background() |
| x.writeEnv("K=1\n") |
| - o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| // No manifest anywhere, no evidence. |
| if err := x.f.Establish(ctx, o, launchInputs("g3"), x); !errors.Is(err, hostfiles.ErrNoEvidence) { |
| t.Fatalf("no manifest, no pin: %v", err) |
| @@ -1048,7 +1172,7 @@ func TestEstablishNeverBootstrapsEmpty(t *testing.T) { |
| } |
| // The predecessor lives in another region: its manifest is not in this |
| // bucket and the chain is not followed there. |
| - far := servingObserved("g5", "g5:uk:g1:ie") |
| + far := servingObserved("g6", "g5:uk:g1:ie", "g6:uk:g5:uk") |
| if err := x.f.Establish(ctx, far, launchInputs("g6"), x); !errors.Is(err, hostfiles.ErrNoEvidence) { |
| t.Fatalf("predecessor in another region: %v", err) |
| } |
| @@ -1074,7 +1198,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("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + o := servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk") |
| if err := x.f.Establish(ctx, o, launchInputs("g3"), x); err != nil { |
| t.Fatal(err) |
| } |
| @@ -1085,8 +1209,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("") |
| - empty.Claim.Holder.Session, empty.Claim.Holder.WriterBox = 0, "" |
| + empty := servingObserved("g1", "g1:uk:@root:uk") // admitted: the holder at g1 after EmptyAuthority |
| in := launchInputs("g1") |
| in.Empty = true |
| if err := x.f.Establish(ctx, empty, in, x); err != nil { |
| @@ -1152,7 +1275,7 @@ func TestEstablishOnThePreparedGenerationContinuesFromSequenceZero(t *testing.T) |
| } |
| x.journal("mv1", pm) |
| x.writeEnv("K=1\nCARLOS_INSTANCE_KEY=abc\n") |
| - o := servingObserved("cur", "cur:ie:@root:ie") |
| + 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"} |
| in := launchInputs("mv1") |
| in.Prepared = true |