import type { AtomicJobBookkeeping, AtomicJobMutation, AtomicJobRetryBookkeeping, BackgroundJobCandidatePage, BackgroundJobExecutionContext, BackgroundJobFailureCode, BackgroundJobLease, BackgroundJobPageQuery, BackgroundJobStoreOptions, BackgroundJobSubmission, JobAttemptOutcome, JobFinalizationResult, OperationExecutionContext } from '@cleocode/contracts/jobs'; import type { NodeSQLiteDatabase } from './sqlite.js'; import { type BackgroundJobStatus } from './tasks-schema.js'; export type { BackgroundJobStatus }; /** Inspectable durable job, including unresolved legacy scope and ownership. */ export interface BackgroundJob { /** Unique persisted job ID. */ id: string; /** Supported operation name. */ operation: string; /** Persisted lifecycle status; cancellation requests are separate. */ status: BackgroundJobStatus; /** ISO-8601 creation time. */ startedAt: string; /** ISO-8601 terminal time, if committed. */ completedAt?: string; /** Existing executor result surface. */ result?: unknown; /** Persisted executor error, when failed. */ error?: string; /** Last reported percentage. */ progress?: number; /** Actor display identity, distinct from the unique lease owner. */ claimedBy?: string; /** Explicit project identity; null discloses unresolved legacy scope. */ projectId: string | null; /** Unique owner of the current claim; null on legacy unowned rows. */ ownerId: string | null; /** Current lease expiration in epoch milliseconds. */ leaseExpiresAt: number | null; /** Last heartbeat in epoch milliseconds. */ heartbeatAt: number; /** Monotonic claim fence. */ fencingEpoch: number; /** Number of issued execution claims. */ attempts: number; /** Persisted cancellation request; does not assert executor termination. */ cancellationRequestedAt: number | null; /** Last fenced JSON checkpoint bytes. */ checkpointJson: string | null; /** Last checkpoint timestamp in epoch milliseconds. */ checkpointAt: number | null; /** Project/operation-scoped retry key. */ idempotencyKey: string | null; /** SHA-256 of immutable proposal bytes. */ proposalHash: string | null; /** Authentic immutable input bytes; null means the original payload is unavailable. */ proposalJson: string | null; /** Ownership assessment at read time, independent of lifecycle status. */ ownership: 'current' | 'expired' | 'unclaimed' | 'legacy-unknown' | 'terminal'; /** Explicit diagnostic failure, when result decoding or local persistence failed. */ diagnosticError?: string; } /** Configuration for the existing executor facade. */ export interface BackgroundJobManagerConfig extends BackgroundJobStoreOptions { /** Maximum running jobs observed in this client's project scope. */ maxJobs?: number; /** Former retention window. @deprecated Job evidence is retained regardless of age. */ retentionMs?: number; } /** * Stable refusal of an unsafe ownership, retry, or transaction operation. * @remarks A refusal preserves the current owner's work and reports no committed transition. */ export declare class BackgroundJobError extends Error { /** Machine-readable refusal reason. */ readonly code: BackgroundJobFailureCode; /** * Construct an explicit job-store refusal. * @param code - Stable failure category. * @param message - Human-readable reason and required correction. * @remarks This error never establishes successful job execution. * @example * ```ts * throw new BackgroundJobError('E_JOB_LEASE_LOST', 'Reclaim expired work first'); * ``` */ constructor(code: BackgroundJobFailureCode, message: string); } /** * Revalidate captured job ownership inside the domain's existing write transaction. * @param db - The same transaction handle that will perform the domain mutation. * @param execution - Captured lifetime and optional immutable job fence. * @throws BackgroundJobError when ownership, scope, proposal, cancellation or file identity changed. * @remarks This read does not start a transaction or authorize arbitrary work. Callers * must own the surrounding write transaction and independently validate their domain * preconditions. Keeping this check inside that transaction fences concurrent claims * and permits domain mutations and receipts to compose in one atomic unit. * @example * ```ts * db.transaction(tx => { assertOperationWriteFence(tx, execution); writePreparedRows(tx); }); * ``` */ export declare function assertOperationWriteFence(db: Pick, execution: OperationExecutionContext): void; /** * Drizzle-backed ownership of the established active job table. * @remarks Sync methods refuse an already-owned native transaction before writing. * No method infers a missing legacy lease to be expired. Rows are never age-pruned. * @example * ```ts * const jobs = new DurableJobStore(db, { projectId: 'project-uuid' }); * const lease = jobs.insert('job-uuid', 'doctor.knowledge', Date.now()); * if (lease) jobs.complete(lease.jobId, { verified: true }, Date.now()); * ``` */ export declare class DurableJobStore { #private; /** Positive lease duration used for claims and heartbeats. */ readonly leaseMs: number; /** * Open a client without changing persisted work. * @param db - Existing canonical database handle; no new connection is opened. * @param options - Explicit scope, actor, and lease duration. * @remarks Unscoped legacy callers retain visible null project identity. * @example * ```ts * const store = new DurableJobStore(db, { projectId: 'project-uuid', leaseMs: 30000 }); * ``` */ constructor(db: NodeSQLiteDatabase, options?: BackgroundJobStoreOptions); /** * Create a leased job, or return the existing immutable scoped submission. * @param id - Proposed UUID for a new job. * @param operation - Supported operation identifier. * @param now - Event timestamp in epoch milliseconds. * @param submission - Optional scoped immutable retry contract. * @param maxRunning - Optional running-job limit checked atomically with creation. * @returns Grant for new work, or null when an identical submission already exists. * @remarks A repeated submission never acquires ownership or re-executes by itself. * @example * ```ts * const grant = store.insert(id, 'doctor.knowledge', Date.now(), submission); * ``` */ insert(id: string, operation: string, now: number, submission?: BackgroundJobSubmission, maxRunning?: number): BackgroundJobLease | null; /** * Find an existing immutable submission without claiming it. * @param operation - Exact supported operation. * @param submission - Project and retry identity. * @returns Persisted job, or undefined. * @remarks Input conflict validation is performed by insert before coalescing. * @example * ```ts * const prior = store.findSubmission('doctor.knowledge', submission); * ``` */ findSubmission(operation: string, submission: BackgroundJobSubmission): BackgroundJob | undefined; /** * Persist authentic pending work without claiming or starting an executor. * @param id - Proposed job identity; matching retries retain the original identity. * @param operation - Exact operation whose supported inputs the domain service validates. * @param now - Submission timestamp in epoch milliseconds. * @param submission - Required project-scoped immutable serialized proposal. * @param execution - Optional original invocation budget; omitted legacy callers retain existing behavior. * @returns Persisted pending job, or unchanged prior matching submission. * @remarks Input and its hash commit in one native transaction. Existing history * without payload is retained, never reconstructed from a hash or overwritten. * @example * ```ts * const pending = store.defer(id, 'docs.projection', Date.now(), submission); * ``` */ defer(id: string, operation: string, now: number, submission: BackgroundJobSubmission, execution?: OperationExecutionContext): BackgroundJob; /** * Read a job with scope and ownership disclosed. * @param id - Job identity. * @returns Job or undefined. * @remarks A missing or expired lease is never represented as current ownership. * @example * ```ts * const job = store.get(id); * ``` */ get(id: string): BackgroundJob | undefined; /** * List visible jobs. * @param status - Optional lifecycle filter. * @returns Matching scoped jobs. * @remarks Project-scoped clients exclude other projects and unscoped legacy rows. * @example * ```ts * const jobs = store.list('running'); * ``` */ list(status?: string): BackgroundJob[]; /** * Read a bounded candidate page without a runtime manager or ownership mutation. * @param query - Exact operation, optional status, payload cap and bound cursor. * @param execution - Original captured caller and deadline; never renewed by this read. * @returns Candidate rows and a last-scanned cursor; the domain must validate principals. * @throws BackgroundJobError for ambiguous scope, mismatched cursors, oversized rows or borrowed transactions. * @remarks SQL limits candidates before payload materialization or JSON parsing. Mutable * claimedBy is not used as principal authority. Existing transaction cleanup bounds lock waits; * synchronous SQLite work is cooperative and cannot be preempted by a JavaScript timer. * @example * ```ts * const page = store.listPage({ operation: 'doctor.knowledge', limit: 20 }, execution); * ``` */ listPage(query: BackgroundJobPageQuery, execution: OperationExecutionContext): BackgroundJobCandidatePage; /** * Claim authentic unstarted work or reclaim an explicitly expired lease. * @param id - Existing job identity. * @param now - Claim timestamp. * @param maxRunning - Optional capacity limit enforced atomically with the claim. * @param execution - Optional original invocation budget; supplying it adds scoped cancellation and deadline checks. * @returns New attempt's lease. * @remarks Preserves checkpoint and cancellation request. Pending work must have * authentic hash-matching inputs and no prior execution; ambiguous legacy rows refuse. * @example * ```ts * const lease = store.claim(expiredJob.id, Date.now()); * ``` */ claim(id: string, now: number, maxRunning?: number, execution?: OperationExecutionContext): BackgroundJobLease; /** * Start an explicit new attempt after retaining terminal or expired owned work atomically. * @param id - Authentic failed, cancelled or explicitly expired owned job with the same immutable proposal. * @param now - New attempt timestamp. * @param execution - Fresh bounded invocation; expired contexts or old fences cannot be renewed. * @param retainOutcome - Trusted synchronous core callback that rechecks domain preconditions, * appends the complete previous row image, and verifies its receipt before returning. * @param maxRunning - Optional capacity limit enforced inside the same transaction. * @returns New owned lease, without reapplying any domain operation. * @throws BackgroundJobError when scope, terminal status, authentic input, counters, or receipt is invalid. * @remarks Complete jobs are never reopened. Expired running rows retain their authentic * uncertain outcome; expiry does not invent failure. Explicit resume acknowledges a retained * previous cancellation request and clears it only with the new claim. Callback failure, cancellation, or claim failure * rolls back both historical bookkeeping and the new claim. The caller must independently * preserve its append-only history in the same database; this is not an arbitrary callback sandbox. * @example * ```ts * const lease = store.retryAtomically(id, Date.now(), context, retainPreviousAttempt); * ``` */ retryAtomically(id: string, now: number, execution: OperationExecutionContext, retainOutcome: AtomicJobRetryBookkeeping, maxRunning?: number): BackgroundJobLease; /** * Renew an owned lease and observe persisted cancellation. * @param id - Owned job identity. * @param now - Heartbeat timestamp. * @returns Current job including cancellation request. * @remarks Expired attempts cannot revive themselves through heartbeat. * @example * ```ts * const job = store.heartbeat(id, Date.now()); * ``` */ heartbeat(id: string, now: number): BackgroundJob; /** * Persist a checkpoint under the current attempt fence. * @param id - Owned job identity. * @param valueJson - Valid serialized checkpoint bytes. * @param now - Checkpoint timestamp. * @remarks Writes atomically with ownership verification; does not authorize domain writes. * @example * ```ts * store.checkpoint(id, JSON.stringify({ nextFile: 8 }), Date.now()); * ``` */ checkpoint(id: string, valueJson: string, now: number): void; /** * Request cancellation without claiming that execution has stopped. * @param id - Visible job identity. * @param now - Request timestamp. * @param execution - Optional original invocation context; never renews its deadline. * @returns Whether pending work was cancelled or running work has a persisted request. * @remarks Terminal results are preserved when cancellation arrives after commit. * @example * ```ts * const requested = store.requestCancel(id, Date.now()); * ``` */ requestCancel(id: string, now: number, execution?: OperationExecutionContext): boolean; /** * Commit actual executor success under ownership. * @param id - Job identity. * @param result - Actual outcome. * @param now - Completion timestamp. * @remarks An earlier cancellation request does not erase work already committed. * @example * ```ts * store.complete(id, { verified: true }, Date.now()); * ``` */ complete(id: string, result: unknown, now: number): void; /** * Commit a synchronous domain mutation, its receipt and job completion atomically. * @param execution - Captured context carrying this client's current claimed fence. * @param mutation - Trusted synchronous domain operation returning JSON receipt bytes. * @returns Exact receipt bytes committed with the domain changes and terminal job row. * @throws BackgroundJobError when the transaction is borrowed, authority changed, * cancellation was observed before commit, or receipt bytes are invalid. * @remarks The store acquires its own write transaction; an ambient BEGIN confers * no authority. Domain code must independently validate its proposal and operate * only on the same SQLite database without transaction control or external effects. * Guards are cooperative boundaries, not preemption. Cancellation observed after * COMMIT cannot erase the committed result. Arbitrary async callbacks are unsupported. * @example * ```ts * const receiptJson = store.completeAtomically(execution, () => applyPreparedRepair()); * ``` */ completeAtomically(execution: OperationExecutionContext, mutation: AtomicJobMutation): string; /** * Finalize trusted receipt/event metadata and a failed or cancelled attempt atomically. * @param execution - Original bounded attempt identity and current ownership fence. * @param outcome - Sourced observed outcome; expiry alone cannot supply this fact. * @param bookkeeping - Synchronous metadata-only service callback returning JSON receipt bytes. * @returns Committed receipt or explicit pending finalization with actual elapsed time. * @remarks Cancellation allows bookkeeping only; domain repair remains forbidden. * An exhausted deadline never acquires a lock. Failed finalization preserves the * lease/checkpoint for inspection and a later explicit recovery attempt. No budget * is renewed, and synchronous SQLite completion is not preempted by a timer. * @example * ```ts * const result = store.finalizeAtomically(context, outcome, () => appendAttemptReceipt()); * ``` */ finalizeAtomically(execution: OperationExecutionContext, outcome: JobAttemptOutcome, bookkeeping: AtomicJobBookkeeping): JobFinalizationResult; /** * Commit actual executor failure under ownership. * @param id - Job identity. * @param error - Failure message. * @param now - Failure timestamp. * @remarks Stale owners cannot replace a newer attempt's outcome. * @example * ```ts * store.fail(id, 'Verification failed', Date.now()); * ``` */ fail(id: string, error: string, now: number): void; /** * Acknowledge an executor's cancellation. * @param id - Owned job identity. * @param now - Acknowledgement timestamp. * @remarks Requires a persisted cancellation request and current ownership. * @example * ```ts * store.cancel(id, Date.now()); * ``` */ cancel(id: string, now: number): void; /** * Update an owned attempt's progress. * @param id - Job identity. * @param progress - Finite percentage, clamped to zero through one hundred. * @param now - Observation timestamp. * @remarks Progress does not renew an expired lease or overwrite another owner. * @example * ```ts * store.progress(id, 50, Date.now()); * ``` */ progress(id: string, progress: number, now: number): void; /** * Retain historical job evidence regardless of age. * @param _cutoffMs - Former deletion threshold, retained for API compatibility. * @returns Zero; any future deletion requires explicit evidence-aware recovery policy. * @remarks No automatic or age-only pruning is performed. * @example * ```ts * const removed = store.purgeOlderThan(Date.now()); // 0 * ``` */ purgeOlderThan(_cutoffMs: number): number; } /** * Existing executor facade with persisted ownership and cooperative cancellation. * @remarks Executors must use a separately authorized transactional service for * domain changes and receipts; this class fences only job metadata/checkpoints. * @example * ```ts * const manager = new BackgroundJobManager(db, { projectId: 'project-uuid' }); * const id = await manager.startJob('inspect', async ({ signal }) => ({ cancelled: signal.aborted })); * ``` */ export declare class BackgroundJobManager { #private; /** * Open the existing facade. * @param db - Canonical store. * @param config - Scope and lease configuration. * @remarks Construction does not orphan or execute persisted work. * @example * ```ts * const manager = new BackgroundJobManager(db, { projectId: 'project-uuid' }); * ``` */ constructor(db: NodeSQLiteDatabase, config?: BackgroundJobManagerConfig); /** * Start new work or coalesce an identical immutable submission. * @param operation - Supported operation. * @param executor - Existing executor, now given cancellation and checkpoint capabilities. * @param submission - Optional scoped retry identity. * @returns New or already persisted job identity. * @remarks Repeated submissions never invoke a second executor. * @example * ```ts * const id = await manager.startJob('inspect', async () => ({ verified: true }), submission); * ``` */ startJob(operation: string, executor: (context: BackgroundJobExecutionContext) => Promise, submission?: BackgroundJobSubmission): Promise; /** * Record optional work durably without scheduling an executor or model. * @param operation - Supported action validated by the calling domain service. * @param submission - Authentic scoped proposal bytes for explicit later resume. * @returns Existing or newly persisted job identity. * @remarks This does not establish completion of the optional projection or a * domain repair receipt. Explicit resume must validate current prerequisites. * @example * ```ts * const id = manager.deferJob('docs.projection', submission); * ``` */ deferJob(operation: string, submission: BackgroundJobSubmission): string; /** * Explicitly start pending work or resume expired ownership using the existing facade. * @param id - Expired job identity. * @param executor - Executor capable of resuming its persisted checkpoint. * @returns Identity of the resumed job. * @remarks The earlier owner is fenced before a replacement executor can run. * @example * ```ts * await manager.resumeJob(id, async ({ checkpoint }) => { checkpoint('{}'); }); * ``` */ resumeJob(id: string, executor: (context: BackgroundJobExecutionContext) => Promise): Promise; /** * Read persisted state and local diagnostic failures. * @param id - Job identity. * @returns Job or undefined. * @remarks Persistence errors are disclosed separately from a committed outcome. * @example * ```ts * const job = manager.getJob(id); * ``` */ getJob(id: string): BackgroundJob | undefined; /** * List persisted scoped jobs. * @param status - Optional lifecycle filter. * @returns Matching jobs. * @remarks Includes cancellation requests and local diagnostic failures. * @example * ```ts * const jobs = manager.listJobs('running'); * ``` */ listJobs(status?: string): BackgroundJob[]; /** * Request cancellation of visible running work. * @param id - Job identity. * @returns Whether the request was recorded, not proof of termination. * @remarks Other owners observe the durable request on heartbeat. * @example * ```ts * const requested = manager.cancelJob(id); * ``` */ cancelJob(id: string): boolean; /** * Report progress from an owned executor. * @param id - Job identity. * @param progress - Finite percentage. * @returns Whether the job exists and is running. * @remarks A visible job owned by another attempt is explicitly refused. * @example * ```ts * manager.updateProgress(id, 50); * ``` */ updateProgress(id: string, progress: number): boolean; /** * Preserve historical evidence. * @returns Zero rows removed. * @remarks Age alone never authorizes deletion of job evidence. * @example * ```ts * const removed = manager.cleanup(); // 0 * ``` */ cleanup(): number; /** * Request cancellation only for this manager's executors and stop its timers. * @remarks Does not claim non-cooperative executors stopped or alter another owner. * @example * ```ts * manager.destroy(); // inspect persisted cancellation/completion separately * ``` */ destroy(): void; } //# sourceMappingURL=background-jobs.d.ts.map