zdd plan 3 final fix: root's staged-exe refusal removes the generation's launch file (T9)
The nested staged start writes launch.gen.<n>.json (it carries the instance's environment) before asking root to start; when root refuses the staged build (ErrStagedExe) nothing was laid out or started, and the file is now removed. The legacy launch.run.json (generation 0) is shared with the original template and is left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+14
−1
internal/activator/backend_exec.go+8 −1internal/activator/swap_nested_test.go+6 −0
diff --git a/internal/activator/backend_exec.go b/internal/activator/backend_exec.go| index 0f25b2d66..230caca69 100644 |
| --- a/internal/activator/backend_exec.go |
| +++ b/internal/activator/backend_exec.go |
| @@ -1630,7 +1630,14 @@ func (b *ExecBackend) spawnUnitFrom(ctx context.Context, rt registry.Route, sock |
| inv, err := b.startRole(ctx, rt, instancectl.RoleRun, gen, exe, tier) |
| if errors.Is(err, instancectl.ErrStagedExe) { |
| // Root refused the staged build before it laid out or started |
| - // anything, so there is nothing to kill and nothing unproven. |
| + // anything, so there is nothing to kill and nothing unproven. The |
| + // generation's own launch file, which this start wrote and which |
| + // carries the instance's environment, goes too (final review T9). |
| + // The legacy launch.run.json (gen 0) is shared with the original |
| + // template and is left as it is. |
| + if gen != 0 { |
| + _ = os.Remove(filepath.Join(p.dataDir, instancectl.GenLaunchFileName(gen))) |
| + } |
| return fail(fmt.Errorf("%s: start unit: %w", rt.Host, err)) |
| } |
| if gen != 0 && (err != nil || inv == "") { |
diff --git a/internal/activator/swap_nested_test.go b/internal/activator/swap_nested_test.go| index 98b8f0cd9..74ba77514 100644 |
| --- a/internal/activator/swap_nested_test.go |
| +++ b/internal/activator/swap_nested_test.go |
| @@ -199,6 +199,12 @@ func TestStartStagedNestedRootRefusalIsNotUnproven(t *testing.T) { |
| if verdictOf(err) != gateNoOwnership { |
| t.Fatalf("verdictOf(%v) = %d, want NoOwnership", err, verdictOf(err)) |
| } |
| + // Final review T9: the launch file this start wrote (it carries the |
| + // instance's environment) is removed after root's definite refusal. |
| + launch := filepath.Join(b.pathsFor(rt).dataDir, instancectl.GenLaunchFileName(7)) |
| + if _, err := os.Stat(launch); !os.IsNotExist(err) { |
| + t.Fatalf("%s after root refused the start: %v, want it removed", launch, err) |
| + } |
| } |
| // A nested successor whose start failed and whose kill is unproven says so |