From 5734acec5a24babe5e0ecc28595a79c0ad03431f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Aug 2026 19:32:32 +0200 Subject: [PATCH 1/3] fix(dpp): expose refersTo propertyAgreement in the wasm reference surface `DocumentPropertyReferenceTarget::PermanentDocument` gained a third field, `property_agreement`, in #4505. The wasm reference serializer added in #4450 destructures the variant exhaustively and was written against a base that predated that field, so the two merged clean as text and left `wasm-dpp2` uncompilable (E0027) on v4.2-dev. Surface the field as `propertyAgreement` rather than ignoring it with `..`: the module's documented contract is that its keys line up with what `contract.toJSON()` shows under `refersTo`, and dropping one would break that. Emitted only when non-empty, mirroring the keyword's own `skip_serializing_if`. Co-Authored-By: Claude Opus 5 --- .../data_contract/document_type_reference.rs | 26 +++++++++++++++++++ .../unit/DocumentPropertyReference.spec.ts | 23 ++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/packages/wasm-dpp2/src/data_contract/document_type_reference.rs b/packages/wasm-dpp2/src/data_contract/document_type_reference.rs index f02c0ce69e1..9ec3c12fdc9 100644 --- a/packages/wasm-dpp2/src/data_contract/document_type_reference.rs +++ b/packages/wasm-dpp2/src/data_contract/document_type_reference.rs @@ -53,6 +53,16 @@ export type DocumentPropertyReferenceTarget = * reference dangling. */ documentType: string; + /** + * `{ referring property: referenced property }` pairs. Consensus + * enforces each pair as an EQUALITY between this document's value and + * the referenced document's value at write time. + * + * Absent when the declaration carries none, matching `refersTo` — an + * empty agreement is omitted from the schema rather than serialized + * as an empty object. + */ + propertyAgreement?: Record; } | { type: 'identityPublicKey'; @@ -131,6 +141,7 @@ fn reference_to_js( DocumentPropertyReferenceTarget::PermanentDocument { contract_id, document_type_name, + property_agreement, } => { let effective = contract_id.unwrap_or(declaring_contract_id); set_field( @@ -145,6 +156,21 @@ fn reference_to_js( &JsValue::from_str(document_type_name), path, )?; + // Mirrors the keyword's own `skip_serializing_if`: an empty + // agreement never appears under `refersTo`, so it must not + // appear here either, or the two stop lining up key for key. + if !property_agreement.is_empty() { + let agreement = Object::new(); + for (referring_property, referenced_property) in property_agreement { + set_field( + &agreement, + referring_property, + &JsValue::from_str(referenced_property), + path, + )?; + } + set_field(&object, "propertyAgreement", &agreement.into(), path)?; + } } DocumentPropertyReferenceTarget::IdentityPublicKey { key_id_property } => { set_field( diff --git a/packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts b/packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts index a63fbee87b5..49f06c7a362 100644 --- a/packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts +++ b/packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts @@ -56,6 +56,7 @@ const schemas = { parentNoteId: identifierProperty(3, { type: 'permanentDocument', documentType: 'note', + propertyAgreement: { signerKeyId: 'signerKeyId' }, }), otherDoc: identifierProperty(4, { type: 'permanentDocument', @@ -103,6 +104,7 @@ type Reference = { type: string; contractId?: { toBase58(): string }; documentType?: string; + propertyAgreement?: Record; keyIdProperty?: string; }; @@ -169,6 +171,27 @@ describe('DataContract — refersTo declarations (v14)', () => { expect(other.documentType).to.equal('thing'); }); + it('should carry propertyAgreement for a declaration that binds properties', () => { + const contract = buildContract(14); + const references = contract.documentTypeReferences('note') as Reference[]; + const parent = references.find((reference) => reference.path === 'parentNoteId')!; + + expect(parent.propertyAgreement).to.deep.equal({ signerKeyId: 'signerKeyId' }); + }); + + /** + * An empty agreement is omitted from `refersTo` rather than serialized + * as an empty object, and the accessor keeps that shape — the module's + * whole contract is that its keys line up with the schema keyword's. + */ + it('should omit propertyAgreement for a declaration that binds none', () => { + const contract = buildContract(14); + const references = contract.documentTypeReferences('note') as Reference[]; + const other = references.find((reference) => reference.path === 'otherDoc')!; + + expect(other).to.not.have.property('propertyAgreement'); + }); + it('should carry keyIdProperty for an identityPublicKey reference', () => { const contract = buildContract(14); const references = contract.documentTypeReferences('note') as Reference[]; From df6bd8ebdb0784d01cefd95e876ed52d28d5851e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Aug 2026 19:32:32 +0200 Subject: [PATCH 2/3] style(dpp): rustfmt the document factory accessor import Landed unformatted in #4510, whose Tests run was cancelled when #4450 pushed over it, so `cargo fmt --check` was never reported red before the release cut. Co-Authored-By: Claude Opus 5 --- packages/rs-dpp/src/document/document_factory/v0/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/rs-dpp/src/document/document_factory/v0/mod.rs b/packages/rs-dpp/src/document/document_factory/v0/mod.rs index 2503d4788c8..faf131c8d5f 100644 --- a/packages/rs-dpp/src/document/document_factory/v0/mod.rs +++ b/packages/rs-dpp/src/document/document_factory/v0/mod.rs @@ -1,6 +1,8 @@ use crate::consensus::basic::document::InvalidDocumentTypeError; use crate::data_contract::accessors::v0::DataContractV0Getters; -use crate::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters}; +use crate::data_contract::document_type::accessors::{ + DocumentTypeV0Getters, DocumentTypeV2Getters, +}; use crate::data_contract::document_type::DocumentTypeRef; use crate::data_contract::errors::DataContractError; use crate::data_contract::DataContract; From 50871436e02a680d7f0a96a0a0f4b65ae208a0fd Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 28 Aug 2026 19:32:32 +0200 Subject: [PATCH 3/3] ci(swift-sdk): authenticate setup-protoc against the anonymous rate limit The macOS release runners share an egress IP and burn through the 60 req/h anonymous GitHub API limit, so resolving the protoc release fails with "API rate limit exceeded" and takes the DashSDKFFI build down with it. Co-Authored-By: Claude Opus 5 --- .github/workflows/release-swift-sdk.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release-swift-sdk.yml b/.github/workflows/release-swift-sdk.yml index 17733555ac9..fa07385bbdc 100644 --- a/.github/workflows/release-swift-sdk.yml +++ b/.github/workflows/release-swift-sdk.yml @@ -127,6 +127,10 @@ jobs: uses: arduino/setup-protoc@v3 with: version: '32.x' + # Without a token the action resolves the protoc release + # unauthenticated, and the shared macOS-runner egress IP burns + # through the 60 req/h anonymous limit. + repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Build DashSDKFFI.xcframework and install into Swift package run: |