architecture¶
two services and one database. neither scales past a single replica, and both
are deployed with strategy: Recreate on purpose.
flowchart LR
user([reviewer]) --> editor
user --> board
editor[editor<br/>hedgedoc 1.x fork] --> pg[(postgres)]
board[spec board<br/>node poller + web] --> pg
board --> gh[(github)]
board -.-> smtp[(smtp)]
board -.-> bots[(review bot endpoints)]
cron[pg_dump cronjob] --> pg
editor¶
the fork adds criticmarkup review on top of hedgedoc 1.x: inline comment
threads, suggestions with accept/reject, approvals in the navbar, a /new/spec
template, and deep links to threads. the rebrand is applied at image build time
(see releases), so the source tree stays upstream-clean.
- one replica: hedgedoc 1.x keeps note state in memory per process, so a second
replica would diverge.
Recreate, RWO uploads volume. - login is github oauth. the per-user token hedgedoc stores in
Users.accessTokenis plaintext (upstream's design) and is requested without thereposcope, so it can read identity and nothing else. - uploads land on a filesystem volume, not in the database.
spec board¶
a single node process: a poll loop plus a small web ui. it owns the
spec_board_* tables and treats hedgedoc's own tables as read-mostly.
- reads notes to find specs (
tags: [spec, <status>]in frontmatter), resolves approvers from.specs/roles.ymlin the target repo, and opens the spec PR when quorum is met and every comment thread is resolved. - the only writes it makes to hedgedoc's tables are
Notes.permission(locking an approved spec) and appending bot review comments toNotes.content. - github access is per namespace: a github app installation token where one is installed, otherwise the service PAT.
- optional: smtp digests, webhook notifications, and review bots that post criticmarkup threads from an external llm endpoint.
- schema changes are forward-only DDL executed at startup, so rolling the image back does not roll the schema back.
trust boundaries¶
- anyone who can edit a note can change spec text, by design. approval works the other way: approvers and quorum come only from the branch-protected target repo, never from the note.
- the board is the only writer to github. its app key or PAT never leaves the pod, and users never hand it their own token.
- review bot api keys live in the database (
spec_board_bots, plaintext), managed from/botsby the accounts inBOARD_ADMINS. - published
/s/views strip every criticmarkup comment, resolved or not.
known risks¶
kept here rather than in a runbook, because they are properties of the deployment rather than incidents:
- single node: control plane, workers, registry and every hostPath volume live
on one machine. the nightly
pg_dumplands on the same disk as the database, so it survives corruption and mistakes but not disk loss. - the uploads volume has no backup at all.
- there is no dynamic storage provisioner: every PV is hand-made and statically bound, so a PVC without a matching PV waits forever.