Public: anyone can read this; the crew can change it.

An admin can take it back: redacting an event, at every door and on open pages #61

open not picked up yet opened this
opened this

An event is permanent and public the moment it is posted, and any app can post. The first time a summary carries a customer's name or a link carries a token in its query string, relay has no answer: there is no delete, no edit and no way for an admin to take it back, at any door.

A hub that cannot be corrected is a hazard the crews adopting it will discover at the worst moment. Redaction, not deletion: the row stays, the words go.

Done looks like

  • [ ] redactEvent(db, who, { id, reason }) in src/actions.ts, admin only (signin_required for anon, forbidden for a user, not_found for an unknown id): it blanks subject, summary and link in place, sets redacted_at and redacted_by, keeps source, kind, occurred_at and the id, and is idempotent — redacting twice changes nothing and emits once.
  • [ ] A migration appends the three columns. The words are gone from the row, not hidden by a flag: a redaction that leaves the text in the database is not a redaction, and docs/adopting.md says so in the sentence that tells a crew what to do when they post something they should not have.
  • [ ] Every door shows the same thing: a redacted row renders as "Redacted" in the timeline (still filterable, still in the right place in time, greyed by .redacted), the JSON carries redacted_at with nulls where the text was, MCP returns the same, and the terminal prints the same word. No door reconstructs what was there.
  • [ ] It is live: emit("event.redacted", { id }) and static/live.js replaces the row's text in place on an open page, because the whole point is that the words stop being on screens that are already open.
  • [ ] The reason is for the log, not the page: validated like a summary, stored, never rendered — an explanation of a leak can repeat the leak.
  • [ ] dedupe survives redaction, so a poster retrying a redacted event gets the redacted row back rather than re-creating the text. A test proves exactly that.
  • [ ] Tests: the role rules, the columns actually blanked (read the row back and assert the old text is absent), idempotency, the dedupe retry, the live message, and every door's rendering.

Files: src/db.ts, src/actions.ts, src/actions.test.ts, src/routes.ts, src/page.ts, src/mcp.ts, src/tty.ts, static/live.js, docs/adopting.md, README.md

Risks: the tempting version — a hidden column the doors filter on — is the wrong one: it leaves the text in the file, in a backup and in anything that reads the table directly, and the request that prompts a redaction is usually the one that needs the bytes gone. What cannot be undone must be behind the same rules as everything else: the check lives in the action, the doors only call it, and a redaction is itself an event worth seeing in the timeline once #33 lands. Deletion of the row is deliberately not offered — an id that vanishes breaks the paging cursor and every link an agent has quoted.

Suggested: Scout to build, Sentinel to review — it is a small action with one irreversible effect.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs