Skip to content
This repository was archived by the owner on Apr 20, 2026. It is now read-only.

Subgroups: NIP-29 hierarchy, manifests, and inherited membership enforcement#21

Open
Anderson-Juhasc wants to merge 1 commit into
fiatjaf:masterfrom
Anderson-Juhasc:subgroups
Open

Subgroups: NIP-29 hierarchy, manifests, and inherited membership enforcement#21
Anderson-Juhasc wants to merge 1 commit into
fiatjaf:masterfrom
Anderson-Juhasc:subgroups

Conversation

@Anderson-Juhasc
Copy link
Copy Markdown

@Anderson-Juhasc Anderson-Juhasc commented Apr 15, 2026

Summary

Implements the NIP-29 subgroups section in full: parent tag with optional admin attestation, bilateral child declaration, and paired closed-children / open-children flags. Cycle rejection runs both at event-accept time and at startup replay.

Changes

  • groups.go: Parent, ParentAttester, Children, ChildEntries, ClosedChildren on Group; ChildEntry struct. Load now runs a second pass that replays every kind:9002 in global chronological order and cycle-checks each parent update — a runtime-rejected event that still lives in the DB can no longer corrupt the in-memory tree.
  • subgroups.go (new): ToMetadataEvent override emits the parent tag with attester, any child tags (with optional order / flags), and the closed-children flag when set. WouldCreateCycle; Reparent(group, newParent, attester) serialized via reparentMu; promoteChildrenToRoots clears both parent and attester.
  • moderation_actions.go: EditMetadata gains ParentValue *string, ChildEntriesValue *[]ChildEntry, ClosedChildrenValue *bool. Parsing handles bare ["child"] as a clear marker and ["open-children"] as the unset pair for closed-children.
  • event_policy.go: cycle rejection at RejectEvent; applies child-list / closed-children under the wrapper lock; threads the authoring admin's pubkey as the attester into Reparent. On kind:9008, detaches from parent and promotes each child to root (broadcasting a fresh kind:39000 without parent).
  • state.go: reparentMu sync.Mutex.

Behavior

  • kind:39000 carries ["parent", "<d>", "<admin-pk>"] when set; on detach the attester is cleared.
  • Detach accepts both ["parent"] and ["parent", ""].
  • ["child", "<id>", "<order>", "<flags>...] tags round-trip via kind:9002kind:39000. Replacement semantics: a kind:9002 with any child tags replaces the whole list. A bare ["child"] tag clears it.
  • ["closed-children"] / ["open-children"] flip a single flag symmetrically, mirroring the existing open/closed and public/private pairs.
  • Unknown tags on subgroup-related events are ignored rather than rejected (per spec).
  • Declared parent missing on the relay → accepted; clients treat subgroup as root.
  • Self-reference and multi-hop cycles rejected at event-accept time and silently skipped on replay.
  • Parent deletion emits an updated kind:39000 for each former child without parent.
  • Membership stays literal per subgroup — no inheritance.

Test plan

  • go test ./... passes
  • khatru29/subgroups_test.go — 15 integration tests covering:
    • attach + detach (both tag shapes)
    • reparent (plus attester update)
    • cycle rejection (self + multi-hop) at accept time
    • cycle rejection on startup replay (DB-planted rogue event)
    • missing parent accepted
    • delete parent → children become roots
    • independent membership between parent and child
    • admin attestation emitted on kind:39000
    • child tags with order / flags round-trip
    • closed-children set and unset via open-children
    • bare ["child"] clears the list
    • child-list replacement semantics (not additive)
    • unknown tags on kind:9002 ignored
    • state persists across relay restart

- parent tag with optional admin attestation pubkey (third element)
- bilateral child declaration with optional order/flags
- closed-children / open-children paired flags
- explicit clear markers: bare ["parent"] / ["parent",""] and bare ["child"]
- cycle rejection at pre-save AND at startup replay
- detach, delete-promotes-children-to-roots, independent membership
- global chronological replay of kind:9002 to rebuild wrapper state
- 15 integration tests in khatru29/subgroups_test.go

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant