# Permission Set deletion: permanent definition lifecycle

Decision: deleting a PermissionSet permanently removes the root and its owned
definition grants. It does not create recycle-bin entries or promise undelete.
This product boundary was explicitly approved after the #4758 review reproduced
irreversible grant loss behind a soft-deleted root.

## Reproduction and ownership

An unassigned PermissionSet containing system, object, field, record-type and
custom grants fails deletion through the deployed authenticated Records route.
The managed FK soft-delete guard rejects its Custom Permission reference. Its
HTTP-shaped conflict lacks a neutral ConflictCondition, so Runtime treats the
refusal as an internal failure and the CRM projection returns 500.

Current consumer path:

    PermissionSetManager / useDeletePermissionSet
      -> dataAPI.deleteRecord
      -> L1 sobjects DELETE / Runtime RecordMutation
      -> Records locked-row delete, validators, cascade, audit, Permission State

The existing PermissionService/DeletePermissionSet repository method has no
production caller in the current Core tree. It must not be wired as a second
public write owner. Permission Authoring Save edits grants; it does not own the
Records DELETE command or its journal.

Per `l0-l1-standard.md` and the PermissionSet/grant entries in `scope-floor.md`,
definition identity and transactional permission enforcement are platform
mechanisms. CRM URLs, errors and UI remain in L1. No new public RPC, command,
table, revision or migration is introduced here.

## Implemented candidate

- Reuse the validated PermissionSet definition inventory and its existing parent
  identity. Explicit `onRootDelete: cascade` declares Runtime retirement of owned
  grants; physical FK `ON DELETE CASCADE` alone grants no such authority.
- Leave assignments, group membership, connected-app access and assignment-policy
  claims restrictive. Their owners must detach them first. This does not silently
  remove another principal's assignment.
- Run parent authorization and before-delete validators before any child effect.
- Execute declared children through the existing Records lifecycle inside the
  parent's pessimistic transaction. Preserve parent locks, guards, row audit,
  Permission State coalescing and exact-row hard deletion for the entire definition.
- Recheck each child's parent on its locked image and verify the reference closure
  after synchronous lifecycle effects, before returning the mutation result.
- Return a neutral conflict for real reference blockers; do not infer semantics
  from an HTTP status or suppress the guard.

## Delete and recovery contract

The Records definition owner selects the existing private, exact-row hard-delete
disposition for the root and each owned child. It does not change the public
RecordMutation contract or depend on an updated Catalog `DeleteMode` projection.
Consequently an already-installed Catalog can use the corrected lifecycle with
the new Core binary, without a schema migration or an install/upgrade.

Individual Object/Field grant deletion retains its existing soft-delete policy.
When the whole definition is deleted later, its previously deleted children are
purged through the existing validated physical-delete seam and their exact
recycle-bin entries are removed in the same transaction. Their logical delete
hooks/audit are not replayed. Other definitions' rows and tombstones are untouched.

Assignments and other external-owner references remain restrictive, including
deleted references: the owning lifecycle must detach/purge those before the root
can be physically removed. A reference conflict remains a domain rejection,
never an implicit FK cascade or an internal-error response.

This operation does not migrate or bulk-purge pre-existing soft-deleted roots.
Their historical recycle-bin eligibility is unchanged. Newly deleted roots and
their grants have no tombstone and cannot be restored. No new backup store,
registry, command journal or table is introduced.

## Record-trigger boundary found by the production walking slice

The real Runtime EventBus initially rejected Custom Permission grant deletion:
its storage row uses a compound ID and the managed catalog deliberately declares
no KeyPrefix. The Flow listener previously treated every record event as a
key-prefix-addressable Flow target, so the complete parent delete rolled back.

The listener now distinguishes positively known unkeyed platform storage from
missing business-object identity. Both the served schema and the canonical
managed definition must identify the same table/type with no KeyPrefix before
the Flow listener returns no work. Unknown schemas, missing prefixes on normally
keyed objects, and type drift still fail closed. Other EventBus consumers are
not suppressed. No invented prefix, table allowlist, event bypass or Catalog
rewrite is used. A compiled Runtime Catalog + real EventBus regression test
covers the five unkeyed owned storage families and the negative cases.

Salesforce documents that assigned Permission Sets cannot be deleted:
https://help.salesforce.com/s/articleView?id=000332257&language=en_US&type=1

## Evidence

Nine real TiDB tests cover populated permanent deletion, whole-transaction rollback,
assignment conflict classified as a domain outcome, managed-root rejection,
historical aliases/disabled grants, concurrent Save versus Delete, previously
tombstoned child cleanup/rollback/isolation, historical external references and
Tab/App visibility cleanup. Canonical DDL and access policy come from
schemacatalog. A CI gate requires all nine to run
and pass, without skips. Tests assert physical absence, no orphan tombstones and
not-found on restore, rather than checking only `IsDeleted`. Final production-path
verification is recorded separately from owner tests; no test2 rollout is
authorized by this PR.

The independent CI dependency/bootstrap issue was addressed by merged #4757.
