From 0e3bd2ac1951d08015c44d5d08fda564a5438b54 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 31 Aug 2026 17:45:43 +0200 Subject: [PATCH 1/3] feat(js-sdk): chained document queries in wasm-sdk and js-evo-sdk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final PR of the chained-document-queries stack — the JS surface for the provable semi-join: - wasm-sdk: chained_document query module — typed ChainedDocumentsQuery/ChainedDocumentsResult TS declarations, getChainedDocuments / getChainedDocumentsWithProofInfo on WasmSdk. The inner half reuses the documents query builder (where/orderBy in the familiar shape); results come back as { innerDocuments, outerDocuments } arrays in inner-proof order, always proof-verified (two grovedb proofs bound to one quorum-signed root, outer query re-derived from the proven inner values). - js-evo-sdk: sdk.documents.chained / chainedWithProof facade methods + a README section ("posts I liked" with the pagination-cursor recipe) next to the refersTo docs it builds on. - platform-test-suite: chained-query case in IndexOnlyDocument.spec — the registered yappr contract's like/post pair queried through the shared EvoSDK (exported from createPlatformProofVerifier), asserting both halves, order, and the base58 identifier surface. wasm-sdk builds for wasm32 and the package bundles; js-evo-sdk compiles against the regenerated types. Co-Authored-By: Claude Fable 5 --- packages/js-evo-sdk/README.md | 35 +++ packages/js-evo-sdk/src/documents/facade.ts | 24 ++ .../lib/test/createPlatformProofVerifier.js | 4 + .../platform/IndexOnlyDocument.spec.js | 33 +++ .../wasm-sdk/src/queries/chained_document.rs | 214 ++++++++++++++++++ packages/wasm-sdk/src/queries/document.rs | 22 +- packages/wasm-sdk/src/queries/mod.rs | 1 + 7 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 packages/wasm-sdk/src/queries/chained_document.rs diff --git a/packages/js-evo-sdk/README.md b/packages/js-evo-sdk/README.md index 16e4fba5dd8..c13c80f44ed 100644 --- a/packages/js-evo-sdk/README.md +++ b/packages/js-evo-sdk/README.md @@ -16,6 +16,7 @@ Evo SDK provides a high-level, strongly-typed interface for interacting with [Da - [Facades](#facades) - [Ranked queries](#ranked-queries) - [Document references (`refersTo`)](#document-references-refersto) +- [Chained queries (provable semi-join)](#chained-queries-provable-semi-join) - [Contributing](#contributing) - [License](#license) @@ -188,6 +189,40 @@ try { } ``` +## Chained queries (provable semi-join) + +A `refersTo: permanentDocument` declaration also lights up the read side: a **chained query** answers `SELECT * FROM post WHERE $id IN (SELECT postId FROM like WHERE $ownerId = me)` in one verified round trip. The node returns the inner indexOnly page and the referenced documents with two proofs bound to one quorum-signed state root, and the SDK re-derives the outer query from the *proven* inner values — the node cannot substitute, omit, or inject joined documents (a missing referenced document fails verification outright, since `permanentDocument` references cannot dangle). + +```ts +// The posts I liked, newest page first by postId. +const page = await sdk.documents.chained({ + dataContractId: YAPPR, + innerDocumentType: 'like', + where: [['$ownerId', '==', me]], + innerLimit: 25, + joinProperty: 'postId', + outerDocumentType: 'post', +}); + +for (const post of page.outerDocuments) { + console.log(post.properties.message); +} + +// Next page: continue past the last proven join value. +const cursor = page.innerDocuments.at(-1)?.properties.postId; +const next = await sdk.documents.chained({ + dataContractId: YAPPR, + innerDocumentType: 'like', + where: [['$ownerId', '==', me], ['postId', '>', cursor]], + orderBy: [['postId', 'asc']], + innerLimit: 25, + joinProperty: 'postId', + outerDocumentType: 'post', +}); +``` + +The inner query must target an indexOnly document type and resolve to an index carrying `joinProperty`, and `joinProperty` must declare a same-contract `refersTo: permanentDocument` targeting `outerDocumentType`. `innerLimit` is required — it bounds the derived outer fetch, so there is no server-default fallback. There are no outer-side clauses by design; filter `outerDocuments` locally. `sdk.documents.chainedWithProof(...)` returns the same result with the metadata and proof envelope attached. + ## Contributing Feel free to dive in! [Open an issue](https://github.com/dashpay/platform/issues/new/choose) or submit PRs. diff --git a/packages/js-evo-sdk/src/documents/facade.ts b/packages/js-evo-sdk/src/documents/facade.ts index 57888b0cb6a..eb0d896f4ab 100644 --- a/packages/js-evo-sdk/src/documents/facade.ts +++ b/packages/js-evo-sdk/src/documents/facade.ts @@ -23,6 +23,30 @@ export class DocumentsFacade { return w.getDocumentsWithProofInfo(query); } + /** + * Chained document query — a provable semi-join: + * `SELECT * FROM WHERE $id IN + * (SELECT FROM WHERE ...)`. + * + * "Posts I liked" in one verified round trip: inner `like` through + * its byLiker-style index, join `postId`, outer `post`. Both halves + * are proof-verified against one quorum-signed state root, and the + * outer half is re-derived from the proven inner values — the + * responding node cannot steer the join. Paginate on the inner query + * with a range clause on the join property. + */ + async chained(query: wasm.ChainedDocumentsQuery): Promise { + const w = await this.sdk.getWasmSdkConnected(); + return w.getChainedDocuments(query); + } + + async chainedWithProof( + query: wasm.ChainedDocumentsQuery, + ): Promise> { + const w = await this.sdk.getWasmSdkConnected(); + return w.getChainedDocumentsWithProofInfo(query); + } + async history(query: wasm.DocumentHistoryQuery): Promise> { const w = await this.sdk.getWasmSdkConnected(); return w.getDocumentHistory(query); diff --git a/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js b/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js index aaa66e7fbf5..a49f0088484 100644 --- a/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js +++ b/packages/platform-test-suite/lib/test/createPlatformProofVerifier.js @@ -277,3 +277,7 @@ function createPlatformProofVerifier({ } module.exports = createPlatformProofVerifier; +// The shared EvoSDK is also what functional specs use to exercise +// WASM-SDK-only read surfaces (e.g. chained document queries) against +// the same network the verifier is bound to. +module.exports.getEvoSdkForNetwork = getEvoSdkForNetwork; diff --git a/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js b/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js index 134e8c854b8..4940e1907d9 100644 --- a/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js +++ b/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js @@ -4,6 +4,7 @@ const { expect } = require('chai'); const createClientWithFundedWallet = require('../../../lib/test/createClientWithFundedWallet'); const generateRandomIdentifier = require('../../../lib/test/utils/generateRandomIdentifier'); const waitForSTPropagated = require('../../../lib/waitForSTPropagated'); +const createPlatformProofVerifier = require('../../../lib/test/createPlatformProofVerifier'); const { Errors: { @@ -297,6 +298,38 @@ describe('Platform', () => { fetchedLike = fullLike; }); + it('should fetch liked posts through a chained query with verified proofs', async () => { + // The provable semi-join: SELECT * FROM post WHERE $id IN + // (SELECT postId FROM like WHERE $ownerId = me). Served by the + // dedicated getChainedDocuments endpoint through the WASM SDK, + // which verifies BOTH grovedb proofs against one quorum-signed + // root and re-derives the outer query from the proven inner + // values — the node cannot steer the join. + const { sdk: evoSdk } = await createPlatformProofVerifier + .getEvoSdkForNetwork(process.env.NETWORK); + + const page = await evoSdk.documents.chained({ + dataContractId: dataContract.getId().toString(), + innerDocumentType: 'like', + where: [['$ownerId', '==', identity.getId().toString()]], + innerLimit: 10, + joinProperty: 'postId', + outerDocumentType: 'post', + }); + + expect(page.innerDocuments).to.have.lengthOf(1); + expect(page.outerDocuments).to.have.lengthOf(1); + + const [likedPost] = page.outerDocuments; + expect(likedPost.id.toBase58()).to.equal(post.getId().toString()); + expect(likedPost.properties.message).to.equal('a post worth liking'); + + // The inner projection carries the pagination cursor. + const [innerLike] = page.innerDocuments; + // Identifier-typed properties surface as base58 strings in JS. + expect(innerLike.properties.postId).to.equal(post.getId().toString()); + }); + it('should fail to query a subset-index projection without proofs', async () => { // The subset index [postId] synthesizes a projection without the // hashtag — and with hashtag optional, serializing it would assert diff --git a/packages/wasm-sdk/src/queries/chained_document.rs b/packages/wasm-sdk/src/queries/chained_document.rs new file mode 100644 index 00000000000..9418278e2f0 --- /dev/null +++ b/packages/wasm-sdk/src/queries/chained_document.rs @@ -0,0 +1,214 @@ +//! Chained document queries — the provable semi-join surface. +//! +//! `SELECT * FROM WHERE $id IN (SELECT FROM +//! WHERE …)` served by the dedicated `getChainedDocuments` RPC: +//! the inner indexOnly page and the outer by-ids fetch derived from its +//! proven values come back as TWO grovedb proofs bound to ONE +//! quorum-signed state root. The SDK verifies the composition — the +//! outer query is re-derived from the PROVEN inner results, never taken +//! from the node — so the join cannot be steered by the responding +//! server. The request deliberately carries no outer clauses; filter +//! the returned outer documents locally if needed. +//! +//! Pagination lives on the inner query alone: order by the join +//! property and continue with `where: [[joinProperty, ">", ]]`. + +use crate::error::WasmSdkError; +use crate::queries::document::{build_documents_query, DocumentsQueryInput}; +use crate::queries::utils::deserialize_required_query; +use crate::queries::ProofMetadataResponseWasm; +use crate::sdk::WasmSdk; +use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; +use dash_sdk::platform::documents::chained_document_query::ChainedDocumentQuery; +use dash_sdk::platform::{ChainedDocuments, Fetch}; +use js_sys::{Array, Object, Reflect}; +use serde::Deserialize; +use serde_json::Value as JsonValue; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use wasm_dpp2::data_contract::document::DocumentWasm; +use wasm_dpp2::identifier::IdentifierWasm; + +#[wasm_bindgen(typescript_custom_section)] +const CHAINED_DOCUMENTS_QUERY_TS: &'static str = r#" +/** + * A chained document query — a provable semi-join: + * `SELECT * FROM WHERE $id IN + * (SELECT FROM WHERE ...)`. + * + * The inner query must target an indexOnly document type and resolve to + * an index carrying `joinProperty`, and `joinProperty` must declare a + * same-contract `refersTo: permanentDocument` targeting + * `outerDocumentType` ("posts I liked": inner `like` through `byLiker`, + * join `postId`, outer `post`). There are no outer-side clauses by + * design — the verifier derives the outer query from the proven inner + * results. + */ +interface ChainedDocumentsQuery { + /** The contract both document types live in. */ + dataContractId: string | Uint8Array; + /** The indexOnly document type queried directly (e.g. "like"). */ + innerDocumentType: string; + /** Inner where clauses, same shape as a documents query's `where`. */ + where?: any[]; + /** Inner ordering, same shape as a documents query's `orderBy`. */ + orderBy?: any[]; + /** + * REQUIRED page size of the inner query — it bounds the derived + * outer query, so there is no server-default fallback. + */ + innerLimit: number; + /** The inner property whose proven values become the outer `$id`s. */ + joinProperty: string; + /** The joined document type — the `refersTo` target (e.g. "post"). */ + outerDocumentType: string; +} + +/** + * Both halves of a verified chained query, in inner-proof order. + */ +interface ChainedDocumentsResult { + /** + * The inner projections exactly as the inner query alone would + * return them; the last one's join property is the pagination + * cursor. + */ + innerDocuments: Document[]; + /** + * The joined outer documents, ordered by first appearance of their + * id among the inner projections (deduplicated). + */ + outerDocuments: Document[]; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ChainedDocumentsQuery")] + pub type ChainedDocumentsQueryJs; +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct ChainedDocumentsQueryInput { + data_contract_id: IdentifierWasm, + inner_document_type: String, + #[serde(rename = "where", default)] + where_clauses: Option>, + #[serde(default)] + order_by: Option>, + inner_limit: u32, + join_property: String, + outer_document_type: String, +} + +async fn parse_chained_documents_query( + sdk: &WasmSdk, + query: ChainedDocumentsQueryJs, +) -> Result { + let input: ChainedDocumentsQueryInput = + deserialize_required_query(query, "Query object is required", "chained documents query")?; + + let inner_limit = input.inner_limit; + let inner = build_documents_query( + sdk, + DocumentsQueryInput { + data_contract_id: input.data_contract_id, + document_type_name: input.inner_document_type, + where_clauses: input.where_clauses, + order_by: input.order_by, + limit: Some(inner_limit), + start_after: None, + start_at: None, + group_by: None, + time_range: None, + }, + ) + .await?; + + Ok(ChainedDocumentQuery::new( + inner.with_limit(inner_limit), + input.join_property, + input.outer_document_type, + )) +} + +fn chained_result_to_js( + chained: &ChainedDocuments, + query: &ChainedDocumentQuery, +) -> Result { + let contract_id = query.inner.data_contract.id(); + let to_array = |documents: &[dash_sdk::platform::Document], type_name: &str| { + let array = Array::new(); + for document in documents { + let wasm_doc = + DocumentWasm::new(document.clone(), contract_id, type_name.to_string(), None); + array.push(&JsValue::from(wasm_doc)); + } + array + }; + let inner_documents = to_array(&chained.inner_documents, &query.inner.document_type_name); + let outer_documents = to_array(&chained.outer_documents, &query.outer_document_type_name); + + let result = Object::new(); + Reflect::set( + &result, + &JsValue::from_str("innerDocuments"), + &inner_documents, + ) + .map_err(|_| WasmSdkError::generic("failed to build chained result object"))?; + Reflect::set( + &result, + &JsValue::from_str("outerDocuments"), + &outer_documents, + ) + .map_err(|_| WasmSdkError::generic("failed to build chained result object"))?; + Ok(result) +} + +#[wasm_bindgen] +impl WasmSdk { + /// Run a chained document query (provable semi-join) and return + /// both verified halves. + /// + /// The composition is always proof-verified: the two grovedb proofs + /// must commit to one quorum-signed root, and the outer half must + /// match the query the SDK derives from the proven inner values — + /// exactly (a missing referenced document is a verification error, + /// not an absence). + #[wasm_bindgen( + js_name = "getChainedDocuments", + unchecked_return_type = "ChainedDocumentsResult" + )] + pub async fn get_chained_documents( + &self, + query: ChainedDocumentsQueryJs, + ) -> Result { + let query = parse_chained_documents_query(self, query).await?; + let chained = ChainedDocuments::fetch(self.as_ref(), query.clone()) + .await? + .unwrap_or_default(); + chained_result_to_js(&chained, &query) + } + + /// [`Self::get_chained_documents`] with the response metadata and + /// proof envelope attached. + #[wasm_bindgen( + js_name = "getChainedDocumentsWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped" + )] + pub async fn get_chained_documents_with_proof_info( + &self, + query: ChainedDocumentsQueryJs, + ) -> Result { + let query = parse_chained_documents_query(self, query).await?; + let (chained, metadata, proof) = + ChainedDocuments::fetch_with_metadata_and_proof(self.as_ref(), query.clone(), None) + .await?; + let result = chained_result_to_js(&chained.unwrap_or_default(), &query)?; + Ok(ProofMetadataResponseWasm::from_sdk_parts( + result, metadata, proof, + )) + } +} diff --git a/packages/wasm-sdk/src/queries/document.rs b/packages/wasm-sdk/src/queries/document.rs index 67ea4f7fda5..ed97c88f8b4 100644 --- a/packages/wasm-sdk/src/queries/document.rs +++ b/packages/wasm-sdk/src/queries/document.rs @@ -195,26 +195,26 @@ extern "C" { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct DocumentsQueryInput { - data_contract_id: IdentifierWasm, - document_type_name: String, +pub(super) struct DocumentsQueryInput { + pub(super) data_contract_id: IdentifierWasm, + pub(super) document_type_name: String, #[serde(rename = "where", default)] - where_clauses: Option>, + pub(super) where_clauses: Option>, #[serde(rename = "orderBy", default)] - order_by: Option>, + pub(super) order_by: Option>, #[serde(default)] - limit: Option, + pub(super) limit: Option, #[serde(rename = "startAfter", default)] - start_after: Option, + pub(super) start_after: Option, #[serde(rename = "startAt", default)] - start_at: Option, + pub(super) start_at: Option, /// Count-query knob: SQL-shaped `GROUP BY` field list, /// mirroring the v1 wire `group_by: repeated string` field /// one-to-one. Ignored by the regular document-fetch path. /// See the TypeScript declaration for the supported shapes. /// Default empty (aggregate count). #[serde(rename = "groupBy", default)] - group_by: Option>, + pub(super) group_by: Option>, // Order direction for count results flows through the existing // `orderBy` field — the first clause's direction controls // split-mode entry ordering and `(In + prove)` walk order. No @@ -222,7 +222,7 @@ struct DocumentsQueryInput { /// Time-range bucket selections (`IN_TIME_RANGE`), each `{ field, /// selector }`. v1-only; resolved server-side from block time. #[serde(rename = "timeRange", default)] - time_range: Option>, + pub(super) time_range: Option>, } #[derive(Deserialize)] @@ -255,7 +255,7 @@ fn parse_document_history_query( }) } -async fn build_documents_query( +pub(super) async fn build_documents_query( sdk: &WasmSdk, input: DocumentsQueryInput, ) -> Result { diff --git a/packages/wasm-sdk/src/queries/mod.rs b/packages/wasm-sdk/src/queries/mod.rs index bcccfc7af19..7c178750e0a 100644 --- a/packages/wasm-sdk/src/queries/mod.rs +++ b/packages/wasm-sdk/src/queries/mod.rs @@ -1,4 +1,5 @@ pub mod address; +pub mod chained_document; pub mod data_contract; pub mod document; pub mod document_ranked; From e53b79c5cf4cdc9cd0466c291f648cde30690859 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 31 Aug 2026 22:51:35 +0200 Subject: [PATCH 2/3] docs(js-sdk): describe the single merged chained proof MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JS surface's behavior is unchanged by the merged-proof rework (the verification lives below the FetchMany boundary), but its docs still described the two-proof envelope — align them. Co-Authored-By: Claude Fable 5 --- packages/js-evo-sdk/README.md | 2 +- packages/js-evo-sdk/src/documents/facade.ts | 9 +++++---- .../platform/IndexOnlyDocument.spec.js | 7 ++++--- .../wasm-sdk/src/queries/chained_document.rs | 20 +++++++++---------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/js-evo-sdk/README.md b/packages/js-evo-sdk/README.md index c13c80f44ed..a87c8c3b8a0 100644 --- a/packages/js-evo-sdk/README.md +++ b/packages/js-evo-sdk/README.md @@ -191,7 +191,7 @@ try { ## Chained queries (provable semi-join) -A `refersTo: permanentDocument` declaration also lights up the read side: a **chained query** answers `SELECT * FROM post WHERE $id IN (SELECT postId FROM like WHERE $ownerId = me)` in one verified round trip. The node returns the inner indexOnly page and the referenced documents with two proofs bound to one quorum-signed state root, and the SDK re-derives the outer query from the *proven* inner values — the node cannot substitute, omit, or inject joined documents (a missing referenced document fails verification outright, since `permanentDocument` references cannot dangle). +A `refersTo: permanentDocument` declaration also lights up the read side: a **chained query** answers `SELECT * FROM post WHERE $id IN (SELECT postId FROM like WHERE $ownerId = me)` in one verified round trip. The node returns the inner indexOnly page and the referenced documents under ONE merged proof — a single quorum-signed state root by construction — and the SDK re-derives the outer query itself and checks it against the *proven* inner values — the node cannot substitute, omit, or inject joined documents (a missing referenced document fails verification outright, since `permanentDocument` references cannot dangle). ```ts // The posts I liked, newest page first by postId. diff --git a/packages/js-evo-sdk/src/documents/facade.ts b/packages/js-evo-sdk/src/documents/facade.ts index eb0d896f4ab..2a3b09793c0 100644 --- a/packages/js-evo-sdk/src/documents/facade.ts +++ b/packages/js-evo-sdk/src/documents/facade.ts @@ -30,10 +30,11 @@ export class DocumentsFacade { * * "Posts I liked" in one verified round trip: inner `like` through * its byLiker-style index, join `postId`, outer `post`. Both halves - * are proof-verified against one quorum-signed state root, and the - * outer half is re-derived from the proven inner values — the - * responding node cannot steer the join. Paginate on the inner query - * with a range clause on the join property. + * ride ONE merged proof — a single quorum-signed state root by + * construction — and the outer query is re-derived and checked + * against the proven inner values, so the responding node cannot + * steer the join. Paginate on the inner query with a range clause on + * the join property. */ async chained(query: wasm.ChainedDocumentsQuery): Promise { const w = await this.sdk.getWasmSdkConnected(); diff --git a/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js b/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js index 4940e1907d9..3711d3b7ab0 100644 --- a/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js +++ b/packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js @@ -302,9 +302,10 @@ describe('Platform', () => { // The provable semi-join: SELECT * FROM post WHERE $id IN // (SELECT postId FROM like WHERE $ownerId = me). Served by the // dedicated getChainedDocuments endpoint through the WASM SDK, - // which verifies BOTH grovedb proofs against one quorum-signed - // root and re-derives the outer query from the proven inner - // values — the node cannot steer the join. + // which verifies ONE merged grovedb proof against the + // quorum-signed root, re-deriving the outer query and checking + // it against the proven inner values — the node cannot steer + // the join. const { sdk: evoSdk } = await createPlatformProofVerifier .getEvoSdkForNetwork(process.env.NETWORK); diff --git a/packages/wasm-sdk/src/queries/chained_document.rs b/packages/wasm-sdk/src/queries/chained_document.rs index 9418278e2f0..217d3487e7a 100644 --- a/packages/wasm-sdk/src/queries/chained_document.rs +++ b/packages/wasm-sdk/src/queries/chained_document.rs @@ -3,11 +3,11 @@ //! `SELECT * FROM WHERE $id IN (SELECT FROM //! WHERE …)` served by the dedicated `getChainedDocuments` RPC: //! the inner indexOnly page and the outer by-ids fetch derived from its -//! proven values come back as TWO grovedb proofs bound to ONE -//! quorum-signed state root. The SDK verifies the composition — the -//! outer query is re-derived from the PROVEN inner results, never taken -//! from the node — so the join cannot be steered by the responding -//! server. The request deliberately carries no outer clauses; filter +//! proven values come back as ONE merged grovedb proof — a single +//! quorum-signed state root by construction. The SDK verifies the +//! composition — the outer query is re-derived from the response's +//! untrusted join-value hint and checked against the PROVEN inner +//! results — so the join cannot be steered by the responding server. The request deliberately carries no outer clauses; filter //! the returned outer documents locally if needed. //! //! Pagination lives on the inner query alone: order by the join @@ -172,11 +172,11 @@ impl WasmSdk { /// Run a chained document query (provable semi-join) and return /// both verified halves. /// - /// The composition is always proof-verified: the two grovedb proofs - /// must commit to one quorum-signed root, and the outer half must - /// match the query the SDK derives from the proven inner values — - /// exactly (a missing referenced document is a verification error, - /// not an absence). + /// The composition is always proof-verified: one merged grovedb + /// proof commits to one quorum-signed root, and the proven outer + /// documents must match the proven inner join values exactly (a + /// missing referenced document is a verification error, not an + /// absence). #[wasm_bindgen( js_name = "getChainedDocuments", unchecked_return_type = "ChainedDocumentsResult" From 18329d87d2be704e609ada62fa92c3b0737abb91 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 1 Sep 2026 01:23:32 +0200 Subject: [PATCH 3/3] docs(wasm-sdk): chained queries ride the getDocuments V1 wire now Behavior is unchanged (the wire fold lives below the Fetch boundary); align the module docs. Co-Authored-By: Claude Fable 5 --- packages/wasm-sdk/src/queries/chained_document.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/wasm-sdk/src/queries/chained_document.rs b/packages/wasm-sdk/src/queries/chained_document.rs index 217d3487e7a..d9007796b93 100644 --- a/packages/wasm-sdk/src/queries/chained_document.rs +++ b/packages/wasm-sdk/src/queries/chained_document.rs @@ -1,7 +1,8 @@ //! Chained document queries — the provable semi-join surface. //! //! `SELECT * FROM WHERE $id IN (SELECT FROM -//! WHERE …)` served by the dedicated `getChainedDocuments` RPC: +//! WHERE …)` riding the typed `getDocuments` V1 wire (the +//! request's `chained` message): //! the inner indexOnly page and the outer by-ids fetch derived from its //! proven values come back as ONE merged grovedb proof — a single //! quorum-signed state root by construction. The SDK verifies the