mirror of
https://github.com/nostr-protocol/nips.git
synced 2026-07-22 07:48:25 +00:00
NIP-29: add subgroups spec (#2319)
This commit is contained in:
62
29.md
62
29.md
@@ -280,6 +280,68 @@ Clients may display these events at the top of the group view.
|
||||
}
|
||||
```
|
||||
|
||||
## Subgroups
|
||||
|
||||
Groups MAY be organized hierarchically. A subgroup is an ordinary NIP-29 group (same `kind:39000`, same moderation events, same `h` tag mechanics) whose metadata carries a `parent` tag pointing at its parent's `d` identifier:
|
||||
|
||||
```jsonc
|
||||
// root group
|
||||
{ "kind": 39000, "tags": [["d", "tech"], ["name", "Tech"], ["child", "nostr"]] }
|
||||
|
||||
// subgroup
|
||||
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "tech"], ["child", "nip29"]] }
|
||||
|
||||
// sub-subgroup
|
||||
{ "kind": 39000, "tags": [["d", "nip29"], ["name", "NIP-29"], ["parent", "nostr"]] }
|
||||
```
|
||||
|
||||
A group without a `parent` tag is a root group. To build the tree, clients fetch `{"kinds":[39000]}` and assemble it locally: events without a `parent` tag are roots; the rest are grouped under the `d` referenced by their `parent` tag. The tree is scoped to a single relay. When aggregating across relays a client MAY see conflicting `parent` values for the same `d`; in that case the `kind:39000` from the relay currently serving the group's events is authoritative.
|
||||
|
||||
### Relay support detection
|
||||
|
||||
A relay that supports subgroups SHOULD advertise it in its NIP-11 relay information document under a `nip29` object:
|
||||
|
||||
```json
|
||||
{
|
||||
"supported_nips": [29],
|
||||
"nip29": { "subgroups": true }
|
||||
}
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Parenting and promotion to root are triggered by a `kind:9002` (`edit-metadata`) event from an authorized admin with the desired `parent` value (or no `parent` tag to detach); the relay then updates the group's `kind:39000` accordingly. The relay also updates the parent's `kind:39000` to include the newly added child as a `child` tag (and vice-versa). If a group is switching from one parent to another parent the relay also updates the previous parent accordingly.
|
||||
- Historical events under the group's `h` tag remain valid across these transitions.
|
||||
- A `kind:9002` MAY carry at most one `parent` tag. The resulting `kind:39000` therefore also carries at most one `parent` tag.
|
||||
- Absence of the `parent` tag makes the group a root.
|
||||
- Relays MUST reject a `kind:9002` whose `parent` value would create a cycle, including self-reference.
|
||||
- If a group's declared parent does not exist on the relay the `kind:9002` MUST be rejected.
|
||||
- When a parent is deleted (`kind:9008`), its remaining children automatically become roots.
|
||||
- Relays MUST reject any `kind:9002` carrying `["parent", "X"]` unless the event author is also an admin of `X` (per `X`'s `kind:39001`).
|
||||
- A parent group's `kind:39000` MUST include `["child", "<id>"]` tags as an ordered list of its children; the position of each tag in the array is the order. By default relays can just append new children to the list as they are created, but the list can be rearranged by the parent's admin via a `kind:9002` carrying the desired `["child", ...]` tags in order, and each edit replaces the previous list. `kind:9002` metadata edits that do not include all the children as `child` tags MUST be rejected.
|
||||
|
||||
### Lifecycle example
|
||||
|
||||
```jsonc
|
||||
// "social" adopts "nostr" (was under "tech")
|
||||
{ "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"], ["parent", "social"]] }
|
||||
// Relay emits:
|
||||
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "social"]] }
|
||||
{ "kind": 39000, "tags": [["d", "social"], ["child", "nostr"]] }
|
||||
|
||||
// Admin promotes "nostr" to root: edit-metadata without a parent tag
|
||||
{ "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"]] }
|
||||
// Relay emits:
|
||||
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"]] }
|
||||
{ "kind": 39000, "tags": [["d", "social"]] }
|
||||
```
|
||||
|
||||
### Membership
|
||||
|
||||
Each subgroup has its own independent membership: users join via `kind:9021` → `kind:9000` like any other group. Nothing is inherited across the parent-child link. Membership does not cascade: being a member of the parent grants no membership in any child, and each group's member list is defined solely by its own `kind:9000`/`kind:9001` events. Admin roles likewise do not inherit; each group's `kind:39001` is authoritative for its own scope, and being an admin of the parent grants no authority over any child unless the child's own `kind:39001` lists the same pubkey.
|
||||
|
||||
This spec defines no automatic propagation of membership between parent and child. A relay that wants such behavior MAY emit the corresponding `kind:9000`/`kind:9001` moderation events itself, but clients MUST NOT assume it: the absence of a `kind:9000` in a child means the user is not a member of that child, regardless of parent membership.
|
||||
|
||||
## Implementation quirks
|
||||
|
||||
### Checking your own membership in a group
|
||||
|
||||
Reference in New Issue
Block a user