Close citation/source living-person leak; add on-demand tree purge #245

Merged
justin merged 1 commits from citation-redaction-and-tree-purge into main 2026-06-10 22:39:16 -04:00
Owner

1. Privacy fix (NN#2/NN#3) — citation/source redaction

The citation & source list endpoints gated only on can_view_tree, so a non-member on a public/unlisted/site_members tree could enumerate citations and sources tied to a redacted living person — leaking that they exist and have sourced facts (and possibly their name via a source title). #46 closed this for events/media/names/relationships but not citations/sources.

Now citation_service.list_citations and source_service.{list_sources,get_source} delegate non-member reads to public_view_service (same pattern as #46):

  • citations shown only when the cited fact resolves to FULL-visibility person(s) — covers person_id, name_id, event_id (person or both-partner), and relationship_id (both-partner) targets.
  • sources shown only when they back ≥1 visible citation; a withheld source 404s.

Tests cover all four citation target types, source withholding, the 404, and member-sees-all.

2. On-demand tree purge

Owners can permanently delete a soft-deleted tree now instead of waiting out the 30-day auto-purge. POST /trees/{id}/purge (owner-only): tree must be in the trash + retype its name to confirm. Media objects are deleted from storage, then one DELETE on trees cascades all tree data via the tree_id ON DELETE CASCADE; the audit entry survives (tree_id SET NULL). Frontend gains a Delete forever button on the Recently-deleted list. No migration.

Suite: 102 passing. (Your three June-7 trashed trees can now be purged from /trees once this deploys, rather than waiting for July 7.)

🤖 Generated with Claude Code

### 1. Privacy fix (NN#2/NN#3) — citation/source redaction The citation & source list endpoints gated only on `can_view_tree`, so a non-member on a public/unlisted/`site_members` tree could enumerate citations and sources tied to a **redacted living person** — leaking that they exist and have sourced facts (and possibly their name via a source title). #46 closed this for events/media/names/relationships but not citations/sources. Now `citation_service.list_citations` and `source_service.{list_sources,get_source}` delegate non-member reads to `public_view_service` (same pattern as #46): - **citations** shown only when the cited fact resolves to FULL-visibility person(s) — covers `person_id`, `name_id`, `event_id` (person or both-partner), and `relationship_id` (both-partner) targets. - **sources** shown only when they back ≥1 visible citation; a withheld source 404s. Tests cover all four citation target types, source withholding, the 404, and member-sees-all. ### 2. On-demand tree purge Owners can permanently delete a soft-deleted tree now instead of waiting out the 30-day auto-purge. `POST /trees/{id}/purge` (owner-only): tree must be in the trash + retype its name to confirm. Media objects are deleted from storage, then one `DELETE` on `trees` cascades all tree data via the `tree_id ON DELETE CASCADE`; the audit entry survives (`tree_id SET NULL`). Frontend gains a **Delete forever** button on the Recently-deleted list. **No migration.** Suite: **102 passing**. (Your three June-7 trashed trees can now be purged from /trees once this deploys, rather than waiting for July 7.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
justin added 1 commit 2026-06-10 22:39:15 -04:00
Two changes.

1. Privacy fix (NN#2/NN#3) — the citation and source list endpoints gated only
   on can_view_tree, so a non-member on a public/unlisted/site_members tree could
   enumerate citations and sources tied to a redacted living person, leaking that
   the person exists and has sourced facts (and possibly their name via a source
   title). #46 closed this for events/media/names/relationships but not
   citations/sources. Now citation_service.list_citations and
   source_service.{list_sources,get_source} delegate non-member reads to
   public_view_service, mirroring the #46 pattern:
   - citations: shown only when the cited fact resolves to FULL-visibility
     person(s) — covers the person_id, name_id, event_id (person or both-partner),
     and relationship_id (both-partner) target paths.
   - sources: shown only when they back at least one visible citation; a withheld
     source 404s (don't reveal it exists).
   Tests cover all four citation target types + source withholding + member-sees-all.

2. On-demand tree purge — owners can permanently delete a soft-deleted tree now
   instead of waiting out the 30-day auto-purge window. POST /trees/{id}/purge
   (owner-only): the tree must already be in the trash, and the caller retypes its
   name to confirm. Media objects are deleted from storage, then a single
   DELETE on trees cascades all tree-owned rows via the tree_id ON DELETE CASCADE;
   the audit entry survives (tree_id SET NULL). Frontend adds a "Delete forever"
   button to the Recently-deleted list. No migration.

Suite: 102 passing.
Signed-off-by: Justin Paul <justin@jpaul.me>
justin merged commit 265f5f4e7a into main 2026-06-10 22:39:16 -04:00
justin deleted branch citation-redaction-and-tree-purge 2026-06-10 22:39:16 -04:00
Sign in to join this conversation.