host: the prepared manifest's installation stamp is validated apart from the admitted session's (whole-branch fixes, item 2)
openPrepared held the journalled binding to the current holder's stamp. A dirty return's install ran at the source installation's stamp S, and the recovery's cold admission then issues a new stamp, so the legitimate prepared manifest was refused. installationStamp is the immutable installation binding's stamp: for a restoring or reverting op the claim's journalled source_stamp (S, under which the return's local ran), validated separately from the admitted session's authority; for a landing the flip's stamp, which the first probe's admission keeps. Regression: the dirty return's installation (the prepared manifest built at S and journalled under the rollback id), the real admission through the writer (a new stamp) and the real C8 materialization together establish P's manifest; a binding at the admitted session's stamp, or any other, is refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 files changed,
+80
−1
internal/host/movesfiles.go+30 −1internal/host/movesrecoveryowners_test.go+50 −0
diff --git a/internal/host/movesfiles.go b/internal/host/movesfiles.go| index a51557cce..521b1883e 100644 |
| --- a/internal/host/movesfiles.go |
| +++ b/internal/host/movesfiles.go |
| @@ -15,6 +15,7 @@ import ( |
| "reflect" |
| "slices" |
| "sort" |
| + "strconv" |
| "strings" |
| "github.com/carlosframework/platform/internal/hostbox" |
| @@ -588,7 +589,11 @@ func (f *movesFiles) openPrepared(ctx context.Context, files release.Store, o ho |
| return nil, fmt.Errorf("%w: the op journal of %s/%s has no prepared files manifest", errLandingChanged, o.Host, gen) |
| } |
| key := hostfiles.ManifestKey(prefix) |
| - pm, err := decodePreparedManifest(raw, key, o.Claim.Op.ID, h.Stamp) |
| + stamp, err := installationStamp(o.Claim) |
| + if err != nil { |
| + return nil, err |
| + } |
| + pm, err := decodePreparedManifest(raw, key, o.Claim.Op.ID, stamp) |
| if err != nil { |
| return nil, err |
| } |
| @@ -641,6 +646,30 @@ func (f *movesFiles) openPrepared(ctx context.Context, files release.Store, o ho |
| return p, nil |
| } |
| +// installationStamp is the stamp the installation that wrote P's prepared |
| +// manifest captured (ir.capture at local), validated apart from the |
| +// admitted session's authority (whole-branch fixes, item 2). A return's |
| +// install (restoring, or a revert's) ran at the source installation's |
| +// stamp S, which the claim journals at the flip (source_stamp) and the |
| +// recovery's cold admission then replaces with a new one; a landing's |
| +// install ran at the flip's stamp, which the first probe's admission |
| +// keeps (KeepStamp, since no writer was admitted between the installing |
| +// CAS and the probe), so it is the holder's. |
| +func installationStamp(c *hostclaim.Claim) (uint64, error) { |
| + switch c.Op.Kind { |
| + case hostclaim.KindRestoring, hostclaim.KindReverting: |
| + stamp, err := strconv.ParseUint(c.Op.Journal[hostclaim.JournalSourceStamp], 10, 64) |
| + if err != nil || stamp == 0 { |
| + return 0, fmt.Errorf("%w: the claim journals no source installation stamp for %s", errLandingChanged, c.Op.ID) |
| + } |
| + return stamp, nil |
| + } |
| + if c.Holder == nil || c.Holder.Stamp == 0 { |
| + return 0, fmt.Errorf("%w: the holder has no installation stamp", errLandingChanged) |
| + } |
| + return c.Holder.Stamp, nil |
| +} |
| + |
| // establishedOver reports whether cur is this box's establishment of |
| // prepared, in full (fix round 2 finding 6, fix round 3): exactly what |
| // this establishment would have written, derived from the prepared |
diff --git a/internal/host/movesrecoveryowners_test.go b/internal/host/movesrecoveryowners_test.go| index 5140430ec..08bea426e 100644 |
| --- a/internal/host/movesrecoveryowners_test.go |
| +++ b/internal/host/movesrecoveryowners_test.go |
| @@ -15,6 +15,7 @@ import ( |
| "github.com/carlosframework/platform/internal/genselect" |
| "github.com/carlosframework/platform/internal/hostbox" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| + "github.com/carlosframework/platform/internal/hostfiles" |
| "github.com/carlosframework/platform/internal/hostguard" |
| "github.com/carlosframework/platform/internal/hostwrite" |
| "github.com/carlosframework/platform/internal/moves" |
| @@ -323,6 +324,55 @@ func TestDirtyReturnProbeIssuesANewStamp(t *testing.T) { |
| } |
| } |
| +// The dirty return's installation, its real admission and the real C8 |
| +// materialization together (whole-branch fixes, item 2): local built the |
| +// prepared manifest at the source installation's stamp S (the claim's |
| +// journalled source_stamp, under which the return's local ran) and |
| +// journalled its binding; the recovery's cold admission issues a new |
| +// stamp; Establish validates the binding's immutable installation stamp |
| +// against S, separately from the admitted session's, and establishes P's |
| +// manifest with the minted env file. A binding at the admitted session's |
| +// stamp, or any other, is not the installation's and is refused. |
| +func TestDirtyReturnEstablishesThePreparedManifestThroughC8(t *testing.T) { |
| + f := newRCFixture(t, rcDirty) |
| + ctx := t.Context() |
| + f.owners.Files = f.realC8() |
| + pm := f.preparedManifestJournalled(rpRB, poOp, rcS) |
| + o := f.read() |
| + in := f.selectAdmission(o) |
| + admitted := f.admitThroughWriter(o, in) |
| + h := admitted.Claim.Holder |
| + if h.Stamp == rcS || !in.Prepared { |
| + t.Fatalf("the dirty admission kept the stamp or is not prepared: %+v %v", h, in.Prepared) |
| + } |
| + if err := f.owners.Materialize(ctx, admitted, in, poWork{action: "spawn", f: f.poFixture}); err != nil { |
| + t.Fatalf("the dirty return's materialization: %v", err) |
| + } |
| + // The env file is the prepared item's (the recovery's start gate holds |
| + // the restored tree, env included, to the recorded digest) and there is |
| + // no adoption item, so the establishment reads the prepared bytes back |
| + // at sequence 0 and publishes nothing; what it proved is the binding. |
| + m, found, err := hostfiles.Load(ctx, casBucketStore{b: f.bucket, bucket: "data"}, movesFilesPrefix("instances/"+poHost, rpRB)) |
| + if err != nil || !found || m.Seq != 0 || m.Platform[hostfiles.EnvItem].SHA256 == "" { |
| + t.Fatalf("P's manifest after the dirty return's probe: %+v %v %v", m, found, err) |
| + } |
| + if raw, _ := f.bucket.Current("data", pm.Key); sha(raw) != pm.SHA256 || f.bucket.LatestID("data", pm.Key) != pm.Version { |
| + t.Fatal("the prepared manifest is not the journalled version") |
| + } |
| + // A binding at the admitted session's stamp, or another, is refused. |
| + for name, stamp := range map[string]uint64{"the admitted session's stamp": h.Stamp, "another stamp": 99} { |
| + g := newRCFixture(t, rcDirty) |
| + g.owners.Files = g.realC8() |
| + g.preparedManifestJournalled(rpRB, poOp, stamp) |
| + o := g.read() |
| + in := g.selectAdmission(o) |
| + admitted := g.admitThroughWriter(o, in) |
| + if err := g.owners.Materialize(ctx, admitted, in, poWork{action: "spawn", f: g.poFixture}); !errors.Is(err, errLandingChanged) { |
| + t.Fatalf("%s: %v", name, err) |
| + } |
| + } |
| +} |
| + |
| // Plan 4 Task 9: a clean return's probe is a cold admission into a fresh |
| // generation restored from src_gen in this region, its coverage |
| // clean-return: the admission records no writes lost. |