Repository navigation
feat(dpp)!: $moderatedAt and $moderatedBy record the last moderator's write (PV14) - #5161
Conversation
…m (PV14) A document type's moderator abilities become one object, `moderatorAbilities` (meta-schema v3): `delete` and `deleteWithin` replace `canBeDeletedByModerators` and `canBeDeletedByModeratorsFor`, and `changeFields` names top-level properties only the contract's moderators write. A report can then be marked handled instead of deleted. - `ContractUserModeration` gains `ChangeDocumentFields`: a moderator sets or removes those fields on any document of the type. The changed document is judged as a replace judges one (schema, propertyConstraints with their totals, distinctFrom, encryptedFor shapes, unique indexes) and stored with the replace's update, `$revision` one higher and `$updatedAt` untouched. Its references are not checked again, so the parser keeps references, their sources, generated fields, required, transient, immutable and contested-index properties out of the list. Proved by the document. - A document's owner sets, changes or removes such a field in a create or a replace only when it moderates the contract (41124). A seated team needs the new `changeDocumentFields` ability on the type. - A type listing fields keeps `$revision` even when `documentsMutable` is false; the whole object is fixed with the type. - New errors 10905, 41123 and 41124; `validate_restored_document_uniqueness` becomes `validate_moderated_document_uniqueness` with the changed fields. - rs-sdk `change_contract_document_fields`, wasm-sdk `contractChangeDocumentFields`, js-evo-sdk `moderatorChangeDocumentFields`, wasm-dpp2 action parts; book page "Moderator Abilities". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilities docs - wasm-dpp2 `ContractUserModeration.fields` returns `null` for a field the change removes (the property conversion read it as `undefined`), and integers as bigints like `Document.properties`; the spec expects both. - Book: `deleteWithin` only beside `delete: true`; 41203 in the changeFields errors; `$revision` on types keeping moderator fields in System Properties and Mutability; the `$createdAt` fallback of `deleteWithin`; field changes in the no-list case and the elected ability, reason and action-share summaries; BasicError range to 10949. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efund the owner (PV14) Two new keys beside `moderatorAbilities.delete`, both frozen with the type: - `deleteKeepsRecord` (default true). With false a deletion writes no removal record and the type gets no records tree; the removals query refuses the type, a restore is refused (41119) before any read, and the deletion is proved by the document's absence. rs-sdk `delete_contract_document` now returns `Option<ContractDocumentRemoval>` and wasm-sdk `contractDeleteDocument` resolves `undefined` for such a type. - `deleteRefundsOwner` (default false). With true the owner is refunded its storage as for its own deletion (the batch omits `ForfeitStorageRefunds`). Review fixes to the moderator fields in the same change: - a field change that changes nothing is refused (10905), and field changes no longer count toward a seated team's action share; - the create/replace gate on moderator fields moved from batch state validation into transformer v0, so check_tx refuses 41124 too; - an elected declaration must give `changeDocumentFields` on every type that lists `changeFields` (10900); - the deletion verifier needs the contract only for a type that keeps no records; - wasm-dpp2/wasm-sdk read `fields` through js_value_to_platform_value, so a BigInt stays an integer; - `SingleDocumentDriveQuery::latest_not_contested` shared by the prover and verifier, `top_level_property` shared by the parser and the reference rules, imports moved to the top; - lookup and list-element refusals give a hint that fits moderator fields; - the field change runs its schema, distinctFrom and encryptedFor checks one after another, stopping at the first refusal; - tests: no-op change, ttl and propertyConstraints refusals, a contract whose config forbids deletion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… write (PV14) Documents of a type that keeps fields for its moderators (`moderatorAbilities.changeFields`) record who last wrote those fields and when: - a `changeDocumentFields` stamps the document with the block time and the signer; - a create or replace by an owner who moderates the contract and writes such a field stamps the owner (the create action derives it from the fields it sets; the batch transformer stamps the replace action, which otherwise carries the stored stamp over, boxed); - transfers, purchases, price updates and restores keep the stamp. Document serialization format 3 (protocol version 14) records the two behind bits 512 and 1024 of its time field flags, so a document without them is written as before. Parser generation 3 lets an index name them on such a type, never in a unique index (10231). Index keys, query values, Drive size estimates and the query layer know both names. The field change's proof must name the signer as `$moderatedBy`. wasm-dpp2 Document gains `moderatedAt` / `moderatedBy`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 106 files, which is 6 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (106)
You can disable this status message by setting the 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 |
|
🔍 Review in progress — actively reviewing now (commit 5c4db3c) · triage: critical |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-29T03:52:07.530Z |
Review fixes for the moderation stamp: - the batch transformer stamps a moderator's create through a new `DocumentCreateTransitionActionV0::moderated` flag, as it stamps the replace; the conversion no longer infers it from the fields set. The create action's `should_store_contest_info` is boxed to keep `BatchedTransitionAction` under clippy's large-variant limit; - the replace action carries `moderated_at` and `moderated_by` as plain fields, each as stored, instead of a boxed pair that dropped a half; - the field change's proof no longer compares `$moderatedBy`: a later moderator's write to another field moves it without undoing the change; - `serialize_specific_version` refuses a stamped document in formats 0 to 2 instead of dropping the stamp; - tests: the stamp survives a transfer and a delete then restore, an unmoderated report stays unstamped, and test names start with should. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the field change proof compares each value in the form the document type stores it (a whole number set on a `number` property reads back as a float); - a type that requires a transient property can not list `moderatorAbilities.changeFields` (10231): the value is never stored, so every moderator's change would fail the schema; - field values read from an object or JSON are put in a canonical form (`ContractUserModerationAction::canonical_field_value`), and rs-sdk and wasm-dpp2 build them in it, so a transition read back signs the same bytes; - wasm-dpp2 `fields`: an `undefined` value is left out rather than removing the field, and the getter defines own properties, so a field named `__proto__` stays a field; - the no-record deletion verifier keeps the contract lookup's answer and reports a failed lookup, or a missing contract or type, when no record proves the deletion either; - the batch transformer returns before walking the fields of a type that keeps no moderator fields; - the version table comment names `validate_moderated_document_uniqueness`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilities-771d75' into claude/moderated-at-by-system-fields # Conflicts: # packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/moderator_abilities_tests.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/transformer/v0/moderator_fields.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/contract_user_moderation/tests/moderator_fields.rs
…oupings-93dcc1 Brings in $moderatedAt/$moderatedBy (#5161) and skipIfAbsent at any position (#5162). Conflicts resolved: - try_from_schema common: resolve the integerRange key type and the skipIfAbsent set on every parse. - insert/delete walkers v2: import both bucket_index_keys and level_reaches_entry. - update walker v1: the bucketed set diff keeps skipIfAbsent's empty side for a version the index skips, for time and integer grids. - layout: skip notes read the bucketing source. - indexOnly synthesis: upstream's index_admissible_for_query already keeps bucketed indexes off the terminal route. - v14 notes: skipIfAbsent 59 (it shared 58 with $moderatedAt), integer-range indexes 60. Adds an e2e test of a listing moving into and out of every window of a skipIfAbsent integer-range index, and book text for the combination. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Basic explanation
What this does: When a moderator writes the fields a document type keeps for its moderators (the
changeFieldsof #5158), the document now records who did it and when, in two new system properties:$moderatedAt(the block time) and$moderatedBy(the moderator's identity). They always hold the last moderator's write. Applications can index them, for example to list the reports a given moderator handled, newest first.Value: Yappr (and any app with reports, flags or tickets) can show "handled by X at Y" and build a moderator's history from the documents themselves, without a separate log and without trusting the client.
Risks: Consensus change at protocol version 14, which is not yet on mainnet. It adds two optional fields to document serialization format 3 behind two unused flag bits, so every document stored without them keeps exactly the same bytes (devnet data included). Nothing sets them on a document type without
changeFields. A few shipped code paths gained arms for the two names; each is reached only through a protocol-version-14 document type (listed below).Issue being fixed or feature implemented
Follows #5158, now merged; this PR targets
v4.2-dev. Sam asked for$moderatedAtand$moderatedBykeywords holding the last moderator to change a document's moderator fields. The defaults agreed before building:changeFields, not opt-in throughrequired(a document has no$moderatedAtuntil someone moderates it);What was done?
The stamp
Before, a report after a moderator handled it:
{ "$id": "...", "$ownerId": "<reporter>", "$revision": 2, "$createdAt": 1700000000000, "postId": "...", "reason": 1, "status": 2 }After:
{ "$id": "...", "$ownerId": "<reporter>", "$revision": 2, "$createdAt": 1700000000000, "$moderatedAt": 1700000500000, "$moderatedBy": "<moderator>", "postId": "...", "reason": 1, "status": 2 }$moderatedAt/$moderatedBychangeDocumentFieldschangeFields$updatedAtdoes not move, as in #5158.Where it is set
DocumentV0gainsmoderated_at: Option<TimestampMillis>andmoderated_by: Option<Identifier>(serde$moderatedAt/$moderatedBy, skipped when absent, so JSON and value output of every other document is unchanged), with getters and setters onDocumentV0Getters/DocumentV0Setters. About 100DocumentV0literals across the workspace (most in tests) gainedmoderated_at: None, moderated_by: None.transform_document_fields_change_v0) stamps the changed document.judge_moderator_field_write(wasmoderator_field_refusal) still refuses a non-moderator's write (41124) and, for a moderator's, stamps the action. It is the only place a create or replace is stamped: an action built without that judgement is written unstamped.DocumentCreateTransitionActionV0gainsmoderated: bool(the conversion stamps the owner at the block's time when set). The create action is the largestBatchedTransitionActionvariant and sat at clippy's large-variant limit, so itsshould_store_contest_info(set only by a create that opens a contest) is now boxed to make room.DocumentReplaceTransitionActionV0gainsmoderated_atandmoderated_by, each carried over from the stored document as it is, and set together when the transformer stamps the replace.Stored bytes
Format 3 (protocol version 14 only) writes
$moderatedAt(8 bytes) behind bit 512 and$moderatedBy(32 bytes) behind bit 1024 of its time field flags. Formats 0 to 2 never meet a stamped document:changeFieldsneeds the config'smoderation, which only config version 2 has, whose documents are always written in format 3.Indexes and queries
ParserGeneration::admit_moderation_stamp_indexes);apply_moderator_abilitiesrefuses them (10231) on a type withoutchangeFieldsand in a unique index (a moderator's change would be refused because another document holds the same stamp). Generations 1 and 2 keep refusing them as undefined (10209).get_raw_for_document_typev0,serialize_value_for_key/deserialize_value_for_keyv0, Drive's estimated key sizes and the query layer's system field types know both names.propertyConstraintsoperands,timeRange.on,required, lookup keys. Nothing but indexes and queries reads them.Proof and SDK
verify_contract_document_change_executiondoes not compare the stamp: a later moderator's write to another field of the document moves it without undoing the change being verified.serialize_specific_versionrefuses a stamped document in formats 0 to 2 (NotSupported) instead of dropping the stamp.is_equal_ignoring_time_based_fields), so a carried stamp does not disturb them.DocumentgainsmoderatedAt/moderatedBygetters and the two keys inDocumentObject/DocumentJSON. Swift, Kotlin and the FFI document struct do not expose them yet; they decode such documents fine.Book
System Properties (new section, table rows), Document Serialization (flag bits), Moderator Abilities, Contract Moderation, Indexes, the keyword index, and
v14.rsitem 58.In-place changes to shipped generations
Each reached only through a document type of protocol version 14 (one listing
changeFields, which only meta-schema v3 admits), so output-identical for every earlier version:DocumentReplaceTransitionActionV0carries the stored stamp (Noneon every document before 14) and its conversion writes it; the create action's conversion (v0) stamps only when the transformer setmoderated. The create action'sshould_store_contest_infois boxed: an in-memory layout change, the same value.serialize_specific_versionrefuses a stamped document in formats 0 to 2; no document carries a stamp before 14, and consensus serializes throughserialize, which is unchanged.get_raw_for_document_typev0 andserialize_value_for_key/deserialize_value_for_keyv0: arms for$moderatedAt/$moderatedBy. Before, the two names fell through to the properties, where no$name is, or failed as unknown fields; only an index naming them reaches the arms, and only parser generation 3 admits such an index.document_info.rs) and the query layer'smeta_field_property_type: the same two arms.How Has This Been Tested?
contract_user_moderation/tests/moderator_fields.rs:should_stamp_the_moderator_who_last_wrote_the_fields(stamp set, proved, restamped by a later moderator, both indexes queried and moved) andshould_stamp_a_moderator_who_writes_the_fields_in_their_own_documents(a moderator's create stamped, a create without moderator fields not, an owner's replace keeping the stamp, a moderator's own replace restamping and a later title-only replace keeping it) andshould_keep_the_stamp_through_a_transfer_and_a_restore(a graded card given away, a handled report deleted and restored). The unmoderated report is checked to stay unstamped. Moderation, batch and contract create/update suites: 1077 passed.changeFields, in a unique index, and before protocol version 14.cargo clippy --workspace --all-targetsandcargo clippy --target wasm32-unknown-unknown -p wasm-dpp2 -p wasm-sdk: clean.cargo fmt --all.moderatedAt / moderatedBycase added; nonode_modulesin the worktree), rs-sdk offline vectors, Swift and Kotlin.Breaking Changes
Consensus, protocol version 14 (unreleased): two new document system properties in serialization format 3, set by moderator field writes, indexable on types with
changeFields. Rust API:DocumentV0gains two public fields and the accessor traits two methods each;DocumentReplaceTransitionActionV0gainsmoderated_atandmoderated_by,DocumentCreateTransitionActionV0gainsmoderatedand boxesshould_store_contest_info(its accessor now returnsOption<&ContestedDocumentVotePollStoredInfo>). Code that buildsDocumentV0or the replace action with a struct literal must add them.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull request (no structure change: the stamp indexes are ordinary document indexes)For repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
5c4db3c/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are donedpp— you own itrs-drive-abci— you own itrs-drive— you own itrs-platform-wallet-ffi(packages/rs-platform-wallet-ffi/src/document.rs) — HashEngineering or ZocoLini or llbartekll or romchornyirs-platform-wallet(packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs,packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs,packages/rs-platform-wallet/src/wallet/identity/network/profile.rs) — HashEngineering or ZocoLini or llbartekll or romchornyirust-sdk-ffi(packages/rs-sdk-ffi/src/document/create.rs,packages/rs-sdk-ffi/src/document/delete.rs,packages/rs-sdk-ffi/src/document/price.rsand 5 more) — lklimek or shumkovrust-sdk(packages/rs-sdk/src/platform/dashpay/contact_request.rs,packages/rs-sdk/src/platform/documents/transitions/delete.rs,packages/rs-sdk/src/platform/documents/transitions/purchase.rsand 3 more) — lklimek or shumkovWhen every box is checked the
PR Hygienecheck passes and this can merge.