| index cf8e51ce6..f86dccdac 100644 |
| --- a/internal/host/movesfiles_test.go |
| +++ b/internal/host/movesfiles_test.go |
| @@ -557,17 +557,14 @@ func TestEstablishKeepsOriginsPastAdmissionPruning(t *testing.T) { |
| if !found || m.Entries["a.txt"].Object != movesFilesPrefix(filesLoc, "g1")+"objects/0000000000000001-"+sha([]byte("A")) { |
| t.Fatalf("g11: %+v %v", m, found) |
| } |
| - // The carried set (fix round 2, finding 3): the previous manifest's |
| - // set, the then-admitted chain and the new generation, so the pruned |
| - // ancestor stays proven by it. |
| - want := []string{} |
| - for _, g := range append(gens, "g11") { |
| - want = append(want, movesFilesPrefix(filesLoc, g)) |
| - } |
| - sort.Strings(want) |
| + // The carried set (fix round 2, finding 3; Task 6b): only what the |
| + // entries reference, g1, plus the new generation. The hints for the |
| + // generations between, which nothing references, are dropped. |
| + want := []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g11")} |
| if !reflect.DeepEqual(m.Origins, want) { |
| t.Fatalf("g11 origins %v, want %v", m.Origins, want) |
| } |
| + _ = gens |
| // Under the location but not a generation's files prefix: refused. |
| for name, object := range map[string]string{ |
| "another location": "instances/other/files/g-g1/objects/0000000000000001-" + sha([]byte("A")), |
| @@ -853,26 +850,22 @@ func TestEstablishRefusesANeverAdmittedOrigin(t *testing.T) { |
| } |
| } |
| // Origins vouch for nothing by themselves (platform-1e's ruling): a |
| - // manifest that adds the never-granted origin to its own carried set, |
| - // with the object named or not, is refused, and so is a carried set |
| - // naming a never-granted origin with no entry under it. |
| - for name, entries := range map[string]map[string]hostfiles.Entry{ |
| - "named and carried": {"a.txt": g1.Entries["a.txt"], "n.txt": {Object: never, SHA256: sha([]byte("N")), Size: 1, Seq: 1}}, |
| - "carried only": {"a.txt": g1.Entries["a.txt"]}, |
| - } { |
| - bad := g1 |
| - bad.Entries = entries |
| - bad.Origins = []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "never")} |
| - x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g1")), mustJSON(t, bad)) |
| - if err := x.f.Establish(ctx, o, launchInputs("g2"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| - t.Fatalf("%s: %v", name, err) |
| - } |
| - if _, found := x.manifest("g2"); found { |
| - t.Fatalf("%s: a manifest was written over a self-vouched origin", name) |
| - } |
| + // manifest that adds the never-granted origin to its own carried set |
| + // and names an object under it is refused. A carried hint that no entry |
| + // references is neither proven nor carried (Task 6b): the |
| + // establishment goes through and the hint does not survive it. |
| + bad := g1 |
| + bad.Entries = map[string]hostfiles.Entry{"a.txt": g1.Entries["a.txt"], "n.txt": {Object: never, SHA256: sha([]byte("N")), Size: 1, Seq: 1}} |
| + bad.Origins = []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "never")} |
| + x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g1")), mustJSON(t, bad)) |
| + if err := x.f.Establish(ctx, o, launchInputs("g2"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| + t.Fatalf("named and carried: %v", err) |
| + } |
| + if _, found := x.manifest("g2"); found { |
| + t.Fatal("a manifest was written over a self-vouched origin") |
| } |
| // The genuine manifest establishes, and the new set is exactly the |
| - // chain and the new generation. |
| + // referenced origin and the new generation. |
| x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g1")), mustJSON(t, g1)) |
| if err := x.f.Establish(ctx, o, launchInputs("g2"), x); err != nil { |
| t.Fatal(err) |
| @@ -881,6 +874,20 @@ func TestEstablishRefusesANeverAdmittedOrigin(t *testing.T) { |
| if want := []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g2")}; !reflect.DeepEqual(m.Origins, want) { |
| t.Fatalf("g2 origins %v, want %v", m.Origins, want) |
| } |
| + // A carried hint that no entry references (planted at g2, the newest |
| + // the chain from g3 reads): the establishment goes through and the |
| + // hint does not survive it. |
| + 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") |
| + if err := x.f.Establish(ctx, o3, launchInputs("g3"), x); err != nil { |
| + t.Fatalf("carried only: %v", err) |
| + } |
| + m3, _ := x.manifest("g3") |
| + if want := []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g2"), movesFilesPrefix(filesLoc, "g3")}; !reflect.DeepEqual(m3.Origins, want) { |
| + t.Fatalf("g3 origins %v, want %v (the env object under g2 is referenced; the hint is not)", m3.Origins, want) |
| + } |
| } |
| |
| // A legacy adopted location, with no incarnation component, gets the same |
| @@ -1060,6 +1067,70 @@ func TestChainProofRefusesAFreeStandingGeneration(t *testing.T) { |
| } |
| } |
| |
| +// countingProof records every generation it is asked about, in order, |
| +// answering from proven. |
| +type countingProof struct { |
| + proven map[string]bool |
| + asked []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 |
| +} |
| + |
| +// Only referenced origins are proven and carried (Task 6b, the moves side |
| +// of claim Plan 15's review): the origins the live manifest's entries |
| +// name, plus the one generation the admission opens (round 4's binding, |
| +// one call). A carried hint nothing references is neither proven nor |
| +// carried forward, so old hints do not accumulate and the proof's cost |
| +// does not grow with the history; a referenced origin is still proven and |
| +// refused when the proof says no; and the count of Proven calls is the |
| +// distinct referenced origins plus the admitted generation, each asked |
| +// once. |
| +func TestEstablishProvesAndCarriesOnlyReferencedOrigins(t *testing.T) { |
| + x := newFilesFixture(t) |
| + ctx := context.Background() |
| + x.writeEnv("K=1\n") |
| + // g5's object, referenced by the live manifest at g1 through a carried |
| + // entry; g7 is a hint nothing references and nothing can prove. |
| + g5obj := movesFilesPrefix(filesLoc, "g5") + "objects/0000000000000001-" + sha([]byte("F")) |
| + x.bucket.Put("data", g5obj, []byte("F")) |
| + live := x.publish("g1", func(p *hostfiles.Publisher) { |
| + if err := p.Stage(ctx, "a.txt", []byte("A"), 0o600, false); err != nil { |
| + t.Fatal(err) |
| + } |
| + }) |
| + live.Entries["f.txt"] = hostfiles.Entry{Object: g5obj, SHA256: sha([]byte("F")), Size: 1, Seq: 1} |
| + live.Origins = []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g5"), movesFilesPrefix(filesLoc, "g7")} |
| + x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g1")), mustJSON(t, live)) |
| + o := servingObserved("g2", "g1:uk:@root:uk", "g2:uk:g1:uk") |
| + // The hint g7 is not proven: the establishment still goes through, and |
| + // g7 is not carried; g1, g5 and the admitted g2 are, each asked once. |
| + proof := &countingProof{proven: map[string]bool{"g1": true, "g2": true, "g5": true}} |
| + x.f.Proof = proof |
| + if err := x.f.Establish(ctx, o, launchInputs("g2"), x); err != nil { |
| + t.Fatalf("an unreferenced hint blocked the establishment: %v", err) |
| + } |
| + m, found := x.manifest("g2") |
| + want := []string{movesFilesPrefix(filesLoc, "g1"), movesFilesPrefix(filesLoc, "g2"), movesFilesPrefix(filesLoc, "g5")} |
| + 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) |
| + } |
| + // A referenced origin the proof refuses still refuses the whole. |
| + x.f.Proof = &countingProof{proven: map[string]bool{"g1": true, "g2": true, "g3": true}} |
| + if err := x.f.Establish(ctx, servingObserved("g3", "g1:uk:@root:uk", "g2:uk:g1:uk", "g3:uk:g2:uk"), launchInputs("g3"), x); !errors.Is(err, hostfiles.ErrOrigin) { |
| + t.Fatalf("a referenced origin the proof refuses: %v", err) |
| + } |
| + if _, found := x.manifest("g3"); found { |
| + t.Fatal("a manifest was written over a refused referenced origin") |
| + } |
| +} |
| + |
| // 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 |