docs: ADR 0005, the public tier
Releases in a git repository per published studyset (the `git` command), learning as a copy that follows (later releases offered, merged per side), handles on the account domain Mastodon's way (`name@halkyon.eu`, changeable until the first publish; a profile of display name, bio, links and avatar), reports and suggestions of our own on plain git rather than a forge's, carried by the sync engine, and ActivityPub through Fedify in a container of its own: federated from the start, built in six usable stages. Decided 2026-10-09; the alternatives and why they lost are in the ADR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
00ceffb36c
commit
b8462d650f
1 changed files with 110 additions and 0 deletions
110
docs/adr/0005-public-tier.md
Normal file
110
docs/adr/0005-public-tier.md
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# ADR 0005: The public tier: releases in git, a copy that follows, federation
|
||||||
|
|
||||||
|
- **Status:** accepted
|
||||||
|
- **Date:** 2026-10-09
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The Japanese studyset is meant to be the project's public example: other people, on this
|
||||||
|
instance and on others, should be able to find it, learn from it, and help improve it.
|
||||||
|
Until now content has two tiers (ADR 0001, ADR 0002): private, a person's own synced space,
|
||||||
|
and shared, spaces granted to people of the same instance. Explore has stayed empty.
|
||||||
|
|
||||||
|
A direction for a third, public tier was recorded on 2026-09-27 (in the working notes, not
|
||||||
|
decided): releases kept in git, published as one-way snapshots, announced over ActivityPub
|
||||||
|
the way PkgFed proposes for package releases, with an index on halky.one, learners'
|
||||||
|
instances mirroring what they learn from, and reports and suggestions as issues and pull
|
||||||
|
requests behind a gateway. This ADR decides it, and the order it is built in.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
1. **The public tier is federated from the start, and built in usable stages.** Each
|
||||||
|
stage works on its own; none is built in a way the later ones would undo:
|
||||||
|
1. public identity (handles, profiles);
|
||||||
|
2. releases in git;
|
||||||
|
3. Explore, and learning from a release (rule 4);
|
||||||
|
4. reports and suggestions (rule 6);
|
||||||
|
5. releases across instances over HTTPS;
|
||||||
|
6. ActivityPub, through Fedify (rule 7).
|
||||||
|
2. **A person appears under a handle on the account domain, Mastodon's way:**
|
||||||
|
`raisondetredev@halkyon.eu`. The account domain is the instance operator's setting,
|
||||||
|
apart from the server's host (learn.halkyon.eu), as Mastodon's `LOCAL_DOMAIN` and
|
||||||
|
`WEB_DOMAIN`; the account domain then answers WebFinger by pointing to the host. It can
|
||||||
|
never change once anyone follows a handle on it. A handle can change until its person
|
||||||
|
first publishes, and is fixed from then on, as followers and links refer to it; moving
|
||||||
|
later goes the fediverse's way (`Move` to another account). A public profile holds a
|
||||||
|
display name, a short bio, links (shown as verified when the linked page links back
|
||||||
|
with `rel="me"`) and an avatar. Only people signed in with an identity provider (the
|
||||||
|
`identified` scope) can have one: anything involving other people needs it.
|
||||||
|
3. **A release is a commit and a tag in a git repository of its own per published
|
||||||
|
studyset**, a bare repository on a volume of the server, worked on through the `git`
|
||||||
|
command, never a library linked into the server. The server builds a release from the
|
||||||
|
publisher's synced rows (which it can read, ADR 0002): no device sends a snapshot. Its
|
||||||
|
layout is a directory per studyset of the tree and a file per card, note, path, step
|
||||||
|
and PDF, with their files, in a stable serialisation, so git's trees give every
|
||||||
|
subtree a hash of its own (a learner of one part can tell whether it changed) and
|
||||||
|
diffs mean something. Releases are numbered and immutable; a release can be withdrawn,
|
||||||
|
never rewritten. Only studysets under a licence that allows passing them on can be
|
||||||
|
published, as mirroring and copying are passing them on; the licence and its credits
|
||||||
|
travel with every release.
|
||||||
|
4. **Learning takes a copy that follows.** The learner gets the release as their own
|
||||||
|
studyset, editable and studied offline like any other; each card keeps a reference to
|
||||||
|
the published card (its studyset's identity and its own, both stable across releases)
|
||||||
|
and the release it came from. A later release is offered, never applied by itself, and
|
||||||
|
merged per side as the dictionary's corrections are (ADR 0003): what only the release
|
||||||
|
changed comes in, what the learner changed stays, a side both changed is a conflict
|
||||||
|
the learner settles. New cards are offered, withdrawn ones marked; progress is never
|
||||||
|
deleted by a release.
|
||||||
|
5. **Names are locations:** a published studyset is `halkyon.eu/@raisondetredev/japanese`;
|
||||||
|
its identity is its UUID, the location only finds it (a move is announced).
|
||||||
|
6. **Reports and suggestions are ours, on plain git, not a forge's.** A report (an issue)
|
||||||
|
is a thread on a published studyset or one of its cards; a suggestion (a pull request)
|
||||||
|
is an edit made in the app's own editors. Both are written as rows in the writer's own
|
||||||
|
synced space, so they work offline and travel with the sync engine; the server routes
|
||||||
|
them to the author's inbox. Accepting a suggestion commits it to the repository with
|
||||||
|
its writer as `Co-authored-by`, and the next release carries it. Across instances they
|
||||||
|
travel later as ForgeFed's tickets and offered patches, through Fedify.
|
||||||
|
7. **ActivityPub is spoken by Fedify, in a container of its own** beside the server
|
||||||
|
(TypeScript, MIT), behind Caddy's federation paths: WebFinger, actors, HTTP signatures
|
||||||
|
(both kinds), delivery queues, its state in PostgreSQL. The Serverpod server records
|
||||||
|
what to announce; Fedify announces it. A release is announced with its hash and where
|
||||||
|
to fetch it, and travels over HTTPS (stage 5), never inside an activity; no core
|
||||||
|
function depends on federation. halky.one may later index releases, never host them.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- An instance's operator chooses an account domain once, and keeps WebFinger answering
|
||||||
|
on it for as long as the instance lives.
|
||||||
|
- Instances hold git repositories on a volume, which the backups must cover; the server's
|
||||||
|
image needs the `git` command.
|
||||||
|
- A learner's progress, notes and edits stay theirs whatever the author does: releases
|
||||||
|
only ever offer.
|
||||||
|
- Reports, suggestions, the author's inbox and the review of a suggestion as cards are
|
||||||
|
ours to build, in the app.
|
||||||
|
- A second runtime (Fedify on Node, Deno or Bun) joins the deployment at stage 6, with its
|
||||||
|
own container.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Publishing and learning on one instance first, federation designed later:** reaches
|
||||||
|
learners sooner, but risks a format and identities other instances can't share.
|
||||||
|
Rejected for building in stages within the federated design instead.
|
||||||
|
- **A plain copy** (the old app's "Add to my library"): simplest, but an author's fix never
|
||||||
|
reaches anyone who already learns. **A read-only mirror with a personal layer of
|
||||||
|
reviews and overlays**: updates arrive by themselves, but cards can't be edited, only
|
||||||
|
overlaid, and much more has to be built.
|
||||||
|
- **The GitHub username as the public name:** would mean storing it, which the instance
|
||||||
|
avoids on purpose (only the numeric GitHub id is kept). **No author shown:** only the
|
||||||
|
licence holder would name anyone.
|
||||||
|
- **Releases as files by their hash, git later:** less to run, but history, diffs and
|
||||||
|
per-subtree hashes would have to be rebuilt by hand.
|
||||||
|
- **libgit2 linked into the server (git2dart):** no processes, but a native library in the
|
||||||
|
server and its build.
|
||||||
|
- **A Forgejo on each instance for issues and pull requests:** mature issues, reviews and
|
||||||
|
a web interface, but a second account system per instance (a Forgejo user per person,
|
||||||
|
usernames and emails stored, deletion to keep in step), an experimental federation that
|
||||||
|
would give a person a second identity in the fediverse, every action a live call (an
|
||||||
|
outbox of its own for offline use), and the review of a suggestion as cards still ours
|
||||||
|
to build in the app.
|
||||||
|
- **Our own ActivityPub in Dart:** one runtime, but every interoperability detail
|
||||||
|
(signatures above all) ours to get right and keep right.
|
||||||
Loading…
Reference in a new issue