Changelog

Kolofon · 5 August 2026

Reader accounts — a foundation with no floor above it

17:06, the single largest commit in the project

Reader accounts — a foundation with no floor above it

Text: Kolofon

A day off, and then the single largest commit in the project's history. Reader accounts: schema, configuration, sessions, sign-in through Google. With no interface, deliberately. The interface arrives separately, on a finished foundation, because the reverse order ends with a login screen that has nothing behind it.

One architectural decision worth saying out loud: there is no tenant identifier here. Kolofon is a single-instance engine — its own worker, its own database, its own domain per deployment. Multi-tenancy is not a feature you add “just in case”. It is a decision that permeates every table and every query for the life of the project. You do not make it in passing.

Sessions live in the database, but the database holds no tokens. Only their SHA-256 digests. The difference shows up in the one scenario nobody enjoys thinking about: somebody reads the contents of the database. With digests they cannot impersonate anyone. HttpOnly cookie, SameSite Lax, valid for ninety days, extended at most once a day.

OAuth 2.0 with PKCE, Google as the provider. The account key is the provider's identifier, not the email address — because email addresses get changed, and when they do, the account has every right to remain the same account. Apple is prepared and waiting on one missing piece: signing the secret with ES256. Every instance registers its own application with the provider, the callback address is local, and there is no central intermediary through which other people's sign-ins would pass.

Two details I am disproportionately pleased with. First: author handles reserve themselves. Without that, a reader could sign a comment with an author's pen name underneath that author's own text, a scenario unpleasant enough that it is better switched off structurally than in the terms of service. Second: in the code that creates the schema, every statement got its own error handling. Previously the first failure broke the loop and silently skipped everything below it — so the database looked ready while being half ready.

The user identifier column on comments is allowed to be empty, and that means “anonymous comment”. Thanks to that, all existing content stays valid with no data migration. The migration you do not have to run is the best-tested migration in the world.

Secrets go through the secrets mechanism, not through the repository. We come back to this in 0.8.2, and it will not be a pleasant visit.