## Purpose Defines anonymous-communication-friendly, client-enforced moderation for darkirc public channels: channel registration and owner-signed action chains in the static DAG, a hardcoded policy model with owner-set defaults and local user overrides, admin hide actions in the rotating DAG, and the ChanServ command interface used by channel owners and admins. ## ADDED Requirements ### Requirement: Public channel registration A user SHALL be able to register a public `#` channel by publishing a registration event to the static DAG that names the channel and embeds an owner public key. Registration is first-come-first-served: when two valid registrations for the same name exist, every node MUST resolve the same winner using deterministic canonical ordering of the static DAG. Only public channels are supported in this version; channels requiring decryption (saltbox channels and direct messages) are out of scope. #### Scenario: Register a channel - **WHEN** a user publishes a signed registration for a channel that has no valid registration - **THEN** every synced node resolves that user's owner key as the channel owner #### Scenario: Registration race resolves identically everywhere - **WHEN** two registrations for the same channel name are published before either node sees the other - **THEN** all nodes that eventually hold both events pick the same registration as authoritative ### Requirement: Owner action chain Ownership-changing and policy-defining actions SHALL form a per-channel chain: each action names the previous action's static event id and is signed by the current owner key. Resolution MUST yield exactly one authoritative chain per channel: the longest chain of correctly linked, correctly signed actions starting from the winning registration, with deterministic tie-breaking. A transfer of ownership makes subsequent actions valid only under the new key. #### Scenario: Transfer changes signing authority - **WHEN** an owner publishes a transfer to a new key and the new key later signs a policy update - **THEN** all nodes accept the policy update and reject any further action signed by the old key #### Scenario: Invalid signature cannot extend the chain - **WHEN** an action is signed by a key that is not the current owner - **THEN** the action is ignored during resolution and the previous chain tip remains authoritative #### Scenario: Competing chains resolve deterministically - **WHEN** two valid chains exist for one channel (e.g. after key compromise) - **THEN** every node selects the same chain by longest-valid-chain rule with canonical ordering as tie-break ### Requirement: Hardcoded policy enum and default policy list Policies SHALL be identified by a `u8` enum from a hardcoded, shared registry (initially: posting allow-list, admin hide set, regex filter). The owner SHALL be able to publish, as a chain action, the channel's default policy list: for each policy, its id, opaque parameters, and a default enabled flag. The serialized format MUST allow future policy ids and parameter shapes without changing stored history. #### Scenario: Publish a default policy list - **WHEN** an owner publishes a policy list naming the admin hide policy with a set of admin public keys, enabled by default - **THEN** synced nodes resolving the channel expose that policy with those parameters as the channel default #### Scenario: Unknown policy id in a list - **WHEN** a resolved policy list contains an id the client does not know - **THEN** the client ignores that policy entry without failing resolution of the rest of the list ### Requirement: Admin hide actions in the rotating DAG A key named in an enabled admin hide policy's parameters SHALL be able to publish signed hide or unhide actions as rotating-DAG events referencing the event id of a target message. Clients MUST mark matching messages as hidden rather than removing them from storage; hidden state MUST be reversible in the UI (e.g. reveal control). Resolution MUST apply the last action per target in canonical rotating-DAG order across the retained window. Hide actions expire with the rotation window exactly as messages do; no permanent record of a hide is created. #### Scenario: Hide is applied on other nodes - **WHEN** an admin publishes a hide action for a message in a channel a user has joined - **THEN** the user's client marks that message hidden and indicates that hidden messages exist #### Scenario: Unhide reverses a hide - **WHEN** a later action by an authorized key unhides the same target - **THEN** the message is shown again for clients applying policy #### Scenario: Unauthorized hide ignored - **WHEN** a hide action is signed by a key not in the channel's admin hide set, or the admin hide policy is disabled - **THEN** clients ignore the action ### Requirement: Posting allow-list policy When the posting allow-list policy is enabled for a channel, clients SHALL render only messages that carry a valid signature by a key in the policy's parameter set. Compliant senders in such channels attach their signer public key and signature to the message. Enforcement is client-side: unsigned messages still propagate on the network but are hidden for clients applying the policy. #### Scenario: Signed message renders - **WHEN** a message in an allow-listed channel carries a valid signature from an allowed key - **THEN** clients applying the policy render it #### Scenario: Unsigned message hidden - **WHEN** a message in an allow-listed channel lacks a signature or is signed by a key outside the set - **THEN** clients applying the policy hide it ### Requirement: Regex filter policy When the regex filter policy is enabled, clients SHALL hide messages whose decoded privmsg matches the filter's parameter rules. Rules are regular expressions evaluated against the privmsg, and MAY match the nick and/or the message content. Filter parameters are opaque to the wire format; invalid or non-compilable rules MUST be ignored during resolution without failing evaluation of the remaining rules. #### Scenario: Matching message hidden - **WHEN** a message whose nick or content matches an enabled filter's regex rules arrives in a joined channel - **THEN** clients applying the policy hide it #### Scenario: Invalid rule ignored - **WHEN** a filter's parameters contain a rule that fails to compile - **THEN** clients skip that rule and evaluate the remaining rules #### Scenario: Encrypted messages filtered after decryption - **WHEN** an encrypted message matching the rules is decrypted locally - **THEN** the filter applies to the decoded plaintext identically to plaintext-channel messages ### Requirement: Local policy overrides A user SHALL be able to override the enabled flag of any policy in a channel's default list locally, per channel, without publishing anything. The initial app UI exposes enable/disable of provided policies only; adding user-defined policies is out of scope for this version. #### Scenario: Override a default - **WHEN** a channel's policy is enabled by owner default and the user disables it locally - **THEN** that user's client applies the policy as disabled while other users remain unaffected #### Scenario: Policy overlay from the channel label - **WHEN** the user taps the channel-name label shown at the top of a channel's chat screen - **THEN** the app opens an overlay for that channel listing the owner's current default policies with their default states, and toggling a policy in the overlay changes the user's local override and re-filters the channel view accordingly ### Requirement: Owner-signed pins with encrypted snapshots A channel owner SHALL be able to pin a message by publishing a chain action that embeds a snapshot of the message content and references its event id. For a channel with a shared key, the snapshot MUST be encrypted under that channel key so the static DAG never carries its plaintext. Pins MUST remain renderable after the original message has rotated out of the window. #### Scenario: Pin outlives rotation - **WHEN** a pinned message's DAG window has expired - **THEN** clients can still render the pinned snapshot from the static DAG #### Scenario: Encrypted channel pin carries no plaintext - **WHEN** an owner pins a message in a saltbox-encrypted channel - **THEN** the static-DAG event contains only ciphertext decryptable by channel members ### Requirement: ChanServ command interface The `irc2` stack SHALL provide a ChanServ service addressed by IRC private message, mirroring the existing NickServ pattern, with commands for registration, info, ownership transfer, policy management, pinning, and hiding. Policy commands SHALL address policies by their registry name (e.g. `FILTER`), parsed to the hardcoded policy id; unknown names are rejected. Owner and admin actions MUST be authenticated by verifying the signing key configured locally; the service MUST refuse actions for which no matching key is configured. #### Scenario: Register via ChanServ - **WHEN** a user with an owner key configured sends the registration command for an unregistered channel - **THEN** the registration event is signed with that key and published to the static DAG #### Scenario: Policy addressed by name - **WHEN** an owner issues a policy command using a policy's name (e.g. `FILTER`) and valid parameters - **THEN** the command applies to that policy's entry in the channel's default list #### Scenario: Unknown policy name rejected - **WHEN** a policy command names a policy the client does not know - **THEN** ChanServ replies with an error and the policy list is unchanged #### Scenario: Action without key refused - **WHEN** a user issues an owner or admin command without the corresponding key configured - **THEN** ChanServ replies with an error and publishes nothing ### Requirement: Signing keys are node-local secrets Owner and admin schnorr keypairs SHALL be provisioned in node configuration (desktop TOML config; app settings store) and MUST NOT be transmitted or published; only public keys appear in DAG events. Signatures apply to action and message content only. #### Scenario: Secret never leaves the node - **WHEN** any owner or admin action is published - **THEN** the corresponding event contains only the public key and signature ### Requirement: RLN semantics unchanged Moderation MUST NOT alter RLN rate-limiting or anonymity: hide actions and signed messages on RLN-enabled networks are ordinary rotating events subject to the same proof requirements as chat messages, and no policy data links an RLN identity to a signing key. #### Scenario: Hide action is rate-limited - **WHEN** an admin publishes a hide action on an RLN-enabled network - **THEN** the event is admitted under the same RLN proof rules as any other rotating event