@open-mercato/scheduler 0.7.0 → 0.7.1-develop.7103.1.41ff100d93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/scheduler/__integration__/TC-SCHED-001.spec.js +2 -2
  3. package/dist/modules/scheduler/__integration__/TC-SCHED-001.spec.js.map +2 -2
  4. package/dist/modules/scheduler/__integration__/TC-SCHED-004.spec.js +9 -4
  5. package/dist/modules/scheduler/__integration__/TC-SCHED-004.spec.js.map +2 -2
  6. package/dist/modules/scheduler/__integration__/TC-SCHED-007.spec.js +3 -3
  7. package/dist/modules/scheduler/__integration__/TC-SCHED-007.spec.js.map +2 -2
  8. package/dist/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.js +72 -0
  9. package/dist/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.js.map +7 -0
  10. package/dist/modules/scheduler/__integration__/TC-SCHED-008.spec.js +66 -0
  11. package/dist/modules/scheduler/__integration__/TC-SCHED-008.spec.js.map +7 -0
  12. package/dist/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.js +3 -3
  13. package/dist/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.js.map +1 -1
  14. package/dist/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.js +107 -44
  15. package/dist/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.js.map +2 -2
  16. package/dist/modules/scheduler/__integration__/helpers/scheduler.js +13 -3
  17. package/dist/modules/scheduler/__integration__/helpers/scheduler.js.map +2 -2
  18. package/dist/modules/scheduler/api/jobs/[id]/executions/route.js +7 -13
  19. package/dist/modules/scheduler/api/jobs/[id]/executions/route.js.map +2 -2
  20. package/dist/modules/scheduler/api/targets/route.js +2 -12
  21. package/dist/modules/scheduler/api/targets/route.js.map +2 -2
  22. package/dist/modules/scheduler/api/trigger/route.js +7 -13
  23. package/dist/modules/scheduler/api/trigger/route.js.map +2 -2
  24. package/dist/modules/scheduler/backend/config/scheduled-jobs/page.js +3 -1
  25. package/dist/modules/scheduler/backend/config/scheduled-jobs/page.js.map +2 -2
  26. package/dist/modules/scheduler/cli.js +32 -1
  27. package/dist/modules/scheduler/cli.js.map +2 -2
  28. package/dist/modules/scheduler/commands/jobs.js +84 -2
  29. package/dist/modules/scheduler/commands/jobs.js.map +2 -2
  30. package/dist/modules/scheduler/data/validators.js +27 -3
  31. package/dist/modules/scheduler/data/validators.js.map +2 -2
  32. package/dist/modules/scheduler/i18n/de.json +2 -0
  33. package/dist/modules/scheduler/i18n/en.json +2 -0
  34. package/dist/modules/scheduler/i18n/es.json +2 -0
  35. package/dist/modules/scheduler/i18n/ko.json +2 -0
  36. package/dist/modules/scheduler/i18n/pl.json +2 -0
  37. package/dist/modules/scheduler/lib/safeQueueTargets.js +157 -0
  38. package/dist/modules/scheduler/lib/safeQueueTargets.js.map +7 -0
  39. package/dist/modules/scheduler/lib/scheduleAccess.js +22 -0
  40. package/dist/modules/scheduler/lib/scheduleAccess.js.map +7 -0
  41. package/dist/modules/scheduler/services/localSchedulerService.js +31 -6
  42. package/dist/modules/scheduler/services/localSchedulerService.js.map +2 -2
  43. package/dist/modules/scheduler/workers/execute-schedule.worker.js +64 -6
  44. package/dist/modules/scheduler/workers/execute-schedule.worker.js.map +2 -2
  45. package/dist/modules/scheduler/workers/test-echo-queue.worker.js +22 -0
  46. package/dist/modules/scheduler/workers/test-echo-queue.worker.js.map +7 -0
  47. package/package.json +7 -6
  48. package/src/modules/scheduler/__integration__/TC-SCHED-001.spec.ts +2 -2
  49. package/src/modules/scheduler/__integration__/TC-SCHED-004.spec.ts +14 -8
  50. package/src/modules/scheduler/__integration__/TC-SCHED-007.spec.ts +3 -3
  51. package/src/modules/scheduler/__integration__/TC-SCHED-008-safe-targets.spec.ts +90 -0
  52. package/src/modules/scheduler/__integration__/TC-SCHED-008.spec.ts +94 -0
  53. package/src/modules/scheduler/__integration__/TC-SCHED-CRUDFORM-001.spec.ts +3 -3
  54. package/src/modules/scheduler/__integration__/TC-UNDO-001-scheduler-jobs.spec.ts +154 -66
  55. package/src/modules/scheduler/__integration__/helpers/scheduler.ts +24 -5
  56. package/src/modules/scheduler/api/jobs/[id]/executions/route.ts +9 -19
  57. package/src/modules/scheduler/api/targets/__tests__/route.test.ts +3 -4
  58. package/src/modules/scheduler/api/targets/route.ts +6 -17
  59. package/src/modules/scheduler/api/trigger/route.ts +9 -19
  60. package/src/modules/scheduler/backend/config/scheduled-jobs/page.tsx +4 -1
  61. package/src/modules/scheduler/cli.ts +44 -1
  62. package/src/modules/scheduler/commands/__tests__/jobs.target-guard.test.ts +177 -0
  63. package/src/modules/scheduler/commands/jobs.ts +137 -3
  64. package/src/modules/scheduler/data/__tests__/safe-targets.test.ts +110 -0
  65. package/src/modules/scheduler/data/__tests__/validators.test.ts +26 -4
  66. package/src/modules/scheduler/data/validators.ts +37 -2
  67. package/src/modules/scheduler/i18n/de.json +2 -0
  68. package/src/modules/scheduler/i18n/en.json +2 -0
  69. package/src/modules/scheduler/i18n/es.json +2 -0
  70. package/src/modules/scheduler/i18n/ko.json +2 -0
  71. package/src/modules/scheduler/i18n/pl.json +2 -0
  72. package/src/modules/scheduler/lib/__tests__/safeQueueTargets.test.ts +311 -0
  73. package/src/modules/scheduler/lib/__tests__/scheduleAccess.test.ts +164 -0
  74. package/src/modules/scheduler/lib/safeQueueTargets.ts +324 -0
  75. package/src/modules/scheduler/lib/scheduleAccess.ts +104 -0
  76. package/src/modules/scheduler/services/__tests__/localSchedulerService.test.ts +179 -0
  77. package/src/modules/scheduler/services/localSchedulerService.ts +39 -6
  78. package/src/modules/scheduler/workers/__tests__/execute-schedule.worker.test.ts +25 -3
  79. package/src/modules/scheduler/workers/execute-schedule.worker.ts +76 -7
  80. package/src/modules/scheduler/workers/test-echo-queue.worker.ts +25 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/scheduler/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from './data/entities.js'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nconst writeLine = (text = '') => {\n process.stdout.write(`${text}\\n`)\n}\n\nconst writeErrorLine = (text: string) => {\n process.stderr.write(`${text}\\n`)\n}\n\nfunction parseArgs(rest: string[]): Record<string, string> {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nconst listCommand: ModuleCli = {\n command: 'list',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null }\n\n // Filter by tenant if provided\n if (args.tenant || args.tenantId) {\n where.tenantId = args.tenant || args.tenantId\n }\n\n // Filter by scope type if provided\n if (args.scope || args.scopeType) {\n where.scopeType = args.scope || args.scopeType\n }\n\n // Filter by enabled status if provided\n if (args.enabled) {\n const parsed = parseBooleanToken(args.enabled)\n if (parsed !== null) {\n where.isEnabled = parsed\n }\n }\n\n const jobs = await em.find(ScheduledJob, where, {\n orderBy: { isEnabled: 'DESC', name: 'ASC' },\n })\n\n if (jobs.length === 0) {\n writeLine('No scheduled jobs found.')\n return\n }\n\n writeLine(`\\nFound ${jobs.length} scheduled job(s):\\n`)\n writeLine('ID'.padEnd(38) + 'Name'.padEnd(35) + 'Type'.padEnd(12) + 'Schedule'.padEnd(20) + 'Status'.padEnd(10) + 'Next Run')\n writeLine('-'.repeat(140))\n\n for (const job of jobs) {\n const id = String(job.id).padEnd(38)\n const name = (job.name || '').substring(0, 33).padEnd(35)\n const type = job.scheduleType.padEnd(12)\n const schedule = (job.scheduleValue || '').substring(0, 18).padEnd(20)\n const status = (job.isEnabled ? '\u2713 Enabled' : '\u2717 Disabled').padEnd(10)\n const nextRun = job.nextRunAt ? new Date(job.nextRunAt).toISOString() : 'N/A'\n writeLine(`${id}${name}${type}${schedule}${status}${nextRun}`)\n }\n\n writeLine('')\n },\n}\n\nconst statusCommand: ModuleCli = {\n command: 'status',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const totalCount = await em.count(ScheduledJob, { deletedAt: null })\n const enabledCount = await em.count(ScheduledJob, { isEnabled: true, deletedAt: null })\n const dueCount = await em.count(ScheduledJob, {\n isEnabled: true,\n deletedAt: null,\n nextRunAt: { $lte: new Date() },\n })\n\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine('\\n\uD83D\uDCCA Scheduler Status\\n')\n writeLine(`Strategy: ${queueStrategy === 'async' ? 'BullMQ (async)' : 'Local (polling)'}`)\n \n if (queueStrategy === 'local') {\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n writeLine(`Poll Interval: ${Math.round(pollInterval / 1000)}s`)\n }\n \n writeLine('')\n writeLine('Schedules:')\n writeLine(` Total: ${totalCount}`)\n writeLine(` Enabled: ${enabledCount}`)\n writeLine(` Due Now: ${dueCount}`)\n writeLine('')\n },\n}\n\nconst runCommand: ModuleCli = {\n command: 'run',\n async run(rest) {\n const scheduleId = rest[0]\n if (!scheduleId) {\n writeErrorLine('Usage: mercato scheduler run <schedule-id>')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const queueService = resolve('queueService') as { getQueue(name: string): { add(name: string, data: unknown): Promise<unknown> } }\n\n const job = await em.findOne(ScheduledJob, { id: scheduleId, deletedAt: null })\n if (!job) {\n writeErrorLine(`Schedule not found: ${scheduleId}`)\n return\n }\n\n writeLine(`\\n\uD83D\uDE80 Manually triggering schedule: ${job.name}\\n`)\n writeLine(` ID: ${job.id}`)\n writeLine(` Type: ${job.scheduleType}`)\n writeLine(` Schedule: ${job.scheduleValue}`)\n writeLine(` Target: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('')\n\n try {\n // Manually enqueue the job (triggering the scheduler-execution worker)\n const schedulerQueue = queueService.getQueue('scheduler-execution')\n await schedulerQueue.add('execute-schedule', { scheduleId: job.id })\n\n writeLine('\u2713 Job successfully triggered via scheduler-execution queue')\n writeLine(` The worker will pick it up and enqueue to: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('\u2713 Manual trigger completed\\n')\n } catch (error: unknown) {\n writeErrorLine(`\u2717 Failed to trigger job: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n },\n}\n\nconst startCommand: ModuleCli = {\n command: 'start',\n async run() {\n const { resolve } = await createRequestContainer()\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine(`\uD83D\uDE80 Starting scheduler (strategy: ${queueStrategy})...\\n`)\n\n if (queueStrategy === 'async') {\n // BullMQ strategy: Sync schedules with BullMQ repeatable jobs\n try {\n const bullmqService = resolve('bullmqSchedulerService') as { syncAll(): Promise<void> } | undefined\n \n if (!bullmqService) {\n writeErrorLine('\u274C BullMQSchedulerService not available.')\n writeErrorLine(' Set QUEUE_STRATEGY=async and configure REDIS_URL.')\n process.exit(1)\n }\n\n // Sync all enabled schedules with BullMQ\n await bullmqService.syncAll()\n\n writeLine('\u2713 Scheduler sync completed')\n writeLine('')\n writeLine('BullMQ is managing all schedules.')\n writeLine('Start workers to process jobs:')\n writeLine(' yarn mercato worker:start')\n writeLine('')\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to sync schedules: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n } else {\n // Local strategy: Start polling engine\n try {\n const localService = resolve('localSchedulerService') as { start(): Promise<void>; stop(): Promise<void> } | undefined\n \n if (!localService) {\n writeErrorLine('\u274C LocalSchedulerService not available.')\n writeErrorLine(' This should not happen in local mode.')\n process.exit(1)\n }\n\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n\n // Start the local polling engine\n await localService.start()\n\n writeLine(`\u2713 Local scheduler started (polling every ${Math.round(pollInterval / 1000)} seconds)`)\n writeLine('Press Ctrl+C to stop.')\n writeLine('')\n writeLine('\uD83D\uDCA1 Tip: For production, use QUEUE_STRATEGY=async with Redis.')\n writeLine('')\n\n // Keep the process alive and handle graceful shutdown\n const gracefulShutdown = async () => {\n writeLine('\\n\uD83D\uDCDB Shutting down...')\n await localService.stop()\n writeLine('\u2713 Stopped')\n process.exit(0)\n }\n\n process.on('SIGINT', gracefulShutdown)\n process.on('SIGTERM', gracefulShutdown)\n\n // Keep process alive\n await new Promise(() => {}) // Never resolves\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to start local scheduler: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n }\n },\n}\n\nexport default [listCommand, statusCommand, runCommand, startCommand]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from './data/entities.js'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { auditSchedulerModuleQueueRows } from './lib/safeQueueTargets'\n\nconst writeLine = (text = '') => {\n process.stdout.write(`${text}\\n`)\n}\n\nconst writeErrorLine = (text: string) => {\n process.stderr.write(`${text}\\n`)\n}\n\nfunction parseArgs(rest: string[]): Record<string, string> {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 2) {\n const key = rest[i]?.replace(/^--/, '')\n const value = rest[i + 1]\n if (key) args[key] = value ?? ''\n }\n return args\n}\n\nconst listCommand: ModuleCli = {\n command: 'list',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null }\n\n // Filter by tenant if provided\n if (args.tenant || args.tenantId) {\n where.tenantId = args.tenant || args.tenantId\n }\n\n // Filter by scope type if provided\n if (args.scope || args.scopeType) {\n where.scopeType = args.scope || args.scopeType\n }\n\n // Filter by enabled status if provided\n if (args.enabled) {\n const parsed = parseBooleanToken(args.enabled)\n if (parsed !== null) {\n where.isEnabled = parsed\n }\n }\n\n const jobs = await em.find(ScheduledJob, where, {\n orderBy: { isEnabled: 'DESC', name: 'ASC' },\n })\n\n if (jobs.length === 0) {\n writeLine('No scheduled jobs found.')\n return\n }\n\n writeLine(`\\nFound ${jobs.length} scheduled job(s):\\n`)\n writeLine('ID'.padEnd(38) + 'Name'.padEnd(35) + 'Type'.padEnd(12) + 'Schedule'.padEnd(20) + 'Status'.padEnd(10) + 'Next Run')\n writeLine('-'.repeat(140))\n\n for (const job of jobs) {\n const id = String(job.id).padEnd(38)\n const name = (job.name || '').substring(0, 33).padEnd(35)\n const type = job.scheduleType.padEnd(12)\n const schedule = (job.scheduleValue || '').substring(0, 18).padEnd(20)\n const status = (job.isEnabled ? '\u2713 Enabled' : '\u2717 Disabled').padEnd(10)\n const nextRun = job.nextRunAt ? new Date(job.nextRunAt).toISOString() : 'N/A'\n writeLine(`${id}${name}${type}${schedule}${status}${nextRun}`)\n }\n\n writeLine('')\n },\n}\n\nconst statusCommand: ModuleCli = {\n command: 'status',\n async run() {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const totalCount = await em.count(ScheduledJob, { deletedAt: null })\n const enabledCount = await em.count(ScheduledJob, { isEnabled: true, deletedAt: null })\n const dueCount = await em.count(ScheduledJob, {\n isEnabled: true,\n deletedAt: null,\n nextRunAt: { $lte: new Date() },\n })\n\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine('\\n\uD83D\uDCCA Scheduler Status\\n')\n writeLine(`Strategy: ${queueStrategy === 'async' ? 'BullMQ (async)' : 'Local (polling)'}`)\n \n if (queueStrategy === 'local') {\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n writeLine(`Poll Interval: ${Math.round(pollInterval / 1000)}s`)\n }\n \n writeLine('')\n writeLine('Schedules:')\n writeLine(` Total: ${totalCount}`)\n writeLine(` Enabled: ${enabledCount}`)\n writeLine(` Due Now: ${dueCount}`)\n writeLine('')\n },\n}\n\nconst runCommand: ModuleCli = {\n command: 'run',\n async run(rest) {\n const scheduleId = rest[0]\n if (!scheduleId) {\n writeErrorLine('Usage: mercato scheduler run <schedule-id>')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const queueService = resolve('queueService') as { getQueue(name: string): { add(name: string, data: unknown): Promise<unknown> } }\n\n const job = await em.findOne(ScheduledJob, { id: scheduleId, deletedAt: null })\n if (!job) {\n writeErrorLine(`Schedule not found: ${scheduleId}`)\n return\n }\n\n writeLine(`\\n\uD83D\uDE80 Manually triggering schedule: ${job.name}\\n`)\n writeLine(` ID: ${job.id}`)\n writeLine(` Type: ${job.scheduleType}`)\n writeLine(` Schedule: ${job.scheduleValue}`)\n writeLine(` Target: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('')\n\n try {\n // Manually enqueue the job (triggering the scheduler-execution worker)\n const schedulerQueue = queueService.getQueue('scheduler-execution')\n await schedulerQueue.add('execute-schedule', { scheduleId: job.id })\n\n writeLine('\u2713 Job successfully triggered via scheduler-execution queue')\n writeLine(` The worker will pick it up and enqueue to: ${job.targetType === 'queue' ? job.targetQueue : job.targetCommand}`)\n writeLine('\u2713 Manual trigger completed\\n')\n } catch (error: unknown) {\n writeErrorLine(`\u2717 Failed to trigger job: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n },\n}\n\nconst startCommand: ModuleCli = {\n command: 'start',\n async run() {\n const { resolve } = await createRequestContainer()\n const queueStrategy = process.env.QUEUE_STRATEGY || 'local'\n\n writeLine(`\uD83D\uDE80 Starting scheduler (strategy: ${queueStrategy})...\\n`)\n\n if (queueStrategy === 'async') {\n // BullMQ strategy: Sync schedules with BullMQ repeatable jobs\n try {\n const bullmqService = resolve('bullmqSchedulerService') as { syncAll(): Promise<void> } | undefined\n \n if (!bullmqService) {\n writeErrorLine('\u274C BullMQSchedulerService not available.')\n writeErrorLine(' Set QUEUE_STRATEGY=async and configure REDIS_URL.')\n process.exit(1)\n }\n\n // Sync all enabled schedules with BullMQ\n await bullmqService.syncAll()\n\n writeLine('\u2713 Scheduler sync completed')\n writeLine('')\n writeLine('BullMQ is managing all schedules.')\n writeLine('Start workers to process jobs:')\n writeLine(' yarn mercato worker:start')\n writeLine('')\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to sync schedules: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n } else {\n // Local strategy: Start polling engine\n try {\n const localService = resolve('localSchedulerService') as { start(): Promise<void>; stop(): Promise<void> } | undefined\n \n if (!localService) {\n writeErrorLine('\u274C LocalSchedulerService not available.')\n writeErrorLine(' This should not happen in local mode.')\n process.exit(1)\n }\n\n const pollInterval = parseInt(process.env.SCHEDULER_POLL_INTERVAL_MS || '30000', 10)\n\n // Start the local polling engine\n await localService.start()\n\n writeLine(`\u2713 Local scheduler started (polling every ${Math.round(pollInterval / 1000)} seconds)`)\n writeLine('Press Ctrl+C to stop.')\n writeLine('')\n writeLine('\uD83D\uDCA1 Tip: For production, use QUEUE_STRATEGY=async with Redis.')\n writeLine('')\n\n // Keep the process alive and handle graceful shutdown\n const gracefulShutdown = async () => {\n writeLine('\\n\uD83D\uDCDB Shutting down...')\n await localService.stop()\n writeLine('\u2713 Stopped')\n process.exit(0)\n }\n\n process.on('SIGINT', gracefulShutdown)\n process.on('SIGTERM', gracefulShutdown)\n\n // Keep process alive\n await new Promise(() => {}) // Never resolves\n } catch (error: unknown) {\n writeErrorLine(`\u274C Failed to start local scheduler: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n }\n }\n },\n}\n\n// Lists every persisted module-authored queue-target row with its current\n// dispatch verdict (#5213). Rows authored before the hardening by a\n// non-user-bound API key are indistinguishable from genuine registrations at\n// rest, so operators review the ALLOWED entries against their known integrations.\nconst auditQueueTargetsCommand: ModuleCli = {\n command: 'audit-queue-targets',\n async run(rest) {\n const args = parseArgs(rest)\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n\n const where: Record<string, unknown> = { deletedAt: null, sourceType: 'module' }\n if (args.tenant || args.tenantId) where.tenantId = args.tenant || args.tenantId\n\n const rows = await em.find(ScheduledJob, where, {\n orderBy: { name: 'ASC' },\n })\n\n const audits = auditSchedulerModuleQueueRows(rows)\n\n writeLine(`Module-authored queue-target schedules: ${audits.length}`)\n let allowedCount = 0\n for (const audit of audits) {\n const verdict = audit.dispatchAllowed ? 'ALLOWED' : 'blocked'\n if (audit.dispatchAllowed) allowedCount += 1\n writeLine(\n `[${verdict}] ${audit.scheduleId}${audit.name ? ` \u2014 ${audit.name}` : ''}` +\n ` module=${audit.sourceModule ?? '(none)'} queue=${audit.targetQueue ?? '(none)'}` +\n ` enabled=${audit.isEnabled ? 'yes' : 'no'}`,\n )\n }\n\n if (allowedCount > 0) {\n writeLine('')\n writeLine(`Review the ${allowedCount} ALLOWED row(s) above against your known module integrations.`)\n writeLine('Pre-upgrade rows created with a non-user-bound API key cannot be distinguished')\n writeLine('from genuine registrations at rest; disable anything unrecognized:')\n writeLine(' yarn mercato scheduler list --tenant <tenantId>')\n }\n },\n}\n\nexport default [listCommand, statusCommand, runCommand, startCommand, auditQueueTargetsCommand]\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,11 @@ import { ScheduledJob } from "../data/entities.js";
7
7
  import { calculateNextRunForWrite } from "../lib/nextRunCalculator.js";
8
8
  import { enforceTenantActiveScheduleLimit } from "../lib/activeScheduleLimits.js";
9
9
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
10
+ import {
11
+ assertSchedulerQueueTargetAuthorized,
12
+ isSchedulerSafeQueue,
13
+ validateSchedulerTargetPayload
14
+ } from "../lib/safeQueueTargets.js";
10
15
  async function loadScheduleSnapshot(em, scheduleId) {
11
16
  const schedule = await em.findOne(ScheduledJob, { id: scheduleId });
12
17
  if (!schedule) return null;
@@ -36,6 +41,48 @@ function toDate(value) {
36
41
  if (!value) return null;
37
42
  return value instanceof Date ? value : new Date(value);
38
43
  }
44
+ function canonicalJson(value) {
45
+ if (value === null || typeof value !== "object") return JSON.stringify(value ?? null);
46
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
47
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
48
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
49
+ }
50
+ function normalizeTargetPayloadValue(value) {
51
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) return null;
52
+ return value;
53
+ }
54
+ function targetPayloadChanged(inputValue, storedValue) {
55
+ return targetValueChanged(
56
+ normalizeTargetPayloadValue(inputValue),
57
+ normalizeTargetPayloadValue(storedValue)
58
+ );
59
+ }
60
+ function targetValueChanged(inputValue, storedValue) {
61
+ if (inputValue === void 0) return false;
62
+ return canonicalJson(inputValue ?? null) !== canonicalJson(storedValue ?? null);
63
+ }
64
+ async function assertQueueTargetAuthorizedForActor(ctx, queue) {
65
+ const rbacService = (() => {
66
+ try {
67
+ return ctx.container?.resolve("rbacService") ?? null;
68
+ } catch {
69
+ return null;
70
+ }
71
+ })();
72
+ const reason = await assertSchedulerQueueTargetAuthorized({
73
+ queue,
74
+ actorUserId: resolveCommandActorUserId(ctx),
75
+ tenantId: ctx.auth?.tenantId ?? null,
76
+ organizationId: ctx.auth?.orgId ?? null,
77
+ isSuperAdmin: ctx.auth?.isSuperAdmin === true,
78
+ rbacService
79
+ });
80
+ if (reason) throw new CrudHttpError(403, { error: reason });
81
+ }
82
+ async function assertQueueTargetPayloadAllowed(queue, targetPayload) {
83
+ const reason = validateSchedulerTargetPayload(queue, targetPayload);
84
+ if (reason) throw new CrudHttpError(422, { error: `Invalid target payload for queue ${queue}: ${reason}` });
85
+ }
39
86
  function resolveCommandActorUserId(ctx) {
40
87
  const auth = ctx.auth;
41
88
  if (!auth) return null;
@@ -120,6 +167,10 @@ const createScheduleCommand = {
120
167
  if (!nextRunAt) {
121
168
  throw new Error("Failed to calculate next run time");
122
169
  }
170
+ if (input.targetType === "queue" && input.targetQueue) {
171
+ await assertQueueTargetAuthorizedForActor(ctx, input.targetQueue);
172
+ await assertQueueTargetPayloadAllowed(input.targetQueue, input.targetPayload);
173
+ }
123
174
  const schedule = em.create(ScheduledJob, {
124
175
  name: input.name,
125
176
  description: input.description ?? null,
@@ -135,8 +186,8 @@ const createScheduleCommand = {
135
186
  targetPayload: input.targetPayload ?? null,
136
187
  requireFeature: input.requireFeature ?? null,
137
188
  isEnabled: input.isEnabled ?? true,
138
- sourceType: input.sourceType ?? "user",
139
- sourceModule: input.sourceModule ?? null,
189
+ sourceType: "user",
190
+ sourceModule: null,
140
191
  nextRunAt,
141
192
  createdByUserId: resolveCommandActorUserId(ctx),
142
193
  createdAt: /* @__PURE__ */ new Date(),
@@ -199,9 +250,40 @@ const updateScheduleCommand = {
199
250
  ensureTenantScope(ctx, schedule.tenantId);
200
251
  if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId);
201
252
  ensureCanManageSystemScopedJob(ctx, schedule);
253
+ const touchesModuleOwnedTarget = schedule.sourceType === "module" && (targetValueChanged(input.targetType, schedule.targetType) || targetValueChanged(input.targetQueue, schedule.targetQueue) || targetValueChanged(input.targetCommand, schedule.targetCommand) || targetPayloadChanged(input.targetPayload, schedule.targetPayload));
254
+ if (touchesModuleOwnedTarget) {
255
+ const { translate } = await resolveTranslations();
256
+ throw new CrudHttpError(403, {
257
+ error: translate(
258
+ "scheduler.errors.module_target_immutable",
259
+ "Module-authored schedules own their target; only operational fields can be updated here."
260
+ )
261
+ });
262
+ }
202
263
  if (input.isEnabled === true && schedule.isEnabled !== true) {
203
264
  await enforceTenantActiveScheduleLimit(em, schedule.tenantId);
204
265
  }
266
+ const effectiveTargetType = input.targetType ?? schedule.targetType;
267
+ const effectiveQueue = input.targetQueue ?? schedule.targetQueue;
268
+ const queueChanged = effectiveTargetType === "queue" && (targetValueChanged(input.targetQueue, schedule.targetQueue) || targetValueChanged(input.targetType, schedule.targetType) || targetPayloadChanged(input.targetPayload, schedule.targetPayload));
269
+ if (effectiveTargetType === "queue" && effectiveQueue) {
270
+ const retargetedToUnapprovedQueue = input.targetQueue !== void 0 && input.targetQueue !== schedule.targetQueue && !isSchedulerSafeQueue(input.targetQueue);
271
+ const keepsUnapprovedQueueActive = effectiveQueue != null && !isSchedulerSafeQueue(effectiveQueue) && input.isEnabled !== false && schedule.isEnabled !== false;
272
+ if ((retargetedToUnapprovedQueue || keepsUnapprovedQueueActive) && schedule.sourceType !== "module") {
273
+ const { translate } = await resolveTranslations();
274
+ throw new CrudHttpError(422, {
275
+ error: translate(
276
+ "scheduler.errors.queue_not_approved",
277
+ "Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected."
278
+ )
279
+ });
280
+ }
281
+ if (queueChanged) {
282
+ await assertQueueTargetAuthorizedForActor(ctx, effectiveQueue);
283
+ const effectivePayload = input.targetPayload !== void 0 ? input.targetPayload : schedule.targetPayload;
284
+ await assertQueueTargetPayloadAllowed(effectiveQueue, effectivePayload);
285
+ }
286
+ }
205
287
  const scheduleChanged = input.scheduleType !== void 0 || input.scheduleValue !== void 0 || input.timezone !== void 0;
206
288
  const nextRunAt = scheduleChanged ? calculateNextRunForWrite(
207
289
  input.scheduleType ?? schedule.scheduleType,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/scheduler/commands/jobs.ts"],
4
- "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from '../data/entities.js'\nimport { calculateNextRunForWrite } from '../lib/nextRunCalculator.js'\nimport { enforceTenantActiveScheduleLimit } from '../lib/activeScheduleLimits.js'\nimport type {\n ScheduleCreateInput,\n ScheduleUpdateInput,\n} from '../data/validators.js'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { BullMQSchedulerService } from '../services/bullmqSchedulerService.js'\n\n/**\n * Snapshot of a schedule for undo/redo\n */\ntype ScheduleSnapshot = {\n id: string\n name: string\n description: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n organizationId: string | null\n tenantId: string | null\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue: string | null\n targetCommand: string | null\n targetPayload: Record<string, unknown> | null\n requireFeature: string | null\n isEnabled: boolean\n sourceType: 'user' | 'module'\n sourceModule: string | null\n nextRunAt: Date | null\n lastRunAt: Date | null\n}\n\n/**\n * Load a schedule snapshot\n */\nasync function loadScheduleSnapshot(\n em: EntityManager,\n scheduleId: string\n): Promise<ScheduleSnapshot | null> {\n const schedule = await em.findOne(ScheduledJob, { id: scheduleId })\n if (!schedule) return null\n\n return {\n id: schedule.id,\n name: schedule.name,\n description: schedule.description ?? null,\n scopeType: schedule.scopeType,\n organizationId: schedule.organizationId ?? null,\n tenantId: schedule.tenantId ?? null,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue ?? null,\n targetCommand: schedule.targetCommand ?? null,\n targetPayload: schedule.targetPayload ?? null,\n requireFeature: schedule.requireFeature ?? null,\n isEnabled: schedule.isEnabled,\n sourceType: schedule.sourceType,\n sourceModule: schedule.sourceModule ?? null,\n nextRunAt: schedule.nextRunAt ?? null,\n lastRunAt: schedule.lastRunAt ?? null,\n }\n}\n\n/**\n * Snapshots are persisted as JSON (in the action log's command payload), so Date\n * fields come back as ISO strings on undo. Coerce them to Date before writing\n * them onto the entity, otherwise MikroORM throws while flushing a Date column.\n */\nfunction toDate(value: Date | string | null | undefined): Date | null {\n if (!value) return null\n return value instanceof Date ? value : new Date(value)\n}\n\nfunction resolveCommandActorUserId(ctx: CommandRuntimeContext): string | null {\n const auth = ctx.auth\n if (!auth) return null\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId.trim()\n if (auth.isApiKey) return null\n return typeof auth.sub === 'string' && auth.sub.trim().length > 0 ? auth.sub.trim() : null\n}\n\n/**\n * Build a full create seed (including the original id and Date-coerced timestamps)\n * from a snapshot. Shared by `materializeScheduleFromSnapshot` (delete-undo) and the\n * create command's id-preserving `redo`.\n */\nfunction scheduleSeedFromSnapshot(snapshot: ScheduleSnapshot): Record<string, unknown> {\n const now = new Date()\n return {\n id: snapshot.id,\n name: snapshot.name,\n description: snapshot.description,\n scopeType: snapshot.scopeType,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n scheduleType: snapshot.scheduleType,\n scheduleValue: snapshot.scheduleValue,\n timezone: snapshot.timezone,\n targetType: snapshot.targetType,\n targetQueue: snapshot.targetQueue,\n targetCommand: snapshot.targetCommand,\n targetPayload: snapshot.targetPayload,\n requireFeature: snapshot.requireFeature,\n isEnabled: snapshot.isEnabled,\n sourceType: snapshot.sourceType,\n sourceModule: snapshot.sourceModule,\n nextRunAt: toDate(snapshot.nextRunAt),\n lastRunAt: toDate(snapshot.lastRunAt),\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n }\n}\n\n/**\n * Re-create a ScheduledJob entity from a snapshot, preserving its original id.\n * Used by delete-undo when the row was hard-removed rather than soft-deleted.\n */\nfunction materializeScheduleFromSnapshot(em: EntityManager, snapshot: ScheduleSnapshot): ScheduledJob {\n return em.create(ScheduledJob, scheduleSeedFromSnapshot(snapshot) as never)\n}\n\n/**\n * Trigger BullMQ sync after undo operations.\n * Best-effort: if BullMQ service is unavailable (e.g. local strategy), this is a no-op.\n */\nasync function syncBullMQAfterUndo(ctx: CommandRuntimeContext, schedule: ScheduledJob | null): Promise<void> {\n try {\n if (!ctx.container?.resolve) return\n const bullmqService = ctx.container.resolve<BullMQSchedulerService>('bullmqSchedulerService')\n if (!bullmqService) return\n\n if (schedule && schedule.isEnabled && !schedule.deletedAt) {\n await bullmqService.register(schedule, { skipNextRunUpdate: true })\n } else if (schedule) {\n await bullmqService.unregister(schedule.id)\n }\n } catch {\n // Best-effort: BullMQ service may not be registered (local strategy)\n }\n}\n\n/**\n * Ensure tenant/org scope for security\n */\nfunction ensureTenantScope(ctx: CommandRuntimeContext, tenantId: string | null | undefined) {\n if (tenantId && ctx.auth?.tenantId && ctx.auth.tenantId !== tenantId) {\n throw new Error('Tenant mismatch')\n }\n}\n\n// Super-admin status is the immutable `isSuperAdmin` flag derived from\n// RoleAcl/UserAcl at session resolution. Never compare role names to a string\n// like 'superadmin' \u2014 role names are tenant-mutable and trivially spoofable.\nfunction isSuperAdminActor(ctx: CommandRuntimeContext): boolean {\n return ctx.auth?.isSuperAdmin === true\n}\n\n// `ensureTenantScope` is a no-op for system-scoped jobs (tenantId === null), so a\n// `scheduler.jobs.manage` holder could otherwise update/delete a system schedule\n// belonging to the whole deployment. System-scoped jobs MUST be super-admin only,\n// mirroring the create route's system-scope gate.\nfunction ensureCanManageSystemScopedJob(\n ctx: CommandRuntimeContext,\n job: { scopeType?: string | null; tenantId?: string | null },\n): void {\n const isSystemScoped = job.scopeType === 'system' || job.tenantId == null\n if (!isSystemScoped) return\n if (isSuperAdminActor(ctx)) return\n throw new CrudHttpError(403, {\n error: 'System-scoped scheduled jobs can only be managed by a super administrator.',\n })\n}\n\n/**\n * CREATE SCHEDULE COMMAND\n */\nconst createScheduleCommand: CommandHandler<ScheduleCreateInput, { id: string }> = {\n id: 'scheduler.jobs.create',\n\n async execute(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n if (input.organizationId) ensureOrganizationScope(ctx, input.organizationId)\n ensureCanManageSystemScopedJob(ctx, { scopeType: input.scopeType, tenantId: input.tenantId })\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n if (input.isEnabled ?? true) {\n await enforceTenantActiveScheduleLimit(em, input.tenantId)\n }\n\n // Calculate next run time\n const nextRunAt = calculateNextRunForWrite(\n input.scheduleType,\n input.scheduleValue,\n input.timezone || 'UTC'\n )\n\n if (!nextRunAt) {\n throw new Error('Failed to calculate next run time')\n }\n\n // Create schedule\n const schedule = em.create(ScheduledJob, {\n name: input.name,\n description: input.description ?? null,\n scopeType: input.scopeType,\n organizationId: input.organizationId ?? null,\n tenantId: input.tenantId ?? null,\n scheduleType: input.scheduleType,\n scheduleValue: input.scheduleValue,\n timezone: input.timezone ?? 'UTC',\n targetType: input.targetType,\n targetQueue: input.targetQueue ?? null,\n targetCommand: input.targetCommand ?? null,\n targetPayload: input.targetPayload ?? null,\n requireFeature: input.requireFeature ?? null,\n isEnabled: input.isEnabled ?? true,\n sourceType: input.sourceType ?? 'user',\n sourceModule: input.sourceModule ?? null,\n nextRunAt,\n createdByUserId: resolveCommandActorUserId(ctx),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n em.persist(schedule)\n await em.flush()\n\n return { id: schedule.id }\n },\n\n async captureAfter(_input, result, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n return await loadScheduleSnapshot(em, result.id)\n },\n\n async buildLog({ result, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.create', 'Create schedule'),\n resourceKind: 'scheduler.job',\n resourceId: result.id,\n tenantId: after?.tenantId || null,\n organizationId: after?.organizationId || null,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const after = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.after\n if (!after) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const schedule = await em.findOne(ScheduledJob, { id: after.id })\n\n if (schedule) {\n await em.remove(schedule).flush()\n await syncBullMQAfterUndo(ctx, schedule)\n }\n },\n\n redo: makeCreateRedo<ScheduledJob, ScheduleSnapshot, ScheduleCreateInput, { id: string }>({\n entityClass: ScheduledJob,\n getSnapshotId: (snapshot) => snapshot.id,\n seedFromSnapshot: scheduleSeedFromSnapshot,\n buildResult: (entity) => ({ id: entity.id }),\n afterRestore: async ({ ctx, entity }) => {\n await syncBullMQAfterUndo(ctx, entity)\n },\n }),\n}\n\n/**\n * UPDATE SCHEDULE COMMAND\n */\nconst updateScheduleCommand: CommandHandler<ScheduleUpdateInput, { ok: boolean }> = {\n id: 'scheduler.jobs.update',\n\n async prepare(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n const before = await loadScheduleSnapshot(em, input.id)\n return { before }\n },\n\n async execute(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null })\n if (!schedule) {\n throw new Error('Schedule not found')\n }\n\n ensureTenantScope(ctx, schedule.tenantId)\n if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId)\n ensureCanManageSystemScopedJob(ctx, schedule)\n\n if (input.isEnabled === true && schedule.isEnabled !== true) {\n await enforceTenantActiveScheduleLimit(em, schedule.tenantId)\n }\n\n const scheduleChanged = input.scheduleType !== undefined || input.scheduleValue !== undefined || input.timezone !== undefined\n const nextRunAt = scheduleChanged\n ? calculateNextRunForWrite(\n input.scheduleType ?? schedule.scheduleType,\n input.scheduleValue ?? schedule.scheduleValue,\n input.timezone ?? schedule.timezone,\n )\n : null\n\n if (scheduleChanged && !nextRunAt) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(422, {\n error: translate('scheduler.error.invalid_schedule_value', 'Invalid schedule value.'),\n })\n }\n\n // Update fields\n if (input.name !== undefined) schedule.name = input.name\n if (input.description !== undefined) schedule.description = input.description ?? null\n if (input.scheduleType !== undefined) schedule.scheduleType = input.scheduleType\n if (input.scheduleValue !== undefined) schedule.scheduleValue = input.scheduleValue\n if (input.timezone !== undefined) schedule.timezone = input.timezone\n if (input.targetPayload !== undefined) schedule.targetPayload = input.targetPayload ?? null\n if (input.requireFeature !== undefined) schedule.requireFeature = input.requireFeature || null\n if (input.isEnabled !== undefined) schedule.isEnabled = input.isEnabled\n \n // Handle target type changes - clear stale values when switching between queue and command\n if (input.targetType !== undefined) {\n schedule.targetType = input.targetType\n \n if (input.targetType === 'queue') {\n // Switching to queue: set new queue and clear command\n if (input.targetQueue !== undefined) schedule.targetQueue = input.targetQueue\n schedule.targetCommand = null\n } else if (input.targetType === 'command') {\n // Switching to command: set new command and clear queue\n if (input.targetCommand !== undefined) schedule.targetCommand = input.targetCommand\n schedule.targetQueue = null\n }\n } else {\n // targetType not changing, but allow updating individual target fields\n if (input.targetQueue !== undefined) schedule.targetQueue = input.targetQueue\n if (input.targetCommand !== undefined) schedule.targetCommand = input.targetCommand\n }\n\n if (nextRunAt) {\n schedule.nextRunAt = nextRunAt\n }\n\n schedule.updatedAt = new Date()\n schedule.updatedByUserId = resolveCommandActorUserId(ctx)\n\n await em.flush()\n\n return { ok: true }\n },\n\n async captureAfter(input, _result, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n return await loadScheduleSnapshot(em, input.id)\n },\n\n async buildLog({ input, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ScheduleSnapshot | undefined\n const after = snapshots.after as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.update', 'Update schedule'),\n resourceKind: 'scheduler.job',\n resourceId: input.id,\n tenantId: after?.tenantId || null,\n organizationId: after?.organizationId || null,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const before = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.before\n if (!before) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const schedule = await em.findOne(ScheduledJob, { id: before.id })\n\n if (schedule) {\n // Restore all fields\n schedule.name = before.name\n schedule.description = before.description\n schedule.scopeType = before.scopeType\n schedule.organizationId = before.organizationId\n schedule.tenantId = before.tenantId\n schedule.scheduleType = before.scheduleType\n schedule.scheduleValue = before.scheduleValue\n schedule.timezone = before.timezone\n schedule.targetType = before.targetType\n schedule.targetQueue = before.targetQueue\n schedule.targetCommand = before.targetCommand\n schedule.targetPayload = before.targetPayload\n schedule.requireFeature = before.requireFeature\n schedule.isEnabled = before.isEnabled\n schedule.sourceType = before.sourceType\n schedule.sourceModule = before.sourceModule\n schedule.nextRunAt = toDate(before.nextRunAt)\n schedule.lastRunAt = toDate(before.lastRunAt)\n schedule.updatedAt = new Date()\n\n await em.flush()\n await syncBullMQAfterUndo(ctx, schedule)\n }\n },\n}\n\n/**\n * DELETE SCHEDULE COMMAND\n */\nconst deleteScheduleCommand: CommandHandler<{ id: string }, { ok: boolean }> = {\n id: 'scheduler.jobs.delete',\n\n async prepare(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n const before = await loadScheduleSnapshot(em, input.id)\n return { before }\n },\n\n async execute(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null })\n if (!schedule) {\n throw new Error('Schedule not found')\n }\n\n ensureTenantScope(ctx, schedule.tenantId)\n if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId)\n ensureCanManageSystemScopedJob(ctx, schedule)\n\n // Soft delete\n schedule.deletedAt = new Date()\n schedule.updatedAt = new Date()\n schedule.updatedByUserId = resolveCommandActorUserId(ctx)\n\n await em.flush()\n\n return { ok: true }\n },\n\n async buildLog({ input, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.delete', 'Delete schedule'),\n resourceKind: 'scheduler.job',\n resourceId: input.id,\n tenantId: before?.tenantId || null,\n organizationId: before?.organizationId || null,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const before = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.before\n if (!before) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const existing = await em.findOne(ScheduledJob, { id: before.id })\n\n // Soft-deleted rows are restored by clearing `deletedAt`. A hard-removed row\n // (no surviving record) is re-materialized from the snapshot so undo is\n // robust to either deletion strategy \u2014 mirroring the sales reference undo.\n const schedule = existing ?? materializeScheduleFromSnapshot(em, before)\n if (existing) {\n schedule.deletedAt = null\n schedule.updatedAt = new Date()\n }\n await em.flush()\n await syncBullMQAfterUndo(ctx, schedule)\n },\n}\n\n// Register all commands\nregisterCommand(createScheduleCommand)\nregisterCommand(updateScheduleCommand)\nregisterCommand(deleteScheduleCommand)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { extractUndoPayload, type UndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { makeCreateRedo } from '@open-mercato/shared/lib/commands/redo'\nimport { ensureOrganizationScope } from '@open-mercato/shared/lib/commands/scope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { ScheduledJob } from '../data/entities.js'\nimport { calculateNextRunForWrite } from '../lib/nextRunCalculator.js'\nimport { enforceTenantActiveScheduleLimit } from '../lib/activeScheduleLimits.js'\nimport type {\n ScheduleCreateInput,\n ScheduleUpdateInput,\n} from '../data/validators.js'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { BullMQSchedulerService } from '../services/bullmqSchedulerService.js'\nimport {\n assertSchedulerQueueTargetAuthorized,\n isSchedulerSafeQueue,\n validateSchedulerTargetPayload,\n} from '../lib/safeQueueTargets.js'\nimport type { SchedulerTargetRbacService } from '../lib/safeQueueTargets.js'\n/**\n * Snapshot of a schedule for undo/redo\n */\ntype ScheduleSnapshot = {\n id: string\n name: string\n description: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n organizationId: string | null\n tenantId: string | null\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue: string | null\n targetCommand: string | null\n targetPayload: Record<string, unknown> | null\n requireFeature: string | null\n isEnabled: boolean\n sourceType: 'user' | 'module'\n sourceModule: string | null\n nextRunAt: Date | null\n lastRunAt: Date | null\n}\n\n/**\n * Load a schedule snapshot\n */\nasync function loadScheduleSnapshot(\n em: EntityManager,\n scheduleId: string\n): Promise<ScheduleSnapshot | null> {\n const schedule = await em.findOne(ScheduledJob, { id: scheduleId })\n if (!schedule) return null\n\n return {\n id: schedule.id,\n name: schedule.name,\n description: schedule.description ?? null,\n scopeType: schedule.scopeType,\n organizationId: schedule.organizationId ?? null,\n tenantId: schedule.tenantId ?? null,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue ?? null,\n targetCommand: schedule.targetCommand ?? null,\n targetPayload: schedule.targetPayload ?? null,\n requireFeature: schedule.requireFeature ?? null,\n isEnabled: schedule.isEnabled,\n sourceType: schedule.sourceType,\n sourceModule: schedule.sourceModule ?? null,\n nextRunAt: schedule.nextRunAt ?? null,\n lastRunAt: schedule.lastRunAt ?? null,\n }\n}\n\n/**\n * Snapshots are persisted as JSON (in the action log's command payload), so Date\n * fields come back as ISO strings on undo. Coerce them to Date before writing\n * them onto the entity, otherwise MikroORM throws while flushing a Date column.\n */\nfunction toDate(value: Date | string | null | undefined): Date | null {\n if (!value) return null\n return value instanceof Date ? value : new Date(value)\n}\n\nfunction canonicalJson(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value ?? null)\n if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => a.localeCompare(b))\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`\n}\n\nfunction normalizeTargetPayloadValue(value: unknown): unknown {\n // `schedulerService.register()` persists `{}` as-is while the edit form sends\n // `null` for an empty payload \u2014 treat the three empty shapes as equal (#5213).\n if (value !== null && typeof value === 'object' && !Array.isArray(value)\n && Object.keys(value as Record<string, unknown>).length === 0) return null\n return value\n}\n\nfunction targetPayloadChanged(inputValue: unknown, storedValue: unknown): boolean {\n return targetValueChanged(\n normalizeTargetPayloadValue(inputValue),\n normalizeTargetPayloadValue(storedValue),\n )\n}\n\nfunction targetValueChanged(inputValue: unknown, storedValue: unknown): boolean {\n if (inputValue === undefined) return false\n return canonicalJson(inputValue ?? null) !== canonicalJson(storedValue ?? null)\n}\n\ntype QueueTargetAuthorizationContext = CommandRuntimeContext\n\nasync function assertQueueTargetAuthorizedForActor(\n ctx: QueueTargetAuthorizationContext,\n queue: string,\n): Promise<void> {\n const rbacService = (() => {\n try {\n return ctx.container?.resolve<SchedulerTargetRbacService>('rbacService') ?? null\n } catch {\n return null\n }\n })()\n const reason = await assertSchedulerQueueTargetAuthorized({\n queue,\n actorUserId: resolveCommandActorUserId(ctx),\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n isSuperAdmin: ctx.auth?.isSuperAdmin === true,\n rbacService,\n })\n if (reason) throw new CrudHttpError(403, { error: reason })\n}\n\nasync function assertQueueTargetPayloadAllowed(queue: string, targetPayload: Record<string, unknown> | null | undefined): Promise<void> {\n const reason = validateSchedulerTargetPayload(queue, targetPayload)\n if (reason) throw new CrudHttpError(422, { error: `Invalid target payload for queue ${queue}: ${reason}` })\n}\n\nfunction resolveCommandActorUserId(ctx: CommandRuntimeContext): string | null {\n const auth = ctx.auth\n if (!auth) return null\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId.trim()\n if (auth.isApiKey) return null\n return typeof auth.sub === 'string' && auth.sub.trim().length > 0 ? auth.sub.trim() : null\n}\n\n/**\n * Build a full create seed (including the original id and Date-coerced timestamps)\n * from a snapshot. Shared by `materializeScheduleFromSnapshot` (delete-undo) and the\n * create command's id-preserving `redo`.\n */\nfunction scheduleSeedFromSnapshot(snapshot: ScheduleSnapshot): Record<string, unknown> {\n const now = new Date()\n return {\n id: snapshot.id,\n name: snapshot.name,\n description: snapshot.description,\n scopeType: snapshot.scopeType,\n organizationId: snapshot.organizationId,\n tenantId: snapshot.tenantId,\n scheduleType: snapshot.scheduleType,\n scheduleValue: snapshot.scheduleValue,\n timezone: snapshot.timezone,\n targetType: snapshot.targetType,\n targetQueue: snapshot.targetQueue,\n targetCommand: snapshot.targetCommand,\n targetPayload: snapshot.targetPayload,\n requireFeature: snapshot.requireFeature,\n isEnabled: snapshot.isEnabled,\n sourceType: snapshot.sourceType,\n sourceModule: snapshot.sourceModule,\n nextRunAt: toDate(snapshot.nextRunAt),\n lastRunAt: toDate(snapshot.lastRunAt),\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n }\n}\n\n/**\n * Re-create a ScheduledJob entity from a snapshot, preserving its original id.\n * Used by delete-undo when the row was hard-removed rather than soft-deleted.\n */\nfunction materializeScheduleFromSnapshot(em: EntityManager, snapshot: ScheduleSnapshot): ScheduledJob {\n return em.create(ScheduledJob, scheduleSeedFromSnapshot(snapshot) as never)\n}\n\n/**\n * Trigger BullMQ sync after undo operations.\n * Best-effort: if BullMQ service is unavailable (e.g. local strategy), this is a no-op.\n */\nasync function syncBullMQAfterUndo(ctx: CommandRuntimeContext, schedule: ScheduledJob | null): Promise<void> {\n try {\n if (!ctx.container?.resolve) return\n const bullmqService = ctx.container.resolve<BullMQSchedulerService>('bullmqSchedulerService')\n if (!bullmqService) return\n\n if (schedule && schedule.isEnabled && !schedule.deletedAt) {\n await bullmqService.register(schedule, { skipNextRunUpdate: true })\n } else if (schedule) {\n await bullmqService.unregister(schedule.id)\n }\n } catch {\n // Best-effort: BullMQ service may not be registered (local strategy)\n }\n}\n\n/**\n * Ensure tenant/org scope for security\n */\nfunction ensureTenantScope(ctx: CommandRuntimeContext, tenantId: string | null | undefined) {\n if (tenantId && ctx.auth?.tenantId && ctx.auth.tenantId !== tenantId) {\n throw new Error('Tenant mismatch')\n }\n}\n\n// Super-admin status is the immutable `isSuperAdmin` flag derived from\n// RoleAcl/UserAcl at session resolution. Never compare role names to a string\n// like 'superadmin' \u2014 role names are tenant-mutable and trivially spoofable.\nfunction isSuperAdminActor(ctx: CommandRuntimeContext): boolean {\n return ctx.auth?.isSuperAdmin === true\n}\n\n// `ensureTenantScope` is a no-op for system-scoped jobs (tenantId === null), so a\n// `scheduler.jobs.manage` holder could otherwise update/delete a system schedule\n// belonging to the whole deployment. System-scoped jobs MUST be super-admin only,\n// mirroring the create route's system-scope gate.\nfunction ensureCanManageSystemScopedJob(\n ctx: CommandRuntimeContext,\n job: { scopeType?: string | null; tenantId?: string | null },\n): void {\n const isSystemScoped = job.scopeType === 'system' || job.tenantId == null\n if (!isSystemScoped) return\n if (isSuperAdminActor(ctx)) return\n throw new CrudHttpError(403, {\n error: 'System-scoped scheduled jobs can only be managed by a super administrator.',\n })\n}\n\n/**\n * CREATE SCHEDULE COMMAND\n */\nconst createScheduleCommand: CommandHandler<ScheduleCreateInput, { id: string }> = {\n id: 'scheduler.jobs.create',\n\n async execute(input, ctx) {\n ensureTenantScope(ctx, input.tenantId)\n if (input.organizationId) ensureOrganizationScope(ctx, input.organizationId)\n ensureCanManageSystemScopedJob(ctx, { scopeType: input.scopeType, tenantId: input.tenantId })\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n if (input.isEnabled ?? true) {\n await enforceTenantActiveScheduleLimit(em, input.tenantId)\n }\n\n // Calculate next run time\n const nextRunAt = calculateNextRunForWrite(\n input.scheduleType,\n input.scheduleValue,\n input.timezone || 'UTC'\n )\n\n if (!nextRunAt) {\n throw new Error('Failed to calculate next run time')\n }\n\n if (input.targetType === 'queue' && input.targetQueue) {\n await assertQueueTargetAuthorizedForActor(ctx, input.targetQueue)\n await assertQueueTargetPayloadAllowed(input.targetQueue, input.targetPayload)\n }\n\n // Create schedule\n const schedule = em.create(ScheduledJob, {\n name: input.name,\n description: input.description ?? null,\n scopeType: input.scopeType,\n organizationId: input.organizationId ?? null,\n tenantId: input.tenantId ?? null,\n scheduleType: input.scheduleType,\n scheduleValue: input.scheduleValue,\n timezone: input.timezone ?? 'UTC',\n targetType: input.targetType,\n targetQueue: input.targetQueue ?? null,\n targetCommand: input.targetCommand ?? null,\n targetPayload: input.targetPayload ?? null,\n requireFeature: input.requireFeature ?? null,\n isEnabled: input.isEnabled ?? true,\n sourceType: 'user',\n sourceModule: null,\n nextRunAt,\n createdByUserId: resolveCommandActorUserId(ctx),\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n\n em.persist(schedule)\n await em.flush()\n\n return { id: schedule.id }\n },\n\n async captureAfter(_input, result, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n return await loadScheduleSnapshot(em, result.id)\n },\n\n async buildLog({ result, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const after = snapshots.after as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.create', 'Create schedule'),\n resourceKind: 'scheduler.job',\n resourceId: result.id,\n tenantId: after?.tenantId || null,\n organizationId: after?.organizationId || null,\n snapshotAfter: after,\n payload: { undo: { after } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const after = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.after\n if (!after) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const schedule = await em.findOne(ScheduledJob, { id: after.id })\n\n if (schedule) {\n await em.remove(schedule).flush()\n await syncBullMQAfterUndo(ctx, schedule)\n }\n },\n\n redo: makeCreateRedo<ScheduledJob, ScheduleSnapshot, ScheduleCreateInput, { id: string }>({\n entityClass: ScheduledJob,\n getSnapshotId: (snapshot) => snapshot.id,\n seedFromSnapshot: scheduleSeedFromSnapshot,\n buildResult: (entity) => ({ id: entity.id }),\n afterRestore: async ({ ctx, entity }) => {\n await syncBullMQAfterUndo(ctx, entity)\n },\n }),\n}\n\n/**\n * UPDATE SCHEDULE COMMAND\n */\nconst updateScheduleCommand: CommandHandler<ScheduleUpdateInput, { ok: boolean }> = {\n id: 'scheduler.jobs.update',\n\n async prepare(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n const before = await loadScheduleSnapshot(em, input.id)\n return { before }\n },\n\n async execute(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null })\n if (!schedule) {\n throw new Error('Schedule not found')\n }\n\n ensureTenantScope(ctx, schedule.tenantId)\n if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId)\n ensureCanManageSystemScopedJob(ctx, schedule)\n\n // Module-authored schedules are trusted infrastructure wiring (#5213): their\n // target and payload are owned by the registering module. Submitting them\n // unchanged is a no-op (the edit form always resends every field) \u2014 only an\n // actual change to provenance-relevant values is rejected.\n const touchesModuleOwnedTarget =\n schedule.sourceType === 'module'\n && (\n targetValueChanged(input.targetType, schedule.targetType)\n || targetValueChanged(input.targetQueue, schedule.targetQueue)\n || targetValueChanged(input.targetCommand, schedule.targetCommand)\n || targetPayloadChanged(input.targetPayload, schedule.targetPayload)\n )\n if (touchesModuleOwnedTarget) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(403, {\n error: translate(\n 'scheduler.errors.module_target_immutable',\n 'Module-authored schedules own their target; only operational fields can be updated here.',\n ),\n })\n }\n\n if (input.isEnabled === true && schedule.isEnabled !== true) {\n await enforceTenantActiveScheduleLimit(em, schedule.tenantId)\n }\n\n const effectiveTargetType = input.targetType ?? schedule.targetType\n const effectiveQueue = input.targetQueue ?? schedule.targetQueue\n const queueChanged =\n effectiveTargetType === 'queue'\n && (\n targetValueChanged(input.targetQueue, schedule.targetQueue)\n || targetValueChanged(input.targetType, schedule.targetType)\n || targetPayloadChanged(input.targetPayload, schedule.targetPayload)\n )\n if (effectiveTargetType === 'queue' && effectiveQueue) {\n const retargetedToUnapprovedQueue =\n input.targetQueue !== undefined\n && input.targetQueue !== schedule.targetQueue\n && !isSchedulerSafeQueue(input.targetQueue)\n // A save that leaves a user-authored row ACTIVE on an unapproved queue\n // restores the invariant the update schema used to enforce; disabling the\n // row stays allowed so admins can remediate legacy targets from the UI.\n // (Dispatch-time re-verification remains the backstop either way.)\n const keepsUnapprovedQueueActive =\n effectiveQueue != null\n && !isSchedulerSafeQueue(effectiveQueue)\n && input.isEnabled !== false\n && schedule.isEnabled !== false\n if (\n (retargetedToUnapprovedQueue || keepsUnapprovedQueueActive)\n && schedule.sourceType !== 'module'\n ) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(422, {\n error: translate(\n 'scheduler.errors.queue_not_approved',\n 'Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected.',\n ),\n })\n }\n if (queueChanged) {\n await assertQueueTargetAuthorizedForActor(ctx, effectiveQueue)\n const effectivePayload = input.targetPayload !== undefined\n ? input.targetPayload\n : schedule.targetPayload\n await assertQueueTargetPayloadAllowed(effectiveQueue, effectivePayload)\n }\n }\n\n const scheduleChanged = input.scheduleType !== undefined || input.scheduleValue !== undefined || input.timezone !== undefined\n const nextRunAt = scheduleChanged\n ? calculateNextRunForWrite(\n input.scheduleType ?? schedule.scheduleType,\n input.scheduleValue ?? schedule.scheduleValue,\n input.timezone ?? schedule.timezone,\n )\n : null\n\n if (scheduleChanged && !nextRunAt) {\n const { translate } = await resolveTranslations()\n throw new CrudHttpError(422, {\n error: translate('scheduler.error.invalid_schedule_value', 'Invalid schedule value.'),\n })\n }\n\n // Update fields\n if (input.name !== undefined) schedule.name = input.name\n if (input.description !== undefined) schedule.description = input.description ?? null\n if (input.scheduleType !== undefined) schedule.scheduleType = input.scheduleType\n if (input.scheduleValue !== undefined) schedule.scheduleValue = input.scheduleValue\n if (input.timezone !== undefined) schedule.timezone = input.timezone\n if (input.targetPayload !== undefined) schedule.targetPayload = input.targetPayload ?? null\n if (input.requireFeature !== undefined) schedule.requireFeature = input.requireFeature || null\n if (input.isEnabled !== undefined) schedule.isEnabled = input.isEnabled\n \n // Handle target type changes - clear stale values when switching between queue and command\n if (input.targetType !== undefined) {\n schedule.targetType = input.targetType\n \n if (input.targetType === 'queue') {\n // Switching to queue: set new queue and clear command\n if (input.targetQueue !== undefined) schedule.targetQueue = input.targetQueue\n schedule.targetCommand = null\n } else if (input.targetType === 'command') {\n // Switching to command: set new command and clear queue\n if (input.targetCommand !== undefined) schedule.targetCommand = input.targetCommand\n schedule.targetQueue = null\n }\n } else {\n // targetType not changing, but allow updating individual target fields\n if (input.targetQueue !== undefined) schedule.targetQueue = input.targetQueue\n if (input.targetCommand !== undefined) schedule.targetCommand = input.targetCommand\n }\n\n if (nextRunAt) {\n schedule.nextRunAt = nextRunAt\n }\n\n schedule.updatedAt = new Date()\n schedule.updatedByUserId = resolveCommandActorUserId(ctx)\n\n await em.flush()\n\n return { ok: true }\n },\n\n async captureAfter(input, _result, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n return await loadScheduleSnapshot(em, input.id)\n },\n\n async buildLog({ input, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ScheduleSnapshot | undefined\n const after = snapshots.after as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.update', 'Update schedule'),\n resourceKind: 'scheduler.job',\n resourceId: input.id,\n tenantId: after?.tenantId || null,\n organizationId: after?.organizationId || null,\n snapshotBefore: before,\n snapshotAfter: after,\n payload: { undo: { before, after } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const before = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.before\n if (!before) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const schedule = await em.findOne(ScheduledJob, { id: before.id })\n\n if (schedule) {\n // Restore all fields\n schedule.name = before.name\n schedule.description = before.description\n schedule.scopeType = before.scopeType\n schedule.organizationId = before.organizationId\n schedule.tenantId = before.tenantId\n schedule.scheduleType = before.scheduleType\n schedule.scheduleValue = before.scheduleValue\n schedule.timezone = before.timezone\n schedule.targetType = before.targetType\n schedule.targetQueue = before.targetQueue\n schedule.targetCommand = before.targetCommand\n schedule.targetPayload = before.targetPayload\n schedule.requireFeature = before.requireFeature\n schedule.isEnabled = before.isEnabled\n schedule.sourceType = before.sourceType\n schedule.sourceModule = before.sourceModule\n schedule.nextRunAt = toDate(before.nextRunAt)\n schedule.lastRunAt = toDate(before.lastRunAt)\n schedule.updatedAt = new Date()\n\n await em.flush()\n await syncBullMQAfterUndo(ctx, schedule)\n }\n },\n}\n\n/**\n * DELETE SCHEDULE COMMAND\n */\nconst deleteScheduleCommand: CommandHandler<{ id: string }, { ok: boolean }> = {\n id: 'scheduler.jobs.delete',\n\n async prepare(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em')\n const before = await loadScheduleSnapshot(em, input.id)\n return { before }\n },\n\n async execute(input, ctx) {\n const em = ctx.container.resolve<EntityManager>('em').fork()\n\n const schedule = await em.findOne(ScheduledJob, { id: input.id, deletedAt: null })\n if (!schedule) {\n throw new Error('Schedule not found')\n }\n\n ensureTenantScope(ctx, schedule.tenantId)\n if (schedule.organizationId) ensureOrganizationScope(ctx, schedule.organizationId)\n ensureCanManageSystemScopedJob(ctx, schedule)\n\n // Soft delete\n schedule.deletedAt = new Date()\n schedule.updatedAt = new Date()\n schedule.updatedByUserId = resolveCommandActorUserId(ctx)\n\n await em.flush()\n\n return { ok: true }\n },\n\n async buildLog({ input, ctx, snapshots }) {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ScheduleSnapshot | undefined\n\n return {\n actionLabel: translate('scheduler.audit.delete', 'Delete schedule'),\n resourceKind: 'scheduler.job',\n resourceId: input.id,\n tenantId: before?.tenantId || null,\n organizationId: before?.organizationId || null,\n snapshotBefore: before,\n payload: { undo: { before } },\n }\n },\n\n async undo({ logEntry, ctx }) {\n const before = extractUndoPayload<UndoPayload<ScheduleSnapshot>>(logEntry)?.before\n if (!before) return\n\n const em = ctx.container.resolve<EntityManager>('em').fork()\n const existing = await em.findOne(ScheduledJob, { id: before.id })\n\n // Soft-deleted rows are restored by clearing `deletedAt`. A hard-removed row\n // (no surviving record) is re-materialized from the snapshot so undo is\n // robust to either deletion strategy \u2014 mirroring the sales reference undo.\n const schedule = existing ?? materializeScheduleFromSnapshot(em, before)\n if (existing) {\n schedule.deletedAt = null\n schedule.updatedAt = new Date()\n }\n await em.flush()\n await syncBullMQAfterUndo(ctx, schedule)\n },\n}\n\n// Register all commands\nregisterCommand(createScheduleCommand)\nregisterCommand(updateScheduleCommand)\nregisterCommand(deleteScheduleCommand)\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,12 +4,16 @@ import { validateInterval } from "../lib/intervalParser.js";
4
4
  import { commandRegistry } from "@open-mercato/shared/lib/commands/registry";
5
5
  import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
6
6
  import { isSchedulerSafeCommandId } from "../lib/scheduler-safe-commands.js";
7
+ import { isSchedulerSafeQueue, validateSchedulerTargetPayload } from "../lib/safeQueueTargets.js";
7
8
  function validateCommandExists(commandId) {
8
9
  return commandRegistry.has(commandId);
9
10
  }
10
11
  function validateCommandIsSchedulable(commandId) {
11
12
  return isSchedulerSafeCommandId(commandId);
12
13
  }
14
+ function validateSchedulerSafeQueue(queue) {
15
+ return isSchedulerSafeQueue(queue);
16
+ }
13
17
  const scheduleBaseSchema = z.object({
14
18
  name: z.string().min(1, "Name is required").max(200),
15
19
  description: z.string().max(500).optional().nullable(),
@@ -24,9 +28,7 @@ const scheduleBaseSchema = z.object({
24
28
  targetCommand: z.string().optional().nullable(),
25
29
  targetPayload: z.record(z.string(), z.unknown()).optional().nullable(),
26
30
  requireFeature: z.string().optional().nullable(),
27
- isEnabled: z.boolean().default(true),
28
- sourceType: z.enum(["user", "module"]).default("user"),
29
- sourceModule: z.string().optional().nullable()
31
+ isEnabled: z.boolean().default(true)
30
32
  });
31
33
  const scheduleCreateSchema = scheduleBaseSchema.refine(
32
34
  (data) => {
@@ -95,6 +97,28 @@ const scheduleCreateSchema = scheduleBaseSchema.refine(
95
97
  message: "Command is not schedulable. Only scheduler-safe commands can be used as schedule targets.",
96
98
  path: ["targetCommand"]
97
99
  }
100
+ ).refine(
101
+ (data) => {
102
+ if (data.targetType === "queue" && data.targetQueue) {
103
+ return validateSchedulerSafeQueue(data.targetQueue);
104
+ }
105
+ return true;
106
+ },
107
+ {
108
+ message: "Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected.",
109
+ path: ["targetQueue"]
110
+ }
111
+ ).refine(
112
+ (data) => {
113
+ if (data.targetType === "queue" && data.targetQueue) {
114
+ return validateSchedulerTargetPayload(data.targetQueue, data.targetPayload) === null;
115
+ }
116
+ return true;
117
+ },
118
+ {
119
+ message: "Invalid target payload for the selected scheduler queue.",
120
+ path: ["targetPayload"]
121
+ }
98
122
  );
99
123
  const scheduleUpdateSchema = z.object({
100
124
  id: z.uuid(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/scheduler/data/validators.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { validateCron } from '../lib/cronParser'\nimport { validateInterval } from '../lib/intervalParser'\nimport { commandRegistry } from '@open-mercato/shared/lib/commands/registry'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isSchedulerSafeCommandId } from '../lib/scheduler-safe-commands'\n\n/**\n * Validate that a command exists in the command registry\n */\nfunction validateCommandExists(commandId: string): boolean {\n return commandRegistry.has(commandId)\n}\n\nfunction validateCommandIsSchedulable(commandId: string): boolean {\n return isSchedulerSafeCommandId(commandId)\n}\n\n/**\n * Base schedule fields\n */\nconst scheduleBaseSchema = z.object({\n name: z.string().min(1, 'Name is required').max(200),\n description: z.string().max(500).optional().nullable(),\n \n scopeType: z.enum(['system', 'organization', 'tenant']),\n organizationId: z.uuid().optional().nullable(),\n tenantId: z.uuid().optional().nullable(),\n \n scheduleType: z.enum(['cron', 'interval']),\n scheduleValue: z.string().min(1, 'Schedule value is required'),\n timezone: z.string().default('UTC'),\n \n targetType: z.enum(['queue', 'command']),\n targetQueue: z.string().optional().nullable(),\n targetCommand: z.string().optional().nullable(),\n targetPayload: z.record(z.string(), z.unknown()).optional().nullable(),\n \n requireFeature: z.string().optional().nullable(),\n \n isEnabled: z.boolean().default(true),\n sourceType: z.enum(['user', 'module']).default('user'),\n sourceModule: z.string().optional().nullable(),\n})\n\n/**\n * Create schedule schema\n */\nexport const scheduleCreateSchema = scheduleBaseSchema\n .refine(\n (data) => {\n if (data.scopeType === 'system') {\n return !data.organizationId && !data.tenantId\n }\n if (data.scopeType === 'organization') {\n return !!data.organizationId && !!data.tenantId\n }\n if (data.scopeType === 'tenant') {\n return !data.organizationId && !!data.tenantId\n }\n return false\n },\n {\n message: 'Invalid scope configuration',\n path: ['scopeType'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'queue') {\n return !!data.targetQueue\n }\n if (data.targetType === 'command') {\n return !!data.targetCommand\n }\n return false\n },\n {\n message: 'Target queue or command is required based on target type',\n path: ['targetType'],\n }\n )\n .refine(\n (data) => {\n if (data.scheduleType === 'cron') {\n return validateCron(data.scheduleValue)\n }\n if (data.scheduleType === 'interval') {\n return validateInterval(data.scheduleValue)\n }\n return false\n },\n {\n message: 'Invalid schedule value. For cron: use valid cron expression (e.g., \"0 0 * * *\"). For interval: use <number><unit> format (e.g., \"15m\", \"2h\", \"1d\")',\n path: ['scheduleValue'],\n }\n )\n .refine(\n (data) => {\n // Validate that command exists if targetType is 'command'\n if (data.targetType === 'command' && data.targetCommand) {\n return validateCommandExists(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command does not exist. Please ensure the command is registered before creating a schedule.',\n path: ['targetCommand'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'command' && data.targetCommand) {\n return validateCommandIsSchedulable(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command is not schedulable. Only scheduler-safe commands can be used as schedule targets.',\n path: ['targetCommand'],\n }\n )\n\n/**\n * Update schedule schema (all fields optional except id)\n */\nexport const scheduleUpdateSchema = z.object({\n id: z.uuid(),\n name: z.string().min(1).max(200).optional(),\n description: z.string().max(500).optional().nullable(),\n \n scheduleType: z.enum(['cron', 'interval']).optional(),\n scheduleValue: z.string().min(1).optional(),\n timezone: z.string().optional(),\n \n targetType: z.enum(['queue', 'command']).optional(),\n targetQueue: z.string().optional().nullable(),\n targetCommand: z.string().optional().nullable(),\n targetPayload: z.record(z.string(), z.unknown()).optional().nullable(),\n requireFeature: z.string().optional().nullable(),\n \n isEnabled: z.boolean().optional(),\n})\n .refine(\n (data) => {\n // If scheduleValue is provided, scheduleType must also be provided\n // This ensures we can validate the value against its type\n if (data.scheduleValue !== undefined && data.scheduleType === undefined) {\n return false\n }\n return true\n },\n {\n message: 'scheduleType is required when updating scheduleValue',\n path: ['scheduleType'],\n }\n )\n .refine(\n (data) => {\n // If scheduleValue is provided, validate it based on scheduleType\n if (data.scheduleValue && data.scheduleType) {\n if (data.scheduleType === 'cron') {\n return validateCron(data.scheduleValue)\n }\n if (data.scheduleType === 'interval') {\n return validateInterval(data.scheduleValue)\n }\n }\n return true\n },\n {\n message: 'Invalid schedule value. For cron: use valid cron expression (e.g., \"0 0 * * *\"). For interval: use <number><unit> format (e.g., \"15m\", \"2h\", \"1d\")',\n path: ['scheduleValue'],\n }\n )\n .refine(\n (data) => {\n // If targetType is provided, ensure appropriate target is set\n if (data.targetType === 'queue') {\n return data.targetQueue !== undefined\n }\n if (data.targetType === 'command') {\n return data.targetCommand !== undefined\n }\n return true\n },\n {\n message: 'When changing target type, you must provide the corresponding targetQueue or targetCommand',\n path: ['targetType'],\n }\n )\n .refine(\n (data) => {\n // Validate that command exists if targetCommand is provided\n if (data.targetCommand) {\n return validateCommandExists(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command does not exist. Please ensure the command is registered before updating.',\n path: ['targetCommand'],\n }\n )\n .refine(\n (data) => {\n if (data.targetCommand) {\n return validateCommandIsSchedulable(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command is not schedulable. Only scheduler-safe commands can be used as schedule targets.',\n path: ['targetCommand'],\n }\n )\n\n/**\n * Delete schedule schema\n */\nexport const scheduleDeleteSchema = z.object({\n id: z.uuid(),\n})\n\n/**\n * List schedules query schema\n */\nexport const scheduleListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n id: z.string().uuid().optional(),\n search: z.string().optional(),\n scopeType: z.enum(['system', 'organization', 'tenant']).optional(),\n isEnabled: z.string().optional().transform((val) => {\n if (val === undefined) return undefined\n return parseBooleanToken(val) ?? undefined\n }),\n sourceType: z.enum(['user', 'module']).optional(),\n sourceModule: z.string().optional(),\n sort: z.string().optional(),\n order: z.enum(['asc', 'desc']).optional(),\n})\n\n/**\n * Trigger schedule schema (manual execution)\n */\nexport const scheduleTriggerSchema = z.object({\n id: z.uuid(),\n})\n\n/**\n * Get schedule runs query schema\n */\nexport const scheduleRunsQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n scheduledJobId: z.uuid().optional(),\n status: z.enum(['running', 'completed', 'failed', 'skipped']).optional(),\n triggerType: z.enum(['scheduled', 'manual']).optional(),\n fromDate: z.string().datetime().optional(),\n toDate: z.string().datetime().optional(),\n sort: z.string().optional(),\n order: z.enum(['asc', 'desc']).optional(),\n})\n\n/**\n * Type exports\n */\nexport type ScheduleCreateInput = z.infer<typeof scheduleCreateSchema>\nexport type ScheduleUpdateInput = z.infer<typeof scheduleUpdateSchema>\nexport type ScheduleDeleteInput = z.infer<typeof scheduleDeleteSchema>\nexport type ScheduleListQuery = z.infer<typeof scheduleListQuerySchema>\nexport type ScheduleTriggerInput = z.infer<typeof scheduleTriggerSchema>\nexport type ScheduleRunsQuery = z.infer<typeof scheduleRunsQuerySchema>\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { validateCron } from '../lib/cronParser'\nimport { validateInterval } from '../lib/intervalParser'\nimport { commandRegistry } from '@open-mercato/shared/lib/commands/registry'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isSchedulerSafeCommandId } from '../lib/scheduler-safe-commands'\nimport { isSchedulerSafeQueue, validateSchedulerTargetPayload } from '../lib/safeQueueTargets'\n\n/**\n * Validate that a command exists in the command registry\n */\nfunction validateCommandExists(commandId: string): boolean {\n return commandRegistry.has(commandId)\n}\n\nfunction validateCommandIsSchedulable(commandId: string): boolean {\n return isSchedulerSafeCommandId(commandId)\n}\n\n/**\n * Validate that a queue was declared scheduler-safe by its owning worker.\n * Internal and system-only workers (webhook processors, indexers, \u2026) stay\n * undiscoverable as schedule targets \u2014 see issue #5213.\n */\nfunction validateSchedulerSafeQueue(queue: string | null | undefined): boolean {\n return isSchedulerSafeQueue(queue)\n}\n\n/**\n * Base schedule fields\n */\nconst scheduleBaseSchema = z.object({\n name: z.string().min(1, 'Name is required').max(200),\n description: z.string().max(500).optional().nullable(),\n \n scopeType: z.enum(['system', 'organization', 'tenant']),\n organizationId: z.uuid().optional().nullable(),\n tenantId: z.uuid().optional().nullable(),\n \n scheduleType: z.enum(['cron', 'interval']),\n scheduleValue: z.string().min(1, 'Schedule value is required'),\n timezone: z.string().default('UTC'),\n \n targetType: z.enum(['queue', 'command']),\n targetQueue: z.string().optional().nullable(),\n targetCommand: z.string().optional().nullable(),\n targetPayload: z.record(z.string(), z.unknown()).optional().nullable(),\n \n requireFeature: z.string().optional().nullable(),\n \n isEnabled: z.boolean().default(true),\n})\n\n/**\n * Create schedule schema\n */\nexport const scheduleCreateSchema = scheduleBaseSchema\n .refine(\n (data) => {\n if (data.scopeType === 'system') {\n return !data.organizationId && !data.tenantId\n }\n if (data.scopeType === 'organization') {\n return !!data.organizationId && !!data.tenantId\n }\n if (data.scopeType === 'tenant') {\n return !data.organizationId && !!data.tenantId\n }\n return false\n },\n {\n message: 'Invalid scope configuration',\n path: ['scopeType'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'queue') {\n return !!data.targetQueue\n }\n if (data.targetType === 'command') {\n return !!data.targetCommand\n }\n return false\n },\n {\n message: 'Target queue or command is required based on target type',\n path: ['targetType'],\n }\n )\n .refine(\n (data) => {\n if (data.scheduleType === 'cron') {\n return validateCron(data.scheduleValue)\n }\n if (data.scheduleType === 'interval') {\n return validateInterval(data.scheduleValue)\n }\n return false\n },\n {\n message: 'Invalid schedule value. For cron: use valid cron expression (e.g., \"0 0 * * *\"). For interval: use <number><unit> format (e.g., \"15m\", \"2h\", \"1d\")',\n path: ['scheduleValue'],\n }\n )\n .refine(\n (data) => {\n // Validate that command exists if targetType is 'command'\n if (data.targetType === 'command' && data.targetCommand) {\n return validateCommandExists(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command does not exist. Please ensure the command is registered before creating a schedule.',\n path: ['targetCommand'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'command' && data.targetCommand) {\n return validateCommandIsSchedulable(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command is not schedulable. Only scheduler-safe commands can be used as schedule targets.',\n path: ['targetCommand'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'queue' && data.targetQueue) {\n return validateSchedulerSafeQueue(data.targetQueue)\n }\n return true\n },\n {\n message: 'Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected.',\n path: ['targetQueue'],\n }\n )\n .refine(\n (data) => {\n if (data.targetType === 'queue' && data.targetQueue) {\n return validateSchedulerTargetPayload(data.targetQueue, data.targetPayload) === null\n }\n return true\n },\n {\n message: 'Invalid target payload for the selected scheduler queue.',\n path: ['targetPayload'],\n }\n )\n\n/**\n * Update schedule schema (all fields optional except id)\n */\nexport const scheduleUpdateSchema = z.object({\n id: z.uuid(),\n name: z.string().min(1).max(200).optional(),\n description: z.string().max(500).optional().nullable(),\n \n scheduleType: z.enum(['cron', 'interval']).optional(),\n scheduleValue: z.string().min(1).optional(),\n timezone: z.string().optional(),\n \n targetType: z.enum(['queue', 'command']).optional(),\n targetQueue: z.string().optional().nullable(),\n targetCommand: z.string().optional().nullable(),\n targetPayload: z.record(z.string(), z.unknown()).optional().nullable(),\n requireFeature: z.string().optional().nullable(),\n \n isEnabled: z.boolean().optional(),\n})\n .refine(\n (data) => {\n // If scheduleValue is provided, scheduleType must also be provided\n // This ensures we can validate the value against its type\n if (data.scheduleValue !== undefined && data.scheduleType === undefined) {\n return false\n }\n return true\n },\n {\n message: 'scheduleType is required when updating scheduleValue',\n path: ['scheduleType'],\n }\n )\n .refine(\n (data) => {\n // If scheduleValue is provided, validate it based on scheduleType\n if (data.scheduleValue && data.scheduleType) {\n if (data.scheduleType === 'cron') {\n return validateCron(data.scheduleValue)\n }\n if (data.scheduleType === 'interval') {\n return validateInterval(data.scheduleValue)\n }\n }\n return true\n },\n {\n message: 'Invalid schedule value. For cron: use valid cron expression (e.g., \"0 0 * * *\"). For interval: use <number><unit> format (e.g., \"15m\", \"2h\", \"1d\")',\n path: ['scheduleValue'],\n }\n )\n .refine(\n (data) => {\n // If targetType is provided, ensure appropriate target is set\n if (data.targetType === 'queue') {\n return data.targetQueue !== undefined\n }\n if (data.targetType === 'command') {\n return data.targetCommand !== undefined\n }\n return true\n },\n {\n message: 'When changing target type, you must provide the corresponding targetQueue or targetCommand',\n path: ['targetType'],\n }\n )\n .refine(\n (data) => {\n // Validate that command exists if targetCommand is provided\n if (data.targetCommand) {\n return validateCommandExists(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command does not exist. Please ensure the command is registered before updating.',\n path: ['targetCommand'],\n }\n )\n .refine(\n (data) => {\n if (data.targetCommand) {\n return validateCommandIsSchedulable(data.targetCommand)\n }\n return true\n },\n {\n message: 'Command is not schedulable. Only scheduler-safe commands can be used as schedule targets.',\n path: ['targetCommand'],\n }\n )\n // Queue-target safety and payload-shape rules for updates are enforced in the\n // scheduler.jobs.update command against the stored row, so unchanged targets\n // (always resent by the edit form) and legacy remediation stays possible (#5213).\n\n/**\n * Delete schedule schema\n */\nexport const scheduleDeleteSchema = z.object({\n id: z.uuid(),\n})\n\n/**\n * List schedules query schema\n */\nexport const scheduleListQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n id: z.string().uuid().optional(),\n search: z.string().optional(),\n scopeType: z.enum(['system', 'organization', 'tenant']).optional(),\n isEnabled: z.string().optional().transform((val) => {\n if (val === undefined) return undefined\n return parseBooleanToken(val) ?? undefined\n }),\n sourceType: z.enum(['user', 'module']).optional(),\n sourceModule: z.string().optional(),\n sort: z.string().optional(),\n order: z.enum(['asc', 'desc']).optional(),\n})\n\n/**\n * Trigger schedule schema (manual execution)\n */\nexport const scheduleTriggerSchema = z.object({\n id: z.uuid(),\n})\n\n/**\n * Get schedule runs query schema\n */\nexport const scheduleRunsQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n scheduledJobId: z.uuid().optional(),\n status: z.enum(['running', 'completed', 'failed', 'skipped']).optional(),\n triggerType: z.enum(['scheduled', 'manual']).optional(),\n fromDate: z.string().datetime().optional(),\n toDate: z.string().datetime().optional(),\n sort: z.string().optional(),\n order: z.enum(['asc', 'desc']).optional(),\n})\n\n/**\n * Type exports\n */\nexport type ScheduleCreateInput = z.infer<typeof scheduleCreateSchema>\nexport type ScheduleUpdateInput = z.infer<typeof scheduleUpdateSchema>\nexport type ScheduleDeleteInput = z.infer<typeof scheduleDeleteSchema>\nexport type ScheduleListQuery = z.infer<typeof scheduleListQuerySchema>\nexport type ScheduleTriggerInput = z.infer<typeof scheduleTriggerSchema>\nexport type ScheduleRunsQuery = z.infer<typeof scheduleRunsQuerySchema>\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -48,6 +48,8 @@
48
48
  "scheduler.error.unauthorized": "Nicht autorisiert",
49
49
  "scheduler.error.update_failed": "Fehler beim Aktualisieren des Zeitplans",
50
50
  "scheduler.errors.id_required": "Zeitplankennung ist erforderlich.",
51
+ "scheduler.errors.module_target_immutable": "Von Modulen erstellte Zeitpläne besitzen ihr Ziel; nur operative Felder können hier geändert werden.",
52
+ "scheduler.errors.queue_not_approved": "Ziel-Queue ist kein genehmigtes Scheduler-Ziel. Nur Worker, die sich angemeldet haben, können ausgewählt werden.",
51
53
  "scheduler.execution.actions": "Aktionen",
52
54
  "scheduler.execution.completed_message": "Diese Ausführung wurde erfolgreich ohne Fehler abgeschlossen.",
53
55
  "scheduler.execution.details_title": "Ausführungsdetails",
@@ -48,6 +48,8 @@
48
48
  "scheduler.error.unauthorized": "Unauthorized",
49
49
  "scheduler.error.update_failed": "Failed to update schedule",
50
50
  "scheduler.errors.id_required": "Schedule identifier is required.",
51
+ "scheduler.errors.module_target_immutable": "Module-authored schedules own their target; only operational fields can be updated here.",
52
+ "scheduler.errors.queue_not_approved": "Target queue is not an approved scheduler target. Only workers that opted into scheduling can be selected.",
51
53
  "scheduler.execution.actions": "Actions",
52
54
  "scheduler.execution.completed_message": "This execution completed successfully without errors.",
53
55
  "scheduler.execution.details_title": "Execution Details",
@@ -48,6 +48,8 @@
48
48
  "scheduler.error.unauthorized": "No autorizado",
49
49
  "scheduler.error.update_failed": "Error al actualizar la programación",
50
50
  "scheduler.errors.id_required": "El identificador de programación es obligatorio.",
51
+ "scheduler.errors.module_target_immutable": "Los programados creados por módulos poseen su destino; solo los campos operativos pueden modificarse aquí.",
52
+ "scheduler.errors.queue_not_approved": "La cola de destino no es un destino aprobado del planificador. Solo se pueden seleccionar workers que hayan optado.",
51
53
  "scheduler.execution.actions": "Acciones",
52
54
  "scheduler.execution.completed_message": "Esta ejecución se completó con éxito sin errores.",
53
55
  "scheduler.execution.details_title": "Detalles de ejecución",
@@ -48,6 +48,8 @@
48
48
  "scheduler.error.unauthorized": "권한이 없습니다",
49
49
  "scheduler.error.update_failed": "일정 수정에 실패했습니다",
50
50
  "scheduler.errors.id_required": "일정 식별자는 필수입니다.",
51
+ "scheduler.errors.module_target_immutable": "모듈이 생성한 일정은 대상을 소유하므로 여기서는 운영 필드만 수정할 수 있습니다.",
52
+ "scheduler.errors.queue_not_approved": "대상 큐는 승인된 스케줄러 대상이 아닙니다. 옵트인한 워커만 선택할 수 있습니다.",
51
53
  "scheduler.execution.actions": "작업",
52
54
  "scheduler.execution.completed_message": "이 실행은 오류 없이 성공적으로 완료되었습니다.",
53
55
  "scheduler.execution.details_title": "실행 상세 정보",
@@ -48,6 +48,8 @@
48
48
  "scheduler.error.unauthorized": "Brak autoryzacji",
49
49
  "scheduler.error.update_failed": "Nie udało się zaktualizować harmonogramu",
50
50
  "scheduler.errors.id_required": "Identyfikator harmonogramu jest wymagany.",
51
+ "scheduler.errors.module_target_immutable": "Harmonogramy utworzone przez moduły są właścicielami swojego celu; tutaj można zmieniać wyłącznie pola operacyjne.",
52
+ "scheduler.errors.queue_not_approved": "Kolejka docelowa nie jest zatwierdzonym celem harmonogramu. Można wybrać tylko workery, które się zadeklarowały.",
51
53
  "scheduler.execution.actions": "Akcje",
52
54
  "scheduler.execution.completed_message": "To wykonanie zakończyło się pomyślnie bez błędów.",
53
55
  "scheduler.execution.details_title": "Szczegóły wykonania",