feat(platform)!: elected moderation teams moderate from their stored charter - #4952
Conversation
…charter (moderation teams D74 to D77) Seating writes nothing: the charter seated on an elected contract is the electedCharter the moderation charters contract's byTargetContract index finds, and the moderation paths read it (billed document queries): - once a charter is seated only its leader and active members moderate (interim moderators get 41101), with the abilities the declaration gives the team (new 41201), and they are protected (41102); - a notYetUsable interim stops blocking the moderated types; - an addedModerator past the target's maxAddedModerators is refused, paid (new 41202), by an inline hook in the batch state validation; - an action fee agreement may name the seated proposal's moderatorsShare of the declared moderators part (rounded down) and is charged that; any other lower amount, or one with no seated charter, is refused (new 40139); the declared amount reads no charter; - the interim team's claim of the moderators pot is refused once a charter is seated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (4)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughElected moderation now uses a seated charter’s team for moderation authority and protection. The changes add state validation for moderator additions and action-fee discounts, introduce related consensus errors, and update tests and documentation. ChangesElected moderation seating
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~60 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant BatchTransformer
participant SeatedModerationCharter
participant Drive
participant ContractUserModeration
BatchTransformer->>SeatedModerationCharter: fetch charter for target contract
SeatedModerationCharter->>Drive: query elected charter
Drive-->>SeatedModerationCharter: return charter or no result
SeatedModerationCharter-->>BatchTransformer: return seated charter
ContractUserModeration->>SeatedModerationCharter: check team membership
SeatedModerationCharter->>Drive: query addition and removal documents
Suggested reviewers: Merge Risk: ⚪ Minimal · up to No actionable regression is identified in the supplied changes; the PR is mergeable after normal checks. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 74.04% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 104 functions across 31 files. (2 skipped: 2 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-24T04:18:19.798Z |
|
🕓 Queued for automated review — 6th in line, estimated start in ~1.4 h (commit 6ead015)
|
…in the contract crate #4898 reshaped the charter contract's single `charter` type into seven types, but the crate's `v1::document_types` constants and its schema test still described the old type, so `should_load_the_schema_at_the_latest_platform_version` fails on v4.2-dev and stops the Rust workspace tests of every pull request. The constants now name each type's properties and indexes, and the test checks all seven types, that there are no others, and that only the elected charter's index is contested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the contract fee claim bills the contract fetch from the fee the fetch returns, not the cache-dependent one a fetch info carries, with a warm versus cold cache regression test; - a submittedCharter create is judged by the proposal's own rules (reward split sums to 100, description byte cap) in document create structure validation 1, since seating runs nothing, so the mempool refuses too; - the seated share is read from the proposal's moderatorsShare alone, and only for a transform result without errors; - the unused `declared_action_fee` accessor is gone; - the addition cap being judged in the block only, and the dpp helpers describing the interim only, are documented; - tests: a seated team restoring and refused a restore, the owner flag under a seated team, a discount on an unmoderated type and on a fee priced by the multiplier, two additions in one batch, an addition at protocol version 13, and a proposal with a bad split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ration-seating-reads # Conflicts: # docs/protocol/moderation-charters.md
…ration-seating-reads # Conflicts: # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/contract_fee_claim/state/v0/mod.rs
… to their own change The submittedCharter create no longer runs validate_submitted_charter in document create structure validation 1; that check is being handled separately. The structure validation is back to what v4.2-dev has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ration-seating-reads # Conflicts: # docs/protocol/moderation-charters.md # packages/moderation-charters-contract/README.md # packages/rs-dpp/src/moderation_charter/mod.rs # packages/rs-platform-version/src/version/v14.rs
…ration-seating-reads # Conflicts: # docs/protocol/moderation-charters.md # packages/moderation-charters-contract/README.md # packages/rs-dpp/src/moderation_charter/mod.rs # packages/rs-platform-version/src/version/v14.rs
Issue being fixed or feature implemented
The seating step of the decentralized moderation teams plan (#4865), as decided on 2026-09-24: seating as reads, resignations as requests, interim moderators stopping at the award, and discounted fees checked only below the full price. Until now an awarded
electedCharterwas stored by the moderation charters contract but seated nobody: an elected contract stayed with its interim moderators forever, anotYetUsablecontract's moderated types stayed blocked, nothing counted additions againstmaxAddedModerators, and a charter'smoderatorsSharepriced nothing.Seating stays a read. Nothing is written when a contest is awarded: the charter seated on a contract is its
electedCharterin the charter contract's storage, found through the contested unique indexbyTargetContract(only a contest's winner is ever written there, and in protocol version 14 a seat is never replaced). Its team is the leader (the charter's owner) plus itsmembersand thememberIdof everyaddedModeratorfor it, less thememberIdof everyremovedModeratorfor it.What was done?
One reader, in drive-abci (
execution/validation/state_transition/common/seated_moderation_charter):fetch_seated_moderation_charter(target): one query ofelectedCharter.byTargetContract.SeatedModerationCharter::seats(identity): whether the identity is on the team, without listing the team. The leader costs nothing more, an elected member one point read ofremovedModerator, anyone else one point read ofaddedModeratorand, when there is one, ofremovedModerator(both are unique onelectedCharterId,memberId).fetch_moderators_share(): thesubmittedCharterby id, itsmoderatorsShare(absent is 100).count_added_moderators(charter, up_to): the additions, limited to the cap.The charter contract comes from the system contract cache, and every read is a document query whose processing cost is billed to the transition (never a
DataContractFetchInfo::fee). The reader is only reached for a contract with an elected declaration, which exists from protocol version 14 only, so it carries no version of its own, likefetch_document_through_lookup.The seated team moderates:
ContractUserModerationstate v0 resolves who moderates (Moderators::{Declared, Seated}). An elected contract with no seated charter keeps its interim rules exactly as before. Once a charter is seated, only the leader and the active members moderate. The interim moderators, the owner among them, are refused with 41101.Before, the owner of a contract with an
AppointedModeratorsorContractOwnerinterim bans after the award:After:
What the interim did stands (bans, suspensions, warnings, removals), and the team may lift it.
Abilities: the seated team holds the abilities the contract's declaration gives it and no others (now enforced, since no team acted before). A deletion or a restore needs
deleteDocumentson the document type. A ban, a suspension or a warning, or lifting one, needs that ability on some moderated type, since the lists are contract-wide. The interim keeps every ability the contract backs, as before. NewContractModerationAbilityNotGrantedError(41201):Protection (41102): once a charter is seated, the leader and the active members are protected from a ban, a suspension, a warning and the deletion of their documents, and the owner too when the declaration sets
ownerProtected. The interim moderators lose the protection. Before: an elected member could be banned by the interim owner. After:Ban { identity_id: <elected member> }by the leader returns 41102, and the same ban of an interim moderator executes.The interim block ends:
contract_moderation_gatev0 reads whether a charter is seated, billed, only when a transition of the batch is on a type anotYetUsableinterim blocks.The cap on additions: an
addedModeratorcreate is refused, paid, when the charter already has the target'smaxAddedModeratorsadditions (ever filed, so a removal frees no slot). NewModerationCharterAddedModeratorLimitReachedError(41202). It is one inline hook in the batch'svalidate_statev0 loop, after the create's own state validation passed (its references proved the charter is seated, the writer is the leader and the member asked to join). It reads the charter by id, its target contract (billing the feeget_contract_with_fetch_info_and_feereturns) and at most the cap's number of additions. Additions an earlier create of the same batch was accepted for count too; that half is dormant whilemax_transitions_in_documents_batchis 1. WithmaxAddedModerators: 2:Discounted fees: a document action on a type an elected contract moderates whose
$actionFeeAgreementnames the full declaredmoderatorsamount is accepted without reading the charter and charged that amount, as today. One naming less (with the declared owner part and pricing) is checked against the seated charter. It must name exactly the proposal'smoderatorsShareof the declared amount, and is charged the agreed amount, which is what the moderators pot receives. With no seated charter, any lower amount is refused.seated_moderators_shares).validate_action_fee_agreementsjudges it from the action alone. It runs in advanced structure validation and on every recheck, which transforms anew.DeclaredDocumentActionFee::agreed_feeis whatresolved_action_feescharges.DocumentActionFeeModeratorsShareMismatchError(40139) names the share, or says no charter is seated.Rounding (the open detail on the issue): the share is applied to the declared amount and rounded down to the credit,
declared * share / 100(moderation_charter::moderators_share_of). The team never charges a fraction of a credit more than its share, and a signer computes the one admissible amount with integer arithmetic. The alternative, admitting only shares that divide exactly, would make most shares unusable on odd amounts.Declared post creation fee
{ owner: 10000000, moderators: 100000000 }, seated proposalmoderatorsShare: 60:The interim's claim: the interim team's
ContractFeeClaimof the moderators pot is refused once a charter is seated (41113). The pot carries over to the seated team, unsettled, as it does under an interim that names nobody. The seated team's claim, split byrewardSplit, comes with the reward split work, which also changes what the claim's proof covers. Before: the owner of acontractOwner-interim contract claims the moderators pot after the award. After: 41113ContractFeeClaimNotAllowedError, and the pot keeps its credits.rs-dpp: three errors appended to
StateError(discriminants 147 to 149, the frozen test pins the new tail),ElectedModerators::allows_on_any_type,DocumentActionFeeAgreement::discounts_moderators_of,moderation_charter::moderators_share_of, doc updates onContractModerators::{may_moderate, protects, interim_blocks_document_type}andContractFeePot::recipients. wasm-dpp maps the three errors generically.Docs: book
contract-moderation.md(the seated team, errors, what comes next),fees/overview.md(the discount),error-codes.md,docs/protocol/moderation-charters.md(a Seating section), the charter contract README and the v14 changelog item 40.The claim's contract fetch billing is now #4954's, merged into this branch from v4.2-dev. This PR only adds the refusal after a seat to that function.
CI fix carried here: v4.2-dev's Rust workspace tests fail on
moderation-charters-contract::should_load_the_schema_at_the_latest_platform_version. #4898 split the singlecharterdocument type into seven, but the crate'sv1::document_typesconstants and that test still describe the old type. Because nextest stops at the first failure, no PR gets a full workspace run. The second commit updates the constants to the seven types and their indexes, and the test now checks all seven.Not in this PR: the seated team's claim with the three-part split and action counters, forced settles before a membership change, the reason check (every action naming a reason the seated proposal lists), and the election parameters read from the target (the windows and the application fund). The proposal's own rules now live in the charter schema: the description's byte cap is
maxBytes(#4957), and the reward split's sum is apropertyConstraintsrule (#4962). The addition cap is judged in the block and not in the mempool, which runs no state validation for a batch, like a unique index conflict.In-place changes to shipped generations
validate_statev0 (drive-abci, selected by every protocol version): the newAddedModeratorCaphook acts only on a create whose contract is the moderation charters system contract (MODERATION_CHARTERS_CONTRACT_ID) and whose type isaddedModerator. That contract is in state from protocol version 14 only (genesis v1 behind the 14 branch, ortransition_to_version_14). A create against a contract that is not in state fails in the batch transformer before this loop. So no batch of protocol version 13 or earlier reaches the hook, and for every such batch the loop does what it did. The same argument as theIndexOnlyBatchEntriestracker beside it.BatchTransitionActionV0::validate_action_fee_agreements,BatchTransitionAction::declared_action_fees,DeclaredDocumentActionFee(rs-drive, unversioned): the new branches apply only to a transition whose document type declaresactionFees, a v3 meta-schema keyword no protocol version before 14 admits. Before 14 every base action hasdeclared_action_fee: None, so both methods return exactly what they did.Every other edited generation is selected by protocol version 14 alone:
ContractUserModerationstate v0,ContractFeeClaimstate v0,contract_moderation_gatev0 (Nonebefore 14) and batchtransform_into_actionv2. No version table moves.How Has This Been Tested?
End to end in drive-abci (
contract_user_moderation/tests/seated_team.rs). Each test files a proposal and an elected charter through the pipeline (join requests written to Drive), then awards the contest by ending its vote poll at the join window's end:should_seat_the_winner_of_the_contest_and_let_its_team_moderate_instead_of_the_interimshould_follow_additions_and_removals_and_protect_the_teamshould_cap_the_members_a_leader_adds_and_free_no_slot_on_a_removalshould_refuse_a_seated_team_an_ability_the_declaration_does_not_give_itshould_end_the_interim_block_once_a_charter_is_seatedshould_charge_the_declared_moderators_part_without_a_read_and_a_discount_with_oneshould_refuse_any_discount_but_the_seated_charters_share_and_agree_on_recheckshould_stop_the_interim_team_claiming_the_moderators_pot_once_a_charter_is_seatedrs-dpp unit tests:
should_take_the_share_of_the_declared_moderators_part_rounded_down,should_ask_for_a_discount_only_below_the_declared_moderators_part,should_give_a_seated_team_the_abilities_of_any_moderated_type_on_the_lists_only, and the frozenStateErrordiscriminants.Run locally (targeted, as the conventions ask; CI runs the rest):
cargo test -p drive-abci --lib -- contract_user_moderation contract_fee_claim action_fee contract_moderation_gate check_tx_verification gas_sponsorship lookup_reference list_element_reference owner_reference reference_expression index_only: 198 passed, 1 failed. The failure was the gate's own read count inshould_block_the_moderated_types_of_an_elected_contract_in_its_interim. The interim block now reads the charter contract once before it refuses, so the test now expects one more billed read: 2 with a type it lets through, 1 for the moderated type alone.cargo test -p drive-abci --lib -- contract_moderation_gate seated_team: 12 passed.cargo test -p drive --lib state_transition_action: 1158 passed.cargo test -p dpp --lib --all-featuresfor the charter, agreement, elected declaration andStateErrortests: 29 passed.cargo clippy -p dpp -p drive -p drive-abci --all-targets --all-features -- -D warnings: clean.cargo fmt --all: clean.cargo check -p wasm-dpp(the three errors are mapped there): compiles.cargo test -p drive-abci --lib -- seated_team contract_moderation_gate contract_fee_claim contract_user_moderation action_fee: 109 passed (14 seated-team tests). The create-structure, encryptedFor and reference tests: 70 passed.cargo test -p drive --lib state_transition_action: 1158 passed. Clippy is clean again.The fee test checks "no charter read" exactly. From the same seated state, the same post (same document, same nonce) is created once agreeing to the declared moderators part and once to the charter's share. The storage fees are equal, and the processing fees differ by exactly the cost of
fetch_seated_moderation_charterplusfetch_moderators_share, measured on their own in a transaction (29,500 + 11,200 = 40,700 credits). Comparing the full-amount action before and after the award does not work: the award removes the contest's prefunded balance from the same sum tree the fee pots live in, which moves the pot write's cost by about 1,000 credits.Breaking Changes
Consensus-breaking for protocol version 14 (unreleased): the moderation transition, the batch gate, the fee agreement and the claim decide differently once a charter is seated, and three consensus errors are appended (40139, 41201, 41202). Nothing changes at protocol version 13 or earlier.
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull request (no structure change)For repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit