The privacy work isn't on the /privacy/ page

The learning progress on /learning doesn't sync between devices. Complete a module on the MacBook and the phone doesn't know. That's not a gap in the implementation. Phase 11.34 shipped the mastery tracking layer as one write and four reads over a device-local file. No remote schema, no sync endpoint, no data sitting on infrastructure outside this machine between sessions. The Duolingo-shaped ask was "a learner should know they've completed 15% of a track, game-progression style." The answer landed as local state. Deliberately.

I didn't call that a privacy decision when I made it. I called it a design decision. Two months earlier I'd have said the same about PR #17.

Improving on the recurring-leak pattern from I patched the same privacy leak four times: that article showed the username reappearing across multiple ships and concluded the lesson wasn't more carefulness. What the following months showed is that the same underlying question keeps returning. Where does this data live, and who is entitled to it? It recurs in decisions that don't present as privacy work at all.

The one that announced itself

PR #17, May 2026. Four lines across two files. The inline plist example in src/content/posts/macos-launchd-tcc.mdx was rendering the macOS username as visible text on the live site. Three absolute-path strings in the scripts directory held the same value. The fix produced a __USERNAME__ placeholder convention for config templates, a sed-substituting installer that resolves it at deploy time, and a pre-commit hook that blocks any staged content carrying the literal username.

That one was visible. There's an article about it. The violation had a rendered form: text on a page, a match in a diff. The shape of the fix was equally legible: grep, replace, enforce.

The one that looked like a design decision

Phase 11.34, from B-051, closed the fifth of the external review's six priorities. The ask was specific: game-progression-style completion tracking visible to the learner on /learning. One write, four reads, device-local.

The alternative was a remote schema. Most progress tracking works that way. Data on a server, synced across devices, following the user from session to session. That's also the architecture you reach for when you want to analyse the data later, or expose it through an API, or build features on top of it. None of those were in scope. More to the point: none of them should be.

Mastery data (how far through a track, which modules covered) is personal in the same way a notebook is personal. It doesn't benefit from being elsewhere. The device-local decision closed off the remote options deliberately. The data stays where the learning happens.

The privacy framing didn't come first. The design reasoning came first and produced the same answer. That's the pattern worth naming: boundary decisions and privacy decisions are often the same decision reached from different directions.

The one that looked like an architecture decision

In July, while planning the /work/ expansion, a standing position got made explicit: Captain Random and the software business are two separate entities. The site must not link them directly. Projects on /work/ get discussed architecture-first.

The first music track had gone live the previous month. Tasogare, cinematic boom bap, audio visualiser, on YouTube. Captain Random publishes music, writing, learning logs. Byte Bridges builds software. The two have different audiences and different contexts, and the site architecture should reflect that separation.

Collapsing them leaks information across a boundary that neither audience chose to cross. Letting one surface become a lookup table for the other does exactly that. Someone reading Captain Random shouldn't receive the business context by default. Someone arriving via Byte Bridges shouldn't have the personal site as an implicit reference. Both exist publicly. The decision is about not coupling the indexing.

"Architecture-first" is the implementation of that principle on /work/. A project discussed in terms of its technical architecture stays in the professional register without pulling across the personal identity attached to the domain. Same shape as the mastery data decision: the boundary isn't secrecy, it's entitlement. Who gets what context, and did they choose to receive it?

What the /privacy/ page covers

The site has had a /privacy/ page since June, shipped in Phase 8-10 alongside the newsletter form and four new static pages. The policy documents the form fields collected, the destination for the data, and the standard statutory disclosures. It's short because the data collection is minimal.

It covers none of the three decisions above. It couldn't. A compliance-shaped privacy policy documents data you've chosen to collect and how you handle it. It has nothing to say about whether to collect at all. Nothing about where a username boundary sits in published technical content. Nothing about how to draw the line between two public identities sharing infrastructure.

Those are structural questions. They arise in the architecture of features and the framing of content decisions, not in drafting a disclosure document. The policy is downstream of them. Getting the structural decisions right first and then writing an accurate policy is the correct order. Treating the policy as a substitute for the structural decisions is where the gap opens.

The underlying question

Three decisions across three months:

  • PR #17: a macOS username in published content identifies the machine it was written on. The site shouldn't do that.
  • Phase 11.34: mastery progress belongs on the device where the learning happens, not on external infrastructure.
  • /work/ architecture: two public identities should not be coupled in ways their respective audiences didn't choose.

None came up in a privacy review. All three answered the same question: where does this domain end and the next one begin?

The username leak was visible because the violation had a rendered form. The other two were invisible to that kind of audit. No grep catches "decision not to build a sync layer." No pre-commit hook flags "implicit coupling between two identities."

That's the improvement on the earlier pattern. The recurring leak was a content-hygiene problem: find the instance, add the enforcement, move on. The broader pattern is structural. The boundary question surfaces in the architecture before it appears in the content. The only way to catch it there is to frame it as a boundary question before the decision is made, not after the violation surfaces.

The /privacy/ page exists for the law. The boundary question is for the design.

All writing