Publish Only Complete and Validated Results

Translated from the Korean original. Read the original

October 2026

Keep the result you are working on apart from the result people can use, and switch what people see only after the new one has passed its checks.

The half-saved report

Every morning a script writes a 12-page report to report.pdf. One day it crashes on page 5. The file still has the right name, in the right place, at the right time. A colleague opens it, sees five pages, and treats them as the final version. Nothing in the file says it is unfinished.

The crash is not the problem. Crashes happen. The problem is that the script wrote straight into the file readers open, so a reader could see the work while it was still in progress.

How the file is written What a reader sees after a crash halfway through
Edit the final file in place Half a result, or a corrupt file
Write a temp file, check it, then swap Yesterday's complete report

The second row is not perfect. Yesterday's report is stale. But stale and complete is something a reader can reason about. Half and unlabeled is not.

Two steps: compute, then publish

The fix splits one step into two.

  1. Write the new result to a temporary file on the same filesystem.
  2. Close the file, read it back, and check it: the format parses, the required sections are there, the counts are what you expected.
  3. Only if the check passes, replace the final path with the temporary file and record that the run finished.

Python's os.replace renames atomically when it succeeds: a reader who opens the path gets the old file or the new one, never a mix. It can fail across filesystems, which is why step 1 says same filesystem (Python docs).

Two properties are easy to confuse here. Atomicity hides the intermediate state from readers. Durability guarantees the write survives a power cut, and that needs the operating system and the disk to flush in the right order as well (SQLite, Atomic Commit). A rename gives you the first. It does not give you the second on its own.

In code: a work folder and a publish folder

Split the folders. Work and validation happen in work/; analysts read only publish/. If the computation dies or the check fails, publish/ still holds yesterday's complete report.

work/report.csvcompute + validatefails: stops here,yesterday's file stayscppublish/.report.csv.tmphidden fromanalystsmv (atomic)report.csvanalystsread only this

I see this pattern often at work and use it a lot. It works because cp finishes far faster than the expensive computation before it, so the window in which a half-written file can show is short. Short is not zero, so copy to a temporary name inside publish/, then only rename.

cd publish
cp ../work/report.csv .report.csv.tmp \
  && mv .report.csv.tmp report.csv
def publish(draft, out):
    validate(draft)  # fails: stops here
    tmp = out / f".{draft.name}.tmp"
    shutil.copyfile(draft, tmp)
    os.replace(tmp, out / draft.name)

In data pipelines: the medallion architecture

Databricks' medallion architecture is the same idea. Data moves through three layers, gaining quality at each, and analysts read only the last one (Databricks docs).

This note Medallion architecture
Compute in work/ Bronze: raw data, ingested as is
Validate Silver: cleaned and validated
Analysts read only publish/ Gold: the tables analysts and BI read
Rename with os.replace Delta commit: readers see the previous version until the transaction finishes

The same line outside software

Field Work in progress Condition for going public
Report Temporary PDF Page count and required tables checked
Event Invitation draft Place and time confirmed
Product page Private edit Price and stock confirmed

In each case the maker's progress and the moment the receiver can use the result are two different things. An invitation sent with the time still blank has published the work in progress. The guests now have to ask, and some of them will guess.

Applying it, and where it stops

To check whether a process follows the principle:

  1. Find every place it writes. Does any of them write directly to the path a reader opens?
  2. Kill the process at each stage: before the check, just before the swap, just after. Readers should only ever see the previous complete version or the new complete version.
  3. If one result is several files, swapping them one by one is not atomic as a whole. Write a versioned bundle and flip a single pointer to it, or use a transaction.

The principle has limits:

The habit fits in one sentence: never let the reader's path be the place where you work.

This principle also lives in my notes: notes.grepinsight.com