feat(platform)!: ownerRefersTo and creatorRefersTo, references on the document's writer and creator (PV14) - #4941
Conversation
…(PV14) A document type may declare one refersTo declaration of its own under the doctype-level ownerRefersTo keyword, whose value is the document's $ownerId, the writer, instead of a property's value. It takes every target an identifier property's refersTo takes but contract and identityPublicKey, propertyAgreement and lookup included (in a lookup "." is the writer). - meta-schema v3 and parser generation 3 read it through the same apply_property_reference 0, onto DocumentTypeV2::owner_reference (DocumentTypeV2Getters::owner_reference); its lookup is checked on both sides like a property's, and it counts one against max_references_per_document - contract registration checks it as a property's declaration, named <documentType>.$ownerId - document create and every replace check the writer against the target, refusing with the target's own error (40120 and the rest) at $ownerId; transfers and purchases are not checked; an identity target reads nothing - adding, removing or changing it is an incompatible schema change on update (validate_schema_compatibility 1) - wasm-dpp2 lists it first in documentTypeReferences / documentReferences Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 10 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (33)
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-23T14:15:31.195Z |
|
🕓 Queued for automated review — 8th in line, estimated start in ~50 min (commit 8ea1ce1)
|
…ransfers, one enumeration - only identity or a permanentDocument lookup: contract, token, a document by id (never the writer's identity id) and identityPublicKey are refused, by meta-schema v3 and on the stored path - refused on a document type whose documents can be transferred or traded, so the writer stays the owner; owner lookups then use the property lookup rules unchanged (the $ownerId exemption is gone) - parsed from the stored schema after the core parse, so the meta-schema reports a malformed declaration first; the gate is checked first - DocumentTypeRef::reference_declarations (with ReferenceHolder) is the one enumeration of a type's references: reference count, lookup checks, both drive-abci validators and wasm-dpp2 - a replace re-validates the owner reference under its target's rules (a bound property changed, or a $ownerId agreement pair), not on every replace - the ownerRefersTo compatibility rule is looked up without expect - inert-before-14 comments on the in-place loops; js-evo-sdk README notes the $ownerId path; tests for no reads and the registration error paths Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or (PV14) The counterpart of ownerRefersTo for document types whose documents can be transferred or traded: one refersTo declaration whose value is the document's $creatorId, which a transfer or a purchase never changes. - only on a type that records creator ids (should_use_creator_id: a transferable or tradeable type of a format-1 contract), exactly where ownerRefersTo is refused, so a type declares at most one of the two - the same meta-schema shape and parser (parse_doctype_reference, now shared by both keywords), the same two targets (identity, or a permanentDocument lookup with "." the creator); stored as DocumentTypeV2::creator_reference and yielded by reference_declarations as ReferenceHolder::Creator - checked against the writer on a create and the stored creator on a replace, under the target's replace rules, never on a transfer or a purchase; errors name $creatorId (<documentType>.$creatorId at registration); an identity target reads nothing - counts one against max_references_per_document and is frozen on update Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # book/src/data-model/documents.md # packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json # packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs # packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs # packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/mod.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_common/data_contract_reference_validation/v0/mod.rs # packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs # packages/rs-platform-version/src/version/v14.rs # packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts
… and allOf An anyOf of two lookups on the owner and an allOf on the creator parse, and a leaf by id is refused at its expression path; through the full pipeline a stepDownNotice writer is admitted through either operand of an anyOf (added moderator or founder seat) and anyone else is refused with the last operand's error at $ownerId. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/rs-dpp/src/data_contract/document_type/property/mod.rs
…fersTo Merges ownerRefersTo/creatorRefersTo (#4941) and the shared is_transient helper (#4949). - ownerRefersTo and creatorRefersTo take a listElement as a target (alone or as a leaf of an expression): an identity id can be an element of a list of identities, the charters' "the writer is a seated member". Meta-schema, parser leaf check and docs updated; enforcement needed no change, the owner and creator references go through the same validator. - The listElement registration checks walk reference_declarations(), so an owner- or creator-held list element is checked as a property's is. - list_element_reference uses the shared is_transient; the changelog item is now 35, after ownerRefersTo's 34. - Composing tests: dpp (ownerRefersTo listElement, alone and in an anyOf, with a bad $id pair refused) and ABCI (a seatedNote a member may write and a stranger may not, refused at $ownerId). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the leader Built on #4940 (listElement), #4941 (ownerRefersTo) and #4942 (anyOf), merged in from v4.2-dev. The resignation request: - may be filed only by a member of the seated team: ownerRefersTo holds an anyOf of a listElement (the writer is in the elected charter's members, the charter found through the electedCharterId $id pair) and a lookup of an addedModerator for the charter keyed by the writer; the leader is in neither list; - carries a message encrypted to the leader, with the leader's decryption key bound to submittedCharter and the member's encryption key bound to joinRequest, the keys join requests already use; - is deletable (Sam), so it is a request the leader acts on with a removal and the member withdraws by deleting it. It changes the team by itself no longer: ElectedCharter::active_members drops its resignations input. The charter changelog item is renumbered 36 after the three new items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue being fixed or feature implemented
The moderation charters contract (#4898) needs rules about who writes a document, not about a value the writer chose. A
resignationRequestmust come from a moderator of the team it resigns from. EveryrefersToform so far constrains a property's value, so the only way to state that rule was a property the writer fills with their own id, a lookup on it, and apropertyAgreementbinding it to the writer:This adds
ownerRefersToat protocol version 14. It is a document type keyword holding onerefersTodeclaration whose value is the document's$ownerId, the writer:reads: the writer must be the
memberIdof anaddedModeratorfor this document'selectedCharterId. In a lookup,"."is the writer.ownerRefersTois only admitted on a type whose documents can be neither transferred nor traded, since a transfer or a purchase would hand a document to an owner the declaration never checked. For types whose documents can change hands this also adds its counterpart,creatorRefersTo. Its value is the document's$creatorId, which a transfer or a purchase never changes:reads: only a seated moderator may mint a badge, and whoever holds it later, it stays a badge a moderator minted. The charters' full rule also needs
listElement(members listed in the elected charter), which comes in its own PR, andanyOf(either source).anyOflanded as #4942, and this PR composes with it (see below). This PR does not edit the charters contract.What was done?
The declaration (rs-dpp; meta-schema v3 and parser generation 3, both unreleased)
ownerRefersTo, which reuses the propertyrefersTodefinition by$refand admits two forms:type: identity, andtype: permanentDocumentwith alookup.ownerRefersTois not among the stray keys pinned by the meta-schema v0 census guard (meta_schema_v0_stray_keyword_tests), so no stored contract can carry it.JsonSchemaError), as a malformedrefersToon a property is. It goes throughparse_owner_reference, which hands the declaration to the sameapply_property_reference0 an identifier property'srefersTogoes through,propertyAgreementandlookupincluded (in a lookup"."is the writer). Only two targets can hold a writer, and the parser refuses every other on the stored path too:contract,token, and apermanentDocumentordeletableDocumentby id: the writer's identity id is never a contract, token or document id, so a type declaring one could never be written, and the keyword cannot change on update;identityPublicKeyin either form: it pairs the value with a key id, which the writer does not carry.DocumentTypeV2::owner_reference: Option<DocumentPropertyReferenceTarget>, read through a newDocumentTypeV2Getters::owner_reference(). It isNoneonDocumentTypeV0/V1 and on every type that declares none. Property types are unchanged.DocumentTypeRef::reference_declarations()yields every reference declaration with itsReferenceHolder:Ownerfirst, thenProperty(path)for each property's own. It covers the reference count, the lookup checks, both drive-abci validators and wasm-dpp2, so none can skip the owner reference.referring_side_error, with the path$ownerId; a"$ownerId"key part passes its owner rule because of the point above). A lookup into a type of the same contract is checked increate_document_types_from_document_schemas1 (see the in-place section below), and one into another contract at registration.SystemLimits::max_references_per_document.Before (v4.2-dev), the contract above was refused at registration: meta-schema v3 closes the document type level (
additionalProperties: false) and had noownerRefersTo. After, it parses:Refused forms (registration: the meta-schema's
JsonSchemaError; stored path: the parser):Composition with
anyOf/allOf(#4942)Both keywords take a reference expression in place of a single target, provided every leaf is one the identity can be: an
identityor apermanentDocumentlookup. A leaf by id is refused as it is alone, named by where it sits.reference_declarations()walk, and each leaf counts againstmax_references_per_document.$ownerId.creatorRefersTo, the creator's counterpartshould_use_creator_id: a transferable or tradeable type of a format-1 contract), checked on every parse. That is exactly whereownerRefersTois refused, so a type declares at most one of the two.parse_doctype_reference), the same two targets,"."the creator in a lookup. It is stored asDocumentTypeV2::creator_reference(DocumentTypeV2Getters::creator_reference()) and yielded byreference_declarations()asReferenceHolder::Creator."$ownerId"key part in its lookup is refused, as in any lookup on a type whose owner moves.$creatorId, and<documentType>.$creatorIdat registration.identitytarget reads nothing. It counts one againstmax_references_per_documentand is frozen on update.Registration (drive-abci
data_contract_reference_validation0)The owner reference goes through the same loop as the property references (
reference_declarations), first, named<documentType>.$ownerId. Every existing check applies unchanged: the referenced contract and type exist, the type is permanent, the lookup into another contract resolves, andpropertyAgreementpairs are valid. The one difference is that itspropertyAgreementmay name$ownerIdon the referring side, the same writer as its value; a property reference may not name itself there.Before, each of these was refused by the meta-schema.
Document create and replace (drive-abci
document_reference_validation0)validate_document_type_references_v0walksreference_declarations. For the owner holder the value is the writer's id instead of a property's, and$ownerIdis the path; everything else is the code a property reference goes through.propertyAgreementreads changed, and on every replace for a pair keyed by$ownerId(a writer gate). Nothing else can change the outcome: the writer is the owner, the target can never be deleted, and its key is fixed. A replace that touches nothing bound reads nothing.identitytarget: fetches nothing, since the transition already proved the writer exists.With
addedModerator { electedCharterId: C1, memberId: M }in state:Before, none of these could be expressed; the contract did not register.
Contract update (rs-dpp
validate_schema_compatibility1, unreleased)Adding, removing or changing
ownerRefersTois an incompatible schema change, as forrefersToon a property. The v14 compatibility generation registers a copy of the sharedrefersTorule underownerRefersTothrough itsoverride_rules, so the shared rule set, which generation 0 also reads, is untouched. The copy is looked up fallibly: were the shared rule ever missing, a diff underownerRefersTowould fail as an unsupported keyword, an error rather than a panic.Clients (wasm-dpp2, js-evo-sdk README)
documentTypeReferences(name)anddocumentReferenceslist the owner reference first, with the path$ownerId, the path consensus errors carry. It is not a property path, and the js-evo-sdk README now says so next to itsreasons[]note:Swift and Kotlin are out of scope.
Docs
rs-platform-version/src/version/v14.rs(feat(platform)!: anyOf and allOf reference expressions (PV14) #4942 took 33);SystemLimits::max_references_per_documentdocs;ownerRefersTo)" inbook/src/data-model/documents.md.Design calls to review
identityand apermanentDocumentlookup are admitted, and only on types that can be neither transferred nor traded. The spec admitted every target an identifier property takes, and did not restrict transferable types.$ownerIdagreement pair), as a property's reference does.identitytarget is free. It does no read, where a property'sidentityreference is a billed identity fetch. It still counts one againstmax_references_per_document.validate_update/common. Its config checks also serve the shippedvalidate_updatev0, so the rule went into the unreleased compatibility generation instead. The error isIncompatibleDocumentTypeSchemaError, the one a propertyrefersTochange gives.DocumentTypeV2Getters, besideaction_fees(), since the data exists only onDocumentTypeV2. The spec saidDocumentTypeV0Getters.creatorRefersTofollows$creatorIdrecording, not only transferability. You asked for transferable or tradeable types only. The rule isshould_use_creator_id, which also needs a format-1 contract with a config version above 0, since elsewhere no creator id is stored to check. Every contract created at protocol version 14 meets that.In-place changes to shipped generations
create_document_types_from_document_schemasv1 (selected by protocol versions 2 to 14): the same-contract lookup loop now walksreference_declarations(). Every protocol version up to 13 parses with generations 0 to 2, which never set an owner reference, so there the iterator yields exactly the properties' references, in the order the old loop walked them. Its error text for a property (property "<path>" refersTo) is unchanged. Shown at protocol version 13 through the dispatcher byshould_refuse_owner_refers_to_before_protocol_version_14_and_read_it_at_14: a registering parse refuses the keyword (meta-schema v2), and a stored parse ignores it, with no owner reference.data_contract_reference_validationv0 (every table carries 0): its only callers are contract create and update state validation 1, selected from protocol version 14. It now walksreference_declarations(), whose owner arm cannot exist before 14.document_reference_validationv0 (every table carries 0): its only callers are document create state validation 2 and replace state validation 1, both selected from protocol version 14. Same reasoning as the previous item.Each edited loop carries a comment stating why it is inert before 14.
owner_can_changeinreference_lookup.rsonly becamepub(crate). Everything else is unreleased and edited in place: meta-schema v3, parser generation 3,validate_schema_compatibility1 andSYSTEM_LIMITS_V4(docs only).How Has This Been Tested?
try_from_schema/v3/owner_reference_tests.rs:propertyAgreementnaming$ownerId;contract(with and without requirements),identityPublicKeyin both forms,token,permanentDocumentby id anddeletableDocument: the parser's message on the stored path, a meta-schemaJsonSchemaErrorat registration;.or with it twice;$ownerIdkey part;max_references_per_document(256 plus one);creatorRefersTo:$ownerId;anyOfof two lookups on the owner and anallOfon the creator parse, and a leaf by id is refused atanyOf[1].batch/tests/document/owner_reference.rs, full ABCI pipeline, fixturereference-validation-contract-owner-refers-to.json:$ownerId), including for another charter;reasonsucceeds, and one movingelectedCharterIdto a charter the writer is not seated for is refused;$ownerId);identitytarget refuses nobody;$creatorId;ownerRefersToanyOf: the writer is admitted through either operand, and anyone else is refused with the last operand's error.identitytarget bills nothing, on create or replace;identitycreator target bills nothing.data_contract_create: registration of the fixture, and atnote.$ownerId: 40121 (unknown type), 40122 (deletable target), 40137 (foreign lookup), agreement invalid; 40121 atnote.$creatorId.cargo test -p dpp --lib;cargo test -p drive-abci --libfiltered onowner,creator,reference,lookup,agreement,data_contract_create,data_contract_update,typed_array,distinct_from;cargo test -p wasm-dpp2and, afteryarn workspace @dashevo/wasm-dpp2 build, the mocha unit suite;cargo clippy -p dpp -p drive-abci --all-features --all-targets -- -D warnings,cargo clippy -p wasm-dpp2 --target wasm32-unknown-unknown -- -D warnings,cargo fmt --all -- --check.Breaking Changes
Consensus: protocol version 14 (unreleased) admits a new document type keyword, and a document write can be refused by it.
DocumentTypeV2Gettersgains a required method,owner_reference. No shipped protocol version changes behaviour.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
PR Hygiene ·
8ea1ce1/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are donejs-wasm-sdk(packages/js-evo-sdk/README.md) — shumkovdpp— you own itrs-drive-abci— you own itWhen every box is checked the
PR Hygienecheck passes and this can merge.