carlos / platform Public

layout: the seed marker and the launch descriptors are platform names

A nested instance directory also holds <db>.seed (written beside a
seeded database for its life) and the unit launch descriptors
launch.<role>.json and launch.gen.<n>.json. The sealer took each for an
app entry beside the files directory and refused the seal, so every
seeded instance and every real host to be moved (all three carry
launch.run.json and a launch.gen.<n>.json) failed at seal (T4 review I4).

NestedPlatformNames gains <db>.seed and launch.<role>.json for every
instancectl role, citing their writers. IsNestedPlatformName adds a
generation's descriptor by an exact pattern: launch.gen.<n>.json as
instancectl.GenLaunchFileName writes it, so launch.gen.07.json or
launch.gen.7.json.bak stay the app's. T3's offline source report now
asks the same question rather than keeping its own list. None of these
is carried in the archive, and the source's retire parks them with the
rest of the instance directory. The comment no longer says the archive
filters them out (review M3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 120af0f15d1fc38c7061871b99579777bcb402cd parent 09cdcd2
3 files changed, +78 −17
  • internal/host/movesoffline.go +7 −13
  • internal/layout/plan.go +46 −4
  • internal/layout/plan_test.go +25 −0
diff --git a/internal/host/movesoffline.go b/internal/host/movesoffline.go
index 9b1832ef6..cb1019574 100644
--- a/internal/host/movesoffline.go
+++ b/internal/host/movesoffline.go
@@ -499,17 +499,12 @@ func isDir(p string) bool {
return err == nil && st.IsDir()
}
-// offlinePlatformNames are a nested instance directory's platform entries:
-// everything else in it is the app's files.
-func offlinePlatformNames(host string) map[string]bool {
- db := host + ".db"
- return map[string]bool{
- db: true, db + "-wal": true, db + "-shm": true, db + "-litestream": true,
- ".activator-" + db + "-litestream": true, db + ".corrupt": true, db + ".gen": true,
- host + ".env": true, host + ".config.env": true, host + "-sidecar.env": true,
- host + ".instance-key-adoption.json": true, ".files-gen": true,
- }
-}
+// offlinePlatformName reports a nested instance directory's platform entry
+// (layout.IsNestedPlatformName: the database and its files, the env files,
+// the generation, seed and files markers, the adoption record, and the
+// launch descriptors, launch.gen.<n>.json included): everything else in it
+// is the app's files.
+func offlinePlatformName(host, name string) bool { return layout.IsNestedPlatformName(host, name) }
// localState reads what this box keeps for host: the DB file or either
// litestream meta dir (S8: a meta dir alone is local state, since
@@ -543,9 +538,8 @@ func (w *movesWorker) localState(host string, rt *registry.Route) (offlineLocal,
if err != nil && !errors.Is(err, os.ErrNotExist) {
return out, err
}
- platform := offlinePlatformNames(host)
for _, e := range ents {
- if !platform[e.Name()] {
+ if !offlinePlatformName(host, e.Name()) {
out.Files = true
break
}
diff --git a/internal/layout/plan.go b/internal/layout/plan.go
index d1d52d435..2fc6a9120 100644
--- a/internal/layout/plan.go
+++ b/internal/layout/plan.go
@@ -10,8 +10,12 @@ package layout
import (
"fmt"
"path/filepath"
+ "strconv"
+ "strings"
+ "github.com/carlosframework/platform/internal/activator"
"github.com/carlosframework/platform/internal/controlname"
+ "github.com/carlosframework/platform/internal/instancectl"
"github.com/carlosframework/platform/internal/instancekey"
"github.com/carlosframework/platform/internal/provision"
"github.com/carlosframework/platform/internal/release"
@@ -168,10 +172,24 @@ const filesMarkerName = ".files-gen"
// litestream shadow; the socket is in the run directory and is not here),
// the database's generation marker (<db>.gen), litestream's stock meta
// directory (<db>-litestream), the files generation marker (.files-gen) and
-// the instance-key adoption record. Everything else in the directory is the
-// app's files. The offline move's archive leaves these out
-// (archive.BuildFiltered), so the files it carries are the app's alone and
-// extract flat.
+// the instance-key adoption record, the seed marker a seeded database
+// keeps beside it for its life (<db>.seed, activator.SeedMarkerSuffix,
+// written by ExecBackend.seededRestore in activator/seedplace.go), and the
+// unit launch descriptors written into the instance directory:
+// launch.<role>.json for every instancectl role
+// (instancectl.LaunchFileName; written by ExecBackend.writeLaunchFor in
+// activator/prepare.go, and launch.run.json also by root's role launch in
+// activator/rolelaunch_root_linux.go). A
+// generation's descriptor, launch.gen.<n>.json (instancectl.
+// GenLaunchFileName, written by writeLaunchFor), has no fixed name:
+// IsNestedPlatformName answers for it. Everything else in the directory is
+// the app's: its files directory <host> and anything it keeps beside it.
+//
+// The offline move's sealer uses these to find what the app keeps beside
+// its files directory, which the move cannot carry. It archives the files
+// directory alone (archive.Build over <instance dir>/<host>), so none of
+// these is ever carried; the source's retire parks every entry of the
+// instance directory, these included.
func NestedPlatformNames(host string) map[string]bool {
const dataDir, socketDir = "/data", "/run/carlos"
raw := rawInstanceDir(dataDir, host)
@@ -184,11 +202,35 @@ func NestedPlatformNames(host string) map[string]bool {
db := host + ".db"
out[db+".gen"] = true
out[db+"-litestream"] = true
+ out[db+activator.SeedMarkerSuffix] = true
out[filesMarkerName] = true
out[host+instancekey.AdoptionSuffix] = true
+ for _, role := range []instancectl.Role{instancectl.RoleRun, instancectl.RoleSeed, instancectl.RoleSidecar, instancectl.RoleCmd} {
+ out[instancectl.LaunchFileName(role)] = true
+ }
return out
}
+// IsNestedPlatformName reports whether name, an entry of host's instance
+// directory, is the platform's: one of NestedPlatformNames, or a
+// generation's launch descriptor, exactly launch.gen.<n>.json with n a
+// decimal number as instancectl.GenLaunchFileName writes it.
+func IsNestedPlatformName(host, name string) bool {
+ if NestedPlatformNames(host)[name] {
+ return true
+ }
+ n, ok := strings.CutPrefix(name, "launch.gen.")
+ if !ok {
+ return false
+ }
+ n, ok = strings.CutSuffix(n, ".json")
+ if !ok || n == "" {
+ return false
+ }
+ gen, err := strconv.ParseUint(n, 10, 64)
+ return err == nil && instancectl.GenLaunchFileName(gen) == name
+}
+
// PlanFor derives the plan for host in the given direction from the host
// name alone (design §8.2 step 3). The endpoints are the row values the
// pass will compare against and swap; the artifacts are the files (and one
diff --git a/internal/layout/plan_test.go b/internal/layout/plan_test.go
index f1d21e34e..eaebf3593 100644
--- a/internal/layout/plan_test.go
+++ b/internal/layout/plan_test.go
@@ -164,6 +164,8 @@ func TestNestedPlatformNamesAreThePlansRawNames(t *testing.T) {
h + ".env": true, h + "-sidecar.env": true, h + ".config.env": true,
".activator-" + h + ".db-litestream": true, ".files-gen": true,
h + ".db.gen": true, h + ".db-litestream": true, h + ".instance-key-adoption.json": true,
+ h + ".db.seed": true,
+ "launch.run.json": true, "launch.seed.json": true, "launch.sidecar.json": true, "launch.cmd.json": true,
}
if len(got) != len(want) {
t.Fatalf("NestedPlatformNames = %v, want %v", got, want)
@@ -188,6 +190,29 @@ func TestNestedPlatformNamesAreThePlansRawNames(t *testing.T) {
}
}
+// IsNestedPlatformName is NestedPlatformNames plus a generation's launch
+// descriptor exactly as instancectl.GenLaunchFileName writes it; anything
+// shaped almost like one is the app's.
+func TestIsNestedPlatformNameMatchesGenerationLaunchFilesExactly(t *testing.T) {
+ const h = "h.example.com"
+ for name := range NestedPlatformNames(h) {
+ if !IsNestedPlatformName(h, name) {
+ t.Errorf("%s: not a platform name", name)
+ }
+ }
+ for _, name := range []string{"launch.gen.0.json", "launch.gen.7.json", "launch.gen.18446744073709551615.json"} {
+ if !IsNestedPlatformName(h, name) {
+ t.Errorf("%s: not a platform name", name)
+ }
+ }
+ for _, name := range []string{h, "launch.gen.json", "launch.gen..json", "launch.gen.07.json", "launch.gen.-1.json", "launch.gen.7.json.bak",
+ "launch.gen.7", "xlaunch.gen.7.json", "launch.gen.x.json", "launch.gen.18446744073709551616.json", "launch.other.json", h + "-blobs", "uploads"} {
+ if IsNestedPlatformName(h, name) {
+ t.Errorf("%s: taken for a platform name", name)
+ }
+ }
+}
+
func TestPlanForRefusesBadInput(t *testing.T) {
if _, err := PlanFor("/data", "/run/carlos", "h.example.com", Mode("sideways")); err == nil {
t.Error("unknown mode accepted")