Moderation: banlist + operator hide via CLI #6

Open
opened 2026-05-13 18:45:25 +02:00 by arne · 0 comments
Owner

What to build

Minimal operator surface for a public room with no rate limit.

State:

  • New `banlist` table: `url PRIMARY KEY, banned_at, reason`.
  • Add `hidden BOOL DEFAULT 0` column to the `messages` table.

Receive-path enforcement:

  • Before `pkg/posta.NewHandler`'s ACL hook, check `banlist`. Banned sender → `403 forbidden` per the ACL contract; nothing is archived; no fan-out.
  • Banning a sender also removes them from `subscribers` (single-transaction so they don't keep accruing perm-failure counters from an in-flight fan-out).

Archive enforcement:

  • The `LatestN` query in the HTML archive (posta/web#2) excludes `hidden=1` rows.
  • Already-fanned-out wrappers are out of the operator's hands — that's the protocol. Hide only affects the public archive surface.

CLI subcommands on the `posta-web` binary. Each opens `feedback.db` directly (no admin HTTP endpoint, no auth):

  • `posta-web ban [--reason "..."]` — add to banlist, remove from subscribers
  • `posta-web unban ` — remove from banlist
  • `posta-web hide ` — set `hidden=1`
  • `posta-web unhide ` — set `hidden=0`
  • `posta-web subscribers [--list|--count]` — read-only inspection

Bans and hides are persistent; the running posta-web reads fresh state on each request (no cache invalidation needed).

Acceptance criteria

  • POST from a banned sender returns `403` with `{"error":"forbidden"}` and is not persisted
  • Banning a current subscriber removes them from `subscribers` atomically
  • Hidden messages are absent from the HTML archive at `/feedback`
  • CLI subcommands (`ban`, `unban`, `hide`, `unhide`, `subscribers`) behave correctly against a temp DB; output is parseable plain text
  • CLI does not require posta-web to be stopped; concurrent reads/writes are correct under SQLite's default locking
  • Unbanning a previously banned sender does not auto-re-subscribe them; they have to POST again

Blocked by

  • posta/web#1 (need the receive path and store schema)
  • posta/web#2 (need the HTML archive query, which now filters `hidden`)
## What to build Minimal operator surface for a public room with no rate limit. State: - New \`banlist\` table: \`url PRIMARY KEY, banned_at, reason\`. - Add \`hidden BOOL DEFAULT 0\` column to the \`messages\` table. Receive-path enforcement: - Before \`pkg/posta.NewHandler\`'s ACL hook, check \`banlist\`. Banned sender → \`403 forbidden\` per the ACL contract; nothing is archived; no fan-out. - Banning a sender also removes them from \`subscribers\` (single-transaction so they don't keep accruing perm-failure counters from an in-flight fan-out). Archive enforcement: - The \`LatestN\` query in the HTML archive (posta/web#2) excludes \`hidden=1\` rows. - Already-fanned-out wrappers are out of the operator's hands — that's the protocol. Hide only affects the public archive surface. CLI subcommands on the \`posta-web\` binary. Each opens \`feedback.db\` directly (no admin HTTP endpoint, no auth): - \`posta-web ban <url> [--reason \"...\"]\` — add to banlist, remove from subscribers - \`posta-web unban <url>\` — remove from banlist - \`posta-web hide <msg-id>\` — set \`hidden=1\` - \`posta-web unhide <msg-id>\` — set \`hidden=0\` - \`posta-web subscribers [--list|--count]\` — read-only inspection Bans and hides are persistent; the running posta-web reads fresh state on each request (no cache invalidation needed). ## Acceptance criteria - [ ] POST from a banned sender returns \`403\` with \`{\"error\":\"forbidden\"}\` and is not persisted - [ ] Banning a current subscriber removes them from \`subscribers\` atomically - [ ] Hidden messages are absent from the HTML archive at \`/feedback\` - [ ] CLI subcommands (\`ban\`, \`unban\`, \`hide\`, \`unhide\`, \`subscribers\`) behave correctly against a temp DB; output is parseable plain text - [ ] CLI does not require posta-web to be stopped; concurrent reads/writes are correct under SQLite's default locking - [ ] Unbanning a previously banned sender does not auto-re-subscribe them; they have to POST again ## Blocked by - posta/web#1 (need the receive path and store schema) - posta/web#2 (need the HTML archive query, which now filters \`hidden\`)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
posta/web#6
No description provided.