My Privacy Policy Described an Export the Code Didn't Build
My privacy policy said the data export held a user's entries, and the code that builds the file showed it left the reflections out.
I sat down to make four small edits to my journaling app's privacy policy. Before I touched the sentence about the data export, I read the code that builds the export file, and the sentence turned out to promise more than the file held. Most of the day went to closing that gap, and to deciding which direction to close it in.
The sentence was wider than the file
The app lets a person download what they have in it as one file. The policy described that file in a single line. It includes your profile, your entries, your streak, and a few kinds of records.
My own notes already listed three things the file was missing, and I expected to word around those three and move on.
Then I read the code. The file left out nine more. Most were small, like the email address and the reminder times. One was not small. Every night the AI companion writes a reflection on the entry, with a title, a short narrative and the things the person was grateful for, and that reflection was nowhere in the file. Anyone reading "your entries" would assume it was in there. I would have.
Nobody had lied. The export was written months ago and the app grew past it one feature at a time, and each of those features shipped with its own careful privacy note. No one went back to the sentence about the file, including me.
What caught me is that I had a list of known gaps and I trusted it. A list like that only holds what somebody once went looking for.
Three ways to make them match
I started thinking of the file as a moving box and the policy as the label on it. The label listed more than the box held, and there were three ways to fix that.
I could rewrite the label. That was an hour of work and it would have been accurate. But a person who asks for a copy of their journal and does not get the part the AI wrote about them has an honest label on a half-packed box. Accuracy was the floor here, and I wanted the copy itself to be worth having.
I could put everything in the box. That sounds like the principled choice, and for a while I leaned toward it. The trouble is two kinds of records the app itself is not allowed to read, the feedback people send and a security log. I closed both off on purpose earlier this week. Putting them in the file meant building a new way to read two tables I had just locked, so I would be loosening a protection to make a download look more complete.
I took the third way. The file now holds everything the signed-in app could already see, with two exceptions I come to next. The policy names what is left out and tells the person to email me for a copy.
What stays out on purpose
Two more things stayed out, and this was the call I thought about longest. Both belong to the person's encryption key. One is a fingerprint of the key, a short code the app uses to notice when a device has the wrong one. The other is a spare copy of the key, locked with a recovery code the person holds.
Neither is something a person can read or use. And the export lands in a downloads folder with no protection on it at all. The spare copy is safe for as long as nobody has both it and the recovery code, and a second copy sitting in a plain file is one more place for half of that pair to leak from.
There is a cost. A request that says "give me everything you hold about me" arguably covers these two as well. So the policy says they exist, says they are not in the file, and offers them on request. I think that is the honest meaning of complete. Everything is accounted for, even where not everything is handed over by default. No attorney has read that call yet, and I logged it as an open question, not a settled one.
Two questions from the test run
I tested the new export on a throwaway account with seven checks, and all seven passed. Two questions I asked along the way were worth more than the passes.
The first was about a name. The file had a field labeled with the database's old internal name for the reflection, and I asked whether that was the same thing as the reflection. It was. The policy said "reflections" and the file said something else, so anyone comparing the two would have hit the same question with nobody to ask. The file uses the policy's word now.
The second was the PDF version of the export. The heading above an entry was a few garbage characters, and the letters of the date were spaced apart like a ransom note. It looked broken because it was, and it had been for a while. The heading carried the entry's mood emoji, the PDF's built-in font has no emoji, and one emoji was enough to scramble the whole line. I had never opened a PDF export of an entry that had a mood. The PDF leaves emoji out now, and the full data file still carries them.
That left one more question, which was whether to rename the database column so the inside matched the outside. I decided against it. Nobody outside the code sees that name any more. Changing it touches the encrypted save path, and a phone with the old version of the app still open would fail to save a reflection until it reloaded. I would have been paying for tidiness inside with risk to the thing people do every night. A comment in the code says the two names mean one thing, and that is enough.
Mental Models
Read the code that writes the file before you describe the file. The policy sentence and my notes agreed with each other. The code disagreed with both, and the code is what the user gets.
A list of known gaps is a record of past searches. It tells you what someone looked for. It says nothing about what they did not look for.
Complete means accounted for. Some things are safer offered on request than dropped into a download, as long as the policy says they exist and how to get them.
Do not unlock a door to pass an inspection. If making an export complete means reopening something I closed for security, the export can name the gap instead.
A tester asking "is this the same thing?" is a finding. The check passed. The confusion was still real, and a user would have had it alone.