feat(platform)!: distinctFrom on identifier properties (PV14) - #4917
Conversation
An identifier property may declare `distinctFrom`: its value must differ from a named property of the same document, or from the document's `$ownerId`. A pure structure rule at protocol version 14: the parser (meta-schema v3, `apply_distinct_from` 0) checks the target at contract registration and update, and document create and replace structure validation 1 refuse an equal pair with `DocumentPropertyNotDistinctError` (10419) after the document's schema validation, reading the transition alone. An absent named property passes. A changed `distinctFrom` is an incompatible schema change on update. `DistinctFrom::violation` judges a single value so that a typed array item can be judged by the same rule. wasm-dpp2 exposes the declarations and the error code; the book and the v14 changelog describe the keyword. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (47)
✨ Finishing Touches📝 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 |
|
|
…inct-from-identifier-properties
…ctFrom tests Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-22T23:01:49.698Z |
Keep the shipped batch advanced-structure generation 0 byte-identical: the replace validator gains `validate_structure_with_owner`, which only the generation 1 batch caller uses, and `validate_structure` keeps its original signature. Move the distinctFrom target check into the shared property parser, gated on the same `apply_distinct_from` version that parses the declarations. Name an object target as such instead of "no property". Drop the unused Deserialize, TryFrom and Display on DistinctFrom. Document that transfers and purchases are not judged, and extend the error code catalog to 10419. Tests: the meta-schema, not the parser, refuses the keyword on a non-identifier; refersTo and distinctFrom on one property; the dpp and replace dispatchers on both sides of the protocol version 14 gate. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, precompute the declaring properties Transfer and purchase structure validation move to generation 1 at protocol version 14: the stored document's `distinctFrom: $ownerId` properties are judged against the new owner the action already carries, so a transfer to, or a purchase by, the identity such a property names is refused with DocumentPropertyNotDistinctError like a create or replace. The update-price dispatcher read the transfer slot instead of its own (both were 0 everywhere, so nothing changes for released tables); it now reads `document_update_price_transition_structure_validation`. The parser records the declaring properties on the V2 document type (`distinct_from_fields`, in schema order) and the write-time check reads that list, so a type without declarations costs nothing per write. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…inct-from-identifier-properties
Typed scalar arrays landed on v4.2-dev (#4922), so the declaration the charters contract needs, on the `members` items, is covered: an identifier typed array carries `distinctFrom` on its `items` and every element must differ from the named value. The parser refuses the keyword on the array itself and on elements of any other type, the meta-schema admits it on identifier elements only, and the write-time check judges each element with `DistinctFrom::violation`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…inct-from-identifier-properties
…m parse Typed array elements are their scalar property type since #4923. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…he shipped v0, and let shipped generations change in place when consensus cannot The conventions rule becomes "shipped generations are frozen unless the change cannot modify consensus": an in-place edit is allowed when the new code is unreachable by construction, or output-identical, at every protocol version that selects the module, with the reason at the edited lines and an "In-place changes to shipped generations" section in the PR description. Written into the coding conventions, the versioned dispatch chapter and the CLAUDE.md summary. Under that rule the transfer and purchase distinctFrom judgement moves from a copied v1 into v0, with the table slots back at 0: below protocol version 14 no parsed property carries the keyword and the dpp gate is None, so the call returns an empty result there. A dispatcher test runs both modules at 13 and 14. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…operties' into feat/distinct-from-identifier-properties
Replace structure v0 takes the writer's owner id and judges the transition's distinctFrom declarations after the schema validation, in place under the revised conventions rule: below protocol version 14 no parsed property carries the keyword and the dpp gate is None, so the call returns an empty result. The copied v1, the second trait method and the table bump go away; both batch call sites pass identity.id. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Keeps the distinctFrom changelog entry as item 26 and moves the word-characters-only name rule to item 27. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Issue being fixed or feature implemented
Document schemas had no way to say that an identifier property must not equal another value of the same document. The first user is the
membersitems ofelectedCharterin the moderation charters contract (#4898), where a charter's members must not include the owner. This adds thedistinctFromproperty keyword at protocol version 14: a pure structure rule, an identifier property's value must differ from the value of a named property of the same document, or from the document's$ownerId. No state is read.What was done?
Meta-schema v3 (edited in place, the unreleased generation):
distinctFromon the property keyword list, a string of 1 to 256 characters, restricted to identifier properties through the samedependentSchemasshape that restrictsrefersTo.Parser (
rs-dpp):DocumentPropertygainsdistinct_from: Option<DistinctFrom>(OwnerIdorProperty(path)), skipped in serde when absent, so contracts that do not declare it serialize exactly as before. The keyword is read byapply_distinct_from, gated on a newapply_distinct_from: OptionalFeatureVersionin the document type schema versions (Nonebefore protocol version 14, where it is ignored likerefersToandrequiredSince;Some(0)inCONTRACT_VERSIONS_V6). Generation 3 then validates every target once all properties are parsed: it must be$ownerIdor an existing identifier property of the same document type, and not the declaring property; any other$system property is refused. Contract registration and update both go through this parse, so a bad target is a clear contract structure error on either.Write time:
DocumentTypeV0Methods::validate_distinct_from_properties(newvalidate_distinct_from: OptionalFeatureVersionin the document type method versions,Some(0)at protocol version 14) walks the flattened properties and judges each declared value withDistinctFrom::violation, which takes the property's own value as a parameter so that a typed array item can be judged by the same rule with the item's value. Document create structure validation 1 (protocol version 14, extended in place) and document replace structure validation 0 (shipped, extended in place, see the section below) call it inline after the document's schema validation, with the owner id the batch already carries; no Drive read, no new processor trait, no table bump. The replace validator'svalidate_structurenow takes the owner id, so the two batch call sites passidentity.id. An equal pair is refused with the new basic errorDocumentPropertyNotDistinctError(10419, the next code in the basic document band 10400-10449;BasicErrordiscriminant 194, appended), which names the document type, the property and what it collided with. An absent named property passes. Transfer and purchase structure validation judge the stored document's$ownerIddeclarations against the new owner the action carries, so a transfer to, or a purchase by, the identity such a property names is refused the same way; both checks were added to the shipped v0 in place (see the section below). The update-price dispatcher read the transfer slot instead of its own (both 0 in every released table); it now readsdocument_update_price_transition_structure_validation. The parser records the declaring properties on the V2 document type (distinct_from_fields) so the write-time check reads a list instead of walking every property.Typed array items: with typed scalar arrays (#4922) on the base, an identifier typed array carries
distinctFromon itsitemsand every element must differ from the named value, which is what the charters contract'smembersneeds. The parser refuses the keyword on the array itself and on elements of any other type, the meta-schema's array-item definition admits it on identifier elements only, and the write-time check judges each element withDistinctFrom::violation.Update rules:
distinctFromgets a compatibility rule injson-schema-compatibility-validatorlikerefersToandrequiredSince: adding, removing or changing it is an incompatible schema change on contract update.Clients:
wasm-dpp2exposes the declarations throughDataContract.documentTypeDistinctFrom(name)and thedocumentDistinctFrommap ({ path, distinctFrom }per declaration, mirroring the reference and immutability getters), plus aDocumentDistinctFromErrorCodeenum with the new code;wasm-dppmirrors the error. Swift and Kotlin parsers are out of scope for this PR.Docs: item 26 of the v14 changelog, a "Distinct Identifier Properties" section in the book's documents chapter, and the conventions rule on shipped generations rewritten: frozen unless the change provably cannot modify consensus, in which case the PR description carries an "In-place changes to shipped generations" section (also in the versioned-dispatch chapter and the CLAUDE.md summary).
In-place changes to shipped generations
Per the conventions rule updated in this PR (
book/src/contributing/coding-conventions.md, "Shipped generations are frozen unless the change cannot modify consensus"), these shipped modules were edited in place rather than copied into a new generation:document_replace_transition_action/advanced_structure_v0(now taking the owner id),document_transfer_transition_action/advanced_structure_v0anddocument_purchase_transition_action/advanced_structure_v0, selected by every protocol version (their table slots stay 0). Each gained one call tovalidate_distinct_from_propertieson the stored document and its new owner. It cannot modify consensus below protocol version 14: the meta-schemas of those versions refusedistinctFrom, their parser ignores it (apply_distinct_fromisNone, so no parsed property carries a declaration), and the dpp method's own gatevalidate_distinct_fromisNonethere, so the call returns an empty result.should_not_judge_distinct_from_on_replace_before_protocol_version_14andshould_not_judge_distinct_from_on_transfer_or_purchase_before_protocol_version_14run the three modules at 13 and 14 through their dispatchers. The batch advanced-structure v0 call site passesidentity.idto the replace validator, which is the only edit to that shipped module; the value is unread below 14.document_update_price_transition_action/mod.rs(the dispatcher, not a generation) read the transfer table slot instead ofdocument_update_price_transition_structure_validation; both are 0 in every table, so the fix changes no outcome.How Has This Been Tested?
cargo test -p dpp --lib -- data_contract::document_type errors::consensus: parsesdistinctFrom: "$ownerId"and top-level and nested property paths, and on the items of an identifier typed array (refused on the array itself and on non-identifier items), judging every element; refuses it on a non-identifier property (parser and meta-schema), on a missing path, on a non-identifier target, on itself, on another$system property and on a non-string; refuses it at protocol version 13 under full validation and ignores it there without; accepts it at 14; a contract round-trips throughserialize_to_bytes_with_platform_versionwith and without it; a contract update that adds, changes or removes it is refused as incompatible while an unchanged one passes; theBasicErrordiscriminant test pins the new variant.cargo test -p json-schema-compatibility-validator: the new rule's examples.cargo test -p drive-abci --lib -- batch::tests::document::distinct_from: a create whose value equals$ownerIdis refused with the new error and nothing is stored, a create with a different value succeeds, a create whose value equals the named sibling is refused, an absent sibling passes, a nested pair is judged by its dotted paths, an array element equal to$ownerIdis refused while distinct elements pass, a replace that makes the two equal (or sets the property to the owner) is refused and leaves the stored document untouched, a replace that keeps them distinct succeeds, a transfer to (or a purchase by) the identity a$ownerIddeclaration names is refused and leaves the owner unchanged while a transfer to (or purchase by) another identity succeeds, and the replace, transfer and purchase dispatchers are exercised at protocol versions 13 and 14. The immutable, creation, transfer, nft and create-structure suites around the touched validators still pass.cargo check -p wasm-dpp2 --target wasm32-unknown-unknown,cargo check -p wasm-dpp --target wasm32-unknown-unknown, clippy ondppanddrive-abci,cargo fmt --all.Breaking Changes
Protocol version 14 only: contracts may carry
distinctFrom; document create and replace refuse a write that violates it, and transfer and purchase refuse a change of owner that would (new consensus error 10419). Earlier protocol versions are unchanged.DocumentReplaceTransitionActionValidation::validate_structurenow takes the owner id.Follow-ups
DataContractParserdo not surface the keyword yet.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code