carlos / platform Public

host: a manifest's origins are held to the location, not the pruned admission chain (Task 6 fix round 1, finding 6, platform-1e P2)

AdmitWriter keeps eight admissions, while a copied manifest carries its
oldest objects for as long as they are live, so after g1..g10 the chain
ends at g3 while the newest manifest still names g1's object; bounding
origins by the chain refused it (ErrOrigin) and blocked every later
wake.

movesFilesOrigins holds each reference to the claim's own provenance
rule (P6-D3): an immutable object within the authorized incarnation
location in this region's bucket, under a generation's files prefix.
The admitted chain selects which manifest is newest; it does not bound
what that manifest may name. This keeps the claim's rule a single
predicate, the one the files live set already pins by
(pinFilesLiveSet), and copies no objects: a copy per establishment would
multiply writes and reinterpret origin-qualified references, which the
spec forbids. The prepared manifest is held to the same rule (a
cross-region landing's names destination-local objects by its build; a
same-region relocation's may retain source-generation objects).

Regression: g1..g10 with an object first written in g1, admissions
pruned to g3..g10, Establish g11 keeps the g1 reference; an object under
another location, not under files/, or not under a generation name,
still refuses.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 342f84d740b98dfdd1fcec6626fa9aef79b00247 parent fc79061
2 files changed, +128 −11
  • internal/host/movesfiles.go +58 −11
  • internal/host/movesfiles_test.go +70 −0
diff --git a/internal/host/movesfiles.go b/internal/host/movesfiles.go
index 58acc08f4..4f524dbc7 100644
--- a/internal/host/movesfiles.go
+++ b/internal/host/movesfiles.go
@@ -367,17 +367,15 @@ func (f *movesFiles) manifestVersion(ctx context.Context, key string) (string, e
}
// newestManifest is the newest valid manifest of the chain, read by exact
-// version, with the chain's prefixes as the origins its entries may name.
-// A generation without a manifest is passed over; the first manifest found
-// is validated and is the baseline, malformed or not (a malformed newest
-// manifest refuses, never an older one in its place). found is false when
-// no generation of the chain has a manifest.
+// version, with the origins its entries name (movesFilesOrigins: the
+// location's rule, not the chain's). A generation without a manifest is
+// passed over; the first manifest found is validated and is the baseline,
+// malformed or not (a malformed newest manifest refuses, never an older
+// one in its place). found is false when no generation of the chain has a
+// manifest.
func (f *movesFiles) newestManifest(ctx context.Context, location string, chain []string) (hostfiles.Manifest, []string, bool, error) {
- origins := make([]string, 0, len(chain))
for _, g := range chain {
- origins = append(origins, movesFilesPrefix(location, g))
- }
- for _, prefix := range origins {
+ prefix := movesFilesPrefix(location, g)
key := hostfiles.ManifestKey(prefix)
version, err := f.manifestVersion(ctx, key)
if err != nil {
@@ -393,6 +391,10 @@ func (f *movesFiles) newestManifest(ctx context.Context, location string, chain
if !found {
return hostfiles.Manifest{}, nil, false, fmt.Errorf("host: the files manifest %s@%s is not readable", key, version)
}
+ origins, err := movesFilesOrigins(location, prefix, m)
+ if err != nil {
+ return hostfiles.Manifest{}, nil, false, fmt.Errorf("host: the files manifest %s@%s: %w", key, version, err)
+ }
if err := hostfiles.ValidateManifest(m, origins); err != nil {
return hostfiles.Manifest{}, nil, false, fmt.Errorf("host: the files manifest %s@%s: %w", key, version, err)
}
@@ -401,13 +403,54 @@ func (f *movesFiles) newestManifest(ctx context.Context, location string, chain
return hostfiles.Manifest{}, nil, false, nil
}
+// movesFilesOrigins is the set of origins a manifest's objects name, each
+// held to the claim's provenance rule (P6-D3): an immutable object within
+// the authorized incarnation location in this region's bucket, under a
+// generation's files prefix, <location>/files/g-<generation>/. A manifest
+// naming nothing has its own prefix as its one origin. The admitted chain
+// selects which manifest is newest; it does not bound the origins that
+// manifest may name, because AdmitWriter keeps eight admissions while a
+// copied manifest carries its oldest objects for as long as they are live
+// (fix round 1, finding 6). The same location rule is the files live
+// set's (pinFilesLiveSet), so every object a live manifest names stays
+// pinned under it.
+func movesFilesOrigins(location, prefix string, m hostfiles.Manifest) ([]string, error) {
+ base := strings.TrimSuffix(location, "/") + "/files/g-"
+ seen := map[string]bool{}
+ var out []string
+ for _, entries := range []map[string]hostfiles.Entry{m.Entries, m.Platform} {
+ for name, e := range entries {
+ if e.Object == "" || e.Deleted || e.Skipped != "" {
+ continue
+ }
+ origin, _, ok := strings.Cut(e.Object, "objects/")
+ rest, under := strings.CutPrefix(origin, base)
+ gen, slash := strings.CutSuffix(rest, "/")
+ if !ok || !under || !slash || !movesFilesGeneration(gen) {
+ return nil, fmt.Errorf("%w: %s names %s, not an object under a generation's files prefix of %s", hostfiles.ErrOrigin, name, e.Object, location)
+ }
+ if !seen[origin] {
+ seen[origin] = true
+ out = append(out, origin)
+ }
+ }
+ }
+ if len(out) == 0 {
+ out = []string{prefix}
+ }
+ return out, nil
+}
+
// openPrepared is the probe's baseline (in.Prepared): P's prepared
// manifest at exactly the version the op journal's binding names (fix
// round 1, finding 2). The binding is decoded and held to P's key, the
// claim's op and the holder's stamp (the install captured the stamp the
// probe's first admission keeps); the bytes are read at that version,
// checked against the journalled digest, and validated as a prepared
-// manifest (sequence 0, objects within the location). The key must
+// manifest (sequence 0, objects within the location by the claim's rule,
+// movesFilesOrigins: a cross-region landing's names destination-local
+// objects by its build, a same-region relocation's may retain source
+// generation objects). The key must
// currently hold either those bytes (the first establishment, which the
// returned publisher replaces by compare-and-swap) or this box's own
// establishment of them (a retry after a crash past the commit: the same
@@ -442,7 +485,11 @@ func (f *movesFiles) openPrepared(ctx context.Context, o hostclaim.Observed, gen
if err != nil || !found {
return nil, fmt.Errorf("host: the prepared files manifest %s@%s: %v", key, pm.Version, err)
}
- if err := hostfiles.ValidateManifest(prepared, []string{prefix}); err != nil {
+ origins, err := movesFilesOrigins(h.Location, prefix, prepared)
+ if err != nil {
+ return nil, fmt.Errorf("host: the prepared files manifest %s@%s: %w", key, pm.Version, err)
+ }
+ if err := hostfiles.ValidateManifest(prepared, origins); err != nil {
return nil, fmt.Errorf("host: the prepared files manifest %s@%s: %w", key, pm.Version, err)
}
if prepared.Seq != 0 {
diff --git a/internal/host/movesfiles_test.go b/internal/host/movesfiles_test.go
index b7a08cc75..a38cfb097 100644
--- a/internal/host/movesfiles_test.go
+++ b/internal/host/movesfiles_test.go
@@ -6,6 +6,7 @@ import (
"encoding/hex"
"encoding/json"
"errors"
+ "fmt"
"os"
"path/filepath"
"reflect"
@@ -481,6 +482,75 @@ func TestEstablishCopiesTheNewestValidManifestByExactVersion(t *testing.T) {
}
}
+// Origins outlive admission retention (fix round 1, finding 6; platform-1e
+// P2). AdmitWriter keeps eight admissions, while a copied manifest carries
+// its oldest objects for as long as they are live: after g1..g10 the
+// chain ends at g3 and the newest manifest still names g1's object. The
+// rule a reference is held to is the claim's (P6-D3): within the
+// incarnation's location in this region's bucket, under a generation's
+// files prefix. The admitted chain selects the newest manifest; it does
+// not bound what that manifest may name. An object outside the location,
+// or under the location but not under a generation's files prefix, still
+// refuses.
+func TestEstablishKeepsOriginsPastAdmissionPruning(t *testing.T) {
+ x := newFilesFixture(t)
+ ctx := context.Background()
+ x.writeEnv("K=1\n")
+ prev := 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 := p.StagePlatform(ctx, hostfiles.EnvItem, []byte("K=1\n")); err != nil {
+ t.Fatal(err)
+ }
+ })
+ gens := []string{"g1"}
+ for i := 2; i <= 10; i++ {
+ gen := fmt.Sprintf("g%d", i)
+ p, err := hostfiles.NewFromCopy(x.files, movesFilesPrefix(filesLoc, gen), prev, []string{movesFilesPrefix(filesLoc, "g1")})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := p.Commit(ctx); err != nil {
+ t.Fatal(err)
+ }
+ prev, _ = x.manifest(gen)
+ gens = append(gens, gen)
+ }
+ // The holder at g10 with the eight newest admissions only: g3..g10.
+ var chain []string
+ for i := 3; i <= 10; i++ {
+ chain = append(chain, fmt.Sprintf("g%d:uk:g%d:uk", i, i-1))
+ }
+ o := servingObserved("g10", chain...)
+ if len(o.Claim.Holder.Admissions) != 8 {
+ t.Fatalf("%d admissions", len(o.Claim.Holder.Admissions))
+ }
+ if err := x.f.Establish(ctx, o, launchInputs("g11"), x); err != nil {
+ t.Fatalf("establish past pruning: %v", err)
+ }
+ m, found := x.manifest("g11")
+ if !found || m.Entries["a.txt"].Object != movesFilesPrefix(filesLoc, "g1")+"objects/0000000000000001-"+sha([]byte("A")) {
+ t.Fatalf("g11: %+v %v", m, found)
+ }
+ // 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")),
+ "not a files prefix": strings.TrimSuffix(filesLoc, "/") + "/g-g1/objects/0000000000000001-" + sha([]byte("A")),
+ "not a generation name": strings.TrimSuffix(filesLoc, "/") + "/files/g-/objects/0000000000000001-" + sha([]byte("A")),
+ } {
+ bad := prev
+ bad.Entries = map[string]hostfiles.Entry{"a.txt": {Object: object, SHA256: sha([]byte("A")), Size: 1, Seq: 1}}
+ x.bucket.Put("data", hostfiles.ManifestKey(movesFilesPrefix(filesLoc, "g10")), mustJSON(t, bad))
+ if err := x.f.Establish(ctx, o, launchInputs("g12"), x); !errors.Is(err, hostfiles.ErrOrigin) {
+ t.Fatalf("%s: %v", name, err)
+ }
+ }
+ if _, found := x.manifest("g12"); found {
+ t.Fatal("a manifest was written over a foreign reference")
+ }
+}
+
// 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