From 3a101430f7f9e2c36e200099448806be56dc36bb Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Thu, 23 Jul 2026 16:28:34 -0400 Subject: [PATCH] fix: wire the deletion barrier into the schedules service adapter; fail fast on missing deps Codex (review of 17534d13a) caught the under-wiring trap recurring in the commit that was meant to fix it: the JS adapter (api/server/services/Schedules) constructs createSchedulesService WITHOUT isUserDeleting, so engineDeps.isOwnerDeleting -> deps.isUserDeleting(userId) would throw "is not a function" on every scheduled fire. The dispatch-boundary barrier check was therefore not just inert but crashing. The TS type requires the dep; the JS adapter is not typechecked against it, so tsc never saw it. Fix: pass methods.isUserDeleting into the adapter. Plus a guardrail so this whole class cannot recur silently: createSchedulesService now validates its required deps at CONSTRUCTION and throws a clear boot-time error, instead of surfacing a missing dep as a cryptic failure deep inside a live fire. --- api/server/services/Schedules/index.js | 3 +++ packages/api/src/schedules/service.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/api/server/services/Schedules/index.js b/api/server/services/Schedules/index.js index f327445991..69f92a3444 100644 --- a/api/server/services/Schedules/index.js +++ b/api/server/services/Schedules/index.js @@ -17,6 +17,9 @@ const service = createSchedulesService({ { upsert: true, new: true }, ).lean(), resolveAgentFireAccess, + // Durable account-deletion barrier consulted at the fire dispatch boundary. Without + // this, engineDeps.isOwnerDeleting would throw on every scheduled fire. + isUserDeleting: methods.isUserDeleting, }); module.exports = { diff --git a/packages/api/src/schedules/service.ts b/packages/api/src/schedules/service.ts index 0be5ec5efc..24e9120f8f 100644 --- a/packages/api/src/schedules/service.ts +++ b/packages/api/src/schedules/service.ts @@ -176,6 +176,25 @@ export interface SchedulesService { export function createSchedulesService(deps: SchedulesServiceDeps): SchedulesService { const { methods } = deps; + // Fail LOUDLY at construction, not per-fire. The JS adapter (api/server/services/ + // Schedules) is not typechecked against SchedulesServiceDeps, so a missing dep would + // otherwise surface only as a `deps.X is not a function` deep inside a live fire — + // which is exactly how the deletion-barrier probe shipped unwired twice. + const REQUIRED_DEPS: Array = [ + 'methods', + 'getAppConfig', + 'findUserById', + 'findBalance', + 'upsertBalance', + 'resolveAgentFireAccess', + 'isUserDeleting', + ]; + for (const key of REQUIRED_DEPS) { + if (deps[key] == null) { + throw new Error(`createSchedulesService: missing required dependency "${key}"`); + } + } + /** * Resolves schedule limits, honoring per-principal (role/user) config overrides * when a user is supplied (routes pass req.user, the fire path passes the owner).