Skip to content

feat: NIP-23 long-form articles (kind:30023)#249

Open
tlongwell-block wants to merge 1 commit intotyler/pr2-nip33-parameterized-replaceablefrom
tyler/pr3-nip23-long-form-articles
Open

feat: NIP-23 long-form articles (kind:30023)#249
tlongwell-block wants to merge 1 commit intotyler/pr2-nip33-parameterized-replaceablefrom
tyler/pr3-nip23-long-form-articles

Conversation

@tlongwell-block
Copy link
Copy Markdown
Collaborator

Summary

Accept kind:30023 (NIP-23 long-form content) in the relay's per-kind scope allowlist, advertise NIP-23 in NIP-11, and ensure global-only storage semantics.

Builds on PR 2 (NIP-33 parameterized replaceable events) — kind:30023 is in the 30000–39999 range, so replacement keyed by (pubkey, kind, d_tag) is handled automatically by the existing replace_parameterized_event() path.

Changes (4 files, ~430 insertions)

Production code (~15 LOC)

  • kind.rs: KIND_LONG_FORM: u32 = 30023 constant + ALL_KINDS registration
  • ingest.rs: KIND_LONG_FORM => Ok(Scope::MessagesWrite) in scope allowlist + extracted is_global_only_kind() helper to ensure channel_id = None for global-only kinds (strips stray h-tags at write time)
  • nip11.rs: Added 23 to SUPPORTED_NIPS (extracted as module-level constant)

Tests (~400 LOC)

  • ingest.rs: 7 unit tests — scope allowlist, channel scoping, global-only invariant, disjointness
  • nip11.rs: 2 unit tests — NIP-23/33 advertised, sorted invariant
  • e2e_long_form.rs: 5 E2E tests — acceptance, retrieval, stray h-tag global storage, NIP-33 replacement, stale-write protection

Design

  • Scope: MessagesWrite — same as kind:1 (text notes). Articles are user-authored content.
  • Storage: Global (channel_id = NULL). Author-owned, not channel-scoped.
  • Replacement: NIP-33 via PR 2's replace_parameterized_event(). Keyed by (pubkey, kind:30023, d_tag).
  • is_global_only_kind(): Extracted from inline matches!() in step 5b of the ingest pipeline. Makes the global-only invariant testable and documents the behavior.

Known Limitation

Stray h tags on global-only kinds remain on stored events (Nostr events are signed — tags can't be stripped without invalidating the signature). The read-path filter matching in filter.rs treats explicit h tags as authoritative, so a stray h tag can still match #h queries. This is pre-existing (kind:0, 1, 3 have the same behavior) and documented in code. Fix belongs in the filter layer as a follow-up.

Verification

  • Unit tests: 156 relay + 42 core — all pass
  • E2E tests: 5/5 against live relay
  • Existing E2E suites: e2e_relay 26/27 (1 pre-existing nip05 failure), e2e_nostr_interop 15/15
  • Live testing: kind:30023 publish, retrieve, NIP-33 replace, NIP-11 — all verified
  • ACP agent harness: no regressions
  • Pre-push hooks: fmt ✅ clippy ✅ tests ✅ desktop build ✅

Dependencies

  • PR 2 (NIP-33 parameterized replaceable) must be merged first

Accept kind:30023 in the per-kind scope allowlist (MessagesWrite), add
NIP-23 to the NIP-11 supported_nips, and extract is_global_only_kind()
to ensure stray h-tags are ignored for global-only kinds.

kind:30023 events are parameterized replaceable (NIP-33, 30000-39999
range) — replacement keyed by (pubkey, kind, d_tag) is handled by PR 2's
existing replace_parameterized_event() path. Events are stored globally
(channel_id = NULL), not channel-scoped.

Changes:
- kind.rs: KIND_LONG_FORM constant (30023) + ALL_KINDS registration
- ingest.rs: scope allowlist + is_global_only_kind() helper + 7 unit tests
- nip11.rs: SUPPORTED_NIPS constant with NIP-23 + 2 unit tests
- e2e_long_form.rs: 5 E2E tests (acceptance, retrieval, stray h-tag,
  NIP-33 replacement, stale-write protection)

Known limitation (documented): stray h-tags on global-only kinds remain on
stored events (Nostr signature integrity) and are matchable via #h queries
on the read path. This is pre-existing (affects kind:0, 1, 3) and tracked
as a follow-up filter-layer fix.
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