Skip to content

ExactExecRetirement: race-free typed exec teardown and record retirement #121

Description

@schickling-assistant

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:

  1. hold the exact canonical declaration-root digest under the shared catalog boundary;
  2. open the exact regular generation record without symlink traversal and retain its inode and bytes;
  3. pin the exact leader with a pidfd and verify its start token;
  4. open the recorded dedicated cgroup-v2 systemd scope without symlink or mount traversal and verify scope/inode/membership authority;
  5. freeze the cgroup and wait for kernel confirmation;
  6. revalidate catalog, record inode+bytes, process generation, scope, and membership;
  7. write cgroup.kill, then prove the cgroup unpopulated and exact generation gone;
  8. move the generation record with renameat2(RENAME_NOREPLACE) into a private retirement slot;
  9. verify the moved inode and bytes; on mismatch, restore with NOREPLACE, preserving both objects if restoration conflicts;
  10. 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

  • generation v2 records bind the dedicated scope unit, cgroup path, mount/device/inode identity, PID, start token, and generation id;
  • CLI and receipt schemas are closed and completion-generated;
  • deterministic tests cover stale generation and stale catalog capabilities;
  • symlink/FIFO/directory/mount and cgroup-inode substitution are rejected;
  • a raced generation-record replacement is never deleted and rollback conflicts preserve both objects;
  • crash injection after every durable phase resumes idempotently;
  • a real private systemd user-scope E2E kills a forked task cgroup while an unrelated sentinel survives;
  • unsupported/degraded environments prove zero signal and zero record mutation;
  • existing PTY behavior is byte-for-byte unaffected;
  • an independent adversarial review approves the implementation.

Normative detail is tracked as R24 in docs/vrs/requirements.md and docs/vrs/spec.md on the implementation branch.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:execExec backend and process-group management · Set: manualarea:reconcileSupervisor run loop, lifecycle, restart, park, and teardown · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: manualtype:featureNew user-visible or system capability · Set: manual

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions