Browse pages
Jobs and Queues
Work that outlives a request runs in a Worker: scheduled jobs from a timetable, ingest pipelines over outside records, engine-carried sagas, and reports too heavy for a request, while people clear queued items through claimed listings. This page states how each kind records its work, runs it once, and recovers. A project selects it for recurring, delayed, ingested, carried, heavy, or human-cleared work.
Stack: any Layer: application
Activation#
Activation scope: local.
Applicable specification kinds: job, use-case, journey, aggregate.
The consumer enables jobs for recurring, delayed, or calendar-based work. Each schedule lives on a job record with its kind, schedule, and run fields, as the automation page states. Selecting it replaces litespec/backend/architecture#declare-the-execution-host-for-work-that-outlives-a-request for scheduled work: a Worker runs the scheduler.
For human queues, a project selects jobs for a use case when the use case lists work items people claim or clears items in bulk. Ordinary reads and single-item commands do not select it.
For ingest, a project selects jobs for a job when the job ingests outside records through drop and enrich steps. A job that drives product sagas or sends notifications does not select it.
For engine-carried sagas, a project selects jobs for a saga aggregate when a workflow engine carries it. A saga driven by reactions or jobs does not select it.
For reports, a use case selects jobs for one documented query or export that the normal request path cannot serve: complex relational SQL, a large result, long processing, output in object storage, or a latency budget beyond a normal request. The use case page carries the measurements that prove the normal path falls short.
Baseline Relationship#
This extension replaces litespec/backend/architecture#declare-the-execution-host-for-work-that-outlives-a-request. The baseline lets a project declare either the WebApi process or a Worker project as the execution host for work that outlives a request. A project selecting jobs runs scheduled work in a Worker outside WebApi request handling, under litespec/ext/jobs#run-scheduled-jobs-in-worker.
The job, setting, and runbook records belong to the core. Schedules bound their retention with setting records and operators recover through runbook records. The request budget and each report bound live in setting records.
A listing query states its claim mode and its score, while a bulk money command states its preview and its confirmBy code. An ingest job lists its steps in run order, names its closed dropReasons set, and states its buffer policy. Each query reading ingested data states the freshness consequence its readers observe. A carried saga sets carriedBy: workflow, maps each engine step to exactly one use case, and states each step's compensation. The engine's retry and timeout configuration stays in Implementation, outside the records. Ordinary queries stay under litespec/backend/persistence#query-through-the-read-session; the report provisions govern only the reads whose cost or duration exceeds that path.
Standards#
Carry the Schedule on the Job Record#
rulerulecheckablelitespec/ext/jobs#carry-the-schedule-on-the-job-recordA scheduled job MUST carry its schedule in structured form with its timezone on its job record.
Applies toartifact: job
Why
The job record states when the work runs: every or cron with its timezone, so tools read the rhythm instead of parsing prose. A schedule kept outside the record runs with no recorded moment attached, and misfire behavior has no stated time to measure against. The automation page states the record fields, and the schedule-invalid check reports a schedule no tool can read.
Checkchecked by a tool
Checked bylitespec model check reports schedule-invalid (level G) for a job schedule that is neither an every duration nor a cron expression; each scheduled job record states its timezone.
Marked checkable: the tool named here checks it.
Run One Command per Item in Selection#
ruleruleguidancelitespec/ext/jobs#run-one-command-per-item-in-selectionA job MUST run one command per item in its selection within its batchSize, each in its own unit of work.
Applies toartifact: job
Why
The selection states what each run selects, the batch size bounds how many items one batch takes, and runs names the command run per item. Each item runs as one command in its own unit of work, as litespec/backend/application#issue-per-record-work-from-a-job requires: a failed item replays without replaying its batch.
Checka reviewer answers
Reviewed forEach job run runs one command per item in its selection within its batch size, each in its own unit of work.
Marked guidance: a reviewer answers this question.
Resume Driven Work Through the Cursor#
ruleruleguidancelitespec/ext/jobs#resume-driven-work-through-the-cursorA job driving a saga MUST name where resume state lives in its cursor.
Applies toartifact: job
Why
A batch that fails halfway resumes instead of restarting: the cursor holds where the run stopped, beside the saga state it drives. Without it, the next run replays finished items or skips unfinished ones, and a scenario covering the remainder resumes has no stated position to assert.
Checka reviewer answers
Reviewed forEach job driving a saga names its resume position in its cursor, with a scenario covering the remainder after a halfway failure.
Marked guidance: a reviewer answers this question.
Run Manual Jobs Under Their Guard#
rulerulecheckablelitespec/ext/jobs#run-manual-jobs-under-their-guardA manual job run MUST stay inside its runsWhere under its guard.
Applies toartifact: job
Why
Manual work is interactive platform work with no aggregate, such as a rehearsal harness, so no model bounds where it runs or who runs it. runsWhere names the deployment scope it may touch and the guard names who may run it and when. The automation page states the record fields, and the job-guard-missing check reports a manual job with no guard.
Checkchecked by a tool
Checked bylitespec model check reports job-guard-missing (level G) for a manual job with no guard; each manual run stays inside its runsWhere.
Marked checkable: the tool named here checks it.
Run Scheduled Jobs in Worker#
ruleruleguidancelitespec/ext/jobs#run-scheduled-jobs-in-workerA scheduled job MUST run in Worker outside WebApi request handling.
Applies toartifact: job
Why
Scheduled work runs in a Worker, outside WebApi request handling. The Worker owns the schedule's lifetime and shuts its replicas down in a controlled order. Request handling has neither a scheduler nor a shutdown order for background work.
Checka reviewer answers
Reviewed forWorker sources hold every scheduled job, and WebApi request handling holds none.
Marked guidance: a reviewer answers this question.
Observe Job Cancellation#
ruleruleguidancelitespec/ext/jobs#observe-job-cancellationA scheduled job MUST observe cancellation.
Applies toartifact: job
Why
Each job observes cancellation, so an occurrence ends its current step instead of running past the moment an operator asked it to stop.
Checka reviewer answers
Reviewed forEach job handler threads cancellation into its waits and stops its current step when cancellation fires.
Marked guidance: a reviewer answers this question.
Bound Job Duration#
ruleruleguidancelitespec/ext/jobs#bound-job-durationA scheduled job MUST complete within a bounded duration.
Applies toartifact: job
Why
Each job bounds its duration, so a Worker stops without stranding work it can no longer watch.
Checka reviewer answers
Reviewed forEach job handler carries a duration bound, and the occurrence store shows completions inside it.
Marked guidance: a reviewer answers this question.
Stop Jobs During Graceful Shutdown#
ruleruleguidancelitespec/ext/jobs#stop-jobs-during-graceful-shutdownA scheduled job MUST stop accepting new work during graceful shutdown.
Applies toartifact: job
Why
Each job stops accepting new work during graceful shutdown. An unbounded job turns every deploy into a wait for work that never ends.
Checka reviewer answers
Reviewed forThe Worker shutdown path stops schedule polling before it drains running occurrences.
Marked guidance: a reviewer answers this question.
Store Execution Leases#
ruleruleguidancelitespec/ext/jobs#store-execution-leasesA scheduled-job store MUST record lease owner, expiry, and fencing value in PostgreSQL.
Applies toartifact: job
Why
Each planned occurrence carries a lease with its owner, expiry, and fencing value in PostgreSQL. The lease names the one replica that may complete that occurrence. The lease field on the job record names the lock each run claims. The outbox extension cites this definition for its own claims.
Checka reviewer answers
Reviewed forThe lease rows in PostgreSQL carry owner, expiry, and fencing value for each occurrence.
Marked guidance: a reviewer answers this question.
Renew Only Owned Leases#
ruleruleguidancelitespec/ext/jobs#renew-only-owned-leasesA Worker replica MUST renew only its active lease and stop when renewal fails.
Applies toartifact: job
Why
A replica renews only the lease it holds and only while it stays active. A failed renewal means another replica owns the occurrence now. The loser stops instead of completing work it no longer owns.
Checka reviewer answers
Reviewed forReplica logs show no renewal after a failed renewal, and the loser stops its occurrence.
Marked guidance: a reviewer answers this question.
Prevent Duplicate Completion#
ruleruleguidancelitespec/ext/jobs#prevent-duplicate-completionA scheduled-job lease MUST prevent two replicas from completing the same occurrence.
Applies toartifact: job
Why
Overlapping replicas must not complete the same occurrence twice. The lease admits one completion per occurrence. Two completions would bill, notify, or reconcile the same planned work twice.
Checka reviewer answers
Reviewed forThe lease admits one completion per occurrence across overlapping replicas.
Marked guidance: a reviewer answers this question.
Generate Monotonic Fencing Values#
ruleruleguidancelitespec/ext/jobs#generate-monotonic-fencing-valuesA scheduled-job store MUST generate each fencing value monotonically per occurrence.
Applies toartifact: job
Why
Each fencing value rises monotonically per occurrence, so a later claim always presents a higher value than an earlier one. A Worker that lost its lease presents a stale value, and litespec/ext/jobs#restrict-completion-by-fencing-value refuses completion on a stale value.
Checka reviewer answers
Reviewed forFencing values in the store rise per occurrence, and a stale value refuses completion.
Marked guidance: a reviewer answers this question.
Scope Occurrences and Leases by Tenant#
ruleruleguidancelitespec/ext/jobs#scope-occurrences-and-leases-by-tenantA scheduled-job occurrence and its lease MUST carry tenant scope when the product isolates tenants.
Applies toartifact: job
Why
Products that isolate tenants scope each occurrence and its lease to one tenant. One tenant's schedules then never trigger, block, or expose another tenant's work. Unscoped leases would let a replica mix tenants inside one occurrence.
Checka reviewer answers
Reviewed forOccurrence and lease rows carry tenant scope in products that isolate tenants.
Marked guidance: a reviewer answers this question.
Dispatch Application Behavior#
ruleruleguidancelitespec/ext/jobs#dispatch-application-behaviorA scheduled job MUST dispatch an Application Command or a declared Application port.
Applies toartifact: job
Why
The job record names the use cases it runs, and the use case boundary lives in Application. Each job therefore dispatches an Application Command or a declared Application port. The schedule stays a trigger while the behavior stays owned.
Checka reviewer answers
Reviewed forEach job handler calls an Application Command or a declared Application port.
Marked guidance: a reviewer answers this question.
Exclude Direct Storage and HTTP Execution#
ruleruleguidancelitespec/ext/jobs#exclude-direct-storage-and-http-executionA scheduled job MUST NOT mutate a Marten session or DbContext directly, bypass Domain behavior, or call WebApi endpoints.
Applies toartifact: job
Why
A job that mutates a Marten session or DbContext directly, bypasses Domain behavior, or calls WebApi endpoints skips the command boundary. That boundary carries authorization, transaction, and validation the job would then lack. Jobs enter through Commands like every other caller.
Checka reviewer answers
Reviewed forJob sources mutate no Marten session or DbContext and call no WebApi endpoint.
Marked guidance: a reviewer answers this question.
Bound Job Retries#
ruleruleguidancelitespec/ext/jobs#bound-job-retriesA scheduled job MUST use bounded attempts, cancellation-aware backoff, and a poison state for permanent failure.
Applies toartifact: job
Why
Bounded attempts with cancellation-aware backoff cap what one failed occurrence consumes. A poison state parks the permanently failing occurrence instead of retrying it forever. Without the bound, one poison occurrence eats the Worker's whole capacity.
Checka reviewer answers
Reviewed forEach schedule declares bounded attempts with cancellation-aware backoff and a poison state.
Marked guidance: a reviewer answers this question.
Tolerate Duplicate Execution#
ruleruleguidancelitespec/ext/jobs#tolerate-duplicate-executionA scheduled job Command and side effect MUST tolerate duplicate execution after lease or process failure.
Applies toartifact: job
Why
A lease or process failure can replay a job whose completion stayed uncertain. The job Command and its side effects tolerate that duplicate execution. Work that assumes exactly-once breaks on the first uncertain failure.
Checka reviewer answers
Reviewed forJob Commands and side effects show one accepted effect after a replayed occurrence.
Marked guidance: a reviewer answers this question.
Treat Unavailable Schedule Stores as Outages#
ruleruleguidancelitespec/ext/jobs#treat-unavailable-schedule-stores-as-outagesA Worker MUST treat an unavailable scheduler store as the store outage litespec/ext/outbox#distinguish-dependency-outage defines, with backed-off, rate-limited logging.
Applies toartifact: job
Why
An unavailable scheduler store is an outage of the dependency, not a failed occurrence. The Worker treats it as the store outage litespec/ext/outbox#distinguish-dependency-outage defines, with backed-off, rate-limited logging. Retry and poison outcomes belong to claimed occurrences, not to a store nobody can reach.
Checka reviewer answers
Reviewed forWorker logs during a store outage show backed-off, rate-limited entries and no poisoned occurrences.
Marked guidance: a reviewer answers this question.
Persist Occurrence Recovery Points#
ruleruleguidancelitespec/ext/jobs#persist-occurrence-recovery-pointsA scheduled-job store MUST persist each occurrence's last scheduled, started, and completed time.
Applies toartifact: job
Why
Last scheduled, started, and completed times persist per occurrence. After a restart, those points separate missed work from completed work. Memory alone cannot make that split, because the restart wipes it.
Checka reviewer answers
Reviewed forEach occurrence row carries last scheduled, started, and completed times.
Marked guidance: a reviewer answers this question.
Define Misfire Recovery#
ruleruleguidancelitespec/ext/jobs#define-misfire-recoveryA scheduled-job specification MUST define skipped, once-replayed, or interval-replayed behavior for missed occurrences.
Applies toartifact: job
Why
Downtime and delay leave occurrences past their time. The specification states whether each missed occurrence is skipped, replayed once, or replayed per interval. The choice controls the business effect: a skipped reminder stays silent while a replayed one arrives late.
Checka reviewer answers
Reviewed forEach schedule states skipped, once-replayed, or interval-replayed behavior for missed occurrences.
Marked guidance: a reviewer answers this question.
Provide Operational Recovery Procedures#
ruleruleguidancelitespec/ext/jobs#provide-operational-recovery-proceduresEach schedule MUST have Runbook records for inspection, manual replay, and disable.
Applies toartifact: job
Why
Stalled, failed, or unsafe schedules need controlled operator actions: inspection, manual replay, and disable. Runbook records carry those procedures. An operator without them improvises against the store during the incident.
Each procedure names the interface that performs it. A procedure that says "disable the schedule" without naming a command, an endpoint, or a configuration value remains undiscovered until the incident it was written for. Avoid an interface that writes to the schedule store directly, because it bypasses the occurrence identity litespec/ext/jobs#derive-stable-occurrence-identity depends on.
Example
A disable procedure names the configuration value or the administrative endpoint that stops the schedule. It states what happens to an occurrence already claimed.
Checka reviewer answers
Reviewed forRunbook records cover inspection, manual replay, and disable for each schedule.
Marked guidance: a reviewer answers this question.
Derive Stable Occurrence Identity#
ruleruleguidancelitespec/ext/jobs#derive-stable-occurrence-identityA scheduled-job store MUST derive each occurrence identity from its schedule ID and scheduled instant.
Applies toartifact: job
Why
Schedule ID plus scheduled instant gives each planned time one stable identity across retries and replicas. Retries of one occurrence then share their idempotency scope, and two replicas recognize the same occurrence.
Checka reviewer answers
Reviewed forOccurrence identities in the store combine schedule ID with scheduled instant.
Marked guidance: a reviewer answers this question.
Remove Occurrences Past Retention#
ruleruleguidancelitespec/ext/jobs#remove-occurrences-past-retentionA scheduled-job store MUST remove occurrences older than the schedule's retention setting record.
Applies toartifact: job
Why
Occurrences older than the retention setting record leave the store so the key space stays bounded.
The key space grows with the schedule's frequency and its retention. A per-second schedule kept for a year holds more than 30 million rows. Quartz states the same practice for a persistent job store.
Example
A daily schedule that retains occurrences for 90 days holds 90 rows. A minute schedule with the same retention holds more than 100,000 rows. That is a different storage decision.
Checka reviewer answers
Reviewed forThe store holds no occurrence older than its schedule's retention setting record.
Marked guidance: a reviewer answers this question.
Persist Occurrence State#
ruleruleguidancelitespec/ext/jobs#persist-occurrence-stateA scheduled-job store MUST persist scheduled, processing, completed, and dead-letter state with safe execution details.
Applies toartifact: job
Why
Scheduled, processing, completed, and dead-letter states persist with attempt, lease, start, completion, and safe error data. The persisted state tells operators and recovery code where each occurrence stands. A store without states cannot separate work in flight from work never started.
Checka reviewer answers
Reviewed forEach occurrence row carries scheduled, processing, completed, or dead-letter state with safe execution details.
Marked guidance: a reviewer answers this question.
Scope Command Idempotency by Occurrence#
ruleruleguidancelitespec/ext/jobs#scope-command-idempotency-by-occurrenceA scheduled job MUST use its occurrence identity as the command idempotency scope.
Applies toartifact: job
Why
The occurrence identity scopes the command idempotency key. Every retry of one occurrence then replays the same scheduled business action. A key scoped any wider would merge separate occurrences into one.
Checka reviewer answers
Reviewed forRetries of one occurrence share the idempotency scope of its occurrence identity.
Marked guidance: a reviewer answers this question.
Restrict Completion by Fencing Value#
ruleruleguidancelitespec/ext/jobs#restrict-completion-by-fencing-valueA Worker MUST mark completion only while its fencing value remains current.
Applies toartifact: job
Why
The Worker marks completion only while its fencing value stays current. A stale value means another replica owns the occurrence now. Completing past that point would write one replica's result over another's ownership.
Checka reviewer answers
Reviewed forCompletion writes fail on a stale fencing value in the store.
Marked guidance: a reviewer answers this question.
Audit Manual Replay#
ruleruleguidancelitespec/ext/jobs#audit-manual-replayA manual replay MUST create a new attempt for the same occurrence identity: an audit record when audit is selected, a standalone record otherwise.
Applies toartifact: job
Why
A manual replay creates a new attempt under the same occurrence identity: an audit record where audit is selected, a standalone record otherwise. The replay keeps the original scheduled instant and its history. A replay recorded as a new occurrence would rewrite when the work was due.
Checka reviewer answers
Reviewed forEach manual replay adds an audited attempt under its original occurrence identity.
Marked guidance: a reviewer answers this question.
Prefer UTC Schedules#
ruleruleguidancelitespec/ext/jobs#prefer-utc-schedulesA scheduled job MUST use a UTC schedule unless its specification names a business-local time requirement and its IANA time zone.
Applies toartifact: job
Why
A local schedule runs twice on one date each year and skips an hour on another. A daylight-saving transition does that to a wall clock. That behavior is correct for a job whose meaning is local, such as a nightly statement at 9 AM for a reader. It is a defect for every other job. Naming the time zone in the specification states the transition behavior for a later reader.
Checka reviewer answers
Reviewed forEach schedule states UTC or names a business-local time requirement with its IANA time zone.
Marked guidance: a reviewer answers this question.
Define Business-Local Clock Behavior#
ruleruleguidancelitespec/ext/jobs#define-business-local-clock-behaviorA business-local schedule MUST record an IANA time-zone identifier and define skipped and repeated local-time behavior.
Applies toartifact: job
Why
Daylight-saving transitions omit one local hour and repeat another. A business-local schedule records its IANA time-zone identifier and defines what happens to occurrences in the skipped and repeated hours. Without that definition, each transition silently drops or doubles the local work.
Checka reviewer answers
Reviewed forEach business-local schedule records its IANA zone with skipped and repeated local-time behavior.
Marked guidance: a reviewer answers this question.
Test Time Transitions#
ruleruleguidancelitespec/ext/jobs#test-time-transitionsA scheduled-job test MUST use an injected clock and representative time-zone transitions.
Applies toartifact: job
Why
An injected clock lets tests move time to any transition on demand. Representative time-zone transitions then run deterministically instead of once a year in production. Tests on the real clock can only wait for the transition to arrive.
Checka reviewer answers
Reviewed forJob tests inject the clock and cover representative time-zone transitions.
Marked guidance: a reviewer answers this question.
Exclude Machine-Local Time#
ruleruleguidancelitespec/ext/jobs#exclude-machine-local-timeScheduled-job production code MUST NOT depend on machine-local time.
Applies toartifact: job
Why
Machine-local time follows each host's configuration. Production code that reads it schedules differently on a developer machine, in staging, and in production. Schedules read their time from the injected clock and the declared zone instead.
Checka reviewer answers
Reviewed forProduction job sources read no machine-local clock.
Marked guidance: a reviewer answers this question.
State the Claim Mode#
ruleruleguidancelitespec/ext/jobs#state-the-claim-modeA work queue MUST state its claim mode as disabled, optional, or required.
Applies toartifact: record:use-case
Why
Two people clearing the same item double-pay, double-approve, or double-refuse. The claim mode says whether items need no claim, accept an optional hold, or require one before action, so the queue never leaves two hands on one item.
Example
The payout queue requires a claim before release, while the review queue leaves claiming optional.
Checka reviewer answers
Reviewed forThe queue use cases state disabled, optional, or required on their claim field.
Marked guidance: a reviewer answers this question.
Order Queue Items by Score#
ruleruleguidancelitespec/ext/jobs#order-queue-items-by-scoreA work queue MUST order its items by a score that cites a calculation rule.
Applies toartifact: record:use-case
Why
A queue without an order is cleared by habit: newest first, loudest first, easiest first. A score citing a calculation rule pins the order to declared inputs, so the riskiest or most valuable item surfaces first by rule instead of by accident.
Example
The dispute queue orders by rule/dispute.urgency-score, which weighs amount, age, and evidence gaps.
Checka reviewer answers
Reviewed forThe queue listings sort by a score citing the calculation rule behind it.
Marked guidance: a reviewer answers this question.
Show the Score Beside Each Item#
ruleruleguidancelitespec/ext/jobs#show-the-score-beside-each-itemA queue listing MUST show each item score with the inputs that set it.
Applies toartifact: record:use-case
Why
A hidden score is an oracle: people work around an order they cannot see or trust. Showing the score with its inputs lets each person check the order and challenge the inputs instead of the queue.
Example
Each dispute row shows urgency 82: EUR 1,900, 6 days old, evidence missing.
Checka reviewer answers
Reviewed forThe queue rows display each score with the inputs that set it.
Marked guidance: a reviewer answers this question.
Release Claims Past Their Lease#
ruleruleguidancelitespec/ext/jobs#release-claims-past-their-leaseA claimed item MUST return to the queue when its claim lease expires.
Applies toartifact: record:use-case
Why
Claims are held by people who go home: a claim with no expiry parks the item forever. The lease returns stale claims to the queue, so an abandoned item waits for the next person instead of for nobody.
Example
A payout claimed Friday and untouched returns to the queue when its one-hour lease expires.
Checka reviewer answers
Reviewed forThe claim tests show untouched items returning after their lease expires.
Marked guidance: a reviewer answers this question.
Show a Preview Before Bulk Money Actions#
ruleruleguidancelitespec/ext/jobs#show-a-preview-before-bulk-money-actionsA bulk money command MUST render a preview of every affected item before acting.
Applies toartifact: record:use-case
Why
Bulk money moves on many items at once, and one wrong filter multiplies one mistake into hundreds. The preview lists every affected item with its amount, so the person confirms the set they meant instead of the set they typed.
Example
The bulk release previews forty-two payouts totaling EUR 18,400 before asking for confirmation.
Checka reviewer answers
Reviewed forThe bulk money commands render every affected item with its amount before acting.
Marked guidance: a reviewer answers this question.
Confirm Bulk Money Actions Out of Band#
ruleruleguidancelitespec/ext/jobs#confirm-bulk-money-actions-out-of-bandA bulk money command MUST require its confirmBy code from outside the session.
Applies toartifact: record:use-case
Why
A preview confirms the set, but the session itself may be hijacked, shared, or left open. An out-of-band code arrives on a channel the session does not control, so the confirmation proves the person, not just the browser.
Example
The bulk release asks for the six-digit code sent to the finance phone before moving any money.
Checka reviewer answers
Reviewed forThe bulk money commands ask for the out-of-band code before moving money.
Marked guidance: a reviewer answers this question.
Declare Ingest Steps in Order#
ruleruleguidancelitespec/ext/jobs#declare-ingest-steps-in-orderAn ingest job MUST list its steps in run order with each step marked drop or enrich.
Applies toartifact: job
Why
Order is the pipeline's meaning: enriching before dropping wastes work, and dropping before enriching loses context. Listing the steps in run order with each kind marked lets a reader replay the pipeline in their head.
Example
The catalog ingest lists drop-duplicates, then enrich-prices, then drop-below-threshold.
Checka reviewer answers
Reviewed forThe ingest job records list steps in run order with each marked drop or enrich.
Marked guidance: a reviewer answers this question.
Define the Closed Drop Reason Set#
ruleruleguidancelitespec/ext/jobs#define-the-closed-drop-reason-setAn ingest job MUST name the closed set of reasons a step drops a record.
Applies toartifact: job
Why
A dropped record with no reason is indistinguishable from a lost one. A closed set forces each drop to say why in words the team chose, so operators count drops by reason instead of guessing.
Example
The catalog ingest names duplicate, below-threshold, and poison as its only drop reasons.
Checka reviewer answers
Reviewed forThe ingest job records name the closed drop reasons with no drop outside them.
Marked guidance: a reviewer answers this question.
Record One Drop Reason per Dropped Record#
ruleruleguidancelitespec/ext/jobs#record-one-drop-reason-per-dropped-recordEach record a step drops MUST carry one reason from the closed set.
Applies toartifact: job
Why
The set alone promises nothing until each drop cites it. One reason on each dropped record turns the set into evidence: every drop answers why, in the team's own words.
Example
A feed row dropped for matching an existing SKU carries duplicate.
Checka reviewer answers
Reviewed forThe dropped records each carry one reason from the job's closed set.
Marked guidance: a reviewer answers this question.
Read Enrichment Data from Declared Sources#
ruleruleguidancelitespec/ext/jobs#read-enrichment-data-from-declared-sourcesAn enrich step MUST read only the sources its job declares.
Applies toartifact: job
Why
Enrichment pulls outside data into product records: prices, categories, ratings. An undeclared source smuggles values no record explains, so each enrich step reads only the sources its job names.
Example
The enrich-prices step reads the vendor price list its job declares, and nothing else.
Checka reviewer answers
Reviewed forThe enrich steps read only the sources named on their job records.
Marked guidance: a reviewer answers this question.
Bound the Ingest Buffer#
ruleruleguidancelitespec/ext/jobs#bound-the-ingest-bufferAn ingest job MUST state its buffer policy with capacity and overflow behavior.
Applies toartifact: job
Why
Feeds burst: a vendor resends a week of rows, a file lands twice. The buffer holds the surge, and the policy states how much it holds and what happens past that: block the feed, drop the oldest, or page an operator. Without the policy, the first surge picks the behavior.
Example
The catalog ingest buffers ten thousand rows and blocks the feed past that until the buffer drains.
Checka reviewer answers
Reviewed forThe ingest job records state buffer capacity with the overflow behavior past it.
Marked guidance: a reviewer answers this question.
Expose Drop Counts per Reason#
ruleruleguidancelitespec/ext/jobs#expose-drop-counts-per-reasonAn ingest job MUST expose a count of dropped records per reason.
Applies toartifact: job
Why
Drop counts are the pipeline's pulse: a spike in poison means a broken feed, a spike in duplicate means a double send. Per-reason counts turn each drop into a signal the team watches.
Example
The catalog dashboard shows twelve poison drops in the last hour beside zero duplicate drops.
Checka reviewer answers
Reviewed forThe pipeline dashboards show dropped-record counts split by reason.
Marked guidance: a reviewer answers this question.
State the Freshness Consequence#
ruleruleguidancelitespec/ext/jobs#state-the-freshness-consequenceEach query reading ingested data MUST state the freshness its readers observe.
Applies toartifact: job
Why
Ingested data lags: the buffer, the batch window, and the enrich calls all take time. The query states what its readers observe, a bounded object with its window or an advisory object with its authority, so a stale price reads as declared behavior instead of a defect.
Example
The catalog search query states {"mode": "bounded", "within": "PT15M"}, matching the ingest job's quarter-hour window.
Checka reviewer answers
Reviewed forThe queries over ingested data state the freshness their readers observe.
Marked guidance: a reviewer answers this question.
Declare the Engine Carrier#
ruleruleguidancelitespec/ext/jobs#declare-the-engine-carrierA saga carried by a workflow engine MUST set carriedBy: workflow.
Applies toartifact: aggregate
Why
A reader meeting a saga asks first what moves it: reactions, jobs, or an engine. The carrier marker answers that question on the aggregate itself, so nobody hunts the engine console for a saga reactions and jobs drive.
Example
The hold aggregate sets carriedBy: workflow while the loan aggregate stays driven by reactions and jobs.
Checka reviewer answers
Reviewed forThe engine-carried aggregates set carriedBy workflow on their aggregate records.
Marked guidance: a reviewer answers this question.
Map Each Engine Step to One Use Case#
ruleruleguidancelitespec/ext/jobs#map-each-engine-step-to-one-use-caseEach engine step MUST map to exactly one use case.
Applies toartifact: aggregate
Why
An engine step with no use case is behavior no record describes, and a step mapped to two use cases is two behaviors wearing one name. One step and one use case keep the engine definition and the records in lockstep: the map reads both ways.
Example
The engine step cancel-hold maps to use-case/lending.cancel-hold and to nothing else.
Checka reviewer answers
Reviewed forThe engine definition lists one use case beside each step with no step sharing or missing.
Marked guidance: a reviewer answers this question.
State Each Step Compensation#
ruleruleguidancelitespec/ext/jobs#state-each-step-compensationEach mapped engine step MUST name its compensation as a use-case object or a none object with the reason.
Applies toartifact: aggregate
Why
Engines fail mid-saga: a step times out, a worker crashes, a saga aborts. The compensation is the step's undo as {"mode": "use-case", "useCase": "use-case/..."}, and a step with no undo says so as {"mode": "none", "reason": "..."}, so recovery follows the record instead of improvising.
Example
The step cancel-hold names {"mode": "use-case", "useCase": "use-case/lending.place-hold"} as its compensation, while borrow-copy names {"mode": "none", "reason": "the copy has left the shelf"}.
Checka reviewer answers
Reviewed forWhich mapped steps lack a compensation object naming its use case or its reason? Look at the map beside the process.
Marked guidance: a reviewer answers this question.
Reject Unmapped Engine Steps#
rulerulecheckablelitespec/ext/jobs#reject-unmapped-engine-stepsAn engine step with no mapped use case MUST fail behavior-unrecorded.
Applies toartifact: aggregate
Why
The map is the whole contract of this extension: every step answers to a use case. A step outside the map is unrecorded behavior by definition, so it fails the same check as code with no record.
Example
A new engine step added in the console with no use case behind it fails behavior-unrecorded at the next check.
Checkchecked by a tool
Checked bylitespec model check reports behavior-unrecorded for an engine step that maps to no use case.
Marked checkable: the tool named here checks it.
Keep Engine Retry Configuration in Implementation#
ruleruleguidancelitespec/ext/jobs#keep-engine-retry-configuration-in-implementationEngine retry and timeout configuration MUST live in code outside the records.
Applies toartifact: aggregate
Why
Retry counts, backoff curves, and step timeouts tune operations, not behavior: they change with load and incidents, not with product decisions. Records state what each step does and undoes, while code holds the knobs operators turn.
Example
The engine's five-attempt retry with exponential backoff lives in the worker configuration, while the records name only the step and its compensation.
Checka reviewer answers
Reviewed forRetry counts, backoff curves, and step timeouts live in worker code with none in the records.
Marked guidance: a reviewer answers this question.
Record Reporting Need#
ruleruleguidancelitespec/ext/jobs#record-reporting-needA reporting proposal MUST record the report, representative data volume, plan or limitation, latency target, maximum result, and operator.
Applies toartifact: record:use-case
Why
Reporting infrastructure costs a Worker, storage, and operating attention, so the team writes down the report, a representative data volume, the plan or limitation, the latency target, the maximum result, and the operator before building any of it. The use case page carries that record, and it shows why a normal request projection cannot meet the boundary.
Checka reviewer answers
Reviewed forThe use case page holds the report, volume, plan, target, maximum, and operator.
Marked guidance: a reviewer answers this question.
Avoid Speculative Reporting Systems#
ruleruleguidancelitespec/ext/jobs#avoid-speculative-reporting-systemsA project MUST NOT create reporting infrastructure for possible future analytics.
Applies toartifact: record:use-case
Why
A reporting system built for analytics the product may never need charges its full operating cost from day one while serving no accepted query. Each report or export earns its infrastructure with a current cost boundary, not a guess about future analysis.
Checka reviewer answers
Reviewed forEach reporting system traces to an accepted current query or export.
Marked guidance: a reviewer answers this question.
Parameterize Report SQL#
ruleruleguidancelitespec/ext/jobs#parameterize-report-sqlA reporting query MUST parameterize every actor, tenant, filter, range, sort, and pagination value.
Applies toartifact: record:use-case
Why
A report concatenates actor, tenant, filters, ranges, sorting, and pagination into SQL that reads across the database. Binding every one of those values as a parameter keeps tenant and actor isolation intact and closes the injection hole string concatenation opens.
Checka reviewer answers
Reviewed forEach report query binds actor, tenant, filter, range, sort, and pagination values as parameters in code.
Marked guidance: a reviewer answers this question.
Keep Raw SQL in Infrastructure#
ruleruleguidancelitespec/ext/jobs#keep-raw-sql-in-infrastructureInfrastructure MUST own raw reporting SQL and connection handling.
Applies toartifact: record:use-case
Why
Raw SQL names tables, dialects, and connection handling that belong to one provider. Keeping it in Infrastructure lets Application and Domain state what the report means without depending on how one database phrases it.
Checka reviewer answers
Reviewed forRaw report SQL and connection handling sit in Infrastructure code.
Marked guidance: a reviewer answers this question.
Review Report Plans#
ruleadviceguidancelitespec/ext/jobs#review-report-plansEach report SHOULD have its query plans and indexes reviewed against representative data.
DeviationA report over a small fixed data set justifies skipping the review.
Applies toartifact: record:use-case
Why
A query that flies over a hundred local rows can scan millions in production. The team reviews query plans and indexes against representative data, so the true cost shows before the report ships rather than after it slows the database.
Checka reviewer answers
Reviewed forThe review record shows query plans and indexes checked against representative data.
Marked guidance: a reviewer answers this question.
Apply Item-Read Authorization#
ruleruleguidancelitespec/ext/jobs#apply-item-read-authorizationA report or export MUST enforce actor, role, ownership, tenant, and sensitive-field rules that apply to item reads.
Applies toartifact: record:use-case
Why
A report that reads ten thousand rows at once crosses the same actor, role, ownership, tenant, and sensitive-field boundaries as ten thousand single reads. Enforcing the item-read rules on the bulk path stops the report from exposing rows its reader may not open one by one.
Checka reviewer answers
Reviewed forEach report and export enforces the item-read rules in the authorization tests.
Marked guidance: a reviewer answers this question.
Run Budget-Exceeding Exports in Worker#
ruleruleguidancelitespec/ext/jobs#run-budget-exceeding-exports-in-workerAn export exceeding its request budget MUST run through Worker, with the budget a setting record the export's use case names.
Applies toartifact: record:use-case
Why
An export that outlasts its request budget holds an HTTP connection open while the reader waits and the server counts threads. Running it in a Worker returns the request immediately, and the budget in its setting record tells the team exactly when an export outgrows the request path.
Checka reviewer answers
Reviewed forEach export past its request budget runs through a Worker, with the budget in its setting record.
Marked guidance: a reviewer answers this question.
Protect Export Output#
ruleruleguidancelitespec/ext/jobs#protect-export-outputAn export worker MUST store output in approved object storage with a named contact, an expiry, and a short-lived authorized download.
Applies toartifact: record:use-case
Why
An export file concentrates sensitive rows into one downloadable object that outlives the request that built it. Approved object storage with a named contact, an expiry, and a short-lived authorized download gives that object controlled access and a defined end instead of a permanent public link.
Checka reviewer answers
Reviewed forExport output sits in approved object storage with a named contact, an expiry, and a short-lived authorized download.
Marked guidance: a reviewer answers this question.
Scope Export Output to the Tenant#
ruleruleguidancelitespec/ext/jobs#scope-export-output-to-the-tenantAn export MUST store its output under a tenant-scoped object-storage path.
Applies toartifact: record:use-case
Why
Export addresses are predictable, and a shared folder lets one tenant guess another tenant's file name. A tenant-scoped storage path puts each tenant's exports under its own prefix, so a guessed address stays inside the guesser's own data.
Checka reviewer answers
Reviewed forEach export stores its output under a tenant-scoped path in object storage.
Marked guidance: a reviewer answers this question.
Define Report Limits#
ruleruleguidancelitespec/ext/jobs#define-report-limitsA report specification MUST state each bound as a setting record: maximum date range, rows, execution time, concurrent jobs, output size, and storage lifetime.
Applies toartifact: record:use-case
Why
Date range, rows, execution time, concurrent jobs, output size, and storage lifetime each cap one dimension of report cost. Setting records hold all six, so a runaway report hits a documented bound instead of an exhausted database, a full disk, or a surprise invoice.
Checka reviewer answers
Reviewed forEach report bound lives as a setting record beside the report.
Marked guidance: a reviewer answers this question.
Declare Export Paging#
ruleruleguidancelitespec/ext/jobs#declare-export-pagingAn export query MUST state paging with mode of pages or none with its reason.
Applies toartifact: record:use-case
Why
An export reads many rows at once, so it states how the read is bounded: {"mode": "pages", ...} bounds each run, and {"mode": "none", "reason": "..."} states why the export reads all rows at once. Without the declaration, a reviewer cannot tell a bounded export from one that grows with the table.
Checka reviewer answers
Reviewed forWhich export queries lack paging with mode pages or mode none with its reason? Look at the export query beside the report.
Marked guidance: a reviewer answers this question.
Audit Export Reads#
ruleruleguidancelitespec/ext/jobs#audit-export-readsAn export query MUST state its audit reason and target.
Applies toartifact: record:use-case
Why
An export file concentrates sensitive rows into one downloadable object, so the use case states why the bulk read is audited and where the audit lands. Without the stated audit, the concentrated read leaves no trace a reviewer can follow.
Checka reviewer answers
Reviewed forWhich export queries lack a stated audit reason and target? Look at the export query beside the report.
Marked guidance: a reviewer answers this question.
Support Report Cancellation#
ruleruleguidancelitespec/ext/jobs#support-report-cancellationA report implementation MUST support cancellation and remove expired outputs.
Applies toartifact: record:use-case
Why
A reader who closes the tab leaves the report running, and an export nobody downloads still occupies storage. Cancellation stops the abandoned work, and expiry removes the abandoned file, so neither consumes resources indefinitely.
Checka reviewer answers
Reviewed forReport work cancels on request and expired output clears in the cancellation tests.
Marked guidance: a reviewer answers this question.
Escape Formula-Leading Export Values#
ruleruleguidancelitespec/ext/jobs#escape-formula-leading-export-valuesA CSV or spreadsheet export MUST escape formula-leading values unless the team intentionally fills a field with a formula.
Applies toartifact: record:use-case
Why
A value starting with =, +, -, or @ becomes a live formula when the export opens in a spreadsheet, running in the opener's application with the opener's data. Escaping those values keeps data as data, except in a field the team intentionally fills with a formula.
Checka reviewer answers
Reviewed forExported values starting a formula are escaped, except in formula fields the team fills intentionally.
Marked guidance: a reviewer answers this question.
Define Export Encoding and Columns#
ruleruleguidancelitespec/ext/jobs#define-export-encoding-and-columnsAn export format MUST use explicit character encoding and a stable column contract.
Applies toartifact: record:use-case
Why
An export without a stated encoding arrives garbled on the first machine that guesses differently, and columns that shift per release break every parser built on them. Explicit encoding with a stable column contract lets consumers parse the file without guessing either.
Checka reviewer answers
Reviewed forEach export states its character encoding and stable columns in its format record.
Marked guidance: a reviewer answers this question.
Conventions#
Place Handlers with Owned Behavior#
conventiondefaultguidancelitespec/ext/jobs#place-handlers-with-owned-behaviorCreate one job handler under the module that owns its behavior.
ReplacementA project keeps job handlers in one shared jobs folder.
Applies toartifact: job
Why
One job handler lives under the module that owns its behavior. The module owns both the business action and what its schedule means. A handlers folder far from the behavior separates each schedule from its reason.
Checka reviewer answers
Reviewed forEach job handler sits under the module that owns its behavior, or the project keeps its handlers in one shared jobs folder.
Marked guidance: a reviewer answers this question.
Use Execution Scopes#
conventiondefaultguidancelitespec/ext/jobs#use-execution-scopesResolve scoped dependencies inside an execution scope and dispose the scope after the Command completes.
ReplacementA project names another scope boundary for occurrence dependencies in its documentation.
Applies toartifact: job
Why
Each occurrence resolves its scoped dependencies inside one execution scope and disposes it after the Command completes. The scope isolates one occurrence's dependencies from the next. A shared scope would leak one occurrence's state into another.
Checka reviewer answers
Reviewed forEach occurrence resolves scoped dependencies in one disposed execution scope, or the project names another scope boundary in its documentation.
Marked guidance: a reviewer answers this question.
Separate Schedule Definitions#
conventiondefaultguidancelitespec/ext/jobs#separate-schedule-definitionsKeep schedule definitions separate from Command behavior.
ReplacementA project keeps schedule definitions beside their Command behavior.
Applies toartifact: job
Why
Timing policy changes when operations needs a new rhythm, and Command behavior changes when the business needs a new outcome. Keeping schedule definitions separate from Command behavior lets each change alone. A schedule buried in the handler makes every timing change a code change.
Checka reviewer answers
Reviewed forSchedule definitions sit apart from Command behavior, or the project keeps them beside it.
Marked guidance: a reviewer answers this question.
Order Unscored Queues by Age#
conventiondefaultguidancelitespec/ext/jobs#order-unscored-queues-by-ageA queue without a score lists its oldest item first.
ReplacementA project names its fallback queue order in its queue documentation.
Applies toartifact: record:use-case
Why
Age is the honest fallback: the item waiting longest goes first. Any other fallback works when the queue states it.
Checka reviewer answers
Reviewed forQueues without a score list oldest first, or the queue documentation names another fallback order.
Marked guidance: a reviewer answers this question.
Discard Poison Records After Three Attempts#
conventiondefaultguidancelitespec/ext/jobs#discard-poison-records-after-three-attemptsA record that fails parsing three times is dropped with the poison reason.
ReplacementA project names its poison threshold in its pipeline documentation.
Applies toartifact: job
Why
One bad row must not stall the feed forever, and one strict parse must not drop rows a retry would save. Three attempts split the difference, and the poison reason keeps the casualty visible.
Checka reviewer answers
Reviewed forUnparsable rows drop as poison after three attempts, or the pipeline documentation names another threshold.
Marked guidance: a reviewer answers this question.
Name Engine Steps After Use Cases#
conventiondefaultguidancelitespec/ext/jobs#name-engine-steps-after-use-casesEach engine step carries the id of its mapped use case.
ReplacementA project records its engine step naming convention in its workflow documentation.
Applies toartifact: aggregate
Why
A step named after its use case needs no map to read: the name is the map. Any other naming works when the map stays beside the process.
Checka reviewer answers
Reviewed forEngine steps carry their use-case ids, or the workflow documentation records the naming convention.
Marked guidance: a reviewer answers this question.
Keep Report Definitions in Application#
conventiondefaultguidancelitespec/ext/jobs#keep-report-definitions-in-applicationKeep report definitions in Application under their module.
ReplacementA consumer records a report-definition convention naming where its report definitions live.
Applies toartifact: record:use-case
Why
What a report means and who may run it are business choices. Keeping the definition in Application under its module puts those choices beside the module's other rules instead of buried in query code.
Checka reviewer answers
Reviewed forReport definitions sit in their module, or the report-definition convention names where they live.
Marked guidance: a reviewer answers this question.
Keep Report Providers in Infrastructure#
conventiondefaultguidancelitespec/ext/jobs#keep-report-providers-in-infrastructureKeep SQL, row mappings, and storage providers in Infrastructure.
ReplacementA consumer records a report-provider convention naming where its SQL, mappings, and storage code live.
Applies toartifact: record:use-case
Why
SQL text, row mappings, and storage calls change per provider while the report's meaning stays fixed. Keeping them in Infrastructure swaps the provider without touching the report's business rules.
Checka reviewer answers
Reviewed forSQL and storage code sit in Infrastructure, or the report-provider convention names where they live.
Marked guidance: a reviewer answers this question.
Separate Worker Orchestration#
conventiondefaultguidancelitespec/ext/jobs#separate-worker-orchestrationKeep Worker orchestration separate from report rules.
ReplacementA consumer records a report-orchestration convention naming where its scheduling and progress tracking live.
Applies toartifact: record:use-case
Why
Scheduling, chunking, and progress tracking run the report without defining it. Keeping Worker orchestration apart from the report's rules lets either change without dragging the other along.
Checka reviewer answers
Reviewed forWorker orchestration sits apart from report rules, or the report-orchestration convention names where it lives.
Marked guidance: a reviewer answers this question.
Dependencies#
Packages: This extension selects no report or object-storage package. Each provider the project uses needs a decision and a manifest pin.
Related extensions: outbox.
Guides: None.