Share the config-entity delete guard and the task-row warming, and key the shipped State column as state
Three findings of the audit at 7c9f1b2a, all of them one thing said more than once.
The delete guard, written three times
Status::preDelete() and Workflow::preDelete() were the same 22-line guard, with exactly three lines between them: the service id, the interface, the sentence. Tenant::preDelete() was the shape a third time, with its own default-tenant case on top. What differs is the type it protects, who counts what still uses it and how it refuses, so those are the arguments now; the loop, the sync and uninstall exemptions and the wording are shared in ConfigDeletionGuard.
The copies also each assembled the counts as 3 orchestra_instance, while the three delete forms rendered the same counts through EntityCountLabelsTrait as 3 process instances - so one refusal read two ways depending on which surface asked. The wording lives in EntityCountLabels now, a function of what it is given, because its two callers reach their services differently: the forms have them injected, the guard runs from a static entity hook. A tenant that cannot go says:
Tenant
acmestill owns 2 process instances, 1 token, 1 process variable, 1 process incident, 1 workflow, so it cannot be deleted; remove or re-scope them first.
A byte-identical preRender()
TaskActionLink and TaskOperations carried the same preRender() down to its comment. Neither half is a property of either column, and the two already share AssignmentCacheTrait, so the warming moves into a trait beside it.
The shipped State column keyed as status
views.view.orchestra_tokens, views.view.orchestra_trace and views.view.orchestra_inbox filed the engine (or work item) state under the key status, residue of the status to state rename, so the same key named two opposite things across the shipped set. A site builder who duplicates one of those and adds the real Status column gets it keyed status_1, and anything reading fields.status - the shape the shipped card preprocessors use - reads the engine state instead. Optional configuration, so the rename reaches new installs and an existing site keeps the copy it imported.
Tests
- The four classes that asserted the raw entity type ids in a refusal assert the words:
TenantDeletionTest,TenantUsageTest,WorkflowDeletionTest,WorkflowStatusDependencyTest. TenantTasksViewTest::testEveryShippedViewKeysStateAndStatusTheSameWayasserts the invariant over every shipped view of both modules, not over the three that were wrong. Toggled: it fails against the unfixed configuration and passes with it.- The warming is a pure move, so the cases that already render both columns and count the queries it removes (
MyTasksViewTest) are what proves it.
phpcs, phpstan level 5 and scripts/check-translations.php are clean.