Copy it, check the copy is really there, then delete
What you will learn
- Why a written note saying a file was copied is not the same as the copy existing
- The order that makes shrinking a file safe: save, check it is really there, then delete
- The two quieter failures: a safety gate pointing the wrong way, and counting that lies while the data is fine
I had a job that stopped a working file from growing forever. Old entries were trimmed out on a schedule. The notes said this was safe, because old entries were already copied into a permanent store. The notes were wrong, and they had been wrong in writing for weeks.
Nothing copied them. The permanent store and the working file were written separately by different things at different times, and the sentence claiming one mirrored the other was an assumption that had been written down, which made it look like a fact. Everybody who read the instruction trusted it, because it was in the notes, and notes are where facts live.
I found it by doing the one thing nobody had done. Before deleting, I went and looked in the permanent store for the entries I was about to remove. They were not there.
A receipt that says "filed in cabinet B" is not the same as the document being in cabinet B. One is a claim about the world. Opening the drawer is the world.
Then it got more interesting, because the next two faults were the opposite shape
The first was a safety gate pointing backwards. It refused to copy any entry into the permanent store unless that entry was already in the permanent store. Read that again, because I had to. The entries most in need of saving, the ones that existed nowhere else, were exactly the ones it would not save.
It never lost anything. It simply never saved them either, so they could never be trimmed, so the file grew forever. A deadlock wearing a safety vest.
The second was quieter and taught me the most. The part that found where one entry ended and the next began used a pattern that could not match four of its own seventeen headings, because those four had an extra piece before the date. A heading it cannot match is not a boundary, so those four entries silently became part of the entry above them.
They were saved. Nothing was lost. But they were saved under the wrong name, and the job reported the wrong count, and every number I would have quoted was wrong while the data itself was perfectly fine.
Loud failures get fixed. A job whose counting lies while its result is correct will pass every test you think to run, until the day the counting is the only thing you have left.
One is a claim about the world. Opening the drawer is the world.
The save, check, delete pattern, plus the two subtler failures: a gate that blocks the thing it should protect, and matching that is too loose to trust. The ordering is the whole point.
1. Never delete on the strength of a written note
What this does: fixes the order, so nothing is removed that has not been written to the permanent store and then read back out of it successfully.
Why step two must read from storage: comparing the thing you were about to write against itself in memory is a check that cannot fail, which is not a check.
# the only correct order
# 1. WRITE the entry to the permanent store
# 2. RE-READ the store and find the entry in it
# 3. COMPARE what came back against the original, exactly
# 4. only now remove it from the working file
# any failure at 1, 2 or 3 -> remove NOTHING and complain loudly
2. Compare the content, not a count
What this does: takes a fingerprint of what you wrote and of what you read back, and compares the fingerprints.
Why not counts: two counts can agree by coincidence. Two fingerprints cannot.
And if you cannot find it: that is a failure too, not a reason to try a looser search.
want = the_entry_text
found = read_back_from_the_store(locate(want))
if fingerprint(found) != fingerprint(want):
refuse_to_delete()
3. Match on anchored boundaries, never on a fragment
What this does: ties the pattern to the start of a line, and allows for the variation your real entries actually contain.
Why this is the fault that produced wrong names and right data: a pattern that looks for a marker anywhere will also find it inside ordinary prose, and will miss a heading laid out slightly differently.
Then prove the pattern sees everything: count the matches, then count the same thing a completely different way, and compare.
# the check that caught my four of seventeen
# headings the pattern matched : 13
# headings counted another way : 17
# a gap means your pattern is blind to some of your own entries
4. Beware the fragment that looks like a match
What this does: warns you off searching for a short name inside a larger body of text.
Why it matters in both directions: searching for something called item1 also finds item10, item11 and my-item1. So "thirty four hits" can mean zero actual entries. I nearly reported a disaster from exactly this, and separately nearly reported an all clear from it.
# anchor it to the exact field and compare whole values,
# rather than hunting for the text anywhere it appears
5. Check your safety gates for being backwards
What this does: makes you write out, in one sentence, what each gate protects and from what.
Why one sentence is enough: if the sentence does not make sense, the gate is pointed the wrong way. That is how you see it.
# the backwards gate, stated plainly
# "refuse to save anything not already saved"
# -> the unique entries are the ones refused
# -> they can never be removed, so the file grows forever
# the right way round
# save unconditionally; treat "is it elsewhere?" as a NOTE on the entry,
# never as permission to save it
A gate that causes a deadlock instead of a loss is a lucky bug. It is still a bug, and it hides behind the fact that nothing was damaged.
6. Make the permanent store add only, and test the refusal
What this does: deliberately breaks things, twice, to watch the guard refuse.
Why the store is never trimmed: it is the floor. Nothing shortens it, nothing rotates it, nothing caps its size. If it gets large, split it by period rather than throwing anything away.
# two deliberate sabotages
# 1. make the store unwritable -> expect: refuse, delete nothing
# 2. corrupt what gets written -> expect: check fails, delete nothing
# then confirm the working file is unchanged, byte for byte
Why this step is not optional: a guard you have never seen refuse is a guard you are hoping about.
7. Make it safe to run twice, because it will be interrupted
What this does: looks for the entry in the store before writing it, so an interrupted run does not leave two copies.
Why it will be interrupted: if the save succeeds and the delete does not, the next run starts again from the beginning.
# before adding, look for it in the store, exactly
# already there -> skip the write, go on to check and delete
# not there -> write, then check, then delete
This protects the entries you point it at. It says nothing about the ones in a store you forgot you had. A correct process over an incomplete list still loses things, so write the list down too.
Related reading: read the record before you believe your own story is the same lesson about trusting a written claim, and a backup you have never tested is just a hope is where checking by actually restoring belongs.
This page in the original Kyber Cypher voice: Archive it, prove the archive has it, then delete