{"id":"f83686446b2de426cdbd9927f78c03324c1de2bae906dca4a26d8d5483ee4fe5","pubkey":"3bc309ba553873e732aaf5174c1fdb2f33730acd671ecf73a2ce2017feeef489","created_at":1786678220,"kind":30817,"tags":[["d","constitutional-lineage-for-nip29-groups"],["title","Constitutional Lineage for NIP-29 Groups"],["alt","Nostr Implementation Possibility: Constitutional Lineage for NIP-29 Groups"],["k","39100","Constitution"],["k","9908","Fork Declaration"],["client","NostrHub"]],"content":"`draft` `optional` `addressable`\n\nThis document defines two event kinds allowing a NIP-29 group to publish a\nconstitution and to declare, checkably, what a fork of that constitution\nchanged and which of its inherited decisions still carry force.\n\nIt is additive. A relay or client that does not implement it remains fully\nNIP-29 conformant, and nothing here modifies NIP-29 or asks a relay to\nenforce anything.\n\n## Motivation\n\nNIP-29 describes more than one way a group can end up on a different relay, and\nthey are not the same operation.\n\nA group can be **migrated**, \"as long as that new relay agrees to receive the\nold group and to continue enforcing the same rules and acknowledging the same\nadmins\". Or it can be **forked**: \"a dissident faction within a group can take\nthe group's history to a different relay that agrees to *change* the rules of\nthat group and assign admin powers to a different set of users.\"\n\nForking is deliberate and endorsed:\n\n> Forks are not bugs. It's a feature of this NIP that the same *id* can\n> simultaneously identify different communities with different governance,\n> hosted by different relays, each with their own `kind:39000` metadata event,\n> their own `kind:39001` admin list and their own message history.\n\nThe specification then concedes what it cannot do about the consequence. Where\na `kind:10009` entry for a group points to a different relay hint than the one\na client was using, that client \"SHOULD notify the user that the group **may\nhave moved or been forked**\".\n\nMay have. The agreement that distinguishes the two is made out of band and\nnothing in the events records it, so from outside a migration that preserved\nthe rules and a fork that rewrote them are indistinguishable. A member asked to\nfollow their community to a new relay cannot tell whether they are following\ntheir community or joining a different one wearing its id. A member ratifying a\nfork by reading it cannot tell whether the rules changed. A moderator citing a\ntwo-year-old decision cannot tell whether it is still evidence about anything.\n\nThis proposes the missing declaration layer, and nothing else.\n\n## Kinds\n\n| kind | name | category |\n|---|---|---|\n| `39100` | Constitution | addressable |\n| `9908` | Fork Declaration | regular |\n\nBoth were checked against the NIPs README and against\n`nostr-protocol/registry-of-kinds` (`schema.yaml`, 257 entries) on 13 August\n2026 and were unassigned in both.\n\n## The constitution (`kind:39100`)\n\nAn addressable event whose `content` is a JSON document with a **dual layer**:\neach clause carries both the prose members ratify and, optionally, a\nmachine-readable rule. A clause with no rule block is non-justiciable by\nconstruction rather than by declaration.\n\n**This event is not relay-signed.** NIP-29's `39000`-`39003` carry the group id\nin a `d` tag and \"MUST be created by the relay master key only\". `39100` also\nuses a `d` tag and sits in the same band, and the resemblance stops there: it is\nsigned by whoever the community's constitution names, and a relay holds no\nspecial authority over it. Were it relay-signed the relay would own the\nconstitution, which is the arrangement this document exists to replace.\n\n```json\n{\n  \"kind\": 39100,\n  \"tags\": [\n    [\"d\", \"<group-id>\"],\n    [\"h\", \"<nip29-group-id>\"],\n    [\"prev\", \"39100:<pubkey>:<group-id>\"],\n    [\"forked-from\", \"39100:<parent-pubkey>:<parent-group-id>\"],\n    [\"version\", \"3\"]\n  ],\n  \"content\": \"{ \\\"clauses\\\": [ { \\\"id\\\": \\\"c1\\\", \\\"text\\\": \\\"...\\\", \\\"rule\\\": { ... } } ] }\"\n}\n```\n\nThe dual layer is the point. A clause whose **prose** changed and whose rule did\nnot has altered what members understand and not what is enforced. A clause whose\n**rule** changed and whose prose did not has altered what is enforced and not what\nmembers understand, and a member ratifying by reading would see nothing. That is\nthe edit this whole scheme exists to make visible, and a client presenting a fork\nfor ratification SHOULD render rule-only changes most prominently, not least.\n\n## Identity is the lineage, not the identifier\n\nNeither the group id, nor the relay, nor the signing key identifies a community.\n**Identity is the position in the lineage graph.** Three relations follow, and a\nclient MUST be able to distinguish them:\n\n| relation | test | authorisation |\n|---|---|---|\n| replica | the same event | none; nothing happened |\n| continuation | `prev` names the predecessor | REQUIRED, per the predecessor's own amendment rule |\n| fork | `forked-from` names a parent | **none, by design** |\n\nA document naming neither edge is unrelated, which is a statement about two\ndocuments rather than a relation between them.\n\nThe asymmetry is deliberate. A continuation asserts that it *is* the\nconstitution now in force, so an unauthorised one is a coup rather than a\nversion; clients MUST NOT treat a new `39100` as canonical merely because it is\nthe latest addressable event. A fork asserts only descent and needs nobody's\npermission, because the right to leave is what a minority holds instead of a\nveto. Requiring consent to fork would hand the majority an instrument against\ndeparture.\n\nKey rotation follows without further machinery: a continuation signed by a\ndifferent key is a rotation, so identity does not depend on any key remaining\nconstant.\n\n## Fork declaration (`kind:9908`)\n\n```json\n{\n  \"kind\": 9908,\n  \"tags\": [\n    [\"h\", \"<parent-group-id>\"],\n    [\"forked-from\", \"39100:<parent-pubkey>:<parent-group-id>\"],\n    [\"destination\", \"39100:<child-pubkey>:<child-group-id>\"],\n    [\"clause_delta\", \"c1:prose\", \"c8:rule\", \"c10:removed\"],\n    [\"procedure_delta\", \"panel composition\", \"nomination rule\"],\n    [\"decay_epoch\", \"<timestamp>\"]\n  ],\n  \"content\": \"Reasons for the fork.\"\n}\n```\n\n**Every declaration is checkable against the documents.** Any party holding both\nconstitutions can recompute the deltas and compare them with what was claimed. A\nfork whose declared delta does not match is making a false statement about its\nown descent, and that is detectable by anybody. This is the property the scheme\nexists to provide; without the check, a declaration is decoration.\n\n## Precedent portability\n\nGiven the clause delta, four dispositions follow mechanically, requiring no\njudgement:\n\n| disposition | condition | effect |\n|---|---|---|\n| `portable` | clause identical in both layers | transfers whole, citable |\n| `persuasive` | prose changed, rule unchanged | outcomes comparable, reasoning written against wording the fork no longer uses |\n| `void` | the executable rule changed | reached under a different rule; not evidence about this one |\n| `orphaned` | clause deleted | nothing to cite it under |\n\n**Procedure overrides all four.** Where a fork alters panel composition, an\nassent threshold, a fallback seat count, an appeal structure or a sanction\nladder, *every* inherited decision was reached under a different procedure. Such\ndecisions remain accurate records of what was found; they are not evidence about\nhow this fork's mechanism behaves, which is the only thing precedent is\nconsulted for.\n\nA fork therefore carries the record whole and its **force** only in part. This\nis not a limitation of the scheme, it is what the scheme is for.\n\n## Siblings are co-equal\n\nA parent may have any number of children. None is the heir, no tag marks a\npreferred one, and the order in which a client receives them carries no meaning.\n\n**Every disposition a client reports for a set of siblings MUST be invariant\nunder permutation of that set.** This is testable, and implementations SHOULD\ntest it: an implementation that sorts on input order has silently privileged a\nbranch.\n\n## What this does not do\n\nStated plainly, because a specification that hides its limits wastes the reader's\ntime.\n\n- **It does not discover siblings.** Finding every child of a parent means asking\n  relays which events name a coordinate as `forked-from`, which returns what\n  those relays chose to serve. A client shown three of four forks reports\n  correctly on the three and cannot know of the fourth. The permutation\n  requirement is a guarantee about even-handedness, not about completeness.\n- **It does not determine which constitution is in force.** Under lineage that is\n  a question about which document a member ratified and which relay enforces it,\n  and both lie outside the graph.\n- **It specifies no adjudication.** There is a larger design behind this\n  concerning how verdicts are reached; it is deliberately not proposed here,\n  because it depends on problems nobody has solved on this substrate.\n- **Why not `kind:34551` (Community Rules)?** Its registry entry declares a\n  `content` type of *free*, which cannot express the dual layer this scheme\n  depends on. That is an inference from a schema rather than a reading of a\n  specification: no defining document for `34551` could be found, and a correction\n  from anyone who knows would be welcome.\n\n- **It does not establish standing.** Descent is self-asserted. Anyone may\n  publish a `9908` naming any parent, including in bad faith to attach\n  themselves to a community they have nothing to do with. The check verifies\n  that a declared delta matches the documents; it says nothing about whether\n  the declarer had any right to fork. That follows from a fork needing nobody's\n  permission and is a boundary rather than an oversight, but it is stated here\n  so that it is not mistaken for one.\n- **A declaration may not reach the parent.** The `9908` above carries an `h`\n  tag naming the parent group. Where that group sets `restricted`, only members\n  may write to it, so the fork most worth declaring, one made by people who\n  have been removed, is the one least able to publish there. A declaration\n  SHOULD therefore be published to the child group, MAY additionally be\n  published to the parent where write access survives, and clients SHOULD\n  discover forks through `forked-from` on the child rather than by reading the\n  parent's timeline.\n- **Relays that whitelist kinds will drop both events.** Nothing here asks a\n  relay to enforce anything, but a relay accepting only known kinds will\n  discard `39100` and `9908` silently. That is a deployment fact rather than a\n  conflict with NIP-29.\n\n## Reference implementation\n\n`tools/genealogy.py` in the Project Granny Bot corpus implements all of the\nabove and runs against documents on disk with no relay, agent or network. It\nships two worked forks of one parent: one declaring falsely on purpose, whose\nthree false declarations are all reported, and one declaring truthfully, so that\nthe sibling case shows one caught and one clean with neither ranked. 35 selftest\nprobes, including the permutation invariant.\n\nCorpus: https://solidarityworksinternational.org/projectgrannybot","sig":"12713315a9fbf60c4085ad180ac48bb01a1182aaf65f0074d798460666c488ccf32f11920c955f3beba7c348a5882e162ec551eaa098aa9ccb0c8e2d6b803b77"}