feat(cat): give the Cat an account, and stop the deploy list from drifting - #766
Merged
Conversation
…fting The Cat is a real profile rather than a rendering convention, which is what lets a Cat reply be an ordinary messages or timeline_events row — read receipts, search, deletion, threading, moderation and realtime all work on it without a branch. The cost of that is exactly this: something has to create the row. ensureCatAccount is idempotent and cheap when it is a no-op (one indexed lookup), so it is safe on every tick, and running it on a schedule makes the identity self-healing: @cat is linked wherever it is written, so a missing profile silently resolves every mention on the platform to nobody. Daily re-assertion turns that from a support ticket into an invariant. The login address is cat@orangecat.invalid, and both halves are load-bearing. `.invalid` is reserved by RFC 2606 and can never be delivered to or registered, which matters because an account's email is its password-reset channel — a bot identity with a routable address is one somebody can eventually take. There is no password either, so there is no credential to phish. The local part is what makes the handle `cat` at all: handle_new_user derives the username from split_part(email, '@', 1). That trigger is also the answer to why production contains handles like m.schaupensteiner and butaeff+ocauth2 — signup derives usernames from email addresses and never sees the validator. It recovers from a half-built account rather than assuming a clean slate: the auth user survives a deleted profile row, so "already registered" is the expected second run, not a failure. When it genuinely cannot establish an account it returns null and says so loudly, because a caller that improvises a sender is worse than one that stays quiet. Also collapses the deploy script's systemd list, which was written three times — the scp arguments, the install loop and the enable calls. Adding a timer meant editing three places, and missing the third shipped a unit file that never ran: the same silent-failure shape as a cron route nothing invokes. There is now one array, and every .timer in it is enabled from that same array. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Cat is a real profile rather than a rendering convention — that's what lets a Cat reply be an ordinary
messagesortimeline_eventsrow, with no branch in read receipts, search, deletion, threading, moderation or realtime. The cost of that is exactly this PR: something has to create the row.Self-healing, not one-shot
ensureCatAccountis idempotent and cheap when it's a no-op — one indexed lookup — so it's safe on every tick. Running it on a schedule matters because@catis linked wherever it's written: a missing profile silently resolves every mention on the platform to nobody. Daily re-assertion turns that from a support ticket into an invariant.The email address is load-bearing in two ways
cat@orangecat.invalid.invalidis reserved by RFC 2606 and can never be delivered to or registered. An account's email is its password-reset channel, so a bot identity with a routable address is one somebody can eventually take. There's no password either — nothing to phish, nothing to reset.catat all:handle_new_userderives the username fromsplit_part(email, '@', 1).That trigger is also the answer to a puzzle from the mention work — why production contains handles like
m.schaupensteinerandbutaeff+ocauth2. Signup derives usernames from email addresses and never sees the validator.Recovers from a half-built account
The auth user survives a deleted profile row, so
"already registered"is the expected second run, not a failure. When it genuinely cannot establish an account it returnsnulland logs loudly — a caller that improvises a sender is worse than one that stays quiet.The deploy list was written three times
The systemd block listed its units in the scp arguments, the install loop, and the enable calls. Adding a timer meant editing three places — and missing the third ships a unit file that never runs. That's the same silent-failure shape as a cron route nothing invokes, which is a bug class this repo has already been bitten by.
There is now one array, and every
.timerin it is enabled from that same array:Verification
npm run verify— exit 0 (2349 tests)bash -non the deploy script, and the array prefix expansion checked directly🤖 Generated with Claude Code