Skip to content

Commit 3af731e

Browse files
NIP-29: refine subgroups (NIP-11 detection, validation on 9002, membership independence)
- advertise subgroup support via nip29.subgroups in NIP-11, drop the write-based probe; keep passive detection via observed parent tags - move at-most-one-parent and cycle checks onto the kind:9002 (the user-published event) instead of the relay-emitted kind:39000 - reference kind:9008 for parent deletion - state re-parenting needs no consent from the previous parent - document the child list as a top-down traversal hint (parent is not filterable) and not an authoritative list - make membership strictly independent: no cascade, no inheritance
1 parent 2f617ea commit 3af731e

1 file changed

Lines changed: 18 additions & 42 deletions

File tree

29.md

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -236,53 +236,27 @@ A group without a `parent` tag is a root group. To build the tree, clients fetch
236236

237237
### Relay support detection
238238

239-
Subgroup support is self-advertising; there is no separate feature flag to query. A client that receives any `kind:39000` with a `parent` tag knows the relay supports them. To probe actively, a client sends a `kind:9002` with a `parent` tag and inspects the resulting `kind:39000`: if the `parent` tag survives, the feature is supported; if the relay emits the event without it, the client MAY assume the feature is not implemented or the tag was ignored. Relays SHOULD ignore unknown tags rather than reject the event.
239+
A relay that supports subgroups SHOULD advertise it in its NIP-11 relay information document under a `nip29` object:
240240

