fix(workflows): ship built-ins disabled for first beta + enabled-based mutex + admin sentinel

Per review: the four cutover built-ins (dunning, gallery_expiring,
gallery_expired, pre_event_email) now ship enabled:false. The mutual-exclusion
guards revert to ENABLED-based (isBuiltinFlowActive, not existence) so the
legacy paths keep running until the admin enables a built-in — enabling cuts
over, disabling reverts to legacy (fixes concern #4's "disable = silent dark"
foot-gun; no automation goes dark on upgrade).

admin_toggled_at sentinel (migration 148) marks admin ownership; the boot
re-seeder applies a shipped default-flip (enabled→disabled) only to
never-touched built-ins and never overwrites an admin's enable/disable/edit
(nit #1). SEED_VERSIONs bumped so the disabled default propagates.

Nit: applyReminder unlinks the just-rendered Mahnung PDF if queueEmail throws
(no orphan file).
This commit is contained in:
Luca
2026-06-23 23:35:56 +02:00
parent 98ab717043
commit 5893ecb27a
5 changed files with 104 additions and 67 deletions
@@ -0,0 +1,24 @@
/**
* Migration 148: mark when an admin has taken ownership of a (built-in) workflow.
*
* The boot seeder re-seeds a built-in on a SEED_VERSION bump and applies the new
* default `enabled` state. Without a sentinel that would re-flip a flow the
* admin had deliberately enabled/disabled. `admin_toggled_at` is stamped on any
* admin enable/disable or edit; the seeder then leaves that flow alone. Nullable
* → existing rows are treated as never-touched (seed defaults apply once).
*/
exports.up = async function (knex) {
if (!(await knex.schema.hasTable('workflows'))) return;
if (!(await knex.schema.hasColumn('workflows', 'admin_toggled_at'))) {
await knex.schema.alterTable('workflows', (t) => {
t.timestamp('admin_toggled_at');
});
}
};
exports.down = async function (knex) {
if (!(await knex.schema.hasTable('workflows'))) return;
if (await knex.schema.hasColumn('workflows', 'admin_toggled_at')) {
await knex.schema.alterTable('workflows', (t) => t.dropColumn('admin_toggled_at'));
}
};