Problem
The exec backend currently observes a strict process generation and then signals numeric -pid; a PID/PGID can be reused between those steps. Its restart/final-GC paths also unlink <id>.pid by path without a generation/inode compare-and-swap. Those are separate check/use races over the same lifecycle boundary.
This issue defines one replacement primitive. It does not extend PTY lifecycle and does not authorize whole-state-directory teardown.
Required public contract
st2 --catalog <catalog> exec retire \
--id <runtime-id> \
--expect-generation-id <sha256:...> \
--expect-catalog-sha256 <lowercase-hex> \
--json
The caller must supply both capabilities from coherent typed receipts. The command never infers them from mutable current state.
Exact transaction
On supported Linux hosts, one durable per-request transaction must:
- hold the exact canonical declaration-root digest under the shared catalog boundary;
- open the exact regular generation record without symlink traversal and retain its inode and bytes;
- pin the exact leader with a pidfd and verify its start token;
- open the recorded dedicated cgroup-v2 systemd scope without symlink or mount traversal and verify scope/inode/membership authority;
- freeze the cgroup and wait for kernel confirmation;
- revalidate catalog, record inode+bytes, process generation, scope, and membership;
- write
cgroup.kill, then prove the cgroup unpopulated and exact generation gone;
- move the generation record with
renameat2(RENAME_NOREPLACE) into a private retirement slot;
- verify the moved inode and bytes; on mismatch, restore with NOREPLACE, preserving both objects if restoration conflicts;
- durably complete a typed
st2.exec-retirement.v1 receipt.
Recovery reopens and revalidates capabilities at each durable phase. Repeating an identical request resumes or returns the completed receipt.
Fail-closed boundary
No fallback to numeric PID/PGID signaling, pathname unlink, PTY operations, or whole-state-directory rotation. Legacy/v1 records, non-systemd hosts, cgroup v1, degraded isolation, missing pidfd/cgroup controls, ambiguous membership, and changed catalog authority must perform zero lifecycle mutation and return a typed error.
Acceptance
Normative detail is tracked as R24 in docs/vrs/requirements.md and docs/vrs/spec.md on the implementation branch.
Problem
The exec backend currently observes a strict process generation and then signals numeric
-pid; a PID/PGID can be reused between those steps. Its restart/final-GC paths also unlink<id>.pidby path without a generation/inode compare-and-swap. Those are separate check/use races over the same lifecycle boundary.This issue defines one replacement primitive. It does not extend PTY lifecycle and does not authorize whole-state-directory teardown.
Required public contract
The caller must supply both capabilities from coherent typed receipts. The command never infers them from mutable current state.
Exact transaction
On supported Linux hosts, one durable per-request transaction must:
cgroup.kill, then prove the cgroup unpopulated and exact generation gone;renameat2(RENAME_NOREPLACE)into a private retirement slot;st2.exec-retirement.v1receipt.Recovery reopens and revalidates capabilities at each durable phase. Repeating an identical request resumes or returns the completed receipt.
Fail-closed boundary
No fallback to numeric PID/PGID signaling, pathname unlink, PTY operations, or whole-state-directory rotation. Legacy/v1 records, non-systemd hosts, cgroup v1, degraded isolation, missing pidfd/cgroup controls, ambiguous membership, and changed catalog authority must perform zero lifecycle mutation and return a typed error.
Acceptance
Normative detail is tracked as R24 in
docs/vrs/requirements.mdanddocs/vrs/spec.mdon the implementation branch.