01
The ledger is append-only, enforced by a missing GRANT
The app's database role has no UPDATE or DELETE on the
ledger table. A bug, a bad migration or a curious admin cannot rewrite history,
because Postgres refuses. Mistakes are fixed with a reversing entry that carries a
mandatory reason, and a balance is never stored: it is a fold over the entries.
✓ SELECT
✓ INSERT
✕ UPDATE
✕ DELETE
A test attempts the UPDATE as the app role and asserts the database refuses.
02
Anonymity is a projection, with one boundary
The author is always stored, so moderation and accountability stay possible.
Anonymity is decided in exactly one place — PostView.From(post, viewer)
— which every read goes through. Admins get a pseudonym and an unmask action that is
audited, and the person who posted can see that it happened. Counters filter
anonymous posts too, so a per-house number cannot give one away.
-
The author
Their own post, marked as theirs
-
A neighbour
Anonymous
-
An admin
A pseudonym and an audited “unmask” button, never a name
03
One vote per house is a domain rule
An upvote on an idea is casual and per person. A formal poll is binding and per
house, however many people live in it. Treating the two as one thing would
make a governance result meaningless, so they are separate concepts with separate
tests.
Idea upvotes · per person
12 votes
Formal poll · per house
4 votes