carlos / platform Public

host: the cut's archive publication and its recovery move into movesarchive.go, no behaviour change (Task 5 fix round 1, ruling 7)

A pure move of cutArchive, publishCutArchive, the journalled identity and
the snapshot helpers, with the source-disk headroom (up to archive.Cap
kept for the move's life, twice that during a rebuild; T6 pricing is a
Plan 6 follow-up) documented in the file's header. movesScanFiles gains the
file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 8bb90ff6908f8155b3f98be2956b56d805811cc7 parent 2d9d3ea
3 files changed, +283 −251
  • approved_moves_copy_test.go +1 −0
  • internal/host/movesarchive.go +282 −0
  • internal/host/movescut.go +0 −251
diff --git a/approved_moves_copy_test.go b/approved_moves_copy_test.go
index ba3a55ebb..a4d21b43a 100644
--- a/approved_moves_copy_test.go
+++ b/approved_moves_copy_test.go
@@ -549,6 +549,7 @@ var movesScanFiles = []string{
"internal/host/movesfloor.go",
"internal/host/movesfloor_linux.go",
"internal/host/movesfloor_other.go",
+ "internal/host/movesarchive.go",
"internal/host/movescut.go",
"internal/host/movesguard.go",
"internal/host/movesheldenv.go",
diff --git a/internal/host/movesarchive.go b/internal/host/movesarchive.go
new file mode 100644
index 000000000..58b1da6b0
--- /dev/null
+++ b/internal/host/movesarchive.go
@@ -0,0 +1,282 @@
+package host
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+
+ "github.com/carlosframework/platform/internal/lease"
+ "github.com/carlosframework/platform/internal/moves"
+ "github.com/carlosframework/platform/internal/moves/archive"
+)
+
+// The cut's archive (moves spec §7.4 staged, §8.3, codex 19, ruling G14):
+// how the stopped files directory's one sealed archive is identified,
+// published create-only into this box's move-snapshots/ and staged into the
+// lane's files/, and how a replay finishes exactly the identity an earlier
+// pass journalled. The cut's phase loop is in movescut.go; this file is the
+// archive's publication and its recovery, on their own so the recovery
+// rules can be read in one place (Task 5 fix round 1, ruling 7).
+//
+// Source-disk headroom. The preparing round keeps the newest built archive
+// on the source's data disk under <JournalDir>/archive/ for the life of
+// the move, so the cut can publish the very object the target verified
+// (G14). That costs up to archive.Cap (256 MiB) of the source's data disk
+// while a move is in flight, and a rebuild (the stopped files differ from
+// the verified archive) holds the old and the new one together until the
+// new one lands and pruneArchives removes the old: up to twice archive.Cap
+// for the length of one build. The target's T6 does not price this
+// source-side headroom; that is a Plan 6 follow-up.
+// cutArchiveStep is the archive's identity in the op journal, written
+// before any PUT so a replay publishes the same object (codex 19). Reuse
+// marks the preparing archive the target verified (G14): the seal names
+// its staged versions and nothing is staged again.
+type cutArchiveStep struct {
+ builtArchive
+ Reuse bool `json:"reuse,omitempty"`
+}
+
+// cutArchive publishes the cut's one sealed archive (§8.3) and returns the
+// seal's files: the sealed object and its manifest by version.
+//
+// Its identity is fixed before any PUT (sealing is randomized), in the op
+// journal. The first run reuses the preparing archive when its plaintext
+// digest is the stopped files' and its staged versions are pinned and its
+// sealed file is still on this disk (G14); otherwise it builds and seals
+// a new one. Then, from the journalled identity:
+// - the sealed file on disk: create-only into move-snapshots/ (an existing
+// object is this cut's earlier PUT), then staged into files/ unless it
+// is the reused archive, whose staged versions the ledger holds;
+// - no file, but both objects in move-snapshots/: staged into files/ by a
+// guarded server-side copy from there (or, reused, nothing to stage);
+// - neither: nothing was published under that identity, so the archive
+// is rebuilt and resealed and the journal entry overwritten.
+//
+// why is a recorded reason the stopped files cannot be archived.
+func (c *cutRun) cutArchive(ctx context.Context, plain string) (builtArchive, []moves.SealedObject, string, error) {
+ cfg := c.cfg()
+ host, id := c.rec.Host, c.g.ID
+ own, vs, err := c.ownStores(ctx)
+ if err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ ledger, err := readMoveLedger(ctx, own, cfg.OwnBucket, moves.LedgerKey(host, id))
+ if err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ j, err := c.journal()
+ if err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ adir := archiveDir(cfg.DataDir, host, id)
+ var step cutArchiveStep
+ journalled := false
+ if raw, ok := j.Steps[cutStepArchive]; ok && json.Unmarshal([]byte(raw), &step) == nil && step.Name == archiveName(step.Sealed) && step.Plain == plain {
+ step.dir = adir
+ journalled = true
+ }
+ snap := moves.SnapshotsPrefix(host, id)
+ if journalled {
+ switch onDisk := sealedOnDisk(step.builtArchive); {
+ case onDisk:
+ case c.snapshotPresent(ctx, vs, snap+step.Name):
+ default:
+ journalled = false // nothing published under it: rebuild
+ }
+ }
+ if !journalled {
+ reuse := ledger.ArchivePlain == plain && ledger.ArchiveVersions != nil && ledger.ArchiveVersions.Pinned() && ledger.ArchiveSealed != ""
+ if reuse {
+ kept := builtArchive{Name: archiveName(ledger.ArchiveSealed), Sealed: ledger.ArchiveSealed, Plain: plain, dir: adir}
+ if m, ok := keptManifest(kept); ok && sealedOnDisk(kept) {
+ kept.Bytes, kept.Entries = m.Bytes, len(m.Entries)
+ if st, err := os.Stat(kept.sealedPath()); err == nil {
+ kept.SealedSize = st.Size()
+ }
+ step = cutArchiveStep{builtArchive: kept, Reuse: true}
+ } else {
+ reuse = false
+ }
+ }
+ if !reuse {
+ a, why, retry, err := c.w.buildArchive(c.rec)
+ if err != nil || why != "" {
+ return builtArchive{}, nil, why, err
+ }
+ if retry {
+ return builtArchive{}, nil, "", errors.New("a stopped file changed while it was archived")
+ }
+ if a.Plain != plain {
+ return builtArchive{}, nil, "", fmt.Errorf("the stopped files' digest is %s, recorded %s", a.Plain, plain)
+ }
+ step = cutArchiveStep{builtArchive: a}
+ }
+ body, err := json.Marshal(step)
+ if err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ j.Mark(cutStepArchive, string(body))
+ if err := j.Write(cfg.DataDir); err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ if err := c.fault("archive-journaled"); err != nil {
+ return builtArchive{}, nil, "", err
+ }
+ }
+ a := step.builtArchive
+ files, err := c.publishCutArchive(ctx, own, vs, step, ledger)
+ return a, files, "", err
+}
+
+// publishCutArchive is cutArchive's PUTs from a journalled identity.
+func (c *cutRun) publishCutArchive(ctx context.Context, own lease.MirrorStore, vs lease.VersionedStore, step cutArchiveStep, ledger moveLedger) ([]moves.SealedObject, error) {
+ cfg := c.cfg()
+ host, id := c.rec.Host, c.g.ID
+ a := step.builtArchive
+ snap := moves.SnapshotsPrefix(host, id)
+ sealedSnap, manifestSnap := snap+a.Name+".sealed", snap+a.Name+".json"
+ onDisk := sealedOnDisk(a)
+ var manifest []byte
+ if onDisk {
+ var err error
+ if manifest, err = os.ReadFile(a.manifestPath()); err != nil {
+ return nil, err
+ }
+ fp, ok := vs.(lease.FilePutIfAbsent)
+ if !ok {
+ return nil, errors.New("this box cannot write the archive create-only")
+ }
+ if _, err := fp.PutFileIfAbsent(ctx, cfg.OwnBucket, sealedSnap, a.sealedPath()); err != nil && !errors.Is(err, lease.ErrExists) {
+ return nil, fmt.Errorf("snapshot the sealed archive: %w", err)
+ }
+ if _, err := vs.PutIfAbsent(ctx, cfg.OwnBucket, manifestSnap, manifest); err != nil && !errors.Is(err, lease.ErrExists) {
+ return nil, fmt.Errorf("snapshot the archive manifest: %w", err)
+ }
+ if err := c.fault("archive-own-put"); err != nil {
+ return nil, err
+ }
+ }
+ if step.Reuse {
+ ref := ledger.ArchiveVersions
+ if ref == nil || !ref.Pinned() || ledger.ArchiveSealed != a.Sealed {
+ return nil, fmt.Errorf("the ledger no longer names the archive %s", a.Name)
+ }
+ msize := int64(len(manifest))
+ if !onDisk {
+ msize = c.snapshotSize(ctx, vs, manifestSnap)
+ }
+ return []moves.SealedObject{
+ {Key: ref.SealedObjectKey, VersionID: ref.SealedVersion, Size: a.SealedSize},
+ {Key: ref.ManifestKey, VersionID: ref.ManifestVersion, Size: msize},
+ }, nil
+ }
+ prefix := moves.FilesPrefix(host, id)
+ sealedKey, manifestKey := prefix+a.Name+".sealed", prefix+a.Name+".json"
+ var sv, mv string
+ var msize int64
+ var err error
+ if onDisk {
+ if sv, err = c.gm.PutFileVersioned(ctx, c.gm.dst, sealedKey, a.sealedPath(), a.SealedSize, digestHex(a.Sealed)); err != nil {
+ return nil, fmt.Errorf("stage the sealed archive: %w", err)
+ }
+ if err := c.fault("archive-first-copy"); err != nil {
+ return nil, err
+ }
+ if mv, err = c.gm.PutVersioned(ctx, c.gm.dst, manifestKey, manifest); err != nil {
+ return nil, fmt.Errorf("stage the archive manifest: %w", err)
+ }
+ msize = int64(len(manifest))
+ } else {
+ // The file is gone, the snapshot is there: a server-side copy from
+ // it, guarded, as the replica lane copies.
+ objs, err := own.List(ctx, cfg.OwnBucket, snap)
+ if err != nil {
+ return nil, fmt.Errorf("list the snapshots: %w", err)
+ }
+ so, sok := objs[sealedSnap]
+ mo, mok := objs[manifestSnap]
+ if !sok || !mok {
+ return nil, fmt.Errorf("the snapshot of %s is incomplete", a.Name)
+ }
+ if _, sv, err = c.gm.CopyVersioned(ctx, cfg.OwnBucket, sealedSnap, so.ETag, c.gm.dst, sealedKey); err != nil {
+ return nil, fmt.Errorf("stage the sealed archive: %w", err)
+ }
+ if err := c.fault("archive-first-copy"); err != nil {
+ return nil, err
+ }
+ if _, mv, err = c.gm.CopyVersioned(ctx, cfg.OwnBucket, manifestSnap, mo.ETag, c.gm.dst, manifestKey); err != nil {
+ return nil, fmt.Errorf("stage the archive manifest: %w", err)
+ }
+ a.SealedSize, msize = so.Size, mo.Size
+ }
+ if err := c.fault("archive-second-copy"); err != nil {
+ return nil, err
+ }
+ if !lease.UsableVersionID(sv) || !lease.UsableVersionID(mv) {
+ return nil, fmt.Errorf("the staged archive has no usable version ids (%q, %q)", sv, mv)
+ }
+ log.Printf("moves: %s: staged the cut's archive %s", host, a.Name)
+ return []moves.SealedObject{{Key: sealedKey, VersionID: sv, Size: a.SealedSize}, {Key: manifestKey, VersionID: mv, Size: msize}}, nil
+}
+
+// sealedOnDisk reports whether the archive's sealed file and manifest are
+// on this disk and the sealed file hashes to its name.
+func sealedOnDisk(a builtArchive) bool {
+ if _, err := os.Stat(a.manifestPath()); err != nil {
+ return false
+ }
+ sum, err := fileSHA256(a.sealedPath())
+ return err == nil && sum == a.Sealed
+}
+
+// keptManifest reads the manifest of an archive kept on disk; it must
+// describe that archive.
+func keptManifest(a builtArchive) (archive.Manifest, bool) {
+ var m archive.Manifest
+ body, err := os.ReadFile(a.manifestPath())
+ if err != nil || json.Unmarshal(body, &m) != nil || m.Sealed != a.Sealed || m.Plain != a.Plain {
+ return m, false
+ }
+ return m, true
+}
+
+// snapshotPresent reports whether move-snapshots/ holds both objects of the
+// archive named base (key without suffix), current and not deleted.
+func (c *cutRun) snapshotPresent(ctx context.Context, vs lease.VersionedStore, base string) bool {
+ return c.snapshotSize(ctx, vs, base+".sealed") >= 0 && c.snapshotSize(ctx, vs, base+".json") >= 0
+}
+
+// snapshotSize is the current size of key in this box's bucket, or -1.
+func (c *cutRun) snapshotSize(ctx context.Context, vs lease.VersionedStore, key string) int64 {
+ vers, err := vs.ListVersions(ctx, c.cfg().OwnBucket, key)
+ if err != nil {
+ return -1
+ }
+ for _, v := range vers {
+ if v.Key == key && v.IsLatest && !v.DeleteMarker {
+ return v.Size
+ }
+ }
+ return -1
+}
+
+// sealNamesArchive reports whether the seal's files are the archive with
+// the sealed digest given.
+func sealNamesArchive(seal moves.Seal, sealed string) bool {
+ if sealed == "" {
+ return false
+ }
+ name := archiveName(sealed)
+ n := 0
+ for _, o := range seal.Files {
+ base := filepath.Base(o.Key)
+ if base == name+".sealed" || base == name+".json" {
+ n++
+ }
+ }
+ return n == 2 && len(seal.Files) == 2
+}
diff --git a/internal/host/movescut.go b/internal/host/movescut.go
index 6c62b013c..d4373eea4 100644
--- a/internal/host/movescut.go
+++ b/internal/host/movescut.go
@@ -20,7 +20,6 @@ import (
"github.com/carlosframework/platform/internal/hostclaim"
"github.com/carlosframework/platform/internal/lease"
"github.com/carlosframework/platform/internal/moves"
- "github.com/carlosframework/platform/internal/moves/archive"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
@@ -1102,239 +1101,6 @@ func (c *cutRun) sealArchive(ctx context.Context) (builtArchive, bool, error) {
return a, true, nil
}
-// cutArchiveStep is the archive's identity in the op journal, written
-// before any PUT so a replay publishes the same object (codex 19). Reuse
-// marks the preparing archive the target verified (G14): the seal names
-// its staged versions and nothing is staged again.
-type cutArchiveStep struct {
- builtArchive
- Reuse bool `json:"reuse,omitempty"`
-}
-
-// cutArchive publishes the cut's one sealed archive (§8.3) and returns the
-// seal's files: the sealed object and its manifest by version.
-//
-// Its identity is fixed before any PUT (sealing is randomized), in the op
-// journal. The first run reuses the preparing archive when its plaintext
-// digest is the stopped files' and its staged versions are pinned and its
-// sealed file is still on this disk (G14); otherwise it builds and seals
-// a new one. Then, from the journalled identity:
-// - the sealed file on disk: create-only into move-snapshots/ (an existing
-// object is this cut's earlier PUT), then staged into files/ unless it
-// is the reused archive, whose staged versions the ledger holds;
-// - no file, but both objects in move-snapshots/: staged into files/ by a
-// guarded server-side copy from there (or, reused, nothing to stage);
-// - neither: nothing was published under that identity, so the archive
-// is rebuilt and resealed and the journal entry overwritten.
-//
-// why is a recorded reason the stopped files cannot be archived.
-func (c *cutRun) cutArchive(ctx context.Context, plain string) (builtArchive, []moves.SealedObject, string, error) {
- cfg := c.cfg()
- host, id := c.rec.Host, c.g.ID
- own, vs, err := c.ownStores(ctx)
- if err != nil {
- return builtArchive{}, nil, "", err
- }
- ledger, err := readMoveLedger(ctx, own, cfg.OwnBucket, moves.LedgerKey(host, id))
- if err != nil {
- return builtArchive{}, nil, "", err
- }
- j, err := c.journal()
- if err != nil {
- return builtArchive{}, nil, "", err
- }
- adir := archiveDir(cfg.DataDir, host, id)
- var step cutArchiveStep
- journalled := false
- if raw, ok := j.Steps[cutStepArchive]; ok && json.Unmarshal([]byte(raw), &step) == nil && step.Name == archiveName(step.Sealed) && step.Plain == plain {
- step.dir = adir
- journalled = true
- }
- snap := moves.SnapshotsPrefix(host, id)
- if journalled {
- switch onDisk := sealedOnDisk(step.builtArchive); {
- case onDisk:
- case c.snapshotPresent(ctx, vs, snap+step.Name):
- default:
- journalled = false // nothing published under it: rebuild
- }
- }
- if !journalled {
- reuse := ledger.ArchivePlain == plain && ledger.ArchiveVersions != nil && ledger.ArchiveVersions.Pinned() && ledger.ArchiveSealed != ""
- if reuse {
- kept := builtArchive{Name: archiveName(ledger.ArchiveSealed), Sealed: ledger.ArchiveSealed, Plain: plain, dir: adir}
- if m, ok := keptManifest(kept); ok && sealedOnDisk(kept) {
- kept.Bytes, kept.Entries = m.Bytes, len(m.Entries)
- if st, err := os.Stat(kept.sealedPath()); err == nil {
- kept.SealedSize = st.Size()
- }
- step = cutArchiveStep{builtArchive: kept, Reuse: true}
- } else {
- reuse = false
- }
- }
- if !reuse {
- a, why, retry, err := c.w.buildArchive(c.rec)
- if err != nil || why != "" {
- return builtArchive{}, nil, why, err
- }
- if retry {
- return builtArchive{}, nil, "", errors.New("a stopped file changed while it was archived")
- }
- if a.Plain != plain {
- return builtArchive{}, nil, "", fmt.Errorf("the stopped files' digest is %s, recorded %s", a.Plain, plain)
- }
- step = cutArchiveStep{builtArchive: a}
- }
- body, err := json.Marshal(step)
- if err != nil {
- return builtArchive{}, nil, "", err
- }
- j.Mark(cutStepArchive, string(body))
- if err := j.Write(cfg.DataDir); err != nil {
- return builtArchive{}, nil, "", err
- }
- if err := c.fault("archive-journaled"); err != nil {
- return builtArchive{}, nil, "", err
- }
- }
- a := step.builtArchive
- files, err := c.publishCutArchive(ctx, own, vs, step, ledger)
- return a, files, "", err
-}
-
-// publishCutArchive is cutArchive's PUTs from a journalled identity.
-func (c *cutRun) publishCutArchive(ctx context.Context, own lease.MirrorStore, vs lease.VersionedStore, step cutArchiveStep, ledger moveLedger) ([]moves.SealedObject, error) {
- cfg := c.cfg()
- host, id := c.rec.Host, c.g.ID
- a := step.builtArchive
- snap := moves.SnapshotsPrefix(host, id)
- sealedSnap, manifestSnap := snap+a.Name+".sealed", snap+a.Name+".json"
- onDisk := sealedOnDisk(a)
- var manifest []byte
- if onDisk {
- var err error
- if manifest, err = os.ReadFile(a.manifestPath()); err != nil {
- return nil, err
- }
- fp, ok := vs.(lease.FilePutIfAbsent)
- if !ok {
- return nil, errors.New("this box cannot write the archive create-only")
- }
- if _, err := fp.PutFileIfAbsent(ctx, cfg.OwnBucket, sealedSnap, a.sealedPath()); err != nil && !errors.Is(err, lease.ErrExists) {
- return nil, fmt.Errorf("snapshot the sealed archive: %w", err)
- }
- if _, err := vs.PutIfAbsent(ctx, cfg.OwnBucket, manifestSnap, manifest); err != nil && !errors.Is(err, lease.ErrExists) {
- return nil, fmt.Errorf("snapshot the archive manifest: %w", err)
- }
- if err := c.fault("archive-own-put"); err != nil {
- return nil, err
- }
- }
- if step.Reuse {
- ref := ledger.ArchiveVersions
- if ref == nil || !ref.Pinned() || ledger.ArchiveSealed != a.Sealed {
- return nil, fmt.Errorf("the ledger no longer names the archive %s", a.Name)
- }
- msize := int64(len(manifest))
- if !onDisk {
- msize = c.snapshotSize(ctx, vs, manifestSnap)
- }
- return []moves.SealedObject{
- {Key: ref.SealedObjectKey, VersionID: ref.SealedVersion, Size: a.SealedSize},
- {Key: ref.ManifestKey, VersionID: ref.ManifestVersion, Size: msize},
- }, nil
- }
- prefix := moves.FilesPrefix(host, id)
- sealedKey, manifestKey := prefix+a.Name+".sealed", prefix+a.Name+".json"
- var sv, mv string
- var msize int64
- var err error
- if onDisk {
- if sv, err = c.gm.PutFileVersioned(ctx, c.gm.dst, sealedKey, a.sealedPath(), a.SealedSize, digestHex(a.Sealed)); err != nil {
- return nil, fmt.Errorf("stage the sealed archive: %w", err)
- }
- if err := c.fault("archive-first-copy"); err != nil {
- return nil, err
- }
- if mv, err = c.gm.PutVersioned(ctx, c.gm.dst, manifestKey, manifest); err != nil {
- return nil, fmt.Errorf("stage the archive manifest: %w", err)
- }
- msize = int64(len(manifest))
- } else {
- // The file is gone, the snapshot is there: a server-side copy from
- // it, guarded, as the replica lane copies.
- objs, err := own.List(ctx, cfg.OwnBucket, snap)
- if err != nil {
- return nil, fmt.Errorf("list the snapshots: %w", err)
- }
- so, sok := objs[sealedSnap]
- mo, mok := objs[manifestSnap]
- if !sok || !mok {
- return nil, fmt.Errorf("the snapshot of %s is incomplete", a.Name)
- }
- if _, sv, err = c.gm.CopyVersioned(ctx, cfg.OwnBucket, sealedSnap, so.ETag, c.gm.dst, sealedKey); err != nil {
- return nil, fmt.Errorf("stage the sealed archive: %w", err)
- }
- if err := c.fault("archive-first-copy"); err != nil {
- return nil, err
- }
- if _, mv, err = c.gm.CopyVersioned(ctx, cfg.OwnBucket, manifestSnap, mo.ETag, c.gm.dst, manifestKey); err != nil {
- return nil, fmt.Errorf("stage the archive manifest: %w", err)
- }
- a.SealedSize, msize = so.Size, mo.Size
- }
- if err := c.fault("archive-second-copy"); err != nil {
- return nil, err
- }
- if !lease.UsableVersionID(sv) || !lease.UsableVersionID(mv) {
- return nil, fmt.Errorf("the staged archive has no usable version ids (%q, %q)", sv, mv)
- }
- log.Printf("moves: %s: staged the cut's archive %s", host, a.Name)
- return []moves.SealedObject{{Key: sealedKey, VersionID: sv, Size: a.SealedSize}, {Key: manifestKey, VersionID: mv, Size: msize}}, nil
-}
-
-// sealedOnDisk reports whether the archive's sealed file and manifest are
-// on this disk and the sealed file hashes to its name.
-func sealedOnDisk(a builtArchive) bool {
- if _, err := os.Stat(a.manifestPath()); err != nil {
- return false
- }
- sum, err := fileSHA256(a.sealedPath())
- return err == nil && sum == a.Sealed
-}
-
-// keptManifest reads the manifest of an archive kept on disk; it must
-// describe that archive.
-func keptManifest(a builtArchive) (archive.Manifest, bool) {
- var m archive.Manifest
- body, err := os.ReadFile(a.manifestPath())
- if err != nil || json.Unmarshal(body, &m) != nil || m.Sealed != a.Sealed || m.Plain != a.Plain {
- return m, false
- }
- return m, true
-}
-
-// snapshotPresent reports whether move-snapshots/ holds both objects of the
-// archive named base (key without suffix), current and not deleted.
-func (c *cutRun) snapshotPresent(ctx context.Context, vs lease.VersionedStore, base string) bool {
- return c.snapshotSize(ctx, vs, base+".sealed") >= 0 && c.snapshotSize(ctx, vs, base+".json") >= 0
-}
-
-// snapshotSize is the current size of key in this box's bucket, or -1.
-func (c *cutRun) snapshotSize(ctx context.Context, vs lease.VersionedStore, key string) int64 {
- vers, err := vs.ListVersions(ctx, c.cfg().OwnBucket, key)
- if err != nil {
- return -1
- }
- for _, v := range vers {
- if v.Key == key && v.IsLatest && !v.DeleteMarker {
- return v.Size
- }
- }
- return -1
-}
-
// archived (§7.4, §7.5): the flip. Its precondition is re-read from the
// claim: the seal names the archive at the record's N, and the record's
// digests are the seal's archive; otherwise this is a replay of staged's
@@ -1391,23 +1157,6 @@ func (c *cutRun) archived(ctx context.Context) (bool, error) {
return true, nil
}
-// sealNamesArchive reports whether the seal's files are the archive with
-// the sealed digest given.
-func sealNamesArchive(seal moves.Seal, sealed string) bool {
- if sealed == "" {
- return false
- }
- name := archiveName(sealed)
- n := 0
- for _, o := range seal.Files {
- base := filepath.Base(o.Key)
- if base == name+".sealed" || base == name+".json" {
- n++
- }
- }
- return n == 2 && len(seal.Files) == 2
-}
-
// revertClaim is the claim half of a revert (§7.4 reverting, §7.5): once
// the source has given up execution, the claim goes to reverting under
// the source with the staging lane's park queued (hostclaim.Revert). While