What it is
Eunha speaks Mastodon's client API and federates over ActivityPub, and
it stores everything in Mastodon's own schema. The tables are upstream's
tables, down to constraint names and index columns; eunha's own tables
live apart, in a eunha schema beside public.
That is the whole design constraint. An instance can move to eunha without exporting or reshaping anything, and can move back. Existing clients keep working, because the API they read is the same API.
It is not a goal to mimic Mastodon's entire feature set or its implementation. Where eunha behaves differently, the difference is written down rather than discovered.
Compatibility, checked rather than claimed
A compatibility claim decays the moment nobody tests it. Four harnesses keep this one honest, and they run in CI:
Schema check
Reads the live database back out of Postgres and diffs it against a
reference built by Mastodon's own ActiveRecord from upstream's
schema.rb — types, nullability, defaults, index
columns, every constraint by name, sequences, views.
Entity parity
A client reads fields by name, so a missing one breaks it. Every REST entity Mastodon's serializers emit is recorded, and real responses from a running eunha are compared field by field.
mise run entities:buildDifferential testing
The same request goes to eunha and to a real Mastodon in Docker, and the answers are compared: 31 reads, nine writes, and the interaction verbs. Nothing in it encodes what the answer should be.
mise run differentialFederation
Twenty-four checks in both directions against a live Mastodon: resolve, follow, deliver, favourite, boost, delete. Two servers sharing nothing but the specification.
mise run federationWhich Mastodon are we?
One file answers that. mastodon.toml names the release
whose schema and API a build implements, the version repeats it as
build metadata, and the build fails if the two disagree:
v0.2.0+mastodon.4.7.0
│ └── the Mastodon release this build implements
└── eunha's own version, which moves independently
Adopting a newer release is a procedure rather than a guess:
mastodon:plan lists the Rails migrations upstream added
since, one eunha migration reproduces them, and the reference schema is
rebuilt so the diff is the delta being adopted.
mise run mastodon:status # is there a newer release?
mise run mastodon:plan --to v4.8.0 # what would adopting it involve?
mise run schema:check # does this database match?
Divergences are data
A difference from Mastodon is either a bug or a decision. The decisions
live in divergences.toml — what Mastodon does, what eunha
does instead, why, and which test would fail if that stopped being true.
They are recorded as data rather than prose because prose is not checked and so stops being true. The test suite reads that file: an entry whose evidence has gone missing fails, and every entry carries the Mastodon release it was last judged against — so adopting a newer release fails the suite until each divergence has been re-examined.
Federation, and where it scales badly
Federation is built on feder, extended where necessary. Outgoing requests are signed with the draft-cavage signatures the network still runs on, and double-knocked with RFC 9421 when a peer refuses; inbound requests are verified either way.
PROTOCOL.md is a design record of the places ActivityPub
scales badly — the dereference storm a boost sets off, the absence of
backfill, identity that cannot outlive a hostname — and what eunha
proposes for them. It says plainly which parts exist today and which do
not.