# 2. cp9 purge is fail-closed per candidate

Status: Accepted

## Context

Broad expired cleanup processes many owner/session candidates in one run. A
single ambiguous or conflicting candidate must not preserve all other expired
resources, and it must not be silently ignored either.

## Decision

`cp9 purge` is fail-closed per candidate:

- A blocked candidate (conflicting tags, failed tag-assurance validation,
  unsupported relationship) is skipped without mutating or deleting it. The
  two Recycle delete-path exceptions in ADR 0001 are supported relationships.
- Safe candidates continue to be processed.
- A candidate whose complete discovery evidence is directly proven absent or
  deletion-terminal is reported as `stale/no-op` and never enters destructive
  execution. A verifier error or unsupported identity is not absence proof.
- A conflicting child that is directly present and carries a different,
  parseable, future-TTL session is reported as `deferred_live_dependency` and
  retried by a later scheduled pass. An expired conflicting session gets one
  convergence retry after the other candidates; missing or malformed identity
  remains blocked.
- A destructive run exits non-zero at the end if any candidate was blocked, so
  residual resources are visible.
- A dry-run reports per-candidate blocks and exits zero for ordinary blocks, but
  exits non-zero for systemic errors (wrong account, wrong role, missing tools,
  malformed input, or AWS API failures that invalidate the whole run).

Tag assurance validates the whole candidate plan before applying any tag
mutation for that candidate, so a late conflict cannot leave half-repaired tags
(no-partial-apply).

## Consequences

- One ambiguous session cannot block cleanup of unrelated expired sessions.
- Operators get a complete dry-run preview while still seeing every block.
- Exit codes distinguish "some candidates blocked" from "the run was invalid".
