Why a correction can change a past day
An order can arrive with the wrong business date, channel or amount. A later event arrives with a higher source version and the correct values. The daily report for the wrong day has already been read; the question is how to revise the order fact and the affected daily group without hiding the change.
This companion story uses the same synthetic retail-orders project as the flagship. The amount is integer AUD cents, dates are UTC, and the orders are not recognised accounting revenue. The pipeline that decides which day the corrected order belongs to is the one explained here.
Correction e6 replaces order o100’s fact. The previous daily contribution is removed and the corrected contribution is added, in one atomic commit. This figure isolates that one correction; the full follow-up batch in the runner also accepts a new order, so its total is higher.
| State | Order | Date (UTC) | Channel | Amount |
|---|---|---|---|---|
| Before | o100 | 2026-01-01 | web | A$100.00 |
| After | o100 | 2026-01-02 | web | A$120.00 |
| Before | Daily order value, all dates and channels | A$195.00 | ||
| After | Daily order value, all dates and channels | A$215.00 | ||
Event time and reporting date are different
Every event carries a source update timestamp that records when the source system wrote the row. Each event also carries a business order date that records which day the order belongs to in the report. The two values can disagree: an event written on 3 January can describe an order that belongs to 1 January, and a later correction can move that business date again.
- The source update timestamp decides ordering between competing events for the same order.
- The business order date decides which daily group the order contributes to.
- An accepted correction can change the business order date, which moves the contribution between daily groups.
How the accepted implementation replaces an existing fact
The current order fact is one row per order. When a corrected event wins by source version, timestamp and stable event identifier, the order state and the order fact are replaced with the corrected values. There is no edit history per field; the order fact always reflects the winning complete state.
Because the order fact has been replaced, the daily groups that aggregate over the order fact change at the same time: the original date-channel group loses the previous contribution and the corrected date-channel group gains the corrected contribution. The two daily groups are rebuilt together inside one commit so the change is atomic.
Which daily result changes and why
Only the daily groups whose date or channel is affected change. Groups whose date and channel stay the same are rebuilt from the same set of contributing orders and therefore show the same values. The committed daily groups are an aggregate of the current order facts, so changing an order fact changes the aggregate.
The change is not a separate post-processing step. Rebuilding the daily groups is part of the same atomic commit that updates the order fact. If any quality check fails on the rebuilt groups, the commit is rolled back and the previous values remain visible.
How stale updates are handled
An event with a lower source version, an equal version with an earlier timestamp, or an equal version and timestamp with a lexicographically smaller event identifier cannot win against the committed order state. The pipeline records the event in the event ledger but does not change the order fact, the daily groups or the quality checks.
These stale or no-op events appear in the run summary as ignored events. They are not errors; they are the documented behaviour when a retry delivers an older correction after a newer correction has already been committed.
How conflicts are handled
If two events in the same batch or one event against the ledger carry the same event identifier with a different raw payload, the pipeline stops before any mutation. The batch is reported as a conflict and the committed state stays authoritative.
When two events share the same order identifier but have different event identifiers and the same source version, the deterministic winner tuple is source version, source update timestamp, then lexicographically greater event identifier. The runner surfaces this situation as a warning rather than a failure, because both payloads are valid; the pipeline simply picks the documented winner.
Why late-correction handling matters operationally
A daily report that cannot move a corrected order between days would mislead anyone reading the previous day. A correction that overwrites silently would make the audit trail unreadable. Recognising that a higher source version wins, rebuilding the affected daily groups atomically, and leaving stale events visible gives an operator a clear answer for every scenario.
For this synthetic project, late-correction handling is demonstrated inside one browser session against a small sample dataset. It does not prove production durable recovery, regulatory retention, accounting treatment or how this rule should be reconciled with downstream financial systems.
One meaningful design trade-off
The pipeline uses a strict tuple (source version, source update timestamp, then lexicographically greater event identifier) to choose the winning complete order state. The alternative would let the most recently processed event win regardless of the tuple, which would make the order fact depend on the order in which retries arrived.
Choosing the tuple keeps the answer reproducible across retries and across processes. The cost is that two competing events at the same tuple position with different payloads must be surfaced as a conflict or a warning rather than absorbed into a silent overwrite.
Evidence and limits of this explanation
The saved result below is shown only when its build, sample data, schema, SQL and runtime identities match this application. It is a captured browser run from a real pipeline, not a fabricated outcome. Its sample is the follow-up batch, which contains a higher-version event for an already-known order, a stale lower-version event for another order and an event that reuses an already-committed event identifier.
Late-correction numbers a reader can read directly off the saved result: the order o100 changed from the initial web order on 2026-01-01 to the corrected web order on 2026-01-02 with a new amount, the inserted count is the order o106 that appeared for the first time in this batch, and the ignored-event count of three includes the stale event that could not outrank the existing order state and the raw-identical repeat of an already-committed event.
The grain of the saved evidence is per-batch: input event rows reconcile to invalid rows, identical valid duplicates removed and unique valid events accepted. The current order facts are one row per order; the daily groups are aggregates by UTC date and channel. Event totals, fact rows and daily groups are separate grains and should not be added together. The example proves behaviour for these small synthetic inputs and this browser pipeline. It does not establish production scale, accounting treatment, durable recovery or regulatory retention. If the engine cannot start, this explanation and the bundled SQL remain available.
Evidence mode: saved browser run captured from build 821aed49a48d with sample file SHA-256 464d7430a9cf and SQL SHA-256 cd70dcad203d. New local runs from the flagship are labelled separately.
Saved browser run, follow-up batch
A captured result from the synthetic project
This is a saved result from a real browser run, captured . It is separate from any new local run below.
- Input event rows
- 7
- Quarantined rows
- 1
- Identical valid repeats
- 1
- Orders inserted
- 1
- Orders updated
- 1
- Stale/no-op events
- 3
- Current order facts
- 5
- Current order value
- A$245.00
3 quality checks passed for this saved run.
| Order date (UTC)VARCHAR | ChannelVARCHAR | OrdersBIGINT | Order valueHUGEINT |
|---|---|---|---|
| 2026-01-01 | store | 1 | A$25.00 (2,500 cents) |
| 2026-01-02 | store | 2 | A$30.00 (3,000 cents) |
| 2026-01-02 | web | 2 | A$190.00 (19,000 cents) |
- Passed: state fact reconciliation
- Passed: daily mart reconciliation
- Passed: nonnegative order amounts
Build identity, asset hashes and measured timings
- Captured at
- Identity schema version
- 2
- Build identity
821aed49a48d31e2 405d54a1 f79325ff 67873684 6762ad4e fc63acf7 ec878460 - Sample file SHA-256
464d7430a9cf05b7 1e02ed1a 83d17876 0ad05cb1 a97d5a5b 1e203f26 5562b7c7 - Schema SHA-256
29db3589925d4d92 65565c18 7378e8df fae68c8f 6fe1f81c d46ef8f0 63542f9c - SQL SHA-256
cd70dcad203d30ce a9c10e55 10eef328 2e247a20 b8007109 10163047 7badd991 - Execution source SHA-256
f878c8f580ca044d f92b6bf9 899e9a68 39d22920 e9fced37 33fb2fcd cd5a3ac7 - Evidence trust source SHA-256
43cc24c47850b136 bb5f029f 49d249c4 85b0606b 2a18d594 0ddccd64 dbb3876f - DuckDB-Wasm package
- 1.32.0
- DuckDB SQL engine
- v1.4.3
- Worker initialization
- 0 ms
- Sample file load
- 7 ms
- SQL pipeline
- 101 ms
- Total run
- 119 ms
Timings describe this captured browser run; they are not a benchmark promise.
Read the flagship end-to-end story
The pipeline steps that decide which day wins
The architecture fragment below is the part of the flagship pipeline that decides which corrected order replaces the previous one and which daily groups change. The event identity gate prevents conflicting payloads from mutating state; the ledger and current state stages pick the winning complete order state; the order fact and the daily groups are rebuilt and checked together.
Select a stage to see its details. The diagram explains the pipeline and does not run it; the full explanation follows below.
- 01
Late-correction event identity gate
Reject the batch if a usable event identifier carries a different raw payload than what is already in the committed ledger or in the same batch. Late corrections must arrive with a fresh event identifier so the order of arrivals is preserved.
- Inputs
- Original event rows from the late batch; Committed event ledger with stored payload hashes
- Outputs
- Either a fatal conflict result that prevents mutation, or rows eligible for validation
- SQL queries
get_event_ledger, insert_event_ledger- Quality checks
- No quality check at this stage
- 02
Late-correction ledger and current state
Apply each unique accepted event to the event ledger and choose the winning complete order state for each affected order by source version, source update timestamp, then lexicographically greater event identifier. Stale and no-op events are recorded but do not change the order fact.
- Inputs
- Unique accepted events from this batch; Committed event ledger and order state
- Outputs
- Updated event ledger; Winning current state per order, with stale events identified
- SQL queries
begin_transaction, insert_event_ledger, upsert_order_state, commit_transaction, rollback_transaction- Quality checks
- No quality check at this stage
- 03
Late-correction order fact
Materialise the corrected winning complete order state as the current fact for the order. Replace the previous fact atomically; the previous daily contribution is removed at the same commit point.
- Inputs
- Winning current state per order from this batch
- Outputs
- Replaced current order fact; Insert, update and ignored event counts
- SQL queries
upsert_order_fact, query_orders_fact- Quality checks
- State-to-fact reconciliation
- 04
Late-correction daily groups
Rebuild the daily groups by UTC date and channel from the replaced order facts. The original date-channel group loses the previous contribution and the corrected date-channel group gains the new one.
- Inputs
- Replaced current order facts
- Outputs
- Updated daily groups that reflect the correction
- SQL queries
create_daily_mart, query_daily_mart- Quality checks
- Daily mart reconciliation
On a narrow screen, follow the complete ordered list above.