Skip to content

feat(platform)!: elected moderation declares whether its seat is contestable (PV14) - #4969

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/seat-contestable
Sep 24, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
claude/seat-contestable

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Part of #4865 (decentralized moderation teams), resolving its open question on the contested key.

A contract that declares elected moderation now says in its declaration whether its moderation seat can be contested again once a team is seated. The moderation charters contract's electedCharter.byTargetContract index stays keyed by the target contract alone: a future challenge (after protocol version 14) will be a new contest on that same unique index, allowed only when the target declares its seat contestable. There is no round component in the key and no separate challenge document type.

The elected declaration is frozen at contract creation, so the key has to exist in 4.2 even though nothing reads it until challenges ship.

What was done?

The declaration gains seatContestable (ElectedModerators, packages/rs-dpp/src/data_contract/config/moderation/elected.rs), protocol version 14, edited in place since 14 is unreleased.

The proposed details are implemented as proposed:

  • Wire name seatContestable, a boolean, required with no default. A default of false would make every team permanent, which removes the only remedy for a leader who lost its keys or went rogue (the leader can not change). A default of true would opt every contract into challenges without asking.
  • challengeCoolDown (always required until now) is required when seatContestable is true and refused when it is false, since a cool-down means nothing for a seat that can not be contested. Its bounds (two weeks to three years) still apply to a contestable seat.
  • Frozen with the rest of the declaration: validate_config_update 2 refuses a change of the key in either direction (DataContractConfigUpdateError, 40002).
  • No behaviour change in 4.2: an electedCharter create for a target whose seat is taken is still refused, paid, with DuplicateUniqueIndexError (40105), whatever the key says.

Rust shape. The proposal left this open. The flag and the cool-down are one field, challenge_cool_down: Option<u32>. Some(x) is seatContestable: true with challengeCoolDown: x; None is seatContestable: false with no cool-down. ElectedModerators::seat_contestable() returns is_some(). I chose this over a bool beside a u32 (0 meaning none) or beside an Option<u32> because a separate flag and cool-down can disagree. The declaration reaches consensus through bincode, not serde, so a pair that disagreed would need a validation rule and an error of its own. With one Option, a declaration can not disagree with itself, whatever the encoding. The change is no larger: each struct literal changes challenge_cool_down: 1_209_600 to Some(1_209_600). The wire rule is enforced where the wire is read: the serde of ContractModerators.

Before, the only accepted shape:

{
  "$type": "elected",
  "challengeCoolDown": 1209600,
  "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] },
  "interim": { "$type": "notYetUsable" }
}

After, a contestable seat:

{
  "$type": "elected",
  "seatContestable": true,
  "challengeCoolDown": 1209600,
  "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] },
  "interim": { "$type": "notYetUsable" }
}

and a seat its first team keeps for good:

{
  "$type": "elected",
  "seatContestable": false,
  "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] },
  "interim": { "$type": "notYetUsable" }
}

Each shape, before and after:

Declaration Before After
the "before" example above (no seatContestable) accepted refused: missing field seatContestable
"seatContestable": true with "challengeCoolDown": 1209600 refused: unknown field seatContestable accepted
"seatContestable": true without challengeCoolDown refused: unknown field, and missing challengeCoolDown refused: missing field challengeCoolDown
"seatContestable": false without challengeCoolDown refused: unknown field, and missing challengeCoolDown accepted: no cool-down to bound
"seatContestable": false with "challengeCoolDown": 1209600 refused: unknown field seatContestable refused: challengeCoolDown is only valid with seatContestable: true
"seatContestable": true, "challengeCoolDown": 1209599 refused: unknown field seatContestable refused (10900, cool-down under two weeks), as a cool-down out of bounds was before
a contract update flipping seatContestable either way not expressible refused (40002), like any other change to the declaration

The Display of a declaration now names the seat: an elected moderation team, in its interim moderated by the contract owner, its seat open to a challenge 1209600 seconds after each seat change, or ..., its seat never contested once a team is seated.

Encoding. The declaration's bincode changes, as it did for electionDelay and maxAddedModerators: the cool-down becomes an Option<u32> (one tag byte more). Protocol version 14 is unreleased, so the change is in place. No pinned length or fixture moves: the wasm-dpp contract transition lengths belong to a contract without moderation, whose config encodes None for it as before, and no JSON fixture under packages/rs-drive-abci/tests/supporting_files/ carries an elected declaration (the ones there only reference elected contracts). Every Rust fixture that builds a declaration (rs-dpp, rs-drive, rs-drive-abci) now builds a contestable seat.

Clients and docs.

  • wasm-dpp2's ContractModerators TypeScript type takes the rule as a type: the elected variant is intersected with ElectedModerationSeat = { seatContestable: true; challengeCoolDown: number } | { seatContestable: false; challengeCoolDown?: never }.
  • The v14 changelog doc comment (packages/rs-platform-version/src/version/v14.rs, the elected declaration item) is extended in place.
  • The book's elected moderation section (book/src/data-model/contract-moderation.md) has a new "The seat" part, the struct and the wire example updated, and the frozen-seat note.
  • The moderation charters protocol guide (docs/protocol/moderation-charters.md) has a new paragraph on the contested key: it stays the target alone, and a challenge will be a new contest on the same index, open only against a contestable seat.

