---
title: "ADR-045: Preflight and contain FlexForm deletion"
manual: "Secrets Vault"
version: "1.2"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-vault:adr-045-flexform-delete-refusal@1.2"
source: "Developer/Adr/ADR-045-FlexFormDeleteRefusal.rst"
rendered: "2026-10-31T22:33:18+00:00"
---

# ADR-045: Preflight and contain FlexForm deletion {#adr-045-flexform-delete-refusal}

## Status {#status}

Accepted; implement through the dependent deletion repair.

## Date {#date}

2026-10-10

## Context {#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 {#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 {#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.

## Related decisions {#related-decisions}

-   [ADR-018: FlexForm secret lifecycle management](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-018-flexform-secret-lifecycle@1.2)
-   [ADR-036: Mutation and audit are all-or-nothing](https://docs.typo3.org/permalink/netresearch/nr-vault:adr-036-mutation-audit-atomicity@1.2)
