hamsa software
← All articles
Automation7 min read

The missing Tuesday

Wednesday’s successful run does not prove Tuesday’s work happened. A period ledger makes missed work selectable, catch-up deliberate and completion something more useful than a green scheduler.

A three-part geometric lattice crosses around a central circle, suggesting a schedule whose individual periods require their own completion evidence.

The most recent run is a very small window

Wednesday’s update succeeds, and the dashboard turns green. Tuesday’s update is still missing. There is no contradiction: the latest run reports that the scheduler can work again, while the business still owes an earlier result. If last_success is the only history available, Wednesday has effectively hidden Tuesday. A separate record of the periods owed gives that missing work somewhere to remain visible after the service recovers.

Publication is the invented example here, but a daily export, expiry check or customer summary can leave the same gap. The schedule says when to try. The service needs to know which periods reached an acceptable outcome, particularly after an outage covered several trigger times. Keeping those two accounts separate also lets recovery run later without pretending that the time of its attempt changes which period the work belongs to.

Kubernetes documents one concrete reason to separate these records: CronJob creation is approximate, and circumstances can produce two jobs or no job, so jobs should be idempotent. [1] The relevant principle also applies to smaller scheduling tools. The trigger attempts to start processing; the application must establish which period that processing concerns and what outcome counts as completing it.

The ledger also needs an expected calendar. A business-day report should not alert for a Saturday it was never meant to produce. Materialise the eligible periods from the agreed schedule or retain enough schedule-version information to derive them consistently. If the calendar later changes, keep the rule that determined Tuesday’s obligation. Otherwise a missing row can remain invisible, or a new schedule can make an old deliberate non-working day look like failed work.

Give the period a durable identity

In the teaching ledger, the keys are period and destination. Monday 1 September 2025 has content version m1 and state published; Tuesday 2 September has version t1 and failed_before_effect; Wednesday 3 September has version w1 and published. The actual time of a later attempt is another field. A Thursday recovery run aimed at Tuesday must not acquire Thursday’s identity simply because that is when the process wakes up.

The destination belongs in the key because publishing an update to the site is not the same effect as sending a newsletter. A completed site update should not suppress an owed newsletter if the product intends both. Conversely, two attempts targeting the same Tuesday site publication must agree that they are competing for one operation. PostgreSQL documents multi-column uniqueness and primary keys as ways to enforce such identities in stored rows. [2] The right columns follow the business obligation rather than a generic job identifier.

Keep attempts in a related history. Tuesday’s failed content load and Thursday’s recovery should both remain visible, so support can see unfinished publication with an attempt in progress. A later success should not delete the failure that explains why recovery was needed.

Recovery also needs the intended content version. Tuesday’s t1 draft may have been edited by Wednesday, so a Thursday rerun has a real choice to make. Preserve t1, or record a reviewed change that deliberately replaces it. Otherwise today’s content can acquire Tuesday’s label merely because the recovery process loaded the latest file. For a report, the equivalent decision is whether it reconstructs the period’s original data or gives readers an explicitly identified later view. Keeping that chosen version also lets the operator compare the eventual destination content with the publication that recovery was meant to produce.

The downloadable SQLite example changes Tuesday from failed_before_effect to claimed only while its expected state still holds. The first claim affects one row; the repeat and the attempt to claim published Wednesday affect none. Tuesday keeps its content version, and Monday and Wednesday remain the published periods. The important interval comes next: while Tuesday is claimed, the operator must be able to see an attempt in progress without reading that ownership as evidence that the destination has published anything.

Wednesday succeeds without repairing Tuesday

An original three-period publication history separates the scheduled period from the time of an attempt. Thursday’s recovery selects Tuesday, keeping its content version and destination.

Scroll sideways to see all columns.

PeriodRecorded outcomeThursday decisionWhat counts as completion
Mon 1 Sep 2025Published · version m1SkipDestination reference recorded
Tue 2 Sep 2025Failed before effect · version t1First claim succeedsStill needs confirmed publication
Tue · second claimantSame period/destinationClaim affects 0 rowsMust not start another effect
Wed 3 Sep 2025Published · version w1SkipLater success does not fill Tuesday
Tue · uncertain remote resultIf publishing outcome unknownHold for reconciliationDo not reset to unsent
The downloadable in-memory SQLite fixture verifies local selection and conditional claims only. It does not contact a publisher or prove exactly-once remote effects. Dates and content versions are synthetic.

