@happyvertical/smrt-jobs 0.49.2 → 0.49.4
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.
- package/AGENTS.md +1 -1
- package/README.md +10 -1
- package/dist/chunks/{runner-BsYIDzhc.js → runner-DPSBS7Wf.js} +53 -17
- package/dist/chunks/{runner-BsYIDzhc.js.map → runner-DPSBS7Wf.js.map} +1 -1
- package/dist/durable-payload-integrity.d.ts +16 -0
- package/dist/durable-payload-integrity.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +56 -3
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +391 -20
- package/dist/runner.d.ts +20 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +2 -2
- package/dist/smrt-knowledge.json +6 -6
- package/package.json +7 -7
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/forge-projection.ts","../src/job-handle.ts","../src/job-builder.ts","../src/object-extension.ts","../src/schedule-runner.ts","../src/index.ts"],"sourcesContent":["// Self-register this package's manifest for consumers importing this module\n// directly. See __smrt-register__.ts (issue #1132).\nimport './__smrt-register__.js';\n\nimport { randomUUID } from 'node:crypto';\nimport {\n detectEngine,\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n requireTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { redactErrorForPersistence } from './error-redaction.js';\n\nconst MAX_OBSERVATION_VERSION = 2_147_483_647;\nconst MAX_FORGE_DURATION_MS = 2_147_483_647;\nconst MAX_DATE_TIMESTAMP_MS = 8_640_000_000_000_000;\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\nexport type ForgeDeliveryStatus =\n | 'pending'\n | 'leased'\n | 'retry'\n | 'completed'\n | 'dead_letter';\n\n/**\n * A provider-neutral observation emitted by a forge delivery adapter.\n *\n * `version` is a monotonically increasing provider/domain revision for one\n * `(projection, subjectKey)` pair. Projectors never infer issue semantics from\n * pull requests; applications choose both the projection and subject identity.\n */\nexport interface ForgeObservation<T = unknown> {\n projection: string;\n subjectKey: string;\n version: number;\n value: T;\n}\n\nexport interface ForgeProjectionContext {\n /** Transaction-scoped database. Projection writes must use this handle. */\n db: DatabaseInterface;\n tenantId: string;\n deliveryId: string;\n}\n\nexport interface ForgeProjector<T = unknown> {\n /**\n * Normalize a provider delivery into a tracker-independent observation.\n * Return null when the delivery is intentionally ignored.\n */\n observe(delivery: ForgeDelivery): Promise<ForgeObservation<T> | null>;\n /**\n * Apply the observation. The callback and checkpoint/delivery updates share\n * one database transaction.\n */\n project(\n observation: ForgeObservation<T>,\n context: ForgeProjectionContext,\n ): Promise<void>;\n}\n\nexport type ForgeProjectionAuditEvent =\n | {\n type: 'delivery.accepted' | 'delivery.duplicate';\n tenantId: string;\n deliveryId: string;\n provider: string;\n }\n | {\n type:\n | 'delivery.leased'\n | 'delivery.completed'\n | 'delivery.retry_scheduled'\n | 'delivery.dead_lettered'\n | 'delivery.replayed'\n | 'observation.stale';\n tenantId: string;\n deliveryId: string;\n attempt: number;\n };\n\nexport type ForgeProjectionAuditHook = (\n event: ForgeProjectionAuditEvent,\n) => void | Promise<void>;\n\n@smrt({\n tableName: '_smrt_forge_deliveries',\n conflictColumns: ['tenant_id', 'provider', 'delivery_id'],\n api: false,\n cli: false,\n mcp: false,\n // `claimReady()` scans two OR-branches every poll (#2364, epic #2382\n // finding A3): `status IN ('pending','retry') AND next_attempt_at <= ?`\n // (the common ready-to-run case) and `status = 'leased' AND\n // lease_expires_at < ?` (reclaiming an expired lease — also the predicate\n // of the lease-expiry-to-dead_letter sweep above it in the same method).\n // Neither branch is a prefix of the other, so both composites are\n // declared; PostgreSQL can combine them with a BitmapOr when planning the\n // OR. Roll this out with `smrt db:migrate --postgres-safe` (#2362), same\n // as `_smrt_jobs` above — deliveries are claimed continuously too.\n indexes: [\n {\n name: '_smrt_forge_deliveries_status_next_attempt_at_idx',\n columns: ['status', 'nextAttemptAt'],\n },\n {\n name: '_smrt_forge_deliveries_status_lease_expires_at_idx',\n columns: ['status', 'leaseExpiresAt'],\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class ForgeDelivery extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @field({ type: 'text', required: true })\n provider: string = '';\n\n @field({ type: 'text', required: true })\n deliveryId: string = '';\n\n @field({ type: 'text', nullable: true })\n installationKey: string | null = null;\n\n @field({ type: 'text', nullable: true })\n repositoryKey: string | null = null;\n\n @field({ type: 'text', required: true })\n eventName: string = '';\n\n @field({ type: 'json' })\n payload: Record<string, unknown> = {};\n\n @field({ type: 'text', required: true, default: 'pending' })\n status: ForgeDeliveryStatus = 'pending';\n\n @field({ type: 'integer', required: true, default: 0 })\n attempts: number = 0;\n\n @field({ type: 'integer', required: true, default: 5 })\n maxAttempts: number = 5;\n\n @field({ type: 'datetime', required: true })\n receivedAt: Date = new Date();\n\n @field({ type: 'datetime', required: true })\n nextAttemptAt: Date = new Date();\n\n @field({ type: 'text', nullable: true })\n leaseOwner: string | null = null;\n\n @field({ type: 'text', nullable: true })\n leaseToken: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n leaseExpiresAt: Date | null = null;\n\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n completedAt: Date | null = null;\n\n @field({ type: 'integer', required: true, default: 0 })\n replayCount: number = 0;\n}\n\n@smrt({\n tableName: '_smrt_forge_projection_checkpoints',\n conflictColumns: ['tenant_id', 'projection', 'subject_key'],\n api: false,\n cli: false,\n mcp: false,\n})\n@TenantScoped({ mode: 'required' })\nexport class ForgeProjectionCheckpoint extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @field({ type: 'text', required: true })\n projection: string = '';\n\n @field({ type: 'text', required: true })\n subjectKey: string = '';\n\n // The current portable field contract persists this as a signed 32-bit\n // integer. `assertObservation()` enforces the matching public range.\n @field({ type: 'integer', required: true })\n observationVersion: number = 0;\n\n @field({ type: 'text', required: true })\n deliveryId: string = '';\n\n @field({ type: 'datetime', required: true })\n observedAt: Date = new Date();\n}\n\nexport interface AcceptForgeDeliveryInput {\n provider: string;\n deliveryId: string;\n installationKey?: string | null;\n repositoryKey?: string | null;\n eventName: string;\n payload: Record<string, unknown>;\n maxAttempts?: number;\n receivedAt?: Date;\n}\n\nexport interface AcceptedForgeDelivery {\n delivery: ForgeDelivery;\n accepted: boolean;\n}\n\nexport interface ClaimForgeDeliveryOptions {\n workerId: string;\n leaseMs: number;\n now?: Date;\n}\n\n/**\n * Durable provider delivery inbox.\n *\n * All tenant-facing operations require ambient tenant context. `claimReady`\n * is the sole cross-tenant worker scan and returns the captured tenant so the\n * runtime can restore it before normalization or projection.\n */\n@smrt({\n api: false,\n cli: false,\n mcp: false,\n})\nexport class ForgeDeliveryCollection extends SmrtCollection<ForgeDelivery> {\n static readonly _itemClass = ForgeDelivery;\n\n async accept(\n input: AcceptForgeDeliveryInput,\n audit?: ForgeProjectionAuditHook,\n ): Promise<AcceptedForgeDelivery> {\n const tenant = requireTenant().tenantId;\n assertNonEmpty('provider', input.provider);\n assertNonEmpty('deliveryId', input.deliveryId);\n assertNonEmpty('eventName', input.eventName);\n\n const now = input.receivedAt ?? new Date();\n const id = randomUUID();\n const inserted = await this.db.query(\n `INSERT INTO _smrt_forge_deliveries (\n id, slug, context, tenant_id, provider, delivery_id, installation_key,\n repository_key, event_name, payload, status, attempts, max_attempts,\n received_at, next_attempt_at, replay_count, created_at, updated_at\n ) VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?, ?, 0, ?, ?)\n ON CONFLICT (tenant_id, provider, delivery_id) DO NOTHING\n RETURNING id`,\n id,\n `${input.provider}-${input.deliveryId}`.slice(0, 255),\n tenant,\n input.provider,\n input.deliveryId,\n input.installationKey ?? null,\n input.repositoryKey ?? null,\n input.eventName,\n JSON.stringify(input.payload),\n clampMaxAttempts(input.maxAttempts ?? 5),\n now.toISOString(),\n now.toISOString(),\n now.toISOString(),\n now.toISOString(),\n );\n const accepted = inserted.rows.length === 1;\n const row = await this.query(\n `SELECT * FROM _smrt_forge_deliveries\n WHERE tenant_id = ? AND provider = ? AND delivery_id = ?\n LIMIT 1`,\n [tenant, input.provider, input.deliveryId],\n );\n const delivery = row[0];\n if (!delivery) throw new Error('Forge delivery insert could not be read');\n await audit?.({\n type: accepted ? 'delivery.accepted' : 'delivery.duplicate',\n tenantId: tenant,\n deliveryId: delivery.deliveryId,\n provider: delivery.provider,\n });\n return { delivery, accepted };\n }\n\n async claimReady(\n options: ClaimForgeDeliveryOptions,\n audit?: ForgeProjectionAuditHook,\n ): Promise<ForgeDelivery | null> {\n assertFinitePositive('leaseMs', options.leaseMs);\n const now = options.now ?? new Date();\n const nowIso = now.toISOString();\n const leaseExpiresAt = tryAddDuration(now, options.leaseMs);\n if (!leaseExpiresAt) {\n throw new Error('leaseMs extends past the supported Date range');\n }\n const token = randomUUID();\n const lockClause =\n getDatabaseEngine(this.db) === 'postgres'\n ? ' FOR UPDATE SKIP LOCKED'\n : '';\n\n // An expired final attempt cannot be safely re-run. Move it to the\n // operator-recoverable terminal state before selecting another candidate.\n await this.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'dead_letter',\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = 'worker lease expired',\n updated_at = ?\n WHERE status = 'leased'\n AND lease_expires_at < ?\n AND attempts >= max_attempts`,\n [nowIso, nowIso],\n { allowRawOnTenantScoped: true },\n );\n\n const claimed = await this.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'leased',\n attempts = attempts + 1,\n lease_owner = ?,\n lease_token = ?,\n lease_expires_at = ?,\n updated_at = ?\n WHERE id IN (\n SELECT id\n FROM _smrt_forge_deliveries\n WHERE attempts < max_attempts\n AND (\n (status IN ('pending', 'retry') AND next_attempt_at <= ?)\n OR (status = 'leased' AND lease_expires_at < ?)\n )\n ORDER BY next_attempt_at ASC, received_at ASC, created_at ASC, id ASC\n LIMIT 1${lockClause}\n )\n AND (\n (status IN ('pending', 'retry') AND next_attempt_at <= ?)\n OR (status = 'leased' AND lease_expires_at < ?)\n )\n RETURNING *`,\n [\n options.workerId,\n token,\n leaseExpiresAt,\n nowIso,\n nowIso,\n nowIso,\n nowIso,\n nowIso,\n ],\n { allowRawOnTenantScoped: true },\n );\n const delivery = claimed[0] ?? null;\n if (delivery) {\n await withTenant({ tenantId: delivery.tenantId }, async () => {\n try {\n await audit?.({\n type: 'delivery.leased',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n } catch {\n // The durable lease already committed. Observability failures must\n // not strand it or consume an attempt before its projector runs.\n }\n });\n }\n return delivery;\n }\n\n async replay(id: string, audit?: ForgeProjectionAuditHook): Promise<boolean> {\n const tenant = requireTenant().tenantId;\n const now = new Date().toISOString();\n const result = await this.db.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'pending',\n attempts = 0,\n next_attempt_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = NULL,\n completed_at = NULL,\n replay_count = replay_count + 1,\n updated_at = ?\n WHERE id = ?\n AND tenant_id = ?\n AND status = 'dead_letter'\n RETURNING delivery_id, replay_count`,\n now,\n now,\n id,\n tenant,\n );\n const replayed = result.rows.length === 1;\n if (replayed) {\n const row = result.rows[0] as {\n delivery_id: string;\n replay_count: number;\n };\n await audit?.({\n type: 'delivery.replayed',\n tenantId: tenant,\n deliveryId: row.delivery_id,\n attempt: Number(row.replay_count),\n });\n }\n return replayed;\n }\n}\n\nexport interface ForgeProjectionRuntimeOptions {\n db: DatabaseInterface;\n workerId: string;\n leaseMs?: number;\n retryBaseMs?: number;\n retryMaxMs?: number;\n audit?: ForgeProjectionAuditHook;\n}\n\n/**\n * Claims and projects one delivery at a time.\n *\n * The ownership assertion, application projection, monotonic checkpoint and\n * inbox completion are one transaction. The initial no-op UPDATE takes a row\n * write lock, preventing an expired lease from being reclaimed while a valid\n * projection transaction is still in progress.\n */\nexport class ForgeProjectionRuntime {\n readonly db: DatabaseInterface;\n readonly workerId: string;\n readonly leaseMs: number;\n readonly retryBaseMs: number;\n readonly retryMaxMs: number;\n readonly audit?: ForgeProjectionAuditHook;\n\n constructor(options: ForgeProjectionRuntimeOptions) {\n this.db = options.db;\n this.workerId = options.workerId;\n this.leaseMs = options.leaseMs ?? 30_000;\n this.retryBaseMs = options.retryBaseMs ?? 1_000;\n this.retryMaxMs = options.retryMaxMs ?? 300_000;\n assertFinitePositive('leaseMs', this.leaseMs);\n assertFiniteNonNegative('retryBaseMs', this.retryBaseMs);\n assertFiniteNonNegative('retryMaxMs', this.retryMaxMs);\n this.audit = options.audit;\n }\n\n async processNext(\n projector: ForgeProjector,\n options: { now?: Date } = {},\n ): Promise<ForgeDelivery | null> {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const delivery = await inbox.claimReady(\n {\n workerId: this.workerId,\n leaseMs: this.leaseMs,\n now: options.now,\n },\n this.audit,\n );\n if (!delivery) return null;\n\n await withTenant({ tenantId: delivery.tenantId }, async () => {\n try {\n const observation = await projector.observe(delivery);\n await this.commitOwned(delivery, observation, projector, options.now);\n } catch (error) {\n await this.failOwned(delivery, error, options.now);\n }\n });\n return delivery;\n }\n\n private async commitOwned(\n delivery: ForgeDelivery,\n observation: ForgeObservation | null,\n projector: ForgeProjector,\n now = new Date(),\n ): Promise<void> {\n if (!this.db.transaction) {\n throw new Error(\n 'Forge projection requires a database adapter with transaction()',\n );\n }\n const nowIso = now.toISOString();\n let stale = false;\n await this.db.transaction(async (tx) => {\n const ownership = await tx.query(\n `UPDATE _smrt_forge_deliveries\n SET updated_at = updated_at\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n nowIso,\n );\n if (ownership.rows.length !== 1) {\n throw new Error('Forge delivery lease ownership was lost');\n }\n\n if (observation) {\n assertObservation(observation);\n // Reserve the subject version before invoking application code. This\n // write serializes concurrent first-observation and update races; a\n // stale contender gets no RETURNING row and never applies side effects.\n const reserved = await tx.query(\n `INSERT INTO _smrt_forge_projection_checkpoints (\n id, slug, context, tenant_id, projection, subject_key,\n observation_version, delivery_id, observed_at, created_at,\n updated_at\n ) VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (tenant_id, projection, subject_key) DO UPDATE SET\n observation_version = excluded.observation_version,\n delivery_id = excluded.delivery_id,\n observed_at = excluded.observed_at,\n updated_at = excluded.updated_at\n WHERE _smrt_forge_projection_checkpoints.observation_version\n < excluded.observation_version\n RETURNING id`,\n randomUUID(),\n `${observation.projection}-${observation.subjectKey}`.slice(0, 255),\n delivery.tenantId,\n observation.projection,\n observation.subjectKey,\n observation.version,\n delivery.deliveryId,\n nowIso,\n nowIso,\n nowIso,\n );\n stale = reserved.rows.length === 0;\n if (!stale) {\n await projector.project(observation, {\n db: tx,\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n });\n }\n }\n\n const completed = await tx.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'completed',\n completed_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = NULL,\n updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n nowIso,\n nowIso,\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n nowIso,\n );\n if (completed.rows.length !== 1) {\n throw new Error('Forge delivery completion lost lease ownership');\n }\n });\n await this.audit?.({\n type: stale ? 'observation.stale' : 'delivery.completed',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n }\n\n private async failOwned(\n delivery: ForgeDelivery,\n error: unknown,\n now = new Date(),\n ): Promise<void> {\n const terminal = delivery.attempts >= delivery.maxAttempts;\n const delay = Math.min(\n this.retryMaxMs,\n this.retryBaseMs * 2 ** Math.max(0, delivery.attempts - 1),\n );\n const retryAt = terminal ? now.toISOString() : tryAddDuration(now, delay);\n const scheduleOverflow = retryAt === null;\n const lastError = scheduleOverflow\n ? `${redactErrorForPersistence(error)}; retry schedule exceeds the supported Date range`\n : redactErrorForPersistence(error);\n const result = await this.db.query(\n `UPDATE _smrt_forge_deliveries\n SET status = ?,\n next_attempt_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = ?,\n updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n terminal || scheduleOverflow ? 'dead_letter' : 'retry',\n retryAt ?? now.toISOString(),\n lastError,\n now.toISOString(),\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n now.toISOString(),\n );\n if (result.rows.length === 1) {\n await this.audit?.({\n type:\n terminal || scheduleOverflow\n ? 'delivery.dead_lettered'\n : 'delivery.retry_scheduled',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n }\n }\n}\n\nfunction assertNonEmpty(name: string, value: string): void {\n if (value.trim().length === 0) throw new Error(`${name} must not be empty`);\n}\n\nfunction assertFinitePositive(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`${name} must be a finite positive number`);\n }\n}\n\nfunction assertFiniteNonNegative(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a finite non-negative number`);\n }\n if (value > MAX_FORGE_DURATION_MS) {\n throw new Error(`${name} must not exceed ${MAX_FORGE_DURATION_MS}`);\n }\n}\n\nfunction tryAddDuration(now: Date, durationMs: number): string | null {\n const timestamp = now.getTime() + durationMs;\n if (\n !Number.isFinite(timestamp) ||\n Math.abs(timestamp) > MAX_DATE_TIMESTAMP_MS\n ) {\n return null;\n }\n return new Date(timestamp).toISOString();\n}\n\nfunction assertObservation(observation: ForgeObservation): void {\n assertNonEmpty('observation.projection', observation.projection);\n assertNonEmpty('observation.subjectKey', observation.subjectKey);\n if (\n !Number.isSafeInteger(observation.version) ||\n observation.version < 0 ||\n observation.version > MAX_OBSERVATION_VERSION\n ) {\n throw new Error(\n `observation.version must be an integer from 0 to ${MAX_OBSERVATION_VERSION}`,\n );\n }\n}\n\nfunction getDatabaseEngine(\n db: DatabaseInterface,\n): ReturnType<typeof detectEngine> {\n const dbWithConfig = db as DatabaseWithConfig;\n return detectEngine(\n db.url || dbWithConfig.config?.url || '',\n dbWithConfig.type || dbWithConfig.config?.type,\n );\n}\n\nfunction clampMaxAttempts(value: number): number {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error('maxAttempts must be a positive safe integer');\n }\n return Math.min(value, 100);\n}\n","import type { JobStatus, SmrtJob, SmrtJobCollection } from './smrt-job.js';\n\n/**\n * Options for waiting on a job\n */\nexport interface WaitOptions {\n /** Maximum time to wait in milliseconds */\n timeout?: number;\n /** Polling interval in milliseconds */\n pollInterval?: number;\n}\n\n/**\n * Result from a completed job\n */\nexport interface JobResult<T = unknown> {\n /** Whether the job completed successfully */\n success: boolean;\n /** The result data (if successful) */\n result?: T;\n /** Error message (if failed) */\n error?: string;\n /** Pointer to where the full result is stored */\n resultPointer?: string | null;\n}\n\n/**\n * Handle for tracking and managing a background job\n *\n * This provides a convenient interface for:\n * - Checking job status\n * - Waiting for completion\n * - Canceling the job\n * - Retrying failed jobs\n */\nexport class JobHandle<T = unknown> {\n private readonly collection: SmrtJobCollection;\n\n constructor(\n public readonly id: string,\n collection: SmrtJobCollection,\n ) {\n this.collection = collection;\n }\n\n /**\n * Get the current job status\n */\n async status(): Promise<JobStatus> {\n const job = await this.getJob();\n return job.status;\n }\n\n /**\n * Get the full job object\n */\n async getJob(): Promise<SmrtJob> {\n const job = await this.collection.get({ id: this.id });\n if (!job) {\n throw new Error(`Job not found: ${this.id}`);\n }\n return job;\n }\n\n /**\n * Wait for the job to complete\n *\n * @param options - Wait configuration\n * @returns The job result\n * @throws Error if the job fails or times out\n */\n async wait(options: WaitOptions = {}): Promise<JobResult<T>> {\n const { timeout = 60000, pollInterval = 100 } = options;\n const startTime = Date.now();\n\n while (true) {\n const job = await this.getJob();\n\n if (job.status === 'completed') {\n return {\n success: true,\n resultPointer: job.resultPointer,\n };\n }\n\n if (job.status === 'failed') {\n return {\n success: false,\n error: job.lastError ?? 'Job failed',\n };\n }\n\n if (job.status === 'cancelled') {\n return {\n success: false,\n error: 'Job was cancelled',\n };\n }\n\n // Check timeout\n if (Date.now() - startTime >= timeout) {\n throw new Error(`Timeout waiting for job ${this.id}`);\n }\n\n // Wait before next poll\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n }\n\n /**\n * Cancel the job\n */\n async cancel(): Promise<void> {\n const job = await this.getJob();\n await job.cancel();\n }\n\n /**\n * Retry a failed job\n */\n async retry(): Promise<void> {\n const job = await this.getJob();\n await job.retry();\n }\n\n /**\n * Check if the job is still running\n */\n async isRunning(): Promise<boolean> {\n const status = await this.status();\n return status === 'pending' || status === 'running';\n }\n\n /**\n * Check if the job has completed (successfully or not)\n */\n async isDone(): Promise<boolean> {\n const status = await this.status();\n return (\n status === 'completed' || status === 'failed' || status === 'cancelled'\n );\n }\n}\n\nexport default JobHandle;\n","import {\n exponential,\n type RetryStrategy,\n type RetryStrategyConfig,\n} from '@happyvertical/jobs';\nimport { clampRetries, DEFAULT_TENANT_JOB_CAP } from './background-policy.js';\nimport { JobHandle } from './job-handle.js';\nimport type { SmrtJobCollection, TimeoutBehavior } from './smrt-job.js';\n\n/**\n * Priority levels for jobs\n */\nexport type Priority = 'critical' | 'high' | 'normal' | 'low' | number;\n\n/**\n * Convert priority to numeric value\n */\nexport function priorityToNumber(priority: Priority): number {\n if (typeof priority === 'number') return priority;\n switch (priority) {\n case 'critical':\n return 100;\n case 'high':\n return 75;\n case 'normal':\n return 50;\n case 'low':\n return 25;\n default:\n return 50;\n }\n}\n\n/**\n * Parse delay string to milliseconds\n */\nexport function parseDelay(delay: string | number): number {\n if (typeof delay === 'number') {\n // Guard against NaN/Infinity: a non-finite delay flows into\n // `new Date(Date.now() + delay)`, persisting an `Invalid Date` `runAt`\n // that the claim query can never match (S3 in the #1401 review).\n if (!Number.isFinite(delay)) {\n throw new Error(`Invalid delay value: ${delay}`);\n }\n return delay;\n }\n\n const match = delay.match(/^(\\d+)(ms|s|m|h|d)?$/);\n if (!match) {\n throw new Error(`Invalid delay format: ${delay}`);\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2] || 'ms';\n\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n return value;\n }\n}\n\n/**\n * Fluent builder for creating background jobs\n *\n * Example:\n * ```typescript\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .queue('summaries')\n * .timeout(300000)\n * .enqueue();\n * ```\n */\nexport class JobBuilder<T = unknown> {\n private _queue: string = 'default';\n private _delay: number = 0;\n private _retries: number = 3;\n private _priority: number = 50;\n private _timeout: number = 300000;\n private _timeoutBehavior: TimeoutBehavior = 'fail';\n private _retryStrategy: RetryStrategy = exponential();\n private _tenantJobCap: number = DEFAULT_TENANT_JOB_CAP;\n\n constructor(\n private readonly objectType: string,\n private readonly objectId: string | null,\n private readonly method: string,\n private readonly args: Record<string, unknown>,\n private readonly collection: SmrtJobCollection,\n ) {}\n\n /**\n * Set the queue name\n */\n queue(name: string): this {\n this._queue = name;\n return this;\n }\n\n /**\n * Set a delay before the job runs\n * @param delay - Delay as milliseconds or string like '5m', '1h', '30s'\n */\n delay(delay: string | number): this {\n this._delay = parseDelay(delay);\n return this;\n }\n\n /**\n * Set when the job should run\n */\n runAt(date: Date): this {\n this._delay = date.getTime() - Date.now();\n return this;\n }\n\n /**\n * Set the maximum number of retry attempts.\n *\n * Clamped to {@link MAX_JOB_RETRIES} so a misconfigured caller cannot pin a\n * worker on a poison job indefinitely (S5 audit #1402).\n */\n retries(count: number): this {\n this._retries = clampRetries(count);\n return this;\n }\n\n /**\n * Set the retry strategy\n */\n retryStrategy(strategy: RetryStrategy): this {\n this._retryStrategy = strategy;\n return this;\n }\n\n /**\n * Set the job priority\n */\n priority(level: Priority): this {\n this._priority = priorityToNumber(level);\n return this;\n }\n\n /**\n * Set the job timeout in milliseconds\n */\n timeout(ms: number): this {\n this._timeout = ms;\n return this;\n }\n\n /**\n * Set what happens when the job times out\n */\n timeoutBehavior(behavior: TimeoutBehavior): this {\n this._timeoutBehavior = behavior;\n return this;\n }\n\n /**\n * Override the per-tenant in-flight job cap for this enqueue.\n *\n * Defaults to {@link DEFAULT_TENANT_JOB_CAP}. Pass `0` (or a negative value)\n * to disable the cap for trusted internal callers (S5 audit #1402).\n */\n tenantJobCap(max: number): this {\n this._tenantJobCap = max;\n return this;\n }\n\n /**\n * Enqueue the job and return a handle\n */\n async enqueue(): Promise<JobHandle<T>> {\n const runAt = new Date(Date.now() + this._delay);\n\n const retryConfig: RetryStrategyConfig =\n 'toConfig' in this._retryStrategy\n ? this._retryStrategy.toConfig()\n : (this._retryStrategy as RetryStrategyConfig);\n\n // Route through the collection's single creation path so the per-tenant\n // in-flight cap and the retry ceiling are enforced in one place, shared with\n // the ScheduleRunner (S5 audit #1402). The cap applies to the ambient tenant\n // (resolved inside enqueueJob); global (no-context) jobs are exempt.\n const job = await this.collection.enqueueJob(\n {\n queue: this._queue,\n objectType: this.objectType,\n objectId: this.objectId,\n method: this.method,\n args: this.args,\n runAt,\n priority: this._priority,\n maxAttempts: this._retries,\n timeout: this._timeout,\n timeoutBehavior: this._timeoutBehavior,\n retryStrategy: retryConfig,\n },\n { tenantJobCap: this._tenantJobCap },\n );\n\n const jobId = job.id;\n if (!jobId) {\n throw new Error('Job was created but has no ID');\n }\n\n return new JobHandle<T>(jobId, this.collection);\n }\n}\n\nexport default JobBuilder;\n","import {\n ObjectRegistry,\n type SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n JobBuilder,\n type Priority,\n parseDelay,\n priorityToNumber,\n} from './job-builder.js';\nimport type { JobHandle } from './job-handle.js';\nimport { SmrtJobCollection } from './smrt-job.js';\n\n/**\n * Options for the simple .bg() method\n */\nexport interface BgOptions {\n /** Queue name */\n queue?: string;\n /** Priority level */\n priority?: 'critical' | 'high' | 'normal' | 'low' | number;\n /** Delay before running (ms or string like '5m') */\n delay?: string | number;\n /** Maximum retries */\n retries?: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n/**\n * Type for the extended SmrtObject with background job methods\n */\nexport interface BackgroundCapable {\n /**\n * Simple background job submission\n *\n * @param method - Method name to invoke\n * @param args - Arguments to pass to the method\n * @param options - Job options\n * @returns JobHandle for tracking the job\n *\n * @example\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n */\n bg<T = unknown>(\n method: string,\n args?: Record<string, unknown>,\n options?: BgOptions,\n ): Promise<JobHandle<T>>;\n\n /**\n * Fluent job builder for advanced options\n *\n * @param method - Method name to invoke\n * @param args - Arguments to pass to the method\n * @returns JobBuilder for fluent configuration\n *\n * @example\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .enqueue();\n */\n background<T = unknown>(\n method: string,\n args?: Record<string, unknown>,\n ): JobBuilder<T>;\n}\n\n// Cache for job collections per database\nconst collectionCache = new WeakMap<DatabaseInterface, SmrtJobCollection>();\n\n/**\n * Get or create a job collection for the given database\n */\nasync function getJobCollection(\n db: DatabaseInterface,\n): Promise<SmrtJobCollection> {\n let collection = collectionCache.get(db);\n if (!collection) {\n collection = await SmrtJobCollection.create({\n db: { type: 'sqlite', url: ':memory:' }, // Placeholder\n });\n // Override internal db reference\n (collection as unknown as { _db: DatabaseInterface })._db = db;\n collectionCache.set(db, collection);\n }\n return collection;\n}\n\nfunction getObjectTypeName(instance: SmrtObject): string {\n const metaType = (instance as { _meta_type?: unknown })._meta_type;\n if (typeof metaType === 'string' && metaType.length > 0) {\n return metaType;\n }\n\n const className = instance.constructor.name;\n return ObjectRegistry.getClass(className)?.qualifiedName || className;\n}\n\n// Type for a SmrtObject constructor. `never[]` (not `any[]`) satisfies\n// noExplicitAny while preserving assignability: constructor parameters are\n// checked contravariantly under strictFunctionTypes, and `never` (the bottom\n// type) is assignable to any concrete parameter type, so a typed-options\n// constructor like `new (options?: SmrtObjectOptions) => Doc` still satisfies\n// the constraint — `unknown[]` would NOT, since `unknown` is not assignable to\n// `SmrtObjectOptions`, breaking the documented `withBackgroundJobs(Document)`\n// API for consumers.\ntype SmrtObjectConstructor = new (...args: never[]) => SmrtObject;\ntype BackgroundCapableConstructor<T extends SmrtObjectConstructor> = T & {\n new (...args: ConstructorParameters<T>): InstanceType<T> & BackgroundCapable;\n};\n\n// Compile-time regression guard (#1658, PR #1658): a typed-options SmrtObject\n// subclass constructor — the documented `withBackgroundJobs(Document)` pattern,\n// where a model's ctor is `(options?: SomeOptions)` — MUST satisfy the\n// constraint. A `unknown[]` rest param (instead of `never[]`) makes this\n// resolve to `false` and fails the build, since `unknown` is not assignable to\n// the typed options parameter. Test files are excluded from `tsc`, so this\n// lives in typechecked source rather than a `*.test.ts`.\ntype AssertTrue<T extends true> = T;\ntype _TypedOptionsCtorStaysAssignable = AssertTrue<\n (new (\n options?: SmrtObjectOptions,\n ) => SmrtObject) extends SmrtObjectConstructor\n ? true\n : false\n>;\n\nfunction requireObjectDb(instance: SmrtObject): DatabaseInterface {\n const db = (instance as unknown as { _db?: DatabaseInterface })._db;\n if (!db) {\n throw new Error('Object not initialized. Call initialize() first.');\n }\n\n return db;\n}\n\nasync function bgImpl<R = unknown>(\n this: SmrtObject,\n method: string,\n args: Record<string, unknown> = {},\n options: BgOptions = {},\n): Promise<JobHandle<R>> {\n const db = requireObjectDb(this);\n const collection = await getJobCollection(db);\n const builder = new JobBuilder<R>(\n getObjectTypeName(this),\n this.id ?? null,\n method,\n args,\n collection,\n );\n\n if (options.queue) builder.queue(options.queue);\n if (options.priority) builder.priority(options.priority);\n if (options.delay) builder.delay(options.delay);\n if (options.retries !== undefined) builder.retries(options.retries);\n if (options.timeout) builder.timeout(options.timeout);\n\n return builder.enqueue();\n}\n\nfunction backgroundImpl<R = unknown>(\n this: SmrtObject,\n method: string,\n args: Record<string, unknown> = {},\n): JobBuilder<R> {\n const db = requireObjectDb(this);\n const objectType = getObjectTypeName(this);\n const objectId = this.id ?? null;\n\n // Create a proxy builder that defers collection access until enqueue().\n const lazyBuilder = {\n _queue: 'default',\n _delay: 0,\n _retries: 3,\n _priority: 50,\n _timeout: 300000,\n _timeoutBehavior: 'fail' as 'fail' | 'kill' | 'warn',\n _retryStrategy: null as unknown,\n // `undefined` => fall through to JobBuilder's DEFAULT_TENANT_JOB_CAP. A\n // caller can override (incl. `0` to disable) via tenantJobCap() below.\n _tenantJobCap: undefined as number | undefined,\n\n queue(name: string) {\n this._queue = name;\n return this;\n },\n delay(d: string | number) {\n this._delay = parseDelay(d);\n return this;\n },\n runAt(date: Date) {\n this._delay = date.getTime() - Date.now();\n return this;\n },\n retries(count: number) {\n this._retries = count;\n return this;\n },\n retryStrategy(strategy: unknown) {\n this._retryStrategy = strategy;\n return this;\n },\n priority(level: Priority) {\n this._priority = priorityToNumber(level);\n return this;\n },\n timeout(ms: number) {\n this._timeout = ms;\n return this;\n },\n timeoutBehavior(behavior: 'fail' | 'kill' | 'warn') {\n this._timeoutBehavior = behavior;\n return this;\n },\n tenantJobCap(max: number) {\n this._tenantJobCap = max;\n return this;\n },\n async enqueue(): Promise<JobHandle<R>> {\n const collection = await getJobCollection(db);\n const builder = new JobBuilder<R>(\n objectType,\n objectId,\n method,\n args,\n collection,\n );\n\n builder.queue(this._queue);\n builder.delay(this._delay);\n builder.retries(this._retries);\n builder.priority(this._priority);\n builder.timeout(this._timeout);\n builder.timeoutBehavior(this._timeoutBehavior);\n // Only forward an explicit override; leaving it unset preserves the\n // JobBuilder default (DEFAULT_TENANT_JOB_CAP).\n if (this._tenantJobCap !== undefined) {\n builder.tenantJobCap(this._tenantJobCap);\n }\n\n if (this._retryStrategy) {\n builder.retryStrategy(\n this._retryStrategy as Parameters<typeof builder.retryStrategy>[0],\n );\n }\n\n return builder.enqueue();\n },\n };\n\n return lazyBuilder as unknown as JobBuilder<R>;\n}\n\n/**\n * Extend a SmrtObject class with background job methods\n *\n * @param BaseClass - The SmrtObject class to extend\n * @returns Extended class with .bg() and .background() methods\n *\n * @example\n * const BackgroundDocument = withBackgroundJobs(Document);\n * const doc = new BackgroundDocument({ ... });\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n */\nexport function withBackgroundJobs<T extends SmrtObjectConstructor>(\n BaseClass: T,\n): BackgroundCapableConstructor<T> {\n const prototype = BaseClass.prototype as SmrtObject &\n Partial<BackgroundCapable>;\n\n if (typeof prototype.bg !== 'function') {\n Object.defineProperty(prototype, 'bg', {\n value: bgImpl,\n writable: true,\n configurable: true,\n });\n }\n\n if (typeof prototype.background !== 'function') {\n Object.defineProperty(prototype, 'background', {\n value: backgroundImpl,\n writable: true,\n configurable: true,\n });\n }\n\n return BaseClass as BackgroundCapableConstructor<T>;\n}\n\nexport default withBackgroundJobs;\n","import { EventEmitter } from 'node:events';\nimport { createLogger } from '@happyvertical/logger';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { createId } from '@happyvertical/utils';\nimport {\n redactErrorForPersistence,\n redactErrorMessage,\n} from './error-redaction.js';\nimport { SmrtJobCollection } from './smrt-job.js';\nimport { SmrtWorkerCollection } from './smrt-worker.js';\nimport { DEFAULT_TASK_HEARTBEAT_INTERVAL_MS } from './stale-recovery.js';\nimport { isWorkerAlive } from './worker-liveness.js';\n\n/**\n * ScheduleRunner configuration\n */\nexport interface ScheduleRunnerConfig {\n /** Runner ID (auto-generated if not provided) */\n id?: string;\n /** Polling interval in milliseconds (default: 60000 - 1 minute) */\n pollInterval?: number;\n /** Maximum schedules to process per poll */\n batchSize?: number;\n /**\n * @deprecated No longer used. Slot reconciliation keys on worker liveness\n * (the `_smrt_workers` lease), not per-job heartbeat staleness (#1474).\n */\n staleJobThresholdMs?: number;\n /**\n * @deprecated No longer used. See {@link staleJobThresholdMs}.\n */\n taskHeartbeatInterval?: number;\n}\n\n/**\n * ScheduleRunner events\n */\nexport interface ScheduleRunnerEvents {\n 'schedule:triggered': (schedule: ScheduleInfo) => void;\n 'schedule:error': (schedule: ScheduleInfo, error: Error) => void;\n 'schedule:completed': (scheduleId: string) => void;\n 'schedule:failed': (scheduleId: string, error: string) => void;\n 'runner:started': () => void;\n 'runner:stopped': () => void;\n 'runner:error': (error: Error) => void;\n}\n\n/**\n * Schedule info for events\n */\nexport interface ScheduleInfo {\n id: string;\n agentType: string;\n agentId: string | null;\n cron: string;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<ScheduleRunnerConfig> = {\n id: '',\n pollInterval: 60000, // 1 minute\n batchSize: 50,\n staleJobThresholdMs: 90000,\n taskHeartbeatInterval: DEFAULT_TASK_HEARTBEAT_INTERVAL_MS,\n};\n\n/**\n * ScheduleRunner polls for due agent schedules and creates jobs for them\n *\n * This runner works in conjunction with TaskRunner:\n * 1. ScheduleRunner checks for due schedules based on cron expressions\n * 2. When a schedule is due, it creates a SmrtJob for the agent\n * 3. TaskRunner picks up and executes the job\n * 4. On job completion/failure, call handleJobCompletion() to update the schedule\n *\n * @example\n * ```typescript\n * const scheduleRunner = new ScheduleRunner({ pollInterval: 30000 });\n * await scheduleRunner.initialize(db);\n * await scheduleRunner.start();\n *\n * // Wire up TaskRunner events to update schedule state\n * taskRunner.on('job:completed', (job) => {\n * const scheduleId = job.args?._scheduleId;\n * if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, true);\n * });\n * taskRunner.on('job:failed', (job, error) => {\n * const scheduleId = job.args?._scheduleId;\n * if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, false, error.message);\n * });\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => scheduleRunner.stop());\n * ```\n */\nexport class ScheduleRunner extends EventEmitter {\n readonly id: string;\n private readonly config: Required<ScheduleRunnerConfig>;\n private jobCollection: SmrtJobCollection | null = null;\n private workerCollection: SmrtWorkerCollection | null = null;\n private running = false;\n private pollTimer: NodeJS.Timeout | null = null;\n private db: DatabaseInterface | null = null;\n private logger = createLogger(true);\n\n constructor(config: ScheduleRunnerConfig = {}) {\n super();\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n id: config.id || `schedule_${createId().slice(0, 8)}`,\n };\n this.id = this.config.id;\n }\n\n /**\n * Initialize the runner with database connection\n */\n async initialize(db: DatabaseInterface): Promise<void> {\n this.db = db;\n this.jobCollection = await SmrtJobCollection.create({ db });\n this.workerCollection = await SmrtWorkerCollection.create({ db });\n }\n\n /**\n * Start processing schedules\n */\n async start(): Promise<void> {\n if (this.running) return;\n if (!this.db) {\n throw new Error(\n 'ScheduleRunner not initialized. Call initialize() first.',\n );\n }\n\n this.running = true;\n\n // Start polling loop\n this.startPolling();\n\n this.emit('runner:started');\n this.logger.info('ScheduleRunner started', { id: this.id });\n }\n\n /**\n * Stop processing schedules\n */\n async stop(): Promise<void> {\n if (!this.running) return;\n\n this.running = false;\n\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n this.emit('runner:stopped');\n this.logger.info('ScheduleRunner stopped', { id: this.id });\n }\n\n /**\n * Check if runner is running\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Handle job completion for a scheduled job.\n *\n * Call this from TaskRunner's job:completed / job:failed events\n * when the job has a `_scheduleId` in its args.\n */\n async handleJobCompletion(\n scheduleId: string,\n success: boolean,\n errorMessage?: string,\n ): Promise<void> {\n if (!this.db) return;\n\n // `last_error` is persisted to a durable schedule row; strip secret-shaped\n // substrings the same way the job runner does (S5 audit #1402).\n const safeErrorMessage = redactErrorMessage(\n errorMessage ?? 'Unknown error',\n );\n\n try {\n if (success) {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'success',\n last_error = NULL,\n run_count = COALESCE(run_count, 0) + 1,\n success_count = COALESCE(success_count, 0) + 1\n WHERE id = ?`,\n new Date().toISOString(),\n scheduleId,\n );\n this.emit('schedule:completed', scheduleId);\n } else {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + 1,\n failure_count = COALESCE(failure_count, 0) + 1\n WHERE id = ?`,\n new Date().toISOString(),\n safeErrorMessage,\n scheduleId,\n );\n this.emit('schedule:failed', scheduleId, safeErrorMessage);\n }\n } catch (err) {\n this.logger.error('Failed to update schedule after job completion', {\n scheduleId,\n error: err,\n });\n }\n }\n\n /**\n * Start the polling loop\n */\n private startPolling(): void {\n const poll = async () => {\n if (!this.running) return;\n\n try {\n await this.poll();\n } catch (error) {\n this.emit('runner:error', error as Error);\n this.logger.error('ScheduleRunner poll error', { error });\n }\n\n // Schedule next poll\n if (this.running) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n }\n };\n\n // Start immediately\n poll();\n }\n\n /**\n * Poll for due schedules and create jobs\n */\n private async poll(): Promise<void> {\n if (!this.db || !this.jobCollection) return;\n\n await this.recoverStaleScheduleState();\n\n const now = new Date().toISOString();\n\n // Find due schedules\n const result = await this.db.query(\n `SELECT * FROM _smrt_agent_schedules\n WHERE enabled = true\n AND status = 'active'\n AND next_run <= ?\n AND COALESCE(running_count, 0) < COALESCE(max_concurrent, 1)\n ORDER BY next_run ASC\n LIMIT ?`,\n now,\n this.config.batchSize,\n );\n\n for (const row of result.rows) {\n await this.triggerSchedule(row as ScheduleRow);\n }\n }\n\n /**\n * Reconcile stuck schedule slots against running jobs.\n *\n * This handles two failure modes:\n * - a running job's owning worker is no longer alive (dead/restarted)\n * - a schedule slot remains occupied even though no running job still exists\n *\n * Staleness keys on worker *liveness* (issue #1474), not per-job heartbeat\n * freshness: a job whose `worker_id` is live in this process or holds a fresh\n * lease in `_smrt_workers` is healthy even if its handler is holding the loop\n * synchronously. ScheduleRunner has no in-process active-job set, so this is\n * its entire correctness mechanism.\n */\n private async recoverStaleScheduleState(): Promise<void> {\n if (!this.db || !this.workerCollection) return;\n\n const schedulesResult = await this.db.query(\n `SELECT id, running_count\n FROM _smrt_agent_schedules\n WHERE COALESCE(running_count, 0) > 0`,\n );\n const schedules = schedulesResult.rows as Array<{\n id: string;\n running_count: number;\n }>;\n if (schedules.length === 0) return;\n\n // Without the workers table we cannot reason about liveness; treat every\n // running job as alive (reconcile slot drift only, never fail jobs).\n const workersReady = await this.workerCollection.tableReady();\n const freshLeaseKeys = workersReady\n ? await this.workerCollection.freshLeaseWorkerKeys()\n : new Set<string>();\n\n const jobsResult = await this.db.query(\n `SELECT id, args, worker_id\n FROM _smrt_jobs\n WHERE status = 'running'`,\n );\n const jobRows = jobsResult.rows as Array<{\n id: string;\n args: unknown;\n worker_id: string | null;\n }>;\n\n type ScheduleState = { live: number; staleJobIds: string[] };\n const stateBySchedule = new Map<string, ScheduleState>();\n for (const schedule of schedules) {\n stateBySchedule.set(schedule.id, { live: 0, staleJobIds: [] });\n }\n\n for (const row of jobRows) {\n const scheduleId = this.getScheduleIdFromJobArgs(row.args);\n if (!scheduleId) continue;\n\n const state = stateBySchedule.get(scheduleId);\n if (!state) continue;\n\n const alive = workersReady\n ? isWorkerAlive(row.worker_id, freshLeaseKeys)\n : true;\n\n if (!alive) {\n state.staleJobIds.push(row.id);\n } else {\n state.live += 1;\n }\n }\n\n const now = new Date().toISOString();\n const staleJobIds = schedules.flatMap((schedule) => {\n const state = stateBySchedule.get(schedule.id);\n return state?.staleJobIds ?? [];\n });\n\n // Only the jobs this pass actually transitioned to 'failed' — RETURNING id\n // (not rowCount, which DuckDB/JSON always report as ≥1) so a job another\n // recoverer already failed isn't double-counted into the schedule's\n // run_count/failure_count.\n const recoveredJobIds = new Set<string>();\n if (staleJobIds.length > 0) {\n const placeholders = staleJobIds.map(() => '?').join(', ');\n const result = await this.db.query(\n `UPDATE _smrt_jobs\n SET status = 'failed',\n completed_at = ?,\n last_error = ?,\n worker_id = NULL,\n worker_heartbeat = NULL\n WHERE status = 'running'\n AND id IN (${placeholders})\n RETURNING id`,\n now,\n 'Recovered orphaned scheduled job: its owning worker is no longer ' +\n 'alive (no fresh liveness lease in _smrt_workers and not running in ' +\n 'this process).',\n ...staleJobIds,\n );\n for (const row of result.rows as Array<{ id?: unknown }>) {\n if (typeof row.id === 'string') recoveredJobIds.add(row.id);\n }\n }\n\n for (const schedule of schedules) {\n const state = stateBySchedule.get(schedule.id);\n if (!state) continue;\n\n const desiredRunningCount = state.live;\n const recoveredCount = state.staleJobIds.filter((id) =>\n recoveredJobIds.has(id),\n ).length;\n\n if (\n Number(schedule.running_count) === desiredRunningCount &&\n recoveredCount === 0\n ) {\n continue;\n }\n\n if (recoveredCount > 0) {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = ?,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + ?,\n failure_count = COALESCE(failure_count, 0) + ?\n WHERE id = ?`,\n desiredRunningCount,\n now,\n `Recovered ${recoveredCount} orphaned scheduled job(s) from dead worker(s)`,\n recoveredCount,\n recoveredCount,\n schedule.id,\n );\n this.emit(\n 'schedule:failed',\n schedule.id,\n `Recovered ${recoveredCount} orphaned scheduled job(s)`,\n );\n } else {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = ?\n WHERE id = ?`,\n desiredRunningCount,\n schedule.id,\n );\n }\n }\n }\n\n private getScheduleIdFromJobArgs(args: unknown): string | null {\n if (!args) return null;\n\n let parsedArgs = args;\n if (typeof parsedArgs === 'string') {\n try {\n parsedArgs = JSON.parse(parsedArgs) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n if (\n !parsedArgs ||\n typeof parsedArgs !== 'object' ||\n Array.isArray(parsedArgs)\n ) {\n return null;\n }\n\n const scheduleId = (parsedArgs as Record<string, unknown>)._scheduleId;\n return typeof scheduleId === 'string' && scheduleId.length > 0\n ? scheduleId\n : null;\n }\n\n /**\n * Trigger a schedule by creating a job\n */\n private async triggerSchedule(schedule: ScheduleRow): Promise<void> {\n if (!this.db || !this.jobCollection) return;\n\n const rawAgentType = schedule.agent_type as string;\n const canonicalAgentType =\n ObjectRegistry.getClass(rawAgentType)?.qualifiedName || rawAgentType;\n\n const scheduleInfo: ScheduleInfo = {\n id: schedule.id as string,\n agentType: canonicalAgentType,\n agentId: schedule.agent_id as string | null,\n cron: schedule.cron as string,\n };\n\n try {\n // Parse method_args and agent_config from JSON strings if needed\n let methodArgs: Record<string, unknown> = {};\n if (schedule.method_args) {\n methodArgs =\n typeof schedule.method_args === 'string'\n ? JSON.parse(schedule.method_args as string)\n : (schedule.method_args as Record<string, unknown>);\n }\n let agentConfig: Record<string, unknown> = {};\n if (schedule.agent_config) {\n agentConfig =\n typeof schedule.agent_config === 'string'\n ? JSON.parse(schedule.agent_config as string)\n : (schedule.agent_config as Record<string, unknown>);\n }\n\n // Compute next run time from cron before creating the job\n const nextRun = getNextCronDate(schedule.cron as string);\n\n // Create a job for this schedule\n // Nest agent_config under _agentConfig so TaskRunner can pass it\n // to the agent constructor separately from method args\n const args: Record<string, unknown> = {\n ...methodArgs,\n _scheduleId: schedule.id,\n };\n if (Object.keys(agentConfig).length > 0) {\n args._agentConfig = agentConfig;\n }\n\n // Enqueue the job BEFORE advancing next_run / incrementing running_count.\n // Previously next_run was advanced and running_count incremented first; if\n // enqueueJob then threw (a transient tenant-cap hit or DB blip), the catch\n // disabled the schedule but never rolled next_run back, permanently losing\n // that run slot AND taking the schedule out of the poll until manual\n // re-activation (#4 in the #1401 review). By enqueuing first, a transient\n // failure leaves next_run untouched, so the same due slot is retried on\n // the next poll and the schedule stays active.\n //\n // Route scheduled jobs through the same centralized creation path as the\n // fluent builder so the per-tenant in-flight cap and retry ceiling apply\n // here too — previously a direct create() bypassed both (S5 audit #1402).\n // The schedule's own tenant is passed explicitly so the cap is enforced\n // for the owning tenant even with no ambient context.\n const job = await this.jobCollection.enqueueJob({\n tenantId:\n typeof schedule.tenant_id === 'string' &&\n schedule.tenant_id.length > 0\n ? (schedule.tenant_id as string)\n : null,\n queue: 'agents',\n objectType: canonicalAgentType,\n objectId: schedule.agent_id as string | null,\n method: (schedule.method as string) || 'run',\n args,\n priority: 75, // High priority for scheduled agents\n maxAttempts: 3,\n timeout: (schedule.timeout as number) || 3600000,\n });\n\n // Only now that the job is durably enqueued do we consume the slot:\n // increment running_count and advance next_run in one update.\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET agent_type = ?,\n running_count = running_count + 1,\n next_run = ?\n WHERE id = ?`,\n canonicalAgentType,\n nextRun.toISOString(),\n schedule.id,\n );\n\n this.emit('schedule:triggered', scheduleInfo);\n this.logger.info('Schedule triggered', {\n scheduleId: schedule.id,\n agentType: canonicalAgentType,\n jobId: job.id,\n nextRun: nextRun.toISOString(),\n });\n } catch (error) {\n // The slot was NOT consumed (enqueue runs before the next_run/\n // running_count advance), so there is nothing to roll back: leave\n // next_run and status='active' untouched so the next poll retries the\n // same due slot rather than skipping it or disabling the schedule on a\n // transient failure (#4). Record last_error for operator visibility only.\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET last_error = ?\n WHERE id = ?`,\n // Tolerate non-Error throwables: a thrown string/object has no\n // `.message`, which would otherwise persist an empty `last_error`.\n redactErrorForPersistence(error),\n schedule.id,\n );\n\n this.emit('schedule:error', scheduleInfo, error as Error);\n this.logger.error('Schedule trigger failed', {\n scheduleId: schedule.id,\n error,\n });\n }\n }\n}\n\n/**\n * Database row type for schedule\n */\ninterface ScheduleRow {\n id: unknown;\n agent_type: unknown;\n agent_id: unknown;\n tenant_id: unknown;\n agent_config: unknown;\n cron: unknown;\n method: unknown;\n method_args: unknown;\n timeout: unknown;\n}\n\n// --- Cron helpers (self-contained, no external dependency) ---\n\n/**\n * Inclusive valid range for each cron field, by position.\n * minute, hour, day-of-month, month, day-of-week.\n * Day-of-week accepts 0-7 where both 0 and 7 represent Sunday.\n */\nconst CRON_FIELD_RANGES: ReadonlyArray<{\n name: string;\n min: number;\n max: number;\n}> = [\n { name: 'minute', min: 0, max: 59 },\n { name: 'hour', min: 0, max: 23 },\n { name: 'day-of-month', min: 1, max: 31 },\n { name: 'month', min: 1, max: 12 },\n { name: 'day-of-week', min: 0, max: 7 },\n];\n\n/**\n * Validate that every numeric component of a single cron field falls within\n * the field's inclusive range. Rejects malformed values up front so an\n * out-of-range field (e.g. `minute=70`) fails fast at schedule-trigger time\n * instead of silently scanning ~525k candidate minutes and never matching\n * (S5 audit #1402).\n */\nfunction validateCronField(\n expr: string,\n range: { name: string; min: number; max: number },\n): void {\n if (expr === '*') return;\n\n const reject = (detail: string): never => {\n throw new Error(\n `Invalid cron expression: ${range.name} field \"${expr}\" ${detail} ` +\n `(valid range ${range.min}-${range.max})`,\n );\n };\n\n const assertInRange = (value: number): void => {\n if (!Number.isInteger(value) || value < range.min || value > range.max) {\n reject('is out of range');\n }\n };\n\n for (const term of expr.split(',')) {\n if (term === '') reject('contains an empty value');\n\n let body = term;\n if (body.includes('/')) {\n // Exactly one '/' is valid (`base/step`). `1/2/3` must be rejected, not\n // silently parsed as `1/2` by dropping the trailing segment.\n const stepParts = body.split('/');\n if (stepParts.length !== 2) {\n reject('has malformed step syntax');\n }\n const [rangePart, stepStr] = stepParts;\n const step = Number(stepStr);\n if (!Number.isInteger(step) || step <= 0) {\n reject('has an invalid step');\n }\n body = rangePart;\n if (body === '*') continue;\n }\n\n if (body.includes('-')) {\n // Exactly one '-' is valid (`start-end`). `1-2-3` must be rejected, not\n // silently parsed as `1-2` by dropping the trailing segment.\n const rangeParts = body.split('-');\n if (rangeParts.length !== 2) {\n reject('has malformed range syntax');\n }\n const [startStr, endStr] = rangeParts;\n if (startStr === '' || endStr === '') {\n reject('has an empty range part');\n }\n const start = Number(startStr);\n const end = Number(endStr);\n assertInRange(start);\n assertInRange(end);\n if (start > end) reject('has an inverted range');\n } else {\n assertInRange(Number(body));\n }\n }\n}\n\n/**\n * Validate a standard 5-field cron expression: field count plus per-field\n * value ranges. Throws a descriptive `Error` on the first invalid field.\n *\n * Exposed so callers (and the agents package, which owns schedule creation)\n * can reject a bad cron at write time rather than letting an out-of-range\n * field silently never match (S5 audit #1402).\n *\n * @param cron - The cron expression to validate.\n * @returns The trimmed, whitespace-split fields when valid.\n */\nexport function validateCronExpression(cron: string): string[] {\n const parts = cron.trim().split(/\\s+/);\n if (parts.length !== 5) {\n throw new Error(\n `Invalid cron expression: expected 5 fields, got ${parts.length}`,\n );\n }\n\n parts.forEach((field, index) => {\n validateCronField(field, CRON_FIELD_RANGES[index]);\n });\n\n return parts;\n}\n\n/**\n * Parse a cron expression and get the next run date.\n * Supports standard 5-field cron: minute hour day-of-month month day-of-week\n *\n * Limitations:\n * - Numeric values only (no abbreviated names like JAN, MON)\n * - Day-of-week accepts 0-7 where both 0 and 7 represent Sunday\n *\n * Out-of-range fields are rejected eagerly (see {@link validateCronExpression}).\n *\n * Exported for unit testing of the matching logic (not re-exported from the\n * package index — the public surface is unchanged).\n */\nexport function getNextCronDate(cron: string): Date {\n const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] =\n validateCronExpression(cron);\n\n const now = new Date();\n const candidate = new Date(now);\n candidate.setSeconds(0);\n candidate.setMilliseconds(0);\n\n // Move to next minute at minimum\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n // Standard cron DOM/DOW semantics:\n // When both day-of-month and day-of-week are restricted (not *),\n // a date matches if EITHER condition is met (OR logic).\n const dayIsWildcard = dayExpr === '*';\n const dowIsWildcard = dowExpr === '*';\n\n // Search for next matching date (limit to 1 year)\n const maxIterations = 525600;\n for (let i = 0; i < maxIterations; i++) {\n const dayMatches = matchesCronField(candidate.getDate(), dayExpr);\n // getDay() returns 0 for Sunday; standard cron accepts both 0 and 7\n const dow = candidate.getDay();\n const dowMatches =\n matchesCronField(dow, dowExpr) ||\n (dow === 0 && matchesCronField(7, dowExpr));\n\n let dayOfMonthOrWeekMatches: boolean;\n if (!dayIsWildcard && !dowIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches || dowMatches;\n } else if (!dayIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches;\n } else if (!dowIsWildcard) {\n dayOfMonthOrWeekMatches = dowMatches;\n } else {\n dayOfMonthOrWeekMatches = true;\n }\n\n if (\n matchesCronField(candidate.getMonth() + 1, monthExpr) &&\n dayOfMonthOrWeekMatches &&\n matchesCronField(candidate.getHours(), hourExpr) &&\n matchesCronField(candidate.getMinutes(), minuteExpr)\n ) {\n return candidate;\n }\n\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n throw new Error(`Could not find next run date for cron: ${cron}`);\n}\n\n/**\n * Check if a value matches a cron field expression\n */\nfunction matchesCronField(value: number, expr: string): boolean {\n if (expr === '*') return true;\n\n // Step values (*/5, 0-30/2)\n if (expr.includes('/')) {\n const [range, stepStr] = expr.split('/');\n const step = parseInt(stepStr, 10);\n if (range === '*') return value % step === 0;\n if (range.includes('-')) {\n const [startStr, endStr] = range.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n if (value < start || value > end) return false;\n return (value - start) % step === 0;\n }\n }\n\n // Ranges (1-5)\n if (expr.includes('-')) {\n const [startStr, endStr] = expr.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n return value >= start && value <= end;\n }\n\n // Lists (1,3,5)\n if (expr.includes(',')) {\n const values = expr.split(',').map((v) => parseInt(v.trim(), 10));\n return values.includes(value);\n }\n\n // Exact match\n return value === parseInt(expr, 10);\n}\n\n/**\n * Create a ScheduleRunner instance\n */\nexport function createScheduleRunner(\n config?: ScheduleRunnerConfig,\n): ScheduleRunner {\n return new ScheduleRunner(config);\n}\n\nexport default ScheduleRunner;\n","/**\n * @happyvertical/smrt-jobs\n *\n * Background job execution for SMRT objects.\n * Provides persistence, retries, and scheduling for any SmrtObject method.\n *\n * @example\n * ```typescript\n * import { withBackgroundJobs, TaskRunner } from '@happyvertical/smrt-jobs';\n *\n * // Add background capabilities to your class\n * const BackgroundDocument = withBackgroundJobs(Document);\n * const doc = new BackgroundDocument({ ... });\n *\n * // Simple background execution\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n *\n * // Fluent builder for advanced options\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .enqueue();\n *\n * // Wait for result\n * const result = await handle.wait();\n *\n * // Start a task runner to process jobs\n * const runner = new TaskRunner({ concurrency: 5 });\n * await runner.initialize(db);\n * await runner.start();\n * ```\n */\n\n// Self-register this package's manifest before any @smrt() decorator fires\n// downstream. Must come first so the side effect runs ahead of the class\n// module loads below. See __smrt-register__.ts for issue #1132 context.\nimport './__smrt-register__.js';\n// Contribute `_smrt_jobs`/`_smrt_job_events` retention to the framework sweep\n// as soon as this package is loaded (#2375), so `smrt db:prune` and any host\n// process see the tasks without an app-level wiring step. Registering is not\n// scheduling: nothing deletes until something runs a sweep, and the policy can\n// still disable either task by name.\nimport { registerJobRetentionTasks } from './retention.js';\n\nregisterJobRetentionTasks();\n\n// Background-job policy controls (retry ceiling, per-tenant cap, opt-in\n// method allowlist). Shared with the agents package (S5 audit #1402).\nexport {\n assertWithinTenantCreationCap,\n type BackgroundEligibleClass,\n backgroundEligible,\n clampRetries,\n DEFAULT_TENANT_JOB_CAP,\n getBackgroundEligibleMethods,\n isBackgroundEligibleMethod,\n MAX_JOB_RETRIES,\n markBackgroundEligible,\n TenantJobCapExceededError,\n} from './background-policy.js';\n// Error-message redaction for durable job error persistence.\nexport {\n redactErrorForPersistence,\n redactErrorMessage,\n} from './error-redaction.js';\n// Durable provider-neutral forge delivery inbox and projection runtime.\nexport {\n type AcceptedForgeDelivery,\n type AcceptForgeDeliveryInput,\n type ClaimForgeDeliveryOptions,\n ForgeDelivery,\n ForgeDeliveryCollection,\n type ForgeDeliveryStatus,\n type ForgeObservation,\n type ForgeProjectionAuditEvent,\n type ForgeProjectionAuditHook,\n ForgeProjectionCheckpoint,\n type ForgeProjectionContext,\n ForgeProjectionRuntime,\n type ForgeProjectionRuntimeOptions,\n type ForgeProjector,\n} from './forge-projection.js';\n// Fluent job builder and utilities\nexport {\n JobBuilder,\n type Priority,\n parseDelay,\n priorityToNumber,\n} from './job-builder.js';\n\n// Job handle for tracking\nexport {\n JobHandle,\n type JobResult,\n type WaitOptions,\n} from './job-handle.js';\n// Logger extension\nexport {\n type JobContext,\n JobContextLogger,\n type JobEventInput,\n type JobExecutionContext,\n type JobProgressInput,\n} from './logger-extension.js';\n// Durable MCP Tasks extension adapter.\nexport {\n type CreateMcpTaskInput,\n createMcpTaskResult,\n getMcpTaskMarker,\n MCP_TASKS_EXTENSION,\n type McpTask,\n McpTaskCancelledError,\n type McpTaskJobMarker,\n McpTaskNotFoundError,\n type McpTaskStatus,\n McpTaskStore,\n requestMcpTaskInput,\n taskFromJob,\n} from './mcp-task.js';\n// Object extension (mixin)\nexport {\n type BackgroundCapable,\n type BgOptions,\n withBackgroundJobs,\n} from './object-extension.js';\n// Job retention — bounded growth for _smrt_jobs / _smrt_job_events (#2375)\nexport {\n DEFAULT_JOB_RETENTION,\n DEFAULT_RETENTION_SWEEP_INTERVAL_MS,\n JOB_EVENTS_RETENTION_TASK,\n JOBS_RETENTION_TASK,\n type JobRetentionOptions,\n type RetentionSweeper,\n type RetentionSweeperOptions,\n registerJobRetentionTasks,\n startRetentionSweeper,\n unregisterJobRetentionTasks,\n} from './retention.js';\n// Task runner\nexport {\n createTaskRunner,\n JobTimeoutError,\n TaskRunner,\n type TaskRunnerConfig,\n type TaskRunnerEvents,\n} from './runner.js';\n\n// Schedule runner (for cron-based agent scheduling)\nexport {\n createScheduleRunner,\n getNextCronDate,\n type ScheduleInfo,\n ScheduleRunner,\n type ScheduleRunnerConfig,\n type ScheduleRunnerEvents,\n validateCronExpression,\n} from './schedule-runner.js';\n// Core job model\nexport {\n type ClaimReadyOptions,\n type JobStatus,\n type ListReadyOptions,\n SmrtJob,\n SmrtJobCollection,\n type SmrtJobData,\n type TimeoutBehavior,\n} from './smrt-job.js';\n\n// Durable job telemetry\nexport {\n type JobEventCursor,\n type ListJobEventsOptions,\n SmrtJobEvent,\n SmrtJobEventCollection,\n type SmrtJobEventData,\n type SmrtJobEventLevel,\n type SmrtJobEventType,\n} from './smrt-job-event.js';\n\n// Worker liveness lease model\nexport {\n type RegisterWorkerInput,\n SmrtWorker,\n SmrtWorkerCollection,\n} from './smrt-worker.js';\nexport {\n createWorkerKey,\n isWorkerAlive,\n registerLiveWorker,\n unregisterLiveWorker,\n} from './worker-liveness.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["result"],"sources":["../src/durable-payload-integrity.ts","../src/forge-projection.ts","../src/job-handle.ts","../src/job-builder.ts","../src/object-extension.ts","../src/schedule-runner.ts","../src/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport interface DurableJobPayloadIntegrity {\n version: 1;\n keyId: string;\n signature: string;\n}\n\nexport interface DurableJobPayloadSigner {\n readonly keyId: string;\n sign(payload: unknown): DurableJobPayloadIntegrity;\n verify(payload: unknown, integrity: DurableJobPayloadIntegrity): boolean;\n}\n\n/** Create a deterministic HMAC signer for JSON job payloads. The key is never persisted. */\nexport function createHmacDurableJobPayloadSigner(options: {\n keyId: string;\n key: string | Uint8Array;\n}): DurableJobPayloadSigner {\n if (!options.keyId || options.keyId.length > 256) {\n throw new Error(\n 'Durable job integrity keyId must contain 1-256 characters',\n );\n }\n const keyLength =\n typeof options.key === 'string'\n ? Buffer.byteLength(options.key)\n : options.key.byteLength;\n if (keyLength < 32) {\n throw new Error('Durable job integrity key must contain at least 32 bytes');\n }\n const signature = (payload: unknown) =>\n createHmac('sha256', options.key)\n .update(canonicalJson(payload))\n .digest('base64url');\n return Object.freeze({\n keyId: options.keyId,\n sign(payload: unknown): DurableJobPayloadIntegrity {\n return {\n version: 1,\n keyId: options.keyId,\n signature: signature(payload),\n };\n },\n verify(payload: unknown, integrity: DurableJobPayloadIntegrity): boolean {\n if (\n integrity?.version !== 1 ||\n integrity.keyId !== options.keyId ||\n typeof integrity.signature !== 'string'\n ) {\n return false;\n }\n const expected = Buffer.from(signature(payload));\n const actual = Buffer.from(integrity.signature);\n return (\n actual.length === expected.length && timingSafeEqual(actual, expected)\n );\n },\n });\n}\n\nfunction canonicalJson(value: unknown): string {\n // Jobs persist `args` as JSON. Normalize through that exact representation\n // before sorting keys so a value with JSON serialization (notably Date) is\n // signed identically before enqueue and after the worker reloads the row.\n let persisted: string | undefined;\n try {\n persisted = JSON.stringify(value);\n } catch {\n throw new Error('Durable job integrity payload must be acyclic JSON');\n }\n if (persisted === undefined) {\n throw new Error('Durable job integrity payload must be JSON-serializable');\n }\n\n const seen = new Set<object>();\n const canonicalize = (item: unknown, inArray = false): unknown => {\n if (\n item === null ||\n typeof item === 'string' ||\n typeof item === 'boolean'\n ) {\n return item;\n }\n if (typeof item === 'number') return Number.isFinite(item) ? item : null;\n if (\n item === undefined ||\n typeof item === 'function' ||\n typeof item === 'symbol'\n ) {\n return inArray ? null : undefined;\n }\n if (typeof item !== 'object' || seen.has(item)) {\n throw new Error('Durable job integrity payload must be acyclic JSON');\n }\n seen.add(item);\n if (Array.isArray(item)) {\n const result = item.map((entry) => canonicalize(entry, true));\n seen.delete(item);\n return result;\n }\n // JSON may contain an own `__proto__` property. A normal object would invoke\n // Object.prototype's legacy prototype setter here and omit that property\n // from the signed representation.\n const result: Record<string, unknown> = Object.create(null);\n for (const key of Object.keys(item as Record<string, unknown>).sort()) {\n const entry = canonicalize((item as Record<string, unknown>)[key]);\n if (entry !== undefined) result[key] = entry;\n }\n seen.delete(item);\n return result;\n };\n return JSON.stringify(canonicalize(JSON.parse(persisted)));\n}\n","// Self-register this package's manifest for consumers importing this module\n// directly. See __smrt-register__.ts (issue #1132).\nimport './__smrt-register__.js';\n\nimport { randomUUID } from 'node:crypto';\nimport {\n detectEngine,\n field,\n SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport {\n requireTenant,\n TenantScoped,\n tenantId,\n withTenant,\n} from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { redactErrorForPersistence } from './error-redaction.js';\n\nconst MAX_OBSERVATION_VERSION = 2_147_483_647;\nconst MAX_FORGE_DURATION_MS = 2_147_483_647;\nconst MAX_DATE_TIMESTAMP_MS = 8_640_000_000_000_000;\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\nexport type ForgeDeliveryStatus =\n | 'pending'\n | 'leased'\n | 'retry'\n | 'completed'\n | 'dead_letter';\n\n/**\n * A provider-neutral observation emitted by a forge delivery adapter.\n *\n * `version` is a monotonically increasing provider/domain revision for one\n * `(projection, subjectKey)` pair. Projectors never infer issue semantics from\n * pull requests; applications choose both the projection and subject identity.\n */\nexport interface ForgeObservation<T = unknown> {\n projection: string;\n subjectKey: string;\n version: number;\n value: T;\n}\n\nexport interface ForgeProjectionContext {\n /** Transaction-scoped database. Projection writes must use this handle. */\n db: DatabaseInterface;\n tenantId: string;\n deliveryId: string;\n}\n\nexport interface ForgeProjector<T = unknown> {\n /**\n * Normalize a provider delivery into a tracker-independent observation.\n * Return null when the delivery is intentionally ignored.\n */\n observe(delivery: ForgeDelivery): Promise<ForgeObservation<T> | null>;\n /**\n * Apply the observation. The callback and checkpoint/delivery updates share\n * one database transaction.\n */\n project(\n observation: ForgeObservation<T>,\n context: ForgeProjectionContext,\n ): Promise<void>;\n}\n\nexport type ForgeProjectionAuditEvent =\n | {\n type: 'delivery.accepted' | 'delivery.duplicate';\n tenantId: string;\n deliveryId: string;\n provider: string;\n }\n | {\n type:\n | 'delivery.leased'\n | 'delivery.completed'\n | 'delivery.retry_scheduled'\n | 'delivery.dead_lettered'\n | 'delivery.replayed'\n | 'observation.stale';\n tenantId: string;\n deliveryId: string;\n attempt: number;\n };\n\nexport type ForgeProjectionAuditHook = (\n event: ForgeProjectionAuditEvent,\n) => void | Promise<void>;\n\n@smrt({\n tableName: '_smrt_forge_deliveries',\n conflictColumns: ['tenant_id', 'provider', 'delivery_id'],\n api: false,\n cli: false,\n mcp: false,\n // `claimReady()` scans two OR-branches every poll (#2364, epic #2382\n // finding A3): `status IN ('pending','retry') AND next_attempt_at <= ?`\n // (the common ready-to-run case) and `status = 'leased' AND\n // lease_expires_at < ?` (reclaiming an expired lease — also the predicate\n // of the lease-expiry-to-dead_letter sweep above it in the same method).\n // Neither branch is a prefix of the other, so both composites are\n // declared; PostgreSQL can combine them with a BitmapOr when planning the\n // OR. Roll this out with `smrt db:migrate --postgres-safe` (#2362), same\n // as `_smrt_jobs` above — deliveries are claimed continuously too.\n indexes: [\n {\n name: '_smrt_forge_deliveries_status_next_attempt_at_idx',\n columns: ['status', 'nextAttemptAt'],\n },\n {\n name: '_smrt_forge_deliveries_status_lease_expires_at_idx',\n columns: ['status', 'leaseExpiresAt'],\n },\n ],\n})\n@TenantScoped({ mode: 'required' })\nexport class ForgeDelivery extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @field({ type: 'text', required: true })\n provider: string = '';\n\n @field({ type: 'text', required: true })\n deliveryId: string = '';\n\n @field({ type: 'text', nullable: true })\n installationKey: string | null = null;\n\n @field({ type: 'text', nullable: true })\n repositoryKey: string | null = null;\n\n @field({ type: 'text', required: true })\n eventName: string = '';\n\n @field({ type: 'json' })\n payload: Record<string, unknown> = {};\n\n @field({ type: 'text', required: true, default: 'pending' })\n status: ForgeDeliveryStatus = 'pending';\n\n @field({ type: 'integer', required: true, default: 0 })\n attempts: number = 0;\n\n @field({ type: 'integer', required: true, default: 5 })\n maxAttempts: number = 5;\n\n @field({ type: 'datetime', required: true })\n receivedAt: Date = new Date();\n\n @field({ type: 'datetime', required: true })\n nextAttemptAt: Date = new Date();\n\n @field({ type: 'text', nullable: true })\n leaseOwner: string | null = null;\n\n @field({ type: 'text', nullable: true })\n leaseToken: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n leaseExpiresAt: Date | null = null;\n\n @field({ type: 'text', nullable: true })\n lastError: string | null = null;\n\n @field({ type: 'datetime', nullable: true })\n completedAt: Date | null = null;\n\n @field({ type: 'integer', required: true, default: 0 })\n replayCount: number = 0;\n}\n\n@smrt({\n tableName: '_smrt_forge_projection_checkpoints',\n conflictColumns: ['tenant_id', 'projection', 'subject_key'],\n api: false,\n cli: false,\n mcp: false,\n})\n@TenantScoped({ mode: 'required' })\nexport class ForgeProjectionCheckpoint extends SmrtObject {\n @tenantId()\n tenantId: string = '';\n\n @field({ type: 'text', required: true })\n projection: string = '';\n\n @field({ type: 'text', required: true })\n subjectKey: string = '';\n\n // The current portable field contract persists this as a signed 32-bit\n // integer. `assertObservation()` enforces the matching public range.\n @field({ type: 'integer', required: true })\n observationVersion: number = 0;\n\n @field({ type: 'text', required: true })\n deliveryId: string = '';\n\n @field({ type: 'datetime', required: true })\n observedAt: Date = new Date();\n}\n\nexport interface AcceptForgeDeliveryInput {\n provider: string;\n deliveryId: string;\n installationKey?: string | null;\n repositoryKey?: string | null;\n eventName: string;\n payload: Record<string, unknown>;\n maxAttempts?: number;\n receivedAt?: Date;\n}\n\nexport interface AcceptedForgeDelivery {\n delivery: ForgeDelivery;\n accepted: boolean;\n}\n\nexport interface ClaimForgeDeliveryOptions {\n workerId: string;\n leaseMs: number;\n now?: Date;\n}\n\n/**\n * Durable provider delivery inbox.\n *\n * All tenant-facing operations require ambient tenant context. `claimReady`\n * is the sole cross-tenant worker scan and returns the captured tenant so the\n * runtime can restore it before normalization or projection.\n */\n@smrt({\n api: false,\n cli: false,\n mcp: false,\n})\nexport class ForgeDeliveryCollection extends SmrtCollection<ForgeDelivery> {\n static readonly _itemClass = ForgeDelivery;\n\n async accept(\n input: AcceptForgeDeliveryInput,\n audit?: ForgeProjectionAuditHook,\n ): Promise<AcceptedForgeDelivery> {\n const tenant = requireTenant().tenantId;\n assertNonEmpty('provider', input.provider);\n assertNonEmpty('deliveryId', input.deliveryId);\n assertNonEmpty('eventName', input.eventName);\n\n const now = input.receivedAt ?? new Date();\n const id = randomUUID();\n const inserted = await this.db.query(\n `INSERT INTO _smrt_forge_deliveries (\n id, slug, context, tenant_id, provider, delivery_id, installation_key,\n repository_key, event_name, payload, status, attempts, max_attempts,\n received_at, next_attempt_at, replay_count, created_at, updated_at\n ) VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, 'pending', 0, ?, ?, ?, 0, ?, ?)\n ON CONFLICT (tenant_id, provider, delivery_id) DO NOTHING\n RETURNING id`,\n id,\n `${input.provider}-${input.deliveryId}`.slice(0, 255),\n tenant,\n input.provider,\n input.deliveryId,\n input.installationKey ?? null,\n input.repositoryKey ?? null,\n input.eventName,\n JSON.stringify(input.payload),\n clampMaxAttempts(input.maxAttempts ?? 5),\n now.toISOString(),\n now.toISOString(),\n now.toISOString(),\n now.toISOString(),\n );\n const accepted = inserted.rows.length === 1;\n const row = await this.query(\n `SELECT * FROM _smrt_forge_deliveries\n WHERE tenant_id = ? AND provider = ? AND delivery_id = ?\n LIMIT 1`,\n [tenant, input.provider, input.deliveryId],\n );\n const delivery = row[0];\n if (!delivery) throw new Error('Forge delivery insert could not be read');\n await audit?.({\n type: accepted ? 'delivery.accepted' : 'delivery.duplicate',\n tenantId: tenant,\n deliveryId: delivery.deliveryId,\n provider: delivery.provider,\n });\n return { delivery, accepted };\n }\n\n async claimReady(\n options: ClaimForgeDeliveryOptions,\n audit?: ForgeProjectionAuditHook,\n ): Promise<ForgeDelivery | null> {\n assertFinitePositive('leaseMs', options.leaseMs);\n const now = options.now ?? new Date();\n const nowIso = now.toISOString();\n const leaseExpiresAt = tryAddDuration(now, options.leaseMs);\n if (!leaseExpiresAt) {\n throw new Error('leaseMs extends past the supported Date range');\n }\n const token = randomUUID();\n const lockClause =\n getDatabaseEngine(this.db) === 'postgres'\n ? ' FOR UPDATE SKIP LOCKED'\n : '';\n\n // An expired final attempt cannot be safely re-run. Move it to the\n // operator-recoverable terminal state before selecting another candidate.\n await this.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'dead_letter',\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = 'worker lease expired',\n updated_at = ?\n WHERE status = 'leased'\n AND lease_expires_at < ?\n AND attempts >= max_attempts`,\n [nowIso, nowIso],\n { allowRawOnTenantScoped: true },\n );\n\n const claimed = await this.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'leased',\n attempts = attempts + 1,\n lease_owner = ?,\n lease_token = ?,\n lease_expires_at = ?,\n updated_at = ?\n WHERE id IN (\n SELECT id\n FROM _smrt_forge_deliveries\n WHERE attempts < max_attempts\n AND (\n (status IN ('pending', 'retry') AND next_attempt_at <= ?)\n OR (status = 'leased' AND lease_expires_at < ?)\n )\n ORDER BY next_attempt_at ASC, received_at ASC, created_at ASC, id ASC\n LIMIT 1${lockClause}\n )\n AND (\n (status IN ('pending', 'retry') AND next_attempt_at <= ?)\n OR (status = 'leased' AND lease_expires_at < ?)\n )\n RETURNING *`,\n [\n options.workerId,\n token,\n leaseExpiresAt,\n nowIso,\n nowIso,\n nowIso,\n nowIso,\n nowIso,\n ],\n { allowRawOnTenantScoped: true },\n );\n const delivery = claimed[0] ?? null;\n if (delivery) {\n await withTenant({ tenantId: delivery.tenantId }, async () => {\n try {\n await audit?.({\n type: 'delivery.leased',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n } catch {\n // The durable lease already committed. Observability failures must\n // not strand it or consume an attempt before its projector runs.\n }\n });\n }\n return delivery;\n }\n\n async replay(id: string, audit?: ForgeProjectionAuditHook): Promise<boolean> {\n const tenant = requireTenant().tenantId;\n const now = new Date().toISOString();\n const result = await this.db.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'pending',\n attempts = 0,\n next_attempt_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = NULL,\n completed_at = NULL,\n replay_count = replay_count + 1,\n updated_at = ?\n WHERE id = ?\n AND tenant_id = ?\n AND status = 'dead_letter'\n RETURNING delivery_id, replay_count`,\n now,\n now,\n id,\n tenant,\n );\n const replayed = result.rows.length === 1;\n if (replayed) {\n const row = result.rows[0] as {\n delivery_id: string;\n replay_count: number;\n };\n await audit?.({\n type: 'delivery.replayed',\n tenantId: tenant,\n deliveryId: row.delivery_id,\n attempt: Number(row.replay_count),\n });\n }\n return replayed;\n }\n}\n\nexport interface ForgeProjectionRuntimeOptions {\n db: DatabaseInterface;\n workerId: string;\n leaseMs?: number;\n retryBaseMs?: number;\n retryMaxMs?: number;\n audit?: ForgeProjectionAuditHook;\n}\n\n/**\n * Claims and projects one delivery at a time.\n *\n * The ownership assertion, application projection, monotonic checkpoint and\n * inbox completion are one transaction. The initial no-op UPDATE takes a row\n * write lock, preventing an expired lease from being reclaimed while a valid\n * projection transaction is still in progress.\n */\nexport class ForgeProjectionRuntime {\n readonly db: DatabaseInterface;\n readonly workerId: string;\n readonly leaseMs: number;\n readonly retryBaseMs: number;\n readonly retryMaxMs: number;\n readonly audit?: ForgeProjectionAuditHook;\n\n constructor(options: ForgeProjectionRuntimeOptions) {\n this.db = options.db;\n this.workerId = options.workerId;\n this.leaseMs = options.leaseMs ?? 30_000;\n this.retryBaseMs = options.retryBaseMs ?? 1_000;\n this.retryMaxMs = options.retryMaxMs ?? 300_000;\n assertFinitePositive('leaseMs', this.leaseMs);\n assertFiniteNonNegative('retryBaseMs', this.retryBaseMs);\n assertFiniteNonNegative('retryMaxMs', this.retryMaxMs);\n this.audit = options.audit;\n }\n\n async processNext(\n projector: ForgeProjector,\n options: { now?: Date } = {},\n ): Promise<ForgeDelivery | null> {\n const inbox = await ForgeDeliveryCollection.create({ db: this.db });\n const delivery = await inbox.claimReady(\n {\n workerId: this.workerId,\n leaseMs: this.leaseMs,\n now: options.now,\n },\n this.audit,\n );\n if (!delivery) return null;\n\n await withTenant({ tenantId: delivery.tenantId }, async () => {\n try {\n const observation = await projector.observe(delivery);\n await this.commitOwned(delivery, observation, projector, options.now);\n } catch (error) {\n await this.failOwned(delivery, error, options.now);\n }\n });\n return delivery;\n }\n\n private async commitOwned(\n delivery: ForgeDelivery,\n observation: ForgeObservation | null,\n projector: ForgeProjector,\n now = new Date(),\n ): Promise<void> {\n if (!this.db.transaction) {\n throw new Error(\n 'Forge projection requires a database adapter with transaction()',\n );\n }\n const nowIso = now.toISOString();\n let stale = false;\n await this.db.transaction(async (tx) => {\n const ownership = await tx.query(\n `UPDATE _smrt_forge_deliveries\n SET updated_at = updated_at\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n nowIso,\n );\n if (ownership.rows.length !== 1) {\n throw new Error('Forge delivery lease ownership was lost');\n }\n\n if (observation) {\n assertObservation(observation);\n // Reserve the subject version before invoking application code. This\n // write serializes concurrent first-observation and update races; a\n // stale contender gets no RETURNING row and never applies side effects.\n const reserved = await tx.query(\n `INSERT INTO _smrt_forge_projection_checkpoints (\n id, slug, context, tenant_id, projection, subject_key,\n observation_version, delivery_id, observed_at, created_at,\n updated_at\n ) VALUES (?, ?, '', ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (tenant_id, projection, subject_key) DO UPDATE SET\n observation_version = excluded.observation_version,\n delivery_id = excluded.delivery_id,\n observed_at = excluded.observed_at,\n updated_at = excluded.updated_at\n WHERE _smrt_forge_projection_checkpoints.observation_version\n < excluded.observation_version\n RETURNING id`,\n randomUUID(),\n `${observation.projection}-${observation.subjectKey}`.slice(0, 255),\n delivery.tenantId,\n observation.projection,\n observation.subjectKey,\n observation.version,\n delivery.deliveryId,\n nowIso,\n nowIso,\n nowIso,\n );\n stale = reserved.rows.length === 0;\n if (!stale) {\n await projector.project(observation, {\n db: tx,\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n });\n }\n }\n\n const completed = await tx.query(\n `UPDATE _smrt_forge_deliveries\n SET status = 'completed',\n completed_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = NULL,\n updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n nowIso,\n nowIso,\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n nowIso,\n );\n if (completed.rows.length !== 1) {\n throw new Error('Forge delivery completion lost lease ownership');\n }\n });\n await this.audit?.({\n type: stale ? 'observation.stale' : 'delivery.completed',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n }\n\n private async failOwned(\n delivery: ForgeDelivery,\n error: unknown,\n now = new Date(),\n ): Promise<void> {\n const terminal = delivery.attempts >= delivery.maxAttempts;\n const delay = Math.min(\n this.retryMaxMs,\n this.retryBaseMs * 2 ** Math.max(0, delivery.attempts - 1),\n );\n const retryAt = terminal ? now.toISOString() : tryAddDuration(now, delay);\n const scheduleOverflow = retryAt === null;\n const lastError = scheduleOverflow\n ? `${redactErrorForPersistence(error)}; retry schedule exceeds the supported Date range`\n : redactErrorForPersistence(error);\n const result = await this.db.query(\n `UPDATE _smrt_forge_deliveries\n SET status = ?,\n next_attempt_at = ?,\n lease_owner = NULL,\n lease_token = NULL,\n lease_expires_at = NULL,\n last_error = ?,\n updated_at = ?\n WHERE id = ? AND tenant_id = ? AND status = 'leased'\n AND lease_token = ?\n AND lease_expires_at >= ?\n RETURNING id`,\n terminal || scheduleOverflow ? 'dead_letter' : 'retry',\n retryAt ?? now.toISOString(),\n lastError,\n now.toISOString(),\n delivery.id,\n delivery.tenantId,\n delivery.leaseToken,\n now.toISOString(),\n );\n if (result.rows.length === 1) {\n await this.audit?.({\n type:\n terminal || scheduleOverflow\n ? 'delivery.dead_lettered'\n : 'delivery.retry_scheduled',\n tenantId: delivery.tenantId,\n deliveryId: delivery.deliveryId,\n attempt: delivery.attempts,\n });\n }\n }\n}\n\nfunction assertNonEmpty(name: string, value: string): void {\n if (value.trim().length === 0) throw new Error(`${name} must not be empty`);\n}\n\nfunction assertFinitePositive(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new Error(`${name} must be a finite positive number`);\n }\n}\n\nfunction assertFiniteNonNegative(name: string, value: number): void {\n if (!Number.isFinite(value) || value < 0) {\n throw new Error(`${name} must be a finite non-negative number`);\n }\n if (value > MAX_FORGE_DURATION_MS) {\n throw new Error(`${name} must not exceed ${MAX_FORGE_DURATION_MS}`);\n }\n}\n\nfunction tryAddDuration(now: Date, durationMs: number): string | null {\n const timestamp = now.getTime() + durationMs;\n if (\n !Number.isFinite(timestamp) ||\n Math.abs(timestamp) > MAX_DATE_TIMESTAMP_MS\n ) {\n return null;\n }\n return new Date(timestamp).toISOString();\n}\n\nfunction assertObservation(observation: ForgeObservation): void {\n assertNonEmpty('observation.projection', observation.projection);\n assertNonEmpty('observation.subjectKey', observation.subjectKey);\n if (\n !Number.isSafeInteger(observation.version) ||\n observation.version < 0 ||\n observation.version > MAX_OBSERVATION_VERSION\n ) {\n throw new Error(\n `observation.version must be an integer from 0 to ${MAX_OBSERVATION_VERSION}`,\n );\n }\n}\n\nfunction getDatabaseEngine(\n db: DatabaseInterface,\n): ReturnType<typeof detectEngine> {\n const dbWithConfig = db as DatabaseWithConfig;\n return detectEngine(\n db.url || dbWithConfig.config?.url || '',\n dbWithConfig.type || dbWithConfig.config?.type,\n );\n}\n\nfunction clampMaxAttempts(value: number): number {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error('maxAttempts must be a positive safe integer');\n }\n return Math.min(value, 100);\n}\n","import type { JobStatus, SmrtJob, SmrtJobCollection } from './smrt-job.js';\n\n/**\n * Options for waiting on a job\n */\nexport interface WaitOptions {\n /** Maximum time to wait in milliseconds */\n timeout?: number;\n /** Polling interval in milliseconds */\n pollInterval?: number;\n}\n\n/**\n * Result from a completed job\n */\nexport interface JobResult<T = unknown> {\n /** Whether the job completed successfully */\n success: boolean;\n /** The result data (if successful) */\n result?: T;\n /** Error message (if failed) */\n error?: string;\n /** Pointer to where the full result is stored */\n resultPointer?: string | null;\n}\n\n/**\n * Handle for tracking and managing a background job\n *\n * This provides a convenient interface for:\n * - Checking job status\n * - Waiting for completion\n * - Canceling the job\n * - Retrying failed jobs\n */\nexport class JobHandle<T = unknown> {\n private readonly collection: SmrtJobCollection;\n\n constructor(\n public readonly id: string,\n collection: SmrtJobCollection,\n ) {\n this.collection = collection;\n }\n\n /**\n * Get the current job status\n */\n async status(): Promise<JobStatus> {\n const job = await this.getJob();\n return job.status;\n }\n\n /**\n * Get the full job object\n */\n async getJob(): Promise<SmrtJob> {\n const job = await this.collection.get({ id: this.id });\n if (!job) {\n throw new Error(`Job not found: ${this.id}`);\n }\n return job;\n }\n\n /**\n * Wait for the job to complete\n *\n * @param options - Wait configuration\n * @returns The job result\n * @throws Error if the job fails or times out\n */\n async wait(options: WaitOptions = {}): Promise<JobResult<T>> {\n const { timeout = 60000, pollInterval = 100 } = options;\n const startTime = Date.now();\n\n while (true) {\n const job = await this.getJob();\n\n if (job.status === 'completed') {\n return {\n success: true,\n resultPointer: job.resultPointer,\n };\n }\n\n if (job.status === 'failed') {\n return {\n success: false,\n error: job.lastError ?? 'Job failed',\n };\n }\n\n if (job.status === 'cancelled') {\n return {\n success: false,\n error: 'Job was cancelled',\n };\n }\n\n // Check timeout\n if (Date.now() - startTime >= timeout) {\n throw new Error(`Timeout waiting for job ${this.id}`);\n }\n\n // Wait before next poll\n await new Promise((resolve) => setTimeout(resolve, pollInterval));\n }\n }\n\n /**\n * Cancel the job\n */\n async cancel(): Promise<void> {\n const job = await this.getJob();\n await job.cancel();\n }\n\n /**\n * Retry a failed job\n */\n async retry(): Promise<void> {\n const job = await this.getJob();\n await job.retry();\n }\n\n /**\n * Check if the job is still running\n */\n async isRunning(): Promise<boolean> {\n const status = await this.status();\n return status === 'pending' || status === 'running';\n }\n\n /**\n * Check if the job has completed (successfully or not)\n */\n async isDone(): Promise<boolean> {\n const status = await this.status();\n return (\n status === 'completed' || status === 'failed' || status === 'cancelled'\n );\n }\n}\n\nexport default JobHandle;\n","import {\n exponential,\n type RetryStrategy,\n type RetryStrategyConfig,\n} from '@happyvertical/jobs';\nimport { clampRetries, DEFAULT_TENANT_JOB_CAP } from './background-policy.js';\nimport { JobHandle } from './job-handle.js';\nimport type { SmrtJobCollection, TimeoutBehavior } from './smrt-job.js';\n\n/**\n * Priority levels for jobs\n */\nexport type Priority = 'critical' | 'high' | 'normal' | 'low' | number;\n\n/**\n * Convert priority to numeric value\n */\nexport function priorityToNumber(priority: Priority): number {\n if (typeof priority === 'number') return priority;\n switch (priority) {\n case 'critical':\n return 100;\n case 'high':\n return 75;\n case 'normal':\n return 50;\n case 'low':\n return 25;\n default:\n return 50;\n }\n}\n\n/**\n * Parse delay string to milliseconds\n */\nexport function parseDelay(delay: string | number): number {\n if (typeof delay === 'number') {\n // Guard against NaN/Infinity: a non-finite delay flows into\n // `new Date(Date.now() + delay)`, persisting an `Invalid Date` `runAt`\n // that the claim query can never match (S3 in the #1401 review).\n if (!Number.isFinite(delay)) {\n throw new Error(`Invalid delay value: ${delay}`);\n }\n return delay;\n }\n\n const match = delay.match(/^(\\d+)(ms|s|m|h|d)?$/);\n if (!match) {\n throw new Error(`Invalid delay format: ${delay}`);\n }\n\n const value = parseInt(match[1], 10);\n const unit = match[2] || 'ms';\n\n switch (unit) {\n case 'ms':\n return value;\n case 's':\n return value * 1000;\n case 'm':\n return value * 60 * 1000;\n case 'h':\n return value * 60 * 60 * 1000;\n case 'd':\n return value * 24 * 60 * 60 * 1000;\n default:\n return value;\n }\n}\n\n/**\n * Fluent builder for creating background jobs\n *\n * Example:\n * ```typescript\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .queue('summaries')\n * .timeout(300000)\n * .enqueue();\n * ```\n */\nexport class JobBuilder<T = unknown> {\n private _queue: string = 'default';\n private _delay: number = 0;\n private _retries: number = 3;\n private _priority: number = 50;\n private _timeout: number = 300000;\n private _timeoutBehavior: TimeoutBehavior = 'fail';\n private _retryStrategy: RetryStrategy = exponential();\n private _tenantJobCap: number = DEFAULT_TENANT_JOB_CAP;\n\n constructor(\n private readonly objectType: string,\n private readonly objectId: string | null,\n private readonly method: string,\n private readonly args: Record<string, unknown>,\n private readonly collection: SmrtJobCollection,\n ) {}\n\n /**\n * Set the queue name\n */\n queue(name: string): this {\n this._queue = name;\n return this;\n }\n\n /**\n * Set a delay before the job runs\n * @param delay - Delay as milliseconds or string like '5m', '1h', '30s'\n */\n delay(delay: string | number): this {\n this._delay = parseDelay(delay);\n return this;\n }\n\n /**\n * Set when the job should run\n */\n runAt(date: Date): this {\n this._delay = date.getTime() - Date.now();\n return this;\n }\n\n /**\n * Set the maximum number of retry attempts.\n *\n * Clamped to {@link MAX_JOB_RETRIES} so a misconfigured caller cannot pin a\n * worker on a poison job indefinitely (S5 audit #1402).\n */\n retries(count: number): this {\n this._retries = clampRetries(count);\n return this;\n }\n\n /**\n * Set the retry strategy\n */\n retryStrategy(strategy: RetryStrategy): this {\n this._retryStrategy = strategy;\n return this;\n }\n\n /**\n * Set the job priority\n */\n priority(level: Priority): this {\n this._priority = priorityToNumber(level);\n return this;\n }\n\n /**\n * Set the job timeout in milliseconds\n */\n timeout(ms: number): this {\n this._timeout = ms;\n return this;\n }\n\n /**\n * Set what happens when the job times out\n */\n timeoutBehavior(behavior: TimeoutBehavior): this {\n this._timeoutBehavior = behavior;\n return this;\n }\n\n /**\n * Override the per-tenant in-flight job cap for this enqueue.\n *\n * Defaults to {@link DEFAULT_TENANT_JOB_CAP}. Pass `0` (or a negative value)\n * to disable the cap for trusted internal callers (S5 audit #1402).\n */\n tenantJobCap(max: number): this {\n this._tenantJobCap = max;\n return this;\n }\n\n /**\n * Enqueue the job and return a handle\n */\n async enqueue(): Promise<JobHandle<T>> {\n const runAt = new Date(Date.now() + this._delay);\n\n const retryConfig: RetryStrategyConfig =\n 'toConfig' in this._retryStrategy\n ? this._retryStrategy.toConfig()\n : (this._retryStrategy as RetryStrategyConfig);\n\n // Route through the collection's single creation path so the per-tenant\n // in-flight cap and the retry ceiling are enforced in one place, shared with\n // the ScheduleRunner (S5 audit #1402). The cap applies to the ambient tenant\n // (resolved inside enqueueJob); global (no-context) jobs are exempt.\n const job = await this.collection.enqueueJob(\n {\n queue: this._queue,\n objectType: this.objectType,\n objectId: this.objectId,\n method: this.method,\n args: this.args,\n runAt,\n priority: this._priority,\n maxAttempts: this._retries,\n timeout: this._timeout,\n timeoutBehavior: this._timeoutBehavior,\n retryStrategy: retryConfig,\n },\n { tenantJobCap: this._tenantJobCap },\n );\n\n const jobId = job.id;\n if (!jobId) {\n throw new Error('Job was created but has no ID');\n }\n\n return new JobHandle<T>(jobId, this.collection);\n }\n}\n\nexport default JobBuilder;\n","import {\n ObjectRegistry,\n type SmrtObject,\n type SmrtObjectOptions,\n} from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n JobBuilder,\n type Priority,\n parseDelay,\n priorityToNumber,\n} from './job-builder.js';\nimport type { JobHandle } from './job-handle.js';\nimport { SmrtJobCollection } from './smrt-job.js';\n\n/**\n * Options for the simple .bg() method\n */\nexport interface BgOptions {\n /** Queue name */\n queue?: string;\n /** Priority level */\n priority?: 'critical' | 'high' | 'normal' | 'low' | number;\n /** Delay before running (ms or string like '5m') */\n delay?: string | number;\n /** Maximum retries */\n retries?: number;\n /** Timeout in milliseconds */\n timeout?: number;\n}\n\n/**\n * Type for the extended SmrtObject with background job methods\n */\nexport interface BackgroundCapable {\n /**\n * Simple background job submission\n *\n * @param method - Method name to invoke\n * @param args - Arguments to pass to the method\n * @param options - Job options\n * @returns JobHandle for tracking the job\n *\n * @example\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n */\n bg<T = unknown>(\n method: string,\n args?: Record<string, unknown>,\n options?: BgOptions,\n ): Promise<JobHandle<T>>;\n\n /**\n * Fluent job builder for advanced options\n *\n * @param method - Method name to invoke\n * @param args - Arguments to pass to the method\n * @returns JobBuilder for fluent configuration\n *\n * @example\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .enqueue();\n */\n background<T = unknown>(\n method: string,\n args?: Record<string, unknown>,\n ): JobBuilder<T>;\n}\n\n// Cache for job collections per database\nconst collectionCache = new WeakMap<DatabaseInterface, SmrtJobCollection>();\n\n/**\n * Get or create a job collection for the given database\n */\nasync function getJobCollection(\n db: DatabaseInterface,\n): Promise<SmrtJobCollection> {\n let collection = collectionCache.get(db);\n if (!collection) {\n collection = await SmrtJobCollection.create({\n db: { type: 'sqlite', url: ':memory:' }, // Placeholder\n });\n // Override internal db reference\n (collection as unknown as { _db: DatabaseInterface })._db = db;\n collectionCache.set(db, collection);\n }\n return collection;\n}\n\nfunction getObjectTypeName(instance: SmrtObject): string {\n const metaType = (instance as { _meta_type?: unknown })._meta_type;\n if (typeof metaType === 'string' && metaType.length > 0) {\n return metaType;\n }\n\n const className = instance.constructor.name;\n return ObjectRegistry.getClass(className)?.qualifiedName || className;\n}\n\n// Type for a SmrtObject constructor. `never[]` (not `any[]`) satisfies\n// noExplicitAny while preserving assignability: constructor parameters are\n// checked contravariantly under strictFunctionTypes, and `never` (the bottom\n// type) is assignable to any concrete parameter type, so a typed-options\n// constructor like `new (options?: SmrtObjectOptions) => Doc` still satisfies\n// the constraint — `unknown[]` would NOT, since `unknown` is not assignable to\n// `SmrtObjectOptions`, breaking the documented `withBackgroundJobs(Document)`\n// API for consumers.\ntype SmrtObjectConstructor = new (...args: never[]) => SmrtObject;\ntype BackgroundCapableConstructor<T extends SmrtObjectConstructor> = T & {\n new (...args: ConstructorParameters<T>): InstanceType<T> & BackgroundCapable;\n};\n\n// Compile-time regression guard (#1658, PR #1658): a typed-options SmrtObject\n// subclass constructor — the documented `withBackgroundJobs(Document)` pattern,\n// where a model's ctor is `(options?: SomeOptions)` — MUST satisfy the\n// constraint. A `unknown[]` rest param (instead of `never[]`) makes this\n// resolve to `false` and fails the build, since `unknown` is not assignable to\n// the typed options parameter. Test files are excluded from `tsc`, so this\n// lives in typechecked source rather than a `*.test.ts`.\ntype AssertTrue<T extends true> = T;\ntype _TypedOptionsCtorStaysAssignable = AssertTrue<\n (new (\n options?: SmrtObjectOptions,\n ) => SmrtObject) extends SmrtObjectConstructor\n ? true\n : false\n>;\n\nfunction requireObjectDb(instance: SmrtObject): DatabaseInterface {\n const db = (instance as unknown as { _db?: DatabaseInterface })._db;\n if (!db) {\n throw new Error('Object not initialized. Call initialize() first.');\n }\n\n return db;\n}\n\nasync function bgImpl<R = unknown>(\n this: SmrtObject,\n method: string,\n args: Record<string, unknown> = {},\n options: BgOptions = {},\n): Promise<JobHandle<R>> {\n const db = requireObjectDb(this);\n const collection = await getJobCollection(db);\n const builder = new JobBuilder<R>(\n getObjectTypeName(this),\n this.id ?? null,\n method,\n args,\n collection,\n );\n\n if (options.queue) builder.queue(options.queue);\n if (options.priority) builder.priority(options.priority);\n if (options.delay) builder.delay(options.delay);\n if (options.retries !== undefined) builder.retries(options.retries);\n if (options.timeout) builder.timeout(options.timeout);\n\n return builder.enqueue();\n}\n\nfunction backgroundImpl<R = unknown>(\n this: SmrtObject,\n method: string,\n args: Record<string, unknown> = {},\n): JobBuilder<R> {\n const db = requireObjectDb(this);\n const objectType = getObjectTypeName(this);\n const objectId = this.id ?? null;\n\n // Create a proxy builder that defers collection access until enqueue().\n const lazyBuilder = {\n _queue: 'default',\n _delay: 0,\n _retries: 3,\n _priority: 50,\n _timeout: 300000,\n _timeoutBehavior: 'fail' as 'fail' | 'kill' | 'warn',\n _retryStrategy: null as unknown,\n // `undefined` => fall through to JobBuilder's DEFAULT_TENANT_JOB_CAP. A\n // caller can override (incl. `0` to disable) via tenantJobCap() below.\n _tenantJobCap: undefined as number | undefined,\n\n queue(name: string) {\n this._queue = name;\n return this;\n },\n delay(d: string | number) {\n this._delay = parseDelay(d);\n return this;\n },\n runAt(date: Date) {\n this._delay = date.getTime() - Date.now();\n return this;\n },\n retries(count: number) {\n this._retries = count;\n return this;\n },\n retryStrategy(strategy: unknown) {\n this._retryStrategy = strategy;\n return this;\n },\n priority(level: Priority) {\n this._priority = priorityToNumber(level);\n return this;\n },\n timeout(ms: number) {\n this._timeout = ms;\n return this;\n },\n timeoutBehavior(behavior: 'fail' | 'kill' | 'warn') {\n this._timeoutBehavior = behavior;\n return this;\n },\n tenantJobCap(max: number) {\n this._tenantJobCap = max;\n return this;\n },\n async enqueue(): Promise<JobHandle<R>> {\n const collection = await getJobCollection(db);\n const builder = new JobBuilder<R>(\n objectType,\n objectId,\n method,\n args,\n collection,\n );\n\n builder.queue(this._queue);\n builder.delay(this._delay);\n builder.retries(this._retries);\n builder.priority(this._priority);\n builder.timeout(this._timeout);\n builder.timeoutBehavior(this._timeoutBehavior);\n // Only forward an explicit override; leaving it unset preserves the\n // JobBuilder default (DEFAULT_TENANT_JOB_CAP).\n if (this._tenantJobCap !== undefined) {\n builder.tenantJobCap(this._tenantJobCap);\n }\n\n if (this._retryStrategy) {\n builder.retryStrategy(\n this._retryStrategy as Parameters<typeof builder.retryStrategy>[0],\n );\n }\n\n return builder.enqueue();\n },\n };\n\n return lazyBuilder as unknown as JobBuilder<R>;\n}\n\n/**\n * Extend a SmrtObject class with background job methods\n *\n * @param BaseClass - The SmrtObject class to extend\n * @returns Extended class with .bg() and .background() methods\n *\n * @example\n * const BackgroundDocument = withBackgroundJobs(Document);\n * const doc = new BackgroundDocument({ ... });\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n */\nexport function withBackgroundJobs<T extends SmrtObjectConstructor>(\n BaseClass: T,\n): BackgroundCapableConstructor<T> {\n const prototype = BaseClass.prototype as SmrtObject &\n Partial<BackgroundCapable>;\n\n if (typeof prototype.bg !== 'function') {\n Object.defineProperty(prototype, 'bg', {\n value: bgImpl,\n writable: true,\n configurable: true,\n });\n }\n\n if (typeof prototype.background !== 'function') {\n Object.defineProperty(prototype, 'background', {\n value: backgroundImpl,\n writable: true,\n configurable: true,\n });\n }\n\n return BaseClass as BackgroundCapableConstructor<T>;\n}\n\nexport default withBackgroundJobs;\n","import { EventEmitter } from 'node:events';\nimport { createLogger } from '@happyvertical/logger';\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { createId } from '@happyvertical/utils';\nimport {\n redactErrorForPersistence,\n redactErrorMessage,\n} from './error-redaction.js';\nimport { SmrtJobCollection } from './smrt-job.js';\nimport { SmrtWorkerCollection } from './smrt-worker.js';\nimport { DEFAULT_TASK_HEARTBEAT_INTERVAL_MS } from './stale-recovery.js';\nimport { isWorkerAlive } from './worker-liveness.js';\n\n/**\n * ScheduleRunner configuration\n */\nexport interface ScheduleRunnerConfig {\n /** Runner ID (auto-generated if not provided) */\n id?: string;\n /** Polling interval in milliseconds (default: 60000 - 1 minute) */\n pollInterval?: number;\n /** Maximum schedules to process per poll */\n batchSize?: number;\n /**\n * @deprecated No longer used. Slot reconciliation keys on worker liveness\n * (the `_smrt_workers` lease), not per-job heartbeat staleness (#1474).\n */\n staleJobThresholdMs?: number;\n /**\n * @deprecated No longer used. See {@link staleJobThresholdMs}.\n */\n taskHeartbeatInterval?: number;\n}\n\n/**\n * ScheduleRunner events\n */\nexport interface ScheduleRunnerEvents {\n 'schedule:triggered': (schedule: ScheduleInfo) => void;\n 'schedule:error': (schedule: ScheduleInfo, error: Error) => void;\n 'schedule:completed': (scheduleId: string) => void;\n 'schedule:failed': (scheduleId: string, error: string) => void;\n 'runner:started': () => void;\n 'runner:stopped': () => void;\n 'runner:error': (error: Error) => void;\n}\n\n/**\n * Schedule info for events\n */\nexport interface ScheduleInfo {\n id: string;\n agentType: string;\n agentId: string | null;\n cron: string;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<ScheduleRunnerConfig> = {\n id: '',\n pollInterval: 60000, // 1 minute\n batchSize: 50,\n staleJobThresholdMs: 90000,\n taskHeartbeatInterval: DEFAULT_TASK_HEARTBEAT_INTERVAL_MS,\n};\n\n/**\n * ScheduleRunner polls for due agent schedules and creates jobs for them\n *\n * This runner works in conjunction with TaskRunner:\n * 1. ScheduleRunner checks for due schedules based on cron expressions\n * 2. When a schedule is due, it creates a SmrtJob for the agent\n * 3. TaskRunner picks up and executes the job\n * 4. On job completion/failure, call handleJobCompletion() to update the schedule\n *\n * @example\n * ```typescript\n * const scheduleRunner = new ScheduleRunner({ pollInterval: 30000 });\n * await scheduleRunner.initialize(db);\n * await scheduleRunner.start();\n *\n * // Wire up TaskRunner events to update schedule state\n * taskRunner.on('job:completed', (job) => {\n * const scheduleId = job.args?._scheduleId;\n * if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, true);\n * });\n * taskRunner.on('job:failed', (job, error) => {\n * const scheduleId = job.args?._scheduleId;\n * if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, false, error.message);\n * });\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => scheduleRunner.stop());\n * ```\n */\nexport class ScheduleRunner extends EventEmitter {\n readonly id: string;\n private readonly config: Required<ScheduleRunnerConfig>;\n private jobCollection: SmrtJobCollection | null = null;\n private workerCollection: SmrtWorkerCollection | null = null;\n private running = false;\n private pollTimer: NodeJS.Timeout | null = null;\n private db: DatabaseInterface | null = null;\n private logger = createLogger(true);\n\n constructor(config: ScheduleRunnerConfig = {}) {\n super();\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n id: config.id || `schedule_${createId().slice(0, 8)}`,\n };\n this.id = this.config.id;\n }\n\n /**\n * Initialize the runner with database connection\n */\n async initialize(db: DatabaseInterface): Promise<void> {\n this.db = db;\n this.jobCollection = await SmrtJobCollection.create({ db });\n this.workerCollection = await SmrtWorkerCollection.create({ db });\n }\n\n /**\n * Start processing schedules\n */\n async start(): Promise<void> {\n if (this.running) return;\n if (!this.db) {\n throw new Error(\n 'ScheduleRunner not initialized. Call initialize() first.',\n );\n }\n\n this.running = true;\n\n // Start polling loop\n this.startPolling();\n\n this.emit('runner:started');\n this.logger.info('ScheduleRunner started', { id: this.id });\n }\n\n /**\n * Stop processing schedules\n */\n async stop(): Promise<void> {\n if (!this.running) return;\n\n this.running = false;\n\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n this.emit('runner:stopped');\n this.logger.info('ScheduleRunner stopped', { id: this.id });\n }\n\n /**\n * Check if runner is running\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Handle job completion for a scheduled job.\n *\n * Call this from TaskRunner's job:completed / job:failed events\n * when the job has a `_scheduleId` in its args.\n */\n async handleJobCompletion(\n scheduleId: string,\n success: boolean,\n errorMessage?: string,\n ): Promise<void> {\n if (!this.db) return;\n\n // `last_error` is persisted to a durable schedule row; strip secret-shaped\n // substrings the same way the job runner does (S5 audit #1402).\n const safeErrorMessage = redactErrorMessage(\n errorMessage ?? 'Unknown error',\n );\n\n try {\n if (success) {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'success',\n last_error = NULL,\n run_count = COALESCE(run_count, 0) + 1,\n success_count = COALESCE(success_count, 0) + 1\n WHERE id = ?`,\n new Date().toISOString(),\n scheduleId,\n );\n this.emit('schedule:completed', scheduleId);\n } else {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = CASE WHEN COALESCE(running_count, 0) > 0 THEN running_count - 1 ELSE 0 END,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + 1,\n failure_count = COALESCE(failure_count, 0) + 1\n WHERE id = ?`,\n new Date().toISOString(),\n safeErrorMessage,\n scheduleId,\n );\n this.emit('schedule:failed', scheduleId, safeErrorMessage);\n }\n } catch (err) {\n this.logger.error('Failed to update schedule after job completion', {\n scheduleId,\n error: err,\n });\n }\n }\n\n /**\n * Start the polling loop\n */\n private startPolling(): void {\n const poll = async () => {\n if (!this.running) return;\n\n try {\n await this.poll();\n } catch (error) {\n this.emit('runner:error', error as Error);\n this.logger.error('ScheduleRunner poll error', { error });\n }\n\n // Schedule next poll\n if (this.running) {\n this.pollTimer = setTimeout(poll, this.config.pollInterval);\n }\n };\n\n // Start immediately\n poll();\n }\n\n /**\n * Poll for due schedules and create jobs\n */\n private async poll(): Promise<void> {\n if (!this.db || !this.jobCollection) return;\n\n await this.recoverStaleScheduleState();\n\n const now = new Date().toISOString();\n\n // Find due schedules\n const result = await this.db.query(\n `SELECT * FROM _smrt_agent_schedules\n WHERE enabled = true\n AND status = 'active'\n AND next_run <= ?\n AND COALESCE(running_count, 0) < COALESCE(max_concurrent, 1)\n ORDER BY next_run ASC\n LIMIT ?`,\n now,\n this.config.batchSize,\n );\n\n for (const row of result.rows) {\n await this.triggerSchedule(row as ScheduleRow);\n }\n }\n\n /**\n * Reconcile stuck schedule slots against running jobs.\n *\n * This handles two failure modes:\n * - a running job's owning worker is no longer alive (dead/restarted)\n * - a schedule slot remains occupied even though no running job still exists\n *\n * Staleness keys on worker *liveness* (issue #1474), not per-job heartbeat\n * freshness: a job whose `worker_id` is live in this process or holds a fresh\n * lease in `_smrt_workers` is healthy even if its handler is holding the loop\n * synchronously. ScheduleRunner has no in-process active-job set, so this is\n * its entire correctness mechanism.\n */\n private async recoverStaleScheduleState(): Promise<void> {\n if (!this.db || !this.workerCollection) return;\n\n const schedulesResult = await this.db.query(\n `SELECT id, running_count\n FROM _smrt_agent_schedules\n WHERE COALESCE(running_count, 0) > 0`,\n );\n const schedules = schedulesResult.rows as Array<{\n id: string;\n running_count: number;\n }>;\n if (schedules.length === 0) return;\n\n // Without the workers table we cannot reason about liveness; treat every\n // running job as alive (reconcile slot drift only, never fail jobs).\n const workersReady = await this.workerCollection.tableReady();\n const freshLeaseKeys = workersReady\n ? await this.workerCollection.freshLeaseWorkerKeys()\n : new Set<string>();\n\n const jobsResult = await this.db.query(\n `SELECT id, args, worker_id\n FROM _smrt_jobs\n WHERE status = 'running'`,\n );\n const jobRows = jobsResult.rows as Array<{\n id: string;\n args: unknown;\n worker_id: string | null;\n }>;\n\n type ScheduleState = { live: number; staleJobIds: string[] };\n const stateBySchedule = new Map<string, ScheduleState>();\n for (const schedule of schedules) {\n stateBySchedule.set(schedule.id, { live: 0, staleJobIds: [] });\n }\n\n for (const row of jobRows) {\n const scheduleId = this.getScheduleIdFromJobArgs(row.args);\n if (!scheduleId) continue;\n\n const state = stateBySchedule.get(scheduleId);\n if (!state) continue;\n\n const alive = workersReady\n ? isWorkerAlive(row.worker_id, freshLeaseKeys)\n : true;\n\n if (!alive) {\n state.staleJobIds.push(row.id);\n } else {\n state.live += 1;\n }\n }\n\n const now = new Date().toISOString();\n const staleJobIds = schedules.flatMap((schedule) => {\n const state = stateBySchedule.get(schedule.id);\n return state?.staleJobIds ?? [];\n });\n\n // Only the jobs this pass actually transitioned to 'failed' — RETURNING id\n // (not rowCount, which DuckDB/JSON always report as ≥1) so a job another\n // recoverer already failed isn't double-counted into the schedule's\n // run_count/failure_count.\n const recoveredJobIds = new Set<string>();\n if (staleJobIds.length > 0) {\n const placeholders = staleJobIds.map(() => '?').join(', ');\n const result = await this.db.query(\n `UPDATE _smrt_jobs\n SET status = 'failed',\n completed_at = ?,\n last_error = ?,\n worker_id = NULL,\n worker_heartbeat = NULL\n WHERE status = 'running'\n AND id IN (${placeholders})\n RETURNING id`,\n now,\n 'Recovered orphaned scheduled job: its owning worker is no longer ' +\n 'alive (no fresh liveness lease in _smrt_workers and not running in ' +\n 'this process).',\n ...staleJobIds,\n );\n for (const row of result.rows as Array<{ id?: unknown }>) {\n if (typeof row.id === 'string') recoveredJobIds.add(row.id);\n }\n }\n\n for (const schedule of schedules) {\n const state = stateBySchedule.get(schedule.id);\n if (!state) continue;\n\n const desiredRunningCount = state.live;\n const recoveredCount = state.staleJobIds.filter((id) =>\n recoveredJobIds.has(id),\n ).length;\n\n if (\n Number(schedule.running_count) === desiredRunningCount &&\n recoveredCount === 0\n ) {\n continue;\n }\n\n if (recoveredCount > 0) {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = ?,\n last_run = ?,\n last_status = 'failed',\n last_error = ?,\n run_count = COALESCE(run_count, 0) + ?,\n failure_count = COALESCE(failure_count, 0) + ?\n WHERE id = ?`,\n desiredRunningCount,\n now,\n `Recovered ${recoveredCount} orphaned scheduled job(s) from dead worker(s)`,\n recoveredCount,\n recoveredCount,\n schedule.id,\n );\n this.emit(\n 'schedule:failed',\n schedule.id,\n `Recovered ${recoveredCount} orphaned scheduled job(s)`,\n );\n } else {\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET running_count = ?\n WHERE id = ?`,\n desiredRunningCount,\n schedule.id,\n );\n }\n }\n }\n\n private getScheduleIdFromJobArgs(args: unknown): string | null {\n if (!args) return null;\n\n let parsedArgs = args;\n if (typeof parsedArgs === 'string') {\n try {\n parsedArgs = JSON.parse(parsedArgs) as Record<string, unknown>;\n } catch {\n return null;\n }\n }\n\n if (\n !parsedArgs ||\n typeof parsedArgs !== 'object' ||\n Array.isArray(parsedArgs)\n ) {\n return null;\n }\n\n const scheduleId = (parsedArgs as Record<string, unknown>)._scheduleId;\n return typeof scheduleId === 'string' && scheduleId.length > 0\n ? scheduleId\n : null;\n }\n\n /**\n * Trigger a schedule by creating a job\n */\n private async triggerSchedule(schedule: ScheduleRow): Promise<void> {\n if (!this.db || !this.jobCollection) return;\n\n const rawAgentType = schedule.agent_type as string;\n const canonicalAgentType =\n ObjectRegistry.getClass(rawAgentType)?.qualifiedName || rawAgentType;\n\n const scheduleInfo: ScheduleInfo = {\n id: schedule.id as string,\n agentType: canonicalAgentType,\n agentId: schedule.agent_id as string | null,\n cron: schedule.cron as string,\n };\n\n try {\n // Parse method_args and agent_config from JSON strings if needed\n let methodArgs: Record<string, unknown> = {};\n if (schedule.method_args) {\n methodArgs =\n typeof schedule.method_args === 'string'\n ? JSON.parse(schedule.method_args as string)\n : (schedule.method_args as Record<string, unknown>);\n }\n let agentConfig: Record<string, unknown> = {};\n if (schedule.agent_config) {\n agentConfig =\n typeof schedule.agent_config === 'string'\n ? JSON.parse(schedule.agent_config as string)\n : (schedule.agent_config as Record<string, unknown>);\n }\n\n // Compute next run time from cron before creating the job\n const nextRun = getNextCronDate(schedule.cron as string);\n\n // Create a job for this schedule\n // Nest agent_config under _agentConfig so TaskRunner can pass it\n // to the agent constructor separately from method args\n const args: Record<string, unknown> = {\n ...methodArgs,\n _scheduleId: schedule.id,\n };\n if (Object.keys(agentConfig).length > 0) {\n args._agentConfig = agentConfig;\n }\n\n // Enqueue the job BEFORE advancing next_run / incrementing running_count.\n // Previously next_run was advanced and running_count incremented first; if\n // enqueueJob then threw (a transient tenant-cap hit or DB blip), the catch\n // disabled the schedule but never rolled next_run back, permanently losing\n // that run slot AND taking the schedule out of the poll until manual\n // re-activation (#4 in the #1401 review). By enqueuing first, a transient\n // failure leaves next_run untouched, so the same due slot is retried on\n // the next poll and the schedule stays active.\n //\n // Route scheduled jobs through the same centralized creation path as the\n // fluent builder so the per-tenant in-flight cap and retry ceiling apply\n // here too — previously a direct create() bypassed both (S5 audit #1402).\n // The schedule's own tenant is passed explicitly so the cap is enforced\n // for the owning tenant even with no ambient context.\n const job = await this.jobCollection.enqueueJob({\n tenantId:\n typeof schedule.tenant_id === 'string' &&\n schedule.tenant_id.length > 0\n ? (schedule.tenant_id as string)\n : null,\n queue: 'agents',\n objectType: canonicalAgentType,\n objectId: schedule.agent_id as string | null,\n method: (schedule.method as string) || 'run',\n args,\n priority: 75, // High priority for scheduled agents\n maxAttempts: 3,\n timeout: (schedule.timeout as number) || 3600000,\n });\n\n // Only now that the job is durably enqueued do we consume the slot:\n // increment running_count and advance next_run in one update.\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET agent_type = ?,\n running_count = running_count + 1,\n next_run = ?\n WHERE id = ?`,\n canonicalAgentType,\n nextRun.toISOString(),\n schedule.id,\n );\n\n this.emit('schedule:triggered', scheduleInfo);\n this.logger.info('Schedule triggered', {\n scheduleId: schedule.id,\n agentType: canonicalAgentType,\n jobId: job.id,\n nextRun: nextRun.toISOString(),\n });\n } catch (error) {\n // The slot was NOT consumed (enqueue runs before the next_run/\n // running_count advance), so there is nothing to roll back: leave\n // next_run and status='active' untouched so the next poll retries the\n // same due slot rather than skipping it or disabling the schedule on a\n // transient failure (#4). Record last_error for operator visibility only.\n await this.db.query(\n `UPDATE _smrt_agent_schedules\n SET last_error = ?\n WHERE id = ?`,\n // Tolerate non-Error throwables: a thrown string/object has no\n // `.message`, which would otherwise persist an empty `last_error`.\n redactErrorForPersistence(error),\n schedule.id,\n );\n\n this.emit('schedule:error', scheduleInfo, error as Error);\n this.logger.error('Schedule trigger failed', {\n scheduleId: schedule.id,\n error,\n });\n }\n }\n}\n\n/**\n * Database row type for schedule\n */\ninterface ScheduleRow {\n id: unknown;\n agent_type: unknown;\n agent_id: unknown;\n tenant_id: unknown;\n agent_config: unknown;\n cron: unknown;\n method: unknown;\n method_args: unknown;\n timeout: unknown;\n}\n\n// --- Cron helpers (self-contained, no external dependency) ---\n\n/**\n * Inclusive valid range for each cron field, by position.\n * minute, hour, day-of-month, month, day-of-week.\n * Day-of-week accepts 0-7 where both 0 and 7 represent Sunday.\n */\nconst CRON_FIELD_RANGES: ReadonlyArray<{\n name: string;\n min: number;\n max: number;\n}> = [\n { name: 'minute', min: 0, max: 59 },\n { name: 'hour', min: 0, max: 23 },\n { name: 'day-of-month', min: 1, max: 31 },\n { name: 'month', min: 1, max: 12 },\n { name: 'day-of-week', min: 0, max: 7 },\n];\n\n/**\n * Validate that every numeric component of a single cron field falls within\n * the field's inclusive range. Rejects malformed values up front so an\n * out-of-range field (e.g. `minute=70`) fails fast at schedule-trigger time\n * instead of silently scanning ~525k candidate minutes and never matching\n * (S5 audit #1402).\n */\nfunction validateCronField(\n expr: string,\n range: { name: string; min: number; max: number },\n): void {\n if (expr === '*') return;\n\n const reject = (detail: string): never => {\n throw new Error(\n `Invalid cron expression: ${range.name} field \"${expr}\" ${detail} ` +\n `(valid range ${range.min}-${range.max})`,\n );\n };\n\n const assertInRange = (value: number): void => {\n if (!Number.isInteger(value) || value < range.min || value > range.max) {\n reject('is out of range');\n }\n };\n\n for (const term of expr.split(',')) {\n if (term === '') reject('contains an empty value');\n\n let body = term;\n if (body.includes('/')) {\n // Exactly one '/' is valid (`base/step`). `1/2/3` must be rejected, not\n // silently parsed as `1/2` by dropping the trailing segment.\n const stepParts = body.split('/');\n if (stepParts.length !== 2) {\n reject('has malformed step syntax');\n }\n const [rangePart, stepStr] = stepParts;\n const step = Number(stepStr);\n if (!Number.isInteger(step) || step <= 0) {\n reject('has an invalid step');\n }\n body = rangePart;\n if (body === '*') continue;\n }\n\n if (body.includes('-')) {\n // Exactly one '-' is valid (`start-end`). `1-2-3` must be rejected, not\n // silently parsed as `1-2` by dropping the trailing segment.\n const rangeParts = body.split('-');\n if (rangeParts.length !== 2) {\n reject('has malformed range syntax');\n }\n const [startStr, endStr] = rangeParts;\n if (startStr === '' || endStr === '') {\n reject('has an empty range part');\n }\n const start = Number(startStr);\n const end = Number(endStr);\n assertInRange(start);\n assertInRange(end);\n if (start > end) reject('has an inverted range');\n } else {\n assertInRange(Number(body));\n }\n }\n}\n\n/**\n * Validate a standard 5-field cron expression: field count plus per-field\n * value ranges. Throws a descriptive `Error` on the first invalid field.\n *\n * Exposed so callers (and the agents package, which owns schedule creation)\n * can reject a bad cron at write time rather than letting an out-of-range\n * field silently never match (S5 audit #1402).\n *\n * @param cron - The cron expression to validate.\n * @returns The trimmed, whitespace-split fields when valid.\n */\nexport function validateCronExpression(cron: string): string[] {\n const parts = cron.trim().split(/\\s+/);\n if (parts.length !== 5) {\n throw new Error(\n `Invalid cron expression: expected 5 fields, got ${parts.length}`,\n );\n }\n\n parts.forEach((field, index) => {\n validateCronField(field, CRON_FIELD_RANGES[index]);\n });\n\n return parts;\n}\n\n/**\n * Parse a cron expression and get the next run date.\n * Supports standard 5-field cron: minute hour day-of-month month day-of-week\n *\n * Limitations:\n * - Numeric values only (no abbreviated names like JAN, MON)\n * - Day-of-week accepts 0-7 where both 0 and 7 represent Sunday\n *\n * Out-of-range fields are rejected eagerly (see {@link validateCronExpression}).\n *\n * Exported for unit testing of the matching logic (not re-exported from the\n * package index — the public surface is unchanged).\n */\nexport function getNextCronDate(cron: string): Date {\n const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] =\n validateCronExpression(cron);\n\n const now = new Date();\n const candidate = new Date(now);\n candidate.setSeconds(0);\n candidate.setMilliseconds(0);\n\n // Move to next minute at minimum\n candidate.setMinutes(candidate.getMinutes() + 1);\n\n // Standard cron DOM/DOW semantics:\n // When both day-of-month and day-of-week are restricted (not *),\n // a date matches if EITHER condition is met (OR logic).\n const dayIsWildcard = dayExpr === '*';\n const dowIsWildcard = dowExpr === '*';\n\n // Search for next matching date (limit to 1 year)\n const maxIterations = 525600;\n for (let i = 0; i < maxIterations; i++) {\n const dayMatches = matchesCronField(candidate.getDate(), dayExpr);\n // getDay() returns 0 for Sunday; standard cron accepts both 0 and 7\n const dow = candidate.getDay();\n const dowMatches =\n matchesCronField(dow, dowExpr) ||\n (dow === 0 && matchesCronField(7, dowExpr));\n\n let dayOfMonthOrWeekMatches: boolean;\n if (!dayIsWildcard && !dowIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches || dowMatches;\n } else if (!dayIsWildcard) {\n dayOfMonthOrWeekMatches = dayMatches;\n } else if (!dowIsWildcard) {\n dayOfMonthOrWeekMatches = dowMatches;\n } else {\n dayOfMonthOrWeekMatches = true;\n }\n\n if (\n matchesCronField(candidate.getMonth() + 1, monthExpr) &&\n dayOfMonthOrWeekMatches &&\n matchesCronField(candidate.getHours(), hourExpr) &&\n matchesCronField(candidate.getMinutes(), minuteExpr)\n ) {\n return candidate;\n }\n\n candidate.setMinutes(candidate.getMinutes() + 1);\n }\n\n throw new Error(`Could not find next run date for cron: ${cron}`);\n}\n\n/**\n * Check if a value matches a cron field expression\n */\nfunction matchesCronField(value: number, expr: string): boolean {\n if (expr === '*') return true;\n\n // Step values (*/5, 0-30/2)\n if (expr.includes('/')) {\n const [range, stepStr] = expr.split('/');\n const step = parseInt(stepStr, 10);\n if (range === '*') return value % step === 0;\n if (range.includes('-')) {\n const [startStr, endStr] = range.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n if (value < start || value > end) return false;\n return (value - start) % step === 0;\n }\n }\n\n // Ranges (1-5)\n if (expr.includes('-')) {\n const [startStr, endStr] = expr.split('-');\n const start = parseInt(startStr, 10);\n const end = parseInt(endStr, 10);\n return value >= start && value <= end;\n }\n\n // Lists (1,3,5)\n if (expr.includes(',')) {\n const values = expr.split(',').map((v) => parseInt(v.trim(), 10));\n return values.includes(value);\n }\n\n // Exact match\n return value === parseInt(expr, 10);\n}\n\n/**\n * Create a ScheduleRunner instance\n */\nexport function createScheduleRunner(\n config?: ScheduleRunnerConfig,\n): ScheduleRunner {\n return new ScheduleRunner(config);\n}\n\nexport default ScheduleRunner;\n","/**\n * @happyvertical/smrt-jobs\n *\n * Background job execution for SMRT objects.\n * Provides persistence, retries, and scheduling for any SmrtObject method.\n *\n * @example\n * ```typescript\n * import { withBackgroundJobs, TaskRunner } from '@happyvertical/smrt-jobs';\n *\n * // Add background capabilities to your class\n * const BackgroundDocument = withBackgroundJobs(Document);\n * const doc = new BackgroundDocument({ ... });\n *\n * // Simple background execution\n * const handle = await doc.bg('generateSummary', { format: 'md' });\n *\n * // Fluent builder for advanced options\n * const handle = await doc.background('generateSummary', { format: 'md' })\n * .delay('5m')\n * .retries(5)\n * .priority('high')\n * .enqueue();\n *\n * // Wait for result\n * const result = await handle.wait();\n *\n * // Start a task runner to process jobs\n * const runner = new TaskRunner({ concurrency: 5 });\n * await runner.initialize(db);\n * await runner.start();\n * ```\n */\n\n// Self-register this package's manifest before any @smrt() decorator fires\n// downstream. Must come first so the side effect runs ahead of the class\n// module loads below. See __smrt-register__.ts for issue #1132 context.\nimport './__smrt-register__.js';\n// Contribute `_smrt_jobs`/`_smrt_job_events` retention to the framework sweep\n// as soon as this package is loaded (#2375), so `smrt db:prune` and any host\n// process see the tasks without an app-level wiring step. Registering is not\n// scheduling: nothing deletes until something runs a sweep, and the policy can\n// still disable either task by name.\nimport { registerJobRetentionTasks } from './retention.js';\n\nregisterJobRetentionTasks();\n\n// Background-job policy controls (retry ceiling, per-tenant cap, opt-in\n// method allowlist). Shared with the agents package (S5 audit #1402).\nexport {\n assertWithinTenantCreationCap,\n type BackgroundEligibleClass,\n backgroundEligible,\n clampRetries,\n DEFAULT_TENANT_JOB_CAP,\n getBackgroundEligibleMethods,\n isBackgroundEligibleMethod,\n MAX_JOB_RETRIES,\n markBackgroundEligible,\n TenantJobCapExceededError,\n} from './background-policy.js';\nexport {\n createHmacDurableJobPayloadSigner,\n type DurableJobPayloadIntegrity,\n type DurableJobPayloadSigner,\n} from './durable-payload-integrity.js';\n// Error-message redaction for durable job error persistence.\nexport {\n redactErrorForPersistence,\n redactErrorMessage,\n} from './error-redaction.js';\n// Durable provider-neutral forge delivery inbox and projection runtime.\nexport {\n type AcceptedForgeDelivery,\n type AcceptForgeDeliveryInput,\n type ClaimForgeDeliveryOptions,\n ForgeDelivery,\n ForgeDeliveryCollection,\n type ForgeDeliveryStatus,\n type ForgeObservation,\n type ForgeProjectionAuditEvent,\n type ForgeProjectionAuditHook,\n ForgeProjectionCheckpoint,\n type ForgeProjectionContext,\n ForgeProjectionRuntime,\n type ForgeProjectionRuntimeOptions,\n type ForgeProjector,\n} from './forge-projection.js';\n// Fluent job builder and utilities\nexport {\n JobBuilder,\n type Priority,\n parseDelay,\n priorityToNumber,\n} from './job-builder.js';\n\n// Job handle for tracking\nexport {\n JobHandle,\n type JobResult,\n type WaitOptions,\n} from './job-handle.js';\n// Logger extension\nexport {\n type JobContext,\n JobContextLogger,\n type JobEventInput,\n type JobExecutionContext,\n type JobProgressInput,\n} from './logger-extension.js';\n// Durable MCP Tasks extension adapter.\nexport {\n type CreateMcpTaskInput,\n createMcpTaskResult,\n getMcpTaskMarker,\n MCP_TASKS_EXTENSION,\n type McpTask,\n McpTaskCancelledError,\n type McpTaskJobMarker,\n McpTaskNotFoundError,\n type McpTaskStatus,\n McpTaskStore,\n requestMcpTaskInput,\n taskFromJob,\n} from './mcp-task.js';\n// Object extension (mixin)\nexport {\n type BackgroundCapable,\n type BgOptions,\n withBackgroundJobs,\n} from './object-extension.js';\n// Job retention — bounded growth for _smrt_jobs / _smrt_job_events (#2375)\nexport {\n DEFAULT_JOB_RETENTION,\n DEFAULT_RETENTION_SWEEP_INTERVAL_MS,\n JOB_EVENTS_RETENTION_TASK,\n JOBS_RETENTION_TASK,\n type JobRetentionOptions,\n type RetentionSweeper,\n type RetentionSweeperOptions,\n registerJobRetentionTasks,\n startRetentionSweeper,\n unregisterJobRetentionTasks,\n} from './retention.js';\n// Task runner\nexport {\n createTaskRunner,\n getActiveJobExecutionContext,\n isRunnerExecutionContext,\n JobTimeoutError,\n TaskRunner,\n type TaskRunnerConfig,\n type TaskRunnerEvents,\n} from './runner.js';\n\n// Schedule runner (for cron-based agent scheduling)\nexport {\n createScheduleRunner,\n getNextCronDate,\n type ScheduleInfo,\n ScheduleRunner,\n type ScheduleRunnerConfig,\n type ScheduleRunnerEvents,\n validateCronExpression,\n} from './schedule-runner.js';\n// Core job model\nexport {\n type ClaimReadyOptions,\n type JobStatus,\n type ListReadyOptions,\n SmrtJob,\n SmrtJobCollection,\n type SmrtJobData,\n type TimeoutBehavior,\n} from './smrt-job.js';\n\n// Durable job telemetry\nexport {\n type JobEventCursor,\n type ListJobEventsOptions,\n SmrtJobEvent,\n SmrtJobEventCollection,\n type SmrtJobEventData,\n type SmrtJobEventLevel,\n type SmrtJobEventType,\n} from './smrt-job-event.js';\n\n// Worker liveness lease model\nexport {\n type RegisterWorkerInput,\n SmrtWorker,\n SmrtWorkerCollection,\n} from './smrt-worker.js';\nexport {\n createWorkerKey,\n isWorkerAlive,\n registerLiveWorker,\n unregisterLiveWorker,\n} from './worker-liveness.js';\n"],"mappings":"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"}
|