Context
#256 introduced a new top-level module, crates/mdk-core/src/state_validation.rs, as part of the receiver-side admin-depletion fix for marmot-security#29. It now holds helpers that reason about MLS group composition before vs. after a staged commit, including:
live_member_identities
post_commit_member_identities
validate_active_admins*
validate_admin_depletion
validate_admin_invariant_after_commit
The module's docs intentionally define this validation in terms of Nostr identities represented by live MLS leaves, not raw MLS leaf count. Multiple MLS leaves may legitimately carry the same Nostr identity, so identity-set deduplication is expected and must be preserved. The public get_members(group_id) API remains the storage-loading wrapper for callers that do not already hold an MlsGroup.
This issue tracks completing that cleanup by moving the remaining MLS group-state and group-evolution validators out of message/group operation modules and into state_validation.rs.
Goal
Make validation easier to reason about by formalizing this split:
crates/mdk-core/src/messages/validation.rs: Nostr transport, event-shape, timestamp, tag, and event-author validation.
crates/mdk-core/src/state_validation.rs: MLS group-state, staged-commit, authorization, identity-in-leaf, admin-identity, group-creation, and group-update invariants.
crates/mdk-core/src/groups.rs: group operations and storage orchestration, calling shared state validators rather than owning bespoke validation logic.
This should be a pure organization/refactor PR. The point is to make invariants discoverable and reusable, not to change protocol behavior.
Move from messages/validation.rs to state_validation.rs
Move the associated unit tests for these functions alongside them where practical.
Move from groups.rs to state_validation.rs
Move or update the associated unit tests so they continue to cover the same behavior after relocation.
Leave in messages/validation.rs
These are Nostr transport / event-shape concerns and should stay put:
verify_rumor_author
validate_event / validate_event_at
validate_created_at_with_now
extract_nostr_group_id
After moving the MLS-state helpers, update the module-level docs so this file clearly describes Nostr/message validation rather than MLS state validation.
Leave in groups.rs
Keep group operations, storage writes, MLS commit creation/merge orchestration, and general group query helpers in groups.rs. It is fine for those methods to call validators from state_validation.rs.
Do not move general identity extraction helpers such as pubkey_from_credential, pubkey_for_leaf_node, pubkey_for_member, or get_own_pubkey unless doing so clearly reduces coupling without widening the behavioral scope.
Constraints / non-goals
Acceptance criteria
Context
#256 introduced a new top-level module,
crates/mdk-core/src/state_validation.rs, as part of the receiver-side admin-depletion fix formarmot-security#29. It now holds helpers that reason about MLS group composition before vs. after a staged commit, including:live_member_identitiespost_commit_member_identitiesvalidate_active_admins*validate_admin_depletionvalidate_admin_invariant_after_commitThe module's docs intentionally define this validation in terms of Nostr identities represented by live MLS leaves, not raw MLS leaf count. Multiple MLS leaves may legitimately carry the same Nostr identity, so identity-set deduplication is expected and must be preserved. The public
get_members(group_id)API remains the storage-loading wrapper for callers that do not already hold anMlsGroup.This issue tracks completing that cleanup by moving the remaining MLS group-state and group-evolution validators out of message/group operation modules and into
state_validation.rs.Goal
Make validation easier to reason about by formalizing this split:
crates/mdk-core/src/messages/validation.rs: Nostr transport, event-shape, timestamp, tag, and event-author validation.crates/mdk-core/src/state_validation.rs: MLS group-state, staged-commit, authorization, identity-in-leaf, admin-identity, group-creation, and group-update invariants.crates/mdk-core/src/groups.rs: group operations and storage orchestration, calling shared state validators rather than owning bespoke validation logic.This should be a pure organization/refactor PR. The point is to make invariants discoverable and reusable, not to change protocol behavior.
Move from
messages/validation.rstostate_validation.rsvalidate_identity_unchangedvalidate_proposal_identityvalidate_commit_identitiesvalidate_commit_authorizationis_pure_self_update_commitis_self_remove_only_commitMove the associated unit tests for these functions alongside them where practical.
Move from
groups.rstostate_validation.rsis_leaf_node_adminvalidate_group_membersprune_and_validate_admin_updateMove or update the associated unit tests so they continue to cover the same behavior after relocation.
Leave in
messages/validation.rsThese are Nostr transport / event-shape concerns and should stay put:
verify_rumor_authorvalidate_event/validate_event_atvalidate_created_at_with_nowextract_nostr_group_idAfter moving the MLS-state helpers, update the module-level docs so this file clearly describes Nostr/message validation rather than MLS state validation.
Leave in
groups.rsKeep group operations, storage writes, MLS commit creation/merge orchestration, and general group query helpers in
groups.rs. It is fine for those methods to call validators fromstate_validation.rs.Do not move general identity extraction helpers such as
pubkey_from_credential,pubkey_for_leaf_node,pubkey_for_member, orget_own_pubkeyunless doing so clearly reduces coupling without widening the behavioral scope.Constraints / non-goals
PublicKey; do not add identity uniqueness checks.pub(crate)where cross-module callers need access, not public SDK API).validate_admin_depletionprecondition: it reads the current/pre-commit group-data extension and is only appropriate for proposal-time checks where no staged commit exists. Commit-time validation must continue to use the staged/post-commit group context.state_validation.rsin this PR unless there is a clear consensus that the churn is worth it.Acceptance criteria
messages/validation.rslive instate_validation.rs.groups.rslive instate_validation.rs.messages/validation.rscontains only Nostr/message transport validators and matching tests.groups.rsdelegates group-state validation tostate_validation.rsinstead of owning bespoke validator methods.cargo test -p mdk-corepasses.just precommitpasses.