Catch-up is a product decision

When a schedule resumes, choose what missing periods mean. Some work must run once for every period: a ledger export may need each daily file. Some work should collapse into one current result: sending three stale “current availability” summaries can mislead people. Other work should expire: an offer reminder delivered after the offer ends may have no useful purpose. The scheduler cannot choose among those meanings from a cron expression. The period identity remains useful for expired work too: it allows a later review to distinguish an authorised skip from a trigger that never created an obligation record.

For the example, assume Tuesday’s update remains useful and should be published once, explicitly as a delayed update. Thursday’s recovery selects Tuesday’s row, preserves the chosen content version and checks that publication is still permitted. It skips Monday and Wednesday. That selection is a recoverable plan the team can inspect, rather than a broad instruction to rerun everything since the last time somebody remembers it working.

Choose the recovery range before releasing it. A long backlog of summaries may deserve review rather than a burst of stale messages to customers. Record which periods were selected and how their stale material will be handled, while allowing current work to continue where dependencies permit. If catch-up and current work touch the same object, their ordering belongs in that plan. The cron expression cannot tell the application which of those effects should win.

The policy may legitimately leave a period unperformed, but the record should say why: expired under a stated rule, superseded by a current summary, deliberately cancelled or awaiting a person. These are outcomes with meanings, not excuses to mark everything successful. A visible disposition lets the next person distinguish a conscious choice from another silent gap, especially when the absence is discovered weeks later.

A deliberate skip should also preserve who or what policy made it, the policy version and the period it covers. Reopening it later is another decision, rather than a surprising side effect of changing the schedule.

A missed period does not always need replay

Catch-up meaning follows the work, independently of the scheduler trigger.

Scroll sideways to see all columns.

Work type in the articleMissing-period policyWhat to record
Daily ledger fileProduce every owed periodPeriod and destination identity
Current availability summaryA single current result may supersede old summariesWhich periods were superseded under which policy
Reminder after offer expiryExpire instead of sending stale workExplicit expiry disposition
Tuesday update in this fixturePublish once as a delayed updatePreserve selected content version t1
A deliberate skip is an accountable outcome, not a successful attempt or an invisible missing row.

The claim stops at the external boundary

A conditional database update can stop a second worker from claiming the same eligible row while the state remains claimed. [3] It cannot make a remote publisher and the local ledger one atomic transaction. The provider might accept Tuesday’s content and then the worker lose the reply before recording success. Resetting that row to failed_before_effect would turn uncertainty into a false assertion that nothing happened.

Keep an explicit unknown-outcome path with the publication reference, stable operation identity or lookup information the destination supports. Query or reconcile before another effect where possible. If the destination offers an appropriate idempotency contract, use it for repeated attempts at this same publication. An expired worker lease is evidence that a local worker stopped renewing ownership; it is not evidence that a remote update never became visible.

Decide what counts as completed at the destination. An API may accept content before it becomes visible, and visibility says nothing about whether a customer read it. Keep the reference and content version with the stage actually confirmed. “Accepted for publication” can be a useful, truthful stopping point for one part of the system. It becomes misleading only when a dashboard quietly promotes it to a promise the provider response never made.

On Thursday, the operator can now ask about Tuesday directly: does it need publication, reconciliation or an explicit expiry decision? The view can show that alongside Wednesday’s success, the age of the gap and any uncertain external effect. Attempts remain useful for finding out why something failed; the period record says whether work is still owed.

Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.

See the Payment Operations Pilot

References & further reading

Primary sources behind this article. Living documentation may change.

  1. Kubernetes: CronJob scheduling limitations and idempotent jobs
  2. PostgreSQL: multi-column unique and primary-key constraints
  3. PostgreSQL: conditional UPDATE and returned row counts
Hamsa Jama

Practical engineering. A human perspective.

More about Hamsa Software and the person behind it. Editorial notes.

A few more things
worth a read.

Back to the reading room
Automation6 min read

A budget that three workers can share

Enforce task limits through atomic reservations and repeat-safe settlement, including the uncomfortable case where a timed-out operation may still be spending.

Automation7 min read

What exactly did that approval authorise?

Bind an assistant’s approval to the action, target and material revision, then enforce that binding when the write happens.