ADR-045: Preflight and contain FlexForm deletion 

Status 

Accepted; implement through the dependent deletion repair.

Date 

2026-10-10

Context 

ADR-018 requires a refused vault deletion to cancel the owning record deletion. The FlexForm hook instead caught a failed secret deletion and left Core's cancellation flag unchanged. A real Core DataHandler test with the actual vault ACL denied the secret deletion, wrote an access_denied audit and then removed the owning record while its secret survived.

ADR-036 L1 separately states the preflight and best-effort boundary for multi-field records. It does not promise that a later independent secret deletion can restore earlier deletions.

Decision 

For a hard-deleting record, collect unique, existing, non-shared references from all its FlexForm columns and preflight every selected identifier through VaultServiceInterface::assertDeletable() before the first secret deletion. Use the same service for the actual deletions.

A reference is a value at a position the column's data structure configures with renderType vaultSecret, as a flat element or inside a section container. Candidate selection walks the data structure with the same matcher the write path uses. A UUID in any other element is ordinary content and never a candidate, whether or not a secret with that identifier exists. A column value without any identifier-shaped string needs no data structure. When such a string exists but the data structure cannot be resolved, or the stored value cannot be parsed, the vault positions cannot be located. That is a discovery failure: it cancels the record deletion like any other, and never reads as a column without vault positions.

On discovery, authorization or deletion failure, stop the cascade and set Core's existing cancellation flag before invoking diagnostics. Report the existing correlation reference to the editor without including the raw cause. On success, leave Core to remove its record normally. A duplicate reference is processed once.

Sharing is an exclusion for the complete identity: if any discovered column is shared, another occurrence in an unshared column cannot select that secret for deletion. This holds regardless of the order of the FlexForm columns.

Storage presence and consumer availability are distinct. Complete sharing exclusions before resolving the remaining identities' presence. A false exists() result does not establish absence: administrative getMetadata() also sees disabled custody. Only its SecretNotFoundException excludes an absent or already-deleted identity. Permission and lookup failures cancel the record deletion before any secret preflight or mutation. Resolve presence for all selected identities before the first assertDeletable() call. Shared identities need no metadata authorization because this record will not delete them. Retain the service's existing metadata and deletion authorization; do not retrieve plaintext or widen consumer availability.

Keep FlexForm soft-delete/recycle behavior: its secrets remain available for record restoration. Translation-shared secrets remain outside this record's cascade. No public interface is extended and no second ACL path is introduced.

Consequences 

A denied preflight leaves all selected secrets and the owning record intact. A deletion failure retains the record and stops the remaining cascade, but already-applied secret deletions cannot be undone automatically. The failing current operation may itself have persisted its deletion before a SecretDeletedEvent observer throws. The editor diagnostic must say that secrets may already have been deleted; it must not imply rollback or promise that the failing target is intact. Cancellation is set before reporting so a diagnostic failure cannot defer that protection.

A record whose FlexForm value holds an identifier-shaped string and whose data structure cannot be resolved cannot be hard-deleted until its data structure resolves again. Deleting it anyway would either miss its secrets or delete secrets this record merely mentions.

This is a preflight within the FlexForm hook. A record mixing plain TCA vault fields and FlexForm fields still needs a separate cross-hook coordination repair; this decision does not claim that guarantee. Copy compensation is also outside this deletion repair.