PlanFile lifecycle ownership
Plan files have two distinct lifecycles: temp files (generated by the plan plugin during a TUI session) and user-provided files (passed via --plan flag, never deleted). Previously, the exec service unconditionally deleted the plan file after a successful apply — a latent bug for user-provided files that happened to be masked because the CLI seed path never reached that code.
We decided that cleanup belongs to whoever knows when the data becomes useless — the plan plugin. The exec service is an I/O adapter and must not make lifecycle decisions. The PlanFile type encodes this as an opaque struct with a Cleanup() method: temp files remove themselves, user-provided files no-op. The caller just calls Cleanup() without knowing the policy.
Consequences
- The exec service’s
Apply()no longer callsos.Remove()on the plan file. It only reads fromPlanFile.Path(). - The plan plugin calls
PlanFile.Cleanup()in itsreset()path — triggered by replan, context switch, or apply success (PlanInvalidatedEvent). - A failed apply leaves the plan file alive. The user can retry. Only when plan decides the data is stale does cleanup happen.
Considered Options
Exec service owns cleanup (status quo)
The service deletes the file after successful apply. Simple, but wrong — it doesn’t know whether the file was temp or user-provided, and a failed apply followed by a retry would fail with “file not found.”
Apply plugin owns cleanup
Apply consumed the file, so it cleans up. Rejected — apply cannot know if the data is still useful (the user might cancel and retry, or plan might want to show the stale plan). Only plan knows when its output artifact is no longer valid.
Dual-path: service deletes temp, ignores user-provided
Would require the service to understand plan file provenance — an abstraction leak into the I/O layer. The opaque Cleanup() approach keeps the service ignorant of lifecycle policy.