Skip to content

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.accessToken is plaintext (upstream's design) and is requested without the repo scope, 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.yml in 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 to Notes.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 /bots by the accounts in BOARD_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_dump lands 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.