fix(workflows): restore the once-per-process seed guard

`booted` was assigned but never read, so the guard's early return was missing
and the builtin workflow seeder ran on every call.

Impact was wasteful, not harmful: seedOneBuiltin is idempotent -- it keys on
builtin_key and returns early when adminOwned or storedVersion >= def.version,
writing a graph only on a fresh insert or a version bump. So repeat calls cost
a lookup per builtin plus a graph rebuild, with no duplicate rows.

`booted = true` stays inside the try, so a seed that never got off the ground
(workflows table not migrated, DB down) leaves the flag clear and retries. A
per-builtin failure is still swallowed by the inner catch and does not block
the flag, unchanged.

Restoring the guard broke workflowEngine.test.js, which calls the boot seeder
seven times in one worker and needs the second call to run in two of them.
Followed the existing _backupPathsBoot/_restoreSettingsBoot precedent:
exported _resetBootForTests().

Refs testplan REPORT.md B3.
This commit is contained in:
Paul Nothaft
2026-09-02 09:43:10 +02:00
parent 355fe4ff43
commit a7d45ddd0d
2 changed files with 53 additions and 17 deletions
+17 -5
View File
@@ -335,10 +335,12 @@ const BUILTINS = [
},
];
// NOTE: written at the end of seedBuiltinWorkflowsAtBoot but never read — the
// intended "seed only once per process" guard is missing its `if (booted) return;`
// check. Left in place so the gap stays visible rather than being silently dropped.
// eslint-disable-next-line no-unused-vars -- write-only boot guard, see note above
// Seed-once-per-process guard. Re-seeding is idempotent (seedOneBuiltin
// updates in place, keyed on builtin_key, and skips admin-owned or
// already-current rows), so the cost of a repeat call is a table scan per
// builtin plus a graph rebuild — wasted work, not duplicate rows. Set inside
// the try, so a seed that never got off the ground (workflows table not
// migrated yet, DB down) leaves the flag clear and a later call can retry.
let booted = false;
function parseSeedConfig(raw) {
@@ -420,6 +422,7 @@ async function seedOneBuiltin(db, logger, def) {
}
async function seedBuiltinWorkflowsAtBoot(db, logger) {
if (booted) return;
try {
if (!(await db.schema.hasTable('workflows'))) return;
for (const def of BUILTINS) {
@@ -435,4 +438,13 @@ async function seedBuiltinWorkflowsAtBoot(db, logger) {
}
}
module.exports = { seedBuiltinWorkflowsAtBoot, buildDunningGraph, DUNNING_KEY, BUILTINS };
// Test-only: reset the module-level boot flag so jest can re-exercise the
// seeder against a fresh test DB inside a single worker. Matches
// _backupPathsBoot / _restoreSettingsBoot.
function _resetBootForTests() {
booted = false;
}
module.exports = {
seedBuiltinWorkflowsAtBoot, buildDunningGraph, DUNNING_KEY, BUILTINS, _resetBootForTests,
};