Skip to content

feat: soft-delete for events/channels, enriched API responses, NIP-29 group management#17

Merged
tlongwell-block merged 1 commit intomainfrom
feat/channel-polish
Mar 10, 2026
Merged

feat: soft-delete for events/channels, enriched API responses, NIP-29 group management#17
tlongwell-block merged 1 commit intomainfrom
feat/channel-polish

Conversation

@tlongwell-block
Copy link
Collaborator

Summary

Implements soft-delete for events and channels, enriches API responses with metadata, and completes NIP-29 group management (kinds 9005/9008). This is the Phase 6 "channel polish" milestone.

12 files changed, +866 -59

What changed

Soft-delete infrastructure

  • soft_delete_event() / soft_delete_channel() — idempotent UPDATE SET deleted_at = NOW(6) WHERE deleted_at IS NULL
  • soft_delete_event_and_update_thread() — atomic transaction wrapping delete + thread counter decrements (reply_count, descendant_count), floors at 0
  • insert_event_with_thread_metadata() — atomic transaction wrapping event insert + thread metadata + counter increments (prevents race where concurrent delete between separate inserts corrupts counters)
  • All read queries filter deleted_at IS NULL: query_events, get_event_by_id, is_member, get_members, get_member_role, get_accessible_channel_ids
  • get_event_by_id_including_deleted() variant for audit/compliance use cases

REST API endpoints

  • DELETE /api/messages/{event_id} — author or channel owner/admin auth, effective author resolution for relay-signed messages (real sender in p-tag), atomic delete + counter decrement, fails hard on metadata lookup errors
  • DELETE /api/channels/{channel_id} — owner-only auth, soft-delete + system message emission

Enriched API responses

  • Channel list (GET /api/channels) — now includes member_count, last_message_at, visibility, topic, purpose, created_by, created_at, updated_at, archived_at
  • Message list (GET /api/channels/{id}/messages) — thread summaries always included (not gated by param), bulk reaction counts embedded per message
  • Thread detail (GET /api/channels/{id}/messages/{id}/thread) — bulk reaction counts for root + all replies

NIP-29 group management completion

  • Kind 9005 (DELETE_EVENT) — pre-storage validation checks author (via p-tag) OR owner/admin, verifies target event belongs to h-tag channel (prevents cross-channel deletes), post-storage side effect performs atomic soft-delete
  • Kind 9008 (DELETE_GROUP) — soft-deletes channel, emits system message
  • NIP-11 supported_nips updated to [1, 11, 25, 29, 42]

Performance

  • Replaced N+1 queries in channels_handler with bulk queries: get_member_counts_bulk() and get_last_message_at_bulk() using QueryBuilder with separated() for IN clauses
  • Bulk reaction fetching via get_reactions_bulk() in message list and thread endpoints
  • reactions_to_json() helper deduplicates serialization logic

Authorization hardening

  • effective_author() / effective_message_author() — extracts real author from p-tag for relay-signed REST messages (used in delete auth and NIP-29 validation)
  • Cross-channel delete prevention in both pre-storage validation and post-storage side effects
  • NIP-29 kind 9005 no longer allows any member to delete — requires authorship or owner/admin role

Quality gates

Check Status
cargo build ✅ Clean, 0 warnings
cargo test --lib ✅ 121 passed
cargo fmt --check ✅ Clean
cargo clippy -- -D warnings ✅ Clean

Review notes

This went through 3 rounds of multi-model crossfire review (Claude, Codex, GPT-5.2, Opus). 13 issues were found and fixed:

  • Round 1: Double-decrement on repeated deletes, missing deleted_at filters on get_event_by_id/query_events, NIP-29 auth too permissive (any member could delete), N+1 queries in channel list
  • Round 2: Deleted channels still accessible via membership helpers (missing JOIN to channels.deleted_at IS NULL)
  • Round 3: REST delete silently corrupted counters on metadata lookup failure, NIP-29 delete emitted false audit records when deleted==false, pre-storage validation missing same-channel check for kind 9005

Known deferred items (not blocking)

  • Effective author on read paths — message lists still show relay pubkey for REST-created messages (requires DB struct changes)
  • last_reply_at recomputation on delete — deleting the latest reply does not recompute the timestamp (can add periodic reconciliation)
  • E2E regression tests for all 13 fixes

…pletion

Implements soft-delete for events (NIP-29 kind 9005) and channels (kind 9008),
replacing TODO stubs with real logic. Adds REST DELETE endpoints for both.

Changes:
- soft_delete_event/channel: UPDATE SET deleted_at = NOW(6) WHERE deleted_at IS NULL
- DELETE /api/messages/{event_id}: author or owner/admin auth, thread counter decrement
- DELETE /api/channels/{channel_id}: owner-only auth
- get_event_by_id now filters deleted_at IS NULL (+ _including_deleted variant)
- query_events now filters deleted_at IS NULL
- list_channels enriched: member_count, last_message_at, topic, purpose, visibility
- list_messages/get_thread: reaction counts embedded via get_reactions_bulk
- Thread summaries always included in channel history (no longer gated by param)
- NIP-11 supported_nips: [1, 11, 25, 29, 42]
- decrement_reply_count wired through Db, guarded against double-decrement
- VISION.md: channel features marked as implemented

Crossfire reviewed (Claude 8/10, Codex 3/10, GPT-5.2 6/10).
Three critical issues found and fixed:
1. Double-decrement guard on repeated NIP-29 deletes
2. get_event_by_id/query_events now filter soft-deleted rows
3. get_event_by_id_including_deleted added for audit use cases

12 files changed, +403 -22 lines. All quality gates pass.
@tlongwell-block tlongwell-block merged commit 69b36d1 into main Mar 10, 2026
8 checks passed
@tlongwell-block tlongwell-block deleted the feat/channel-polish branch March 10, 2026 20:01
tlongwell-block added a commit that referenced this pull request Mar 11, 2026
* origin/main:
  feat: soft-delete for events/channels, enriched API responses, NIP-29 group management (#17)
  feat: Channel management, messaging, threads, DMs, reactions, and NIP-29 support (#16)
  Improve chat scrolling and multiline composer (#14)
  chore: remove redundant inline comments across all crates (#13)
  Initial backend revisions, workflow expansion (#5)
  Add desktop Home feed (#12)
  Add desktop Playwright e2e harness (#11)
  Update desktop icon and persist window state (#9)
  feat: add channel creation flow (#8)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant