plan 2 (containment) from its writer; index: D1 daemon-reload measurement gate, root validation of nested exec mount
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+23
−4
docs/superpowers/plans/2026-10-07-zero-downtime-deploys-0-index.md+17 −0docs/superpowers/plans/2026-10-07-zero-downtime-deploys-2-containment.md+6 −4
CI failed — run details
diff --git a/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-0-index.md b/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-0-index.md| index 9a168c070..017a11f2c 100644 |
| --- a/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-0-index.md |
| +++ b/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-0-index.md |
| @@ -213,3 +213,20 @@ type StagedIdentity struct { |
| set; |
| - so plan 5's agent writes the file **before its first spawn**, and |
| rewrites it at every change to an operation set. |
| +- **Plan 2's decisions D1–D6, accepted, with one guard on D1.** |
| + - **D1:** each nested generation unit gets its slice from a runtime |
| + drop-in plus `daemon-reload`. **Measure `daemon-reload` on dev**, on a |
| + box with titogo-scale unit counts, before the flag goes on. If one |
| + reload per swap costs more than about 1 s, switch nested generation |
| + units to transient units (`StartTransientUnit` with the template's |
| + properties) in a follow-up. The roll notes in plan 6 carry the check. |
| + - The parent limit during an overlap is the incumbent's tier plus the |
| + successor's. |
| + - The legacy sidecar and seed move to `role-sidecar` and `role-seed` |
| + child cgroups. |
| + - Two existing breakages under an empty parent get fixed: the metrics |
| + sampler and `Cleanup.Members()`. |
| +- **Root validates the staged path for a nested executable mount** (plan |
| + 3). It must lie under `/opt/carlos/versions/<acct>/<app>/<version>/` |
| + for that host's own app, resolved without following symlinks, and its |
| + digest must match the attempt's. |
diff --git a/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-2-containment.md b/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-2-containment.md| index af44cefae..db8f2dec0 100644 |
| --- a/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-2-containment.md |
| +++ b/docs/superpowers/plans/2026-10-07-zero-downtime-deploys-2-containment.md |
| @@ -18,7 +18,7 @@ |
| - Spec §7.6, verbatim: "The slice and the data mounts are keyed by host ID only. The launch file, the executable mount, the log sockets and exit records are keyed by generation." "The old template stays until no box runs it." |
| - Shared names from the index, used exactly: `activator.Gen` and its field `N`. This plan adds no field to `Gen`. |
| - **Branch:** `zdd-2-containment`, cut from amadan's `main` **after plan 1 (`…-1-generations.md`) has merged**. Never push to `main`. Commit after every task, with a message that names the plan and task (`zdd plan 2 task N: …`) and the attribution trailer your session gives you. |
| -- Build gate before every commit (AGENTS.md): `go build ./...`, `go vet ./...`, `gofmt -l .` (prints nothing), and the touched packages' tests. Task 13 runs the whole gate. |
| +- Build gate before every commit (AGENTS.md): `go build ./...`, `go vet ./...`, `gofmt -l .` (prints nothing), and the touched packages' tests. Task 13 runs the whole gate. The code blocks in this plan are not guaranteed to be gofmt-aligned: run `gofmt -w` on every Go file you create or edit before the gate. |
| - CI cross-compiles `darwin`, `windows` and `linux-arm64`. Every Linux-only syscall goes in a `_linux.go` file with a `//go:build linux` tag and a `!linux` stub that returns `ErrStrictUnsupported` (or the package's existing unsupported error). |
| - Go build cache: use the default (`~/.cache/go-build`). Do not set `GOCACHE`, `GOMODCACHE` or `GOTMPDIR`. If a go command fails with "read-only file system" inside the sandbox, rerun that one command with the sandbox off. Do not move the cache. If `go build` says "error obtaining VCS status", add `GOFLAGS=-buildvcs=false` to that command. |
| - Socket-binding tests (`internal/instancectl`, the nested tests in `internal/activator`) need a short `TMPDIR`: `mkdir -p /tmp/k` and prefix the command with `TMPDIR=/tmp/k`. |
| @@ -1227,7 +1227,9 @@ func TestSetParentLimitRecordsWhatItWrites(t *testing.T) { |
| if err := child.Start(exec.Command("/bin/true")); !errors.Is(err, ErrStrictUnsupported) { |
| t.Fatalf("child start after an API raise: %v, want it to reach the spawn", err) |
| } |
| - if err := strictWrite(p.limits["memory.max"], "1"); err != nil { |
| + // strictWrite does not truncate a fixture file: "9" over "1073741824" |
| + // leaves "9073741824", a limit this API never set. |
| + if err := strictWrite(p.limits["memory.max"], "9"); err != nil { |
| t.Fatal(err) |
| } |
| other, err := p.PrepareGen(2, TierDefault) |
| @@ -3929,7 +3931,7 @@ with: |
| b.Cgroups.CleanupHost(host) |
| ``` |
| -**`startSidecarChild`.** Before the spawn, add: |
| +**`startSidecarChild`.** Directly after the `fileEnv, err := readEnvFile(b.pathsFor(rt).sidecarEnv)` call and its error check, add: |
| ```go |
| child, _, err := b.placeFor(spawnSidecar, 0) |
| @@ -3938,7 +3940,7 @@ with: |
| } |
| ``` |
| -Change `cmd, err := b.spawnInCgroup(rt, func() *exec.Cmd {` to `cmd, err := b.spawnInCgroupAt(rt, child, false, func() *exec.Cmd {`, and in its goroutine put `b.Cgroups.RemoveChild(rt.Host, child)` on the line before `b.Cgroups.CleanupHost(rt.Host)`. Because `err` is now declared earlier, change `fileEnv, err := readEnvFile(…)` to keep compiling: put the `placeFor` block after the `readEnvFile` call and its error check. |
| +(`child` is new, so `:=` compiles.) Change `cmd, err := b.spawnInCgroup(rt, func() *exec.Cmd {` to `cmd, err := b.spawnInCgroupAt(rt, child, false, func() *exec.Cmd {`, and in its goroutine put `b.Cgroups.RemoveChild(rt.Host, child)` on the line before `b.Cgroups.CleanupHost(rt.Host)`. |
| **`seedChild`.** Directly before `cmd, err := b.spawnInCgroup(rt, func() *exec.Cmd {` (line ~484), add: |