241-
### Rules
242-
243-
- A `kind:39000` SHOULD have at most one `parent` tag. An empty value (`["parent"]` or `["parent", ""]`) is equivalent to the tag being absent and indicates a root group.
244-
- Relays SHOULD reject any `kind:39000` whose `parent` tag would create a cycle, including self-reference.
245-
- If a group's declared parent does not exist on the relay, the subgroup is treated as a root. When a parent is deleted, its remaining children automatically become roots.
246-
- Re-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. Historical events under the group's `h` tag remain valid across these transitions.
247-
- The `parent` tag MAY carry a third element with the pubkey of the admin whose `kind:9002` authored the current parent relationship. Relays that support this SHOULD copy that pubkey into the emitted `kind:39000`. Clients MAY use it as an attestation path (see *Parent consent*).
248-
- A parent group's `kind:39000` MAY include one or more `["child", "<id>"]` tags and/or a `["closed-children"]` flag; see *Parent consent*. A `child` tag MAY carry additional optional elements: `["child", "<id>", "<order>", "<flags>"]`. `<order>` is an ASCII string for sibling ordering (lexicographic); `<flags>` MAY include `"suggested"` as a UX hint. Unknown elements MUST be ignored.
249-
- Editing the child list via `kind:9002`: if any `child` tag is present, the relay SHOULD replace the parent's full child list with the provided entries. A single `["child"]` tag with no id acts as an explicit clear marker (empty list). A `kind:9002` with no `child` tags at all leaves the list unchanged (partial-update semantics). Clients MUST therefore send the complete desired list in every edit that touches it; `child` tags are replacement, never additive. Omitting a currently-present child removes it, so to remove a specific child the admin republishes the remaining children, omitting the target.
250-
251-
### Parent consent
252-
253-
The `parent` tag is self-declared: nothing in the protocol prevents a group from claiming any other group as its parent. The spec does not guarantee truth; it defines how clients should interpret conflicting declarations.
254-
255-
Two optional mechanisms let a client distinguish a legitimate subgroup from an unilateral claim:
256-
257-
**1. Bilateral declaration.** A parent group MAY publish its own list of accepted children as `["child", "<id>"]` tags in its `kind:39000`, and MAY add a `["closed-children"]` flag to signal that only explicitly listed children are legitimate. A `["closed-children"]` flag with no `child` tags means the group accepts no children at all. The flag follows NIP-29's paired-flag convention and is toggled off by a `kind:9002` carrying `["open-children"]` (symmetric to `open`/`closed` and `public`/`private`).
258-
259-
```jsonc
260-
// parent with an explicit acceptance list
261-
{ "kind": 39000, "tags": [
262-
["d", "tech"],
263-
["name", "Tech"],
264-
["child", "nostr"],
265-
["child", "music"],
266-
["closed-children"]
267-
]}
241+
```json
242+
{
243+
"supported_nips": [29],
244+
"nip29": { "subgroups": true }
245+
}
268246
```
269247

270-
**2. Admin attestation.** The relay MAY copy the pubkey of the admin whose `kind:9002` set the current `parent` into a third element of the emitted `parent` tag. A client that observes this pubkey in the parent's `kind:39001` admin list can confirm the relationship without the parent ever publishing a `child` tag. This is inspired by how Matrix spaces confirm child claims via power-level inspection. Attestation is evaluated against the parent's *current* `kind:39001`: if the attesting pubkey is later removed from the admin list, the relationship reverts to **unverified** without requiring any new event.
248+
The `nip29` object is a namespace for NIP-29 feature flags; clients MUST ignore keys they do not understand, and the absence of the object or of `subgroups` means the relay makes no claim of support. This lets a client check capability with a single NIP-11 request, before publishing anything.
271249

272-
```jsonc
273-
// Admin of "tech" reparents "nostr"
274-
{ "kind": 9002, "pubkey": "<admin-pk>", "tags": [["h", "nostr"], ["parent", "tech"]] }
275-
// Relay emits (carrying the admin pubkey):
276-
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "tech", "<admin-pk>"]] }
277-
```
250+
Support is also observable directly: a client that receives any `kind:39000` with a `parent` tag knows the relay honors them, whatever NIP-11 says. A relay that does not support subgroups SHOULD ignore the `parent` tag rather than reject the event.
278251

279-
Clients SHOULD classify each parent-child relationship as:
280-
281-
- **confirmed**: either (a) both sides declare the relationship (the child has `["parent", "X", ...]` and X's `kind:39000` has `["child", "<child-d>", ...]`), or (b) the third element of the child's `parent` tag is a pubkey currently listed in X's `kind:39001`. Render as a normal subgroup.
282-
- **unverified**: only the child declares and no attestation is present. Clients MAY render it, but SHOULD flag it visually (greyed out, badge, tooltip).
283-
- **invalid**: the parent has `["closed-children"]` and does not list the child. Clients SHOULD ignore the claim, overriding any attestation.
252+
### Rules
284253

285-
Relays MAY enforce additional policies on `parent` declarations (for example, requiring the event author to be an admin of the parent group, or rejecting `parent: X` when X declares `closed-children` and hasn't listed the child). Enforcement is optional and local; clients MUST NOT rely on it and MUST apply the classification above regardless.
254+
- A `kind:9002` SHOULD carry at most one `parent` tag. An empty value (`["parent"]` or `["parent", ""]`) is equivalent to the tag being absent and makes the group a root. The resulting `kind:39000` therefore also carries at most one `parent` tag.
255+
- Relays SHOULD reject a `kind:9002` whose `parent` value would create a cycle, including self-reference, rather than emit the resulting `kind:39000`.
256+
- If a group's declared parent does not exist on the relay, the subgroup is treated as a root. When a parent is deleted (`kind:9008`), its remaining children automatically become roots.
257+
- Re-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. Historical events under the group's `h` tag remain valid across these transitions.
258+
- Relays MUST reject any `kind:9002` carrying `["parent", "X"]` unless the event author is an admin of `X` (per `X`'s `kind:39001`). Because `kind:9002` already requires admin status on the child group it edits, this means a parent-child relationship can only be established or changed by someone authorized on both sides, i.e. bilateral consent enforced at write time by the relay. Moving a group to a new parent requires no consent from its previous parent: only the child's own admin (already required to edit it) and the admin of the new parent need to agree. Detaching or re-parenting is unilateral from the old parent's side, the same way leaving a group never requires the counterparty's permission.
259+
- A parent group's `kind:39000` MAY include `["child", "<id>"]` tags as an optional ordered list of its children; the position of each tag in the array is the order. The list is set by the parent's admin via a `kind:9002` carrying the desired `["child", ...]` tags in order, and each edit replaces the previous list. The `parent` tag on each child remains the source of truth for the relationship; `child` entries that do not correspond to a group whose `parent` tag points back MUST be ignored. This list also lets a client walk the tree top-down without fetching every group: because `parent` is a multi-character tag it cannot be used in a relay filter, so the only way to find a group's children server-side is to read the parent's `child` ids and fetch them by `["#d", ...]`. The list is a convenience hint, not an authoritative list: a child whose `parent` points at the group but that the parent's admin never added will be missed, so a client needing exhaustive discovery still falls back to fetching all `kind:39000` and grouping by `parent`.
286260

287261
### Lifecycle example
288262

@@ -300,7 +274,9 @@ Relays MAY enforce additional policies on `parent` declarations (for example, re
300274

301275
### Membership
302276

303-
Each subgroup has its own independent membership: users join via `kind:9021``kind:9000` like any other group. Admin roles do not inherit across parent-child; 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. Relays MAY implement member cascading (for example, emitting `kind:9000` on a subgroup when a member is added to the parent) through normal moderation events.
277+
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.
278+
279+
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.
304280

305281
## Implementation quirks
306282

0 commit comments

Comments
 (0)