How Has This Been Tested?

New tests (rs-dpp, config/moderation/elected.rs and config/methods/validate_update/v2):

  • should_round_trip_each_value_of_seat_contestable_through_json_and_platform_value: true with the minimum and the maximum cool-down, and false; challengeCoolDown is on the wire exactly when the seat is contestable.
  • should_refuse_a_declaration_without_seat_contestable, with and without a cool-down, through JSON and platform value.
  • should_require_the_cool_down_of_a_contestable_seat_and_refuse_it_on_a_permanent_one, a zero cool-down on a permanent seat included.
  • should_bound_the_cool_down_of_a_contestable_seat_only; the existing should_accept_every_bound_and_refuse_one_second_outside_each still checks the cool-down one second outside each bound.
  • should_freeze_every_field_of_an_elected_declaration now refuses a contestable seat made permanent, and a permanent seat opened to challenges.
  • The misspelled-key and Display tests cover the new key (a non-boolean seatContestable, the key under another moderator kind).

New test (rs-drive-abci, contract_user_moderation/tests/seated_team.rs): should_keep_the_seat_of_a_seated_team_whether_or_not_it_is_contestable. For each seat kind, a contest is awarded, then a rival files a proposal and an elected charter for the same target. The charter is refused, paid, with DuplicateUniqueIndexError (40105), and the seated charter still holds the seat. The pipeline tests in contract_user_moderation/tests.rs also accept a contract create with a permanent seat, and refuse an update that makes the seat permanent.

Run locally:

  • cargo test -p dpp --lib -- data_contract::config data_contract::document_type::property: 573 passed
  • cargo test -p drive-abci --lib -- contract_user_moderation charter_election_tests contract_moderation_gate election elected owner_protected: 91 passed
  • cargo test -p drive --lib -- fetch_charter_election_windows: 4 passed
  • cargo test -p platform-version --lib: 23 passed
  • cargo check -p wasm-dpp2 --target wasm32-unknown-unknown: clean
  • cargo clippy -p dpp -p drive -p drive-abci --all-targets --all-features -- --no-deps -D warnings: clean
  • cargo fmt --all: clean

Not run locally: the full workspace suites (CI runs them) and the nightly-only wasm-dpp JS tests. Their pinned contract transition lengths do not move, since that contract declares no moderation.

Breaking Changes

Consensus-breaking for protocol version 14 (unreleased): the elected moderation declaration's bincode encoding changes (the cool-down becomes optional), and a declaration without seatContestable no longer parses from JSON or a platform value. No released protocol version is affected, since elected moderation exists only from 14.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 5077904

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build running
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

…estable (PV14)

An elected moderation declaration now says whether its seat can be contested
again once a team is seated: `seatContestable`, a boolean, required with no
default and frozen with the rest of the declaration. `challengeCoolDown` is
required when it is true and refused when it is false. In Rust the two are one
field, `challenge_cool_down: Option<u32>`, so a declaration can not disagree
with itself whatever its encoding.

The moderation charters contract's `electedCharter.byTargetContract` index
stays keyed by the target contract alone: a future challenge is a new contest
on that index, allowed only against a contestable seat. Nothing reads the key
in protocol version 14, where a seated charter keeps the seat either way
(another charter for the target is refused with 40105).

Protocol version 14 is unreleased, so the declaration's encoding changes in
place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 19 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 407d7662-67fd-4864-b43f-95dbefbf585f

📥 Commits

Reviewing files that changed from the base of the PR and between 68aa586 and 5077904.

📒 Files selected for processing (14)
  • book/src/data-model/contract-moderation.md
  • docs/protocol/moderation-charters.md
  • packages/rs-dpp/src/data_contract/config/methods/validate_update/v2/mod.rs
  • packages/rs-dpp/src/data_contract/config/moderation/elected.rs
  • packages/rs-dpp/src/data_contract/config/moderation/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/creation.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/contract_moderation_gate/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/contract_user_moderation/tests.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/contract_user_moderation/tests/seated_team.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/masternode_vote/charter_election_tests.rs
  • packages/rs-drive/src/drive/document/insert_contested/fetch_charter_election_windows/v0/mod.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp2/src/data_contract/model.rs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-09-24T06:33:59.889Z

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 24, 2026
@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 24, 2026
@thepastaclaw

thepastaclaw commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 3rd in line, estimated start in ~1.0 h (commit 5077904)
Estimated review time once started: ~40 min (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

@QuantumExplorer
QuantumExplorer merged commit 5bf351d into v4.2-dev Sep 24, 2026
43 of 44 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/seat-contestable branch September 24, 2026 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants