Skip to content

feat(cat): give the Cat an account, and stop the deploy list from drifting - #766

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/cat-account
Aug 26, 2026
Merged

feat(cat): give the Cat an account, and stop the deploy list from drifting#766
github-actions[bot] merged 1 commit into
mainfrom
feat/cat-account

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

The Cat is a real profile rather than a rendering convention — that's what lets a Cat reply be an ordinary messages or timeline_events row, 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

ensureCatAccount is 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 @cat is 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

  • .invalid is 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.
  • 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 a puzzle from the mention work — why production contains handles like m.schaupensteiner and butaeff+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 returns null and 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 .timer in it is enabled from that same array:

for u in "${units[@]}"; do
  case "$u" in
    *.timer) systemctl enable --now "$u" >/dev/null ;;
  esac
done

Verification

  • npm run verify — exit 0 (2349 tests)
  • 6 new tests: no duplicate Cat, recovery from a deleted profile, refusal on real failure, and that the address can never receive mail
  • bash -n on the deploy script, and the array prefix expansion checked directly

🤖 Generated with Claude Code

…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>
@github-actions
github-actions Bot merged commit 06772cc into main Aug 26, 2026
7 checks passed
@github-actions
github-actions Bot deleted the feat/cat-account branch August 26, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant