I changed one line yesterday: delete the photos people upload after 7 days
instead of 30.
Vercel reported a successful deployment. The privacy page said 7 days. The API
said 30.
{"ok":true,"store":"supabase","video":"kie","storage":"supabase","photoRetentionDays":30}
For about ten minutes the site told anyone who read it something that wasn't
true. Nothing looked wrong. That's the part worth writing down, because I've made
this mistake in a few different shapes now.
The page is static HTML, so the number a visitor reads ships with the commit.
The number the app acts on comes from an environment variable:
const days = Number(process.env.PHOTO_RETENTION_DAYS ?? 7);
?? only falls through when the variable is missing. Mine wasn't missing. I set
it when the app first went live, back when 30 was the number, and then forgot it
existed. New code, new copy, same behaviour.
I caught it while checking something else. I'd added a field to my health endpoint
an hour earlier and wanted to see it render:
{"ok":true,...,"photoRetentionDays":7,"photoRetentionSetting":30}
The first number is what the server will do. The second is what somebody
configured. Printing both is one line of code, and it turns "is my site keeping
data longer than it says it does?" into a single request instead of a dashboard
login. The dashboard would have shown me 30, and I would have believed the page.
Defaults and promises are not the same thing
Anything a user can act on gets written down more than once. Retention. Price.
Retry count. How long a link stays alive. There's the code, the config, the copy,
and whatever support has in their notes. Each one looks correct by itself. The
copy is the one that matters, because it's the one somebody made a decision from.
A default can be overridden in either direction. A promise can't. So the number I
print for users shouldn't be a default. It should be a ceiling:
// The longest retention we promise. Config may ask for less — deleting sooner
// is always fine — but never more, because the copy can't change because
// somebody edited a dashboard.
const PHOTO_TTL_DAYS_MAX = 7;
const setting = positiveInteger('PHOTO_RETENTION_DAYS', PHOTO_TTL_DAYS_MAX, env);
const days = Math.min(setting, PHOTO_TTL_DAYS_MAX);
The leftover variable is harmless now, and so is the next one somebody sets by
accident.
The check that would have caught it
My tests never looked at the copy. They tested the code, and the code was correct.
So now four checks compare files that used to agree only by luck:
const DAYS = 7;
report('the privacy page states the retention the sweep enforces',
privacyHtml.includes(`currently ${DAYS} days`));
report('the FAQ states the retention the sweep enforces',
faqHtml.includes(`deleted within ${DAYS} days`));
report('the code ceiling matches the retention the sweep enforces',
/PHOTO_TTL_DAYS_MAX = 7\b/.test(appSource));
report('a setting above the promise is clamped, not obeyed',
(await healthz({ PHOTO_RETENTION_DAYS: '30' })).photoRetentionDays === DAYS);
That last one boots the app with the bad value and asserts behaviour, not
arithmetic. I also put the copy back to "30 days" on purpose to watch the whole
gate go red and exit non-zero, because I don't trust a check I've never seen fail.
It happened twice more in the same codebase
"Failed videos are refunded automatically" is a sentence on my pricing page.
Making it true means the ledger claims an event before it grants anything, and
keys the claim on the delivery id and the order id, because a retry can arrive
carrying a fresh delivery id. The test delivers the same event eight times and
asserts one grant. A failed render refunds what that render spent, never more than
the credit lot has room for, and free credits never come back.
All of that is testable without a payment provider. Here's the uncomfortable half:
my gate was green while the live path was broken. The vendor SDK returns checkout
metadata as data.orderMetadata. The code read data.metadata, and my stand-in
delivered data.metadata too, so both halves agreed with each other and disagreed
with the vendor. A local fake proves your invariants. It cannot prove a vendor's
field names. Only their published types can do that, plus one real request.
Then there's the video library. The provider hands back a result URL that expires,
so storing that URL gives you a library of dead links that works perfectly for a
day. When a job finishes we download the MP4 (capped at 128 MB) into our own
storage and keep the key, re-signing a URL on every read. What nothing does is
delete those videos. The cleanup sweep only walks photos. My privacy page says
videos stay "while your account is active", so it isn't lying, but it's a promise
I made by not thinking about it, and storage now grows forever at 7.4 MB a clip.
That one is in my notes as an open item now rather than waiting to appear on a
bill.
What I changed in the process
Numbers a user can act on get asserted in the build, against the copy string
itself and not against the constant next to it. Promised numbers are ceilings.
The health endpoint prints effective values. And I stopped treating "the page
looks new" as proof that a deploy did what I meant.
This all happened on a small product of mine,
zombieai.info — two photos in, a 15-second video out,
photos deleted after 7 days, failed renders refunded automatically. Nothing was
visibly broken. One JSON response told me.
If you have a number in your copy that a customer can act on, go ask your server
what it thinks that number is. It's one request.

Top comments (0)