carlos / platform Public

zdd rev 19: probe checks only platform launches; rule 4 numbers

zddprobe enforces rule 5 only on a platform launch, the one that passes
--db (the flag package reads -db and --db alike). Vet's boot passes no
--db, no CARLOS_HOST and a socket name of its own, so it skips the check
and serves; before this the probe could not pass vet at all. Tests cover
both vet forms (-socket/--socket, any name) and both db forms.

The amendment's rule 4 now states the measured bound: the host alone, at
the largest generation number, so flat hosts up to 59 bytes get
generation paths against 82 flag-off, and why a row must not change
treatment as the counter grows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 4dba454f844bc4e118c171f452884d8c47a1ae2b parent 19c07b2
3 files changed, +73 −36
  • cmd/zddprobe/main.go +19 −13
  • cmd/zddprobe/main_test.go +46 −23
  • docs/superpowers/specs/2026-10-13-zero-downtime-deploys-rev19-amendment.md +8 −0
diff --git a/cmd/zddprobe/main.go b/cmd/zddprobe/main.go
index 66d7f10a7..a13cf9574 100644
--- a/cmd/zddprobe/main.go
+++ b/cmd/zddprobe/main.go
@@ -9,14 +9,16 @@
// `<binary> -socket <sock>`. The probe takes both. It keeps no data, so --db
// is accepted and ignored.
//
-// Before it listens, the probe checks what a real app may rely on (zero-
-// downtime spec rev 19, rule 5), and exits 1 with a line on stderr if
-// either is wrong: CARLOS_HOST must be set, and the file name of --socket
-// must be the flag-off one, app.sock (nested) or $CARLOS_HOST.sock (flat).
-// A generation launch that named its socket anything else crash-looped a
-// tenant whose framework reads its host from that name (amadan #272); the
-// probe ignored the name and passed. A vet boot of the probe needs
-// CARLOS_HOST in its environment too.
+// On a platform launch, the one that passes --db, the probe checks before
+// it listens what a real app may rely on (zero-downtime spec rev 19, rule
+// 5), and exits 1 with a line on stderr if either is wrong: CARLOS_HOST
+// must be set, and the file name of --socket must be the flag-off one,
+// app.sock (nested) or $CARLOS_HOST.sock (flat). A generation launch that
+// named its socket anything else crash-looped a tenant whose framework
+// reads its host from that name (amadan #272); the probe ignored the name
+// and passed. Vet's boot passes no --db, no CARLOS_HOST and a socket name
+// of its own choosing, so it skips the check and the probe serves. (The
+// flag package reads -db and --db alike, so --db being given is the test.)
//
// version is set at build time with -ldflags "-X main.version=...".
//
@@ -51,9 +53,9 @@ func main() {
}
}
-// checkLaunch is rule 5's check of the launch: host (CARLOS_HOST) is set,
-// and sock's file name is app.sock or <host>.sock, the names a flag-off
-// launch gives.
+// checkLaunch is rule 5's check of a platform launch: host (CARLOS_HOST)
+// is set, and sock's file name is app.sock or <host>.sock, the names a
+// flag-off launch gives.
func checkLaunch(sock, host string) error {
if host == "" {
return errors.New("zddprobe: CARLOS_HOST is empty; the platform sets it on every instance launch")
@@ -75,8 +77,12 @@ func run(args []string, stop <-chan os.Signal) error {
if *sock == "" {
return errors.New("zddprobe: --socket is required")
}
- if err := checkLaunch(*sock, os.Getenv("CARLOS_HOST")); err != nil {
- return err
+ platform := false
+ fs.Visit(func(f *flag.Flag) { platform = platform || f.Name == "db" })
+ if platform {
+ if err := checkLaunch(*sock, os.Getenv("CARLOS_HOST")); err != nil {
+ return err
+ }
}
_ = os.Remove(*sock)
l, err := net.Listen("unix", *sock)
diff --git a/cmd/zddprobe/main_test.go b/cmd/zddprobe/main_test.go
index 1a9db42ae..28dfbda6b 100644
--- a/cmd/zddprobe/main_test.go
+++ b/cmd/zddprobe/main_test.go
@@ -123,7 +123,9 @@ func TestProbeServesWithThePlatformsRunArgv(t *testing.T) {
func TestProbeServesWithVetsArgv(t *testing.T) {
sock := filepath.Join(t.TempDir(), "app.sock")
- launch(t, []string{"CARLOS_HOST=shop.example.com"}, "-socket", sock) // internal/vet/vet.go bootHealthz
+ // Vet's argv: no --db and no CARLOS_HOST. Rule 5 is a platform-launch
+ // check and does not apply.
+ launch(t, []string{"CARLOS_HOST="}, "-socket", sock) // internal/vet/vet.go bootHealthz: no --db, no CARLOS_HOST
if body := getOverUnix(t, sock, "/"); !strings.HasPrefix(body, "ok ") {
t.Fatalf("body %q", body)
}
@@ -161,10 +163,24 @@ func TestProbeServesOnTheFlatHostSocketName(t *testing.T) {
}
}
-// Rev 19, rule 5: a launch whose socket file name is not a flag-off one, or
-// that has no CARLOS_HOST, exits 1 with a line on stderr, before it
-// listens, so a probe on such a launch fails its health check as the
-// tenant did.
+// A vet boot is not checked whatever its socket is called: vet picks the
+// name, and CARLOS_HOST is not set.
+func TestProbeServesVetsArgvWithAnyName(t *testing.T) {
+ for _, form := range []string{"-socket", "--socket"} {
+ t.Run(form, func(t *testing.T) {
+ sock := filepath.Join(t.TempDir(), "probe.3.sock")
+ launch(t, []string{"CARLOS_HOST="}, form, sock)
+ if body := getOverUnix(t, sock, "/"); !strings.HasPrefix(body, "ok ") {
+ t.Fatalf("body %q", body)
+ }
+ })
+ }
+}
+
+// Rev 19, rule 5: a platform launch (it passes --db) whose socket file name
+// is not a flag-off one, or that has no CARLOS_HOST, exits 1 with a line on
+// stderr, before it listens, so a probe on such a launch fails its health
+// check as the tenant did. -db and --db are the same flag.
func TestProbeRefusesALaunchARealAppMightRefuse(t *testing.T) {
for _, tc := range []struct {
name, host, file, want string
@@ -175,24 +191,31 @@ func TestProbeRefusesALaunchARealAppMightRefuse(t *testing.T) {
{"no CARLOS_HOST, nested name", "", "app.sock", "CARLOS_HOST is empty"},
{"no CARLOS_HOST, flat name", "", "shop.example.com.sock", "CARLOS_HOST is empty"},
} {
- t.Run(tc.name, func(t *testing.T) {
- sock := filepath.Join(t.TempDir(), tc.file)
- cmd := exec.Command(os.Args[0], "--socket", sock, "--db", "x.db")
- cmd.Env = append(os.Environ(), "ZDDPROBE_AS_MAIN=1", "CARLOS_HOST="+tc.host)
- var stderr strings.Builder
- cmd.Stderr = &stderr
- err := cmd.Run()
- var exit *exec.ExitError
- if !errors.As(err, &exit) || exit.ExitCode() != 1 {
- t.Fatalf("exit = %v, want status 1", err)
- }
- if !strings.Contains(stderr.String(), tc.want) {
- t.Fatalf("stderr %q, want it to say %q", stderr.String(), tc.want)
- }
- if _, err := os.Lstat(sock); !os.IsNotExist(err) {
- t.Fatalf("the probe listened before refusing (lstat %s: %v)", sock, err)
- }
- })
+ for _, form := range []string{"--db", "-db"} {
+ t.Run(tc.name+" "+form, func(t *testing.T) {
+ refused(t, tc.host, tc.file, form, tc.want)
+ })
+ }
+ }
+}
+
+func refused(t *testing.T, host, file, dbFlag, want string) {
+ t.Helper()
+ sock := filepath.Join(t.TempDir(), file)
+ cmd := exec.Command(os.Args[0], "--socket", sock, dbFlag, "x.db")
+ cmd.Env = append(os.Environ(), "ZDDPROBE_AS_MAIN=1", "CARLOS_HOST="+host)
+ var stderr strings.Builder
+ cmd.Stderr = &stderr
+ err := cmd.Run()
+ var exit *exec.ExitError
+ if !errors.As(err, &exit) || exit.ExitCode() != 1 {
+ t.Fatalf("exit = %v, want status 1", err)
+ }
+ if !strings.Contains(stderr.String(), want) {
+ t.Fatalf("stderr %q, want it to say %q", stderr.String(), want)
+ }
+ if _, err := os.Lstat(sock); !os.IsNotExist(err) {
+ t.Fatalf("the probe listened before refusing (lstat %s: %v)", sock, err)
}
}
diff --git a/docs/superpowers/specs/2026-10-13-zero-downtime-deploys-rev19-amendment.md b/docs/superpowers/specs/2026-10-13-zero-downtime-deploys-rev19-amendment.md
index 672cad915..1e2a533c5 100644
--- a/docs/superpowers/specs/2026-10-13-zero-downtime-deploys-rev19-amendment.md
+++ b/docs/superpowers/specs/2026-10-13-zero-downtime-deploys-rev19-amendment.md
@@ -62,6 +62,14 @@ ignores its socket name.
because the flag-off path `/run/carlos/<host>.sock` is already near the
bound for the same host. Nested paths are bounded by the 40-byte instance
identifier and stay well under it.
+
+ The bound is measured on the host alone: the control socket
+ `/run/carlos/g/<n>/<host>.sock.control` at the largest generation number
+ (20 digits). So flat hosts of up to 59 bytes get generation paths,
+ against 82 bytes for the flag-off control socket. Measuring at the
+ current number would let a row change treatment as the counter grows,
+ even in the middle of a swap: an attempt admitted as an overlap could
+ meet a generation that cannot bind.
5. **The probe checks it.** `cmd/zddprobe` exits 1, with a clear line on
stderr, before it listens if either of these is wrong:
- the base name of `--socket` is neither `app.sock` nor