AshReferentialActions. Changes. EnsureNotRestricted
(ash_referential_actions v0.1.0)
Copy Markdown
View Source
Runtime guard added to every destroy action of a resource that declares
restrict_has_many/has_one: the destroy (hard delete, or archive when the resource is
archival) is rejected while live restricting records exist.
Restricting records are enumerated through the resource's reverse relationships
and counted via each relationship's declared read_action (or the
referring resource's primary read — a verifier rejects a filtered primary read as
the default), so action-level read filters cannot silently hide live
restricting records. Archival's global is_nil(archived_at) preparation still
applies — an archived restricting record does not block. Inside a transaction, restricting records archived
earlier in the same transaction are already invisible, which lets an
reference cascade pass deterministically as long as it archives restricting records
before the restricted resource (see the archive_last option of AshReferentialActions.Archival).
Before counting, the record being destroyed is locked FOR UPDATE where the
data layer supports it, pairing with the FOR SHARE lock
AshReferentialActions.Changes.EnsureTargetLive takes on the same row. That closes the
window where a lock is created concurrently with a destroy. On data layers
without lock support (e.g. ETS) both stay plain application-level checks and
the race remains.