Skip to content

test(test-suite): indexOnly document lifecycle functional spec - #4510

Merged
QuantumExplorer merged 2 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:test/index-only-lifecycle-spec
Aug 28, 2026
Merged

test(test-suite): indexOnly document lifecycle functional spec#4510
QuantumExplorer merged 2 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:test/index-only-lifecycle-spec

Conversation

@PastaPastaPasta

@PastaPastaPasta PastaPastaPasta commented Aug 28, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

indexOnly document types (protocol v14) had full e2e coverage at the Rust layers (rs-drive index_only_e2e_tests, rs-drive-abci batch/tests/document/index_only), but no JS functional spec in platform-test-suite — the layer that exercises the whole stack (js-dash-sdk → wasm-dpp → DAPI → drive-abci → drive) against a live network. Two gaps blocked writing one, and the spec surfaced a third:

  1. The legacy DocumentFactory (the construction path wasm-dpp / js-dash-sdk use) mapped every delete to a by-id DocumentDeleteTransition, which the structure gates reject for indexOnly types — deletion of an indexOnly document is its own transition kind, DocumentIndexOnlyDeleteTransition (delete-by-values).
  2. Found by the new spec's first live run: DriveDocumentQuery::execute_raw_results_no_proof — which the non-proof GetDocuments handler calls directly — returned the raw grovedb elements for indexOnly types. Those are 32-byte row commitments, not documents, so clients blew up parsing them (Document::from_bytes: unknown version, received: 111).

What was done?

  • feat(dpp): DocumentFactoryV0::document_delete_transitions now picks the delete kind from the doctype's storage mode, mirroring the dispatch already in the batch-transition v0/v1 builders: index_only()DocumentIndexOnlyDeleteTransition::from_document (no revision gate — synthesized index-only documents carry none), stored types → the by-id path, byte-identical to before. Unit test pins the dispatch, the carried data, and that $createdAt stays out when the doctype doesn't require it. No wasm-dpp changes were needed — its transition wrappers already handle the kind.
  • fix(drive): execute_raw_results_no_proof_internal routes indexOnly queries through the same synthesis executor Drive::query_documents uses and serializes the synthesized documents into the wire bytes clients expect. A subset-index projection lacks required properties the serialized document format cannot omit (serialize_v3 has no presence flag for required fields), so it is refused with guidance to query through a covering index — projections still travel the proved read surface, where the client synthesizes them from the proof itself. New rs-drive e2e test pins the covering-index from_bytes round trip and the subset refusal.
  • test(suite): new test/functional/platform/IndexOnlyDocument.spec.js, modeled on Document.spec.js/contacts.spec.js. It registers an inline yappr-style contract (stored post + indexOnly like with refersTo/propertyAgreement) and covers, in order:
    1. contract with an indexOnly doctype registers and round-trips;
    2. a like on a nonexistent post is rejected (ReferencedEntityNotFoundError, 40120);
    3. a like whose hashtag disagrees with its post is rejected (propertyAgreement, ReferencedDocumentPropertyMismatchError, 40127);
    4. an agreeing like creates;
    5. an identical like by the same identity is rejected (DuplicateUniqueIndexError, 40105 — any existing entry collides);
    6. querying through the covering [hashtag, postId] index returns documents synthesized from index positions, carrying the created values and owner, with a deterministic $id that is not the create's id;
    7. a subset-index query (postId ==, byPost) is refused on the non-proof wire with covering guidance;
    8. fetching by $id is rejected with guidance (no primary-key tree);
    9. a by-values delete through documents.broadcast({ delete: [...] }) succeeds and the entry disappears from queries;
    10. a second identity can like the same post (same values, different terminal — not a duplicate), and cannot delete the first identity's like: deletes are self-authorizing (the entry tuple is computed with owner = signer), so it surfaces as DocumentNotFoundError (40101).

Every broadcast in the suite also verifies its executed-transition proof via the EvoSDK-backed proof verifier, which for indexOnly batches takes the ExecutionNotProvedwaitForAffectedState path — so the spec exercises the indexOnly executed-proof surface (outcome always AffectedState, never ExecutionProved) on each create and delete. The skipped/broken waitForStateTransitionResult.spec.js was deliberately not extended (it is describe.skip'd and stale).

How Has This Been Tested?

  • IndexOnlyDocument.spec.js: 10/10 passing against a local dashmate network (yarn setup + yarn start) built from this branch.
  • Document.spec.js: 8/8 passing on the same network (stored-type deletes/queries unaffected).
  • cargo test -p dpp (4018 passed) and cargo test -p drive --lib (3478 passed, includes the 33 indexOnly e2e tests and the new raw-reads test).
  • cargo clippy --all-targets -- -D warnings clean for dpp and drive.
  • yarn lint clean in platform-test-suite.

Breaking Changes

None. The by-id delete path is byte-identical for stored types; the raw no-proof read path previously returned unparseable bytes for indexOnly types (nothing could have depended on it).

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for deleting index-only documents using their property values, without requiring a revision.
    • Index-only query results can now be returned as complete serialized documents when the index covers all required properties.
  • Bug Fixes

    • Queries using incomplete index projections are now rejected with a clear unsupported-query error.
    • Added validation for duplicate references, authorization, deletion behavior, and document property consistency.

The legacy DocumentFactory mapped every delete to a by-id delete transition, which the network rejects for indexOnly document types. Branch on the doctype's storage mode and build DocumentIndexOnlyDeleteTransition (delete-by-values) instead, skipping the revision gate since synthesized index-only documents carry none. The by-id path is unchanged for stored types.
… reads

execute_raw_results_no_proof returned the raw grovedb elements for indexOnly types — 32-byte row commitments, not documents — so the non-proof GetDocuments endpoint served bytes no client could parse. Route indexOnly queries through the synthesis executor and serialize the synthesized documents. A subset-index projection lacks required properties the serialized document format cannot omit, so it is refused with guidance; projections still travel the proved read surface, where the client synthesizes them from the proof.
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 28, 2026
@PastaPastaPasta PastaPastaPasta changed the title test(suite): indexOnly document lifecycle functional spec test(test-suite): indexOnly document lifecycle functional spec Aug 28, 2026
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 78f4813b-84e1-4499-8d03-e59dd0a17717

📥 Commits

Reviewing files that changed from the base of the PR and between 52e8d4e and 15dcbcf.

📒 Files selected for processing (4)
  • packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js
  • packages/rs-dpp/src/document/document_factory/v0/mod.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_e2e_tests.rs
  • packages/rs-drive/src/query/mod.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds index-only document lifecycle coverage. It creates index-only delete transitions without revisions, serializes synthesized documents from covering indexes, rejects incomplete projections, and tests creation, querying, deletion, duplicate prevention, and owner authorization.

Changes

Index-only document behavior

Layer / File(s) Summary
Contract setup and index-only creation
packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js
The suite defines and registers index-only schemas. It tests reference validation, property agreement, and duplicate prevention.
Query synthesis and raw serialization
packages/rs-drive/src/query/mod.rs, packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_e2e_tests.rs, packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js
Index-only raw queries synthesize and serialize documents when indexes cover required properties. Incomplete projections return QuerySyntaxError::Unsupported. ID fetches remain rejected.
Value-based deletion transitions
packages/rs-dpp/src/document/document_factory/v0/mod.rs, packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js
Index-only documents now produce DocumentIndexOnlyDeleteTransition values without a revision. Tests verify property-based deletion and the resulting transition data.
Owner-scoped deletion and uniqueness
packages/platform-test-suite/test/functional/platform/IndexOnlyDocument.spec.js
Tests verify that one identity cannot delete another identity’s entry and that equivalent entries can exist for separate owners.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to 15dcb

The PR adds index-only document deletion and changes client-visible query serialization; lifecycle and unauthorized-deletion tests cover the main paths, but signer-to-owner authorization binding and atomic recovery across deletion-related updates still need explicit owner awareness or follow-up. The PR is mergeable with that bounded security and reliability risk acknowledged.

Sequence Diagram(s)

sequenceDiagram
  participant PlatformClient
  participant DocumentFactoryV0
  participant DocumentIndexOnlyDeleteTransition
  participant Drive
  PlatformClient->>DocumentFactoryV0: request deletion of synthesized indexOnly document
  DocumentFactoryV0->>DocumentIndexOnlyDeleteTransition: build transition from document properties
  DocumentIndexOnlyDeleteTransition-->>Drive: broadcast value-based deletion
  Drive-->>PlatformClient: deletion result
Loading

Suggested reviewers: quantumexplorer

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the added functional test coverage for the indexOnly document lifecycle. It does not mention the supporting DPP and Drive fixes, but the title need not cover every chan…
Full details: Title check

Explanation

The title accurately identifies the added functional test coverage for the indexOnly document lifecycle. It does not mention the supporting DPP and Drive fixes, but the title need not cover every change.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit 15dcbcf)

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Codex/Sol only (Phase 2 disabled)

The delete-transition and covering-index serialization changes match the PR's goal, but raw no-proof validation remains result-dependent for non-covering indexes. Empty or fully offset result sets succeed even though the identical query is rejected once it returns a row, so this remains an in-scope query-correctness suggestion. Source: reviewer backends — Codex (exact backend model ID unavailable in the supplied evidence) and Claude (exact backend model ID unavailable in the supplied evidence); final verifier backend — grok-4.5; orchestration only — openclaw-agent/cliproxy/gpt-5.6-sol (not reviewer evidence).

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-drive/src/query/mod.rs`:
- [SUGGESTION] packages/rs-drive/src/query/mod.rs:2539-2557: Reject non-covering indexes before examining query results
  Index coverage is currently validated only when serializing each synthesized document and converting `MissingRequiredKey` into `QuerySyntaxError::Unsupported`. If the storage query returns no documents—because there are no matches, the relevant path is absent, or the offset skips all matches—the iterator is empty and `collect()` succeeds. The same subset-index query therefore returns an empty successful response until a matching row becomes visible, at which point it starts failing. Validate the selected index's ability to reconstruct every required serializable field independently of the returned rows, and add an empty-result regression case so query validity remains state-independent.

Comment on lines +2539 to +2557
let serialized = documents
.into_iter()
.map(|document| {
document
.serialize(self.document_type, self.contract, platform_version)
.map_err(|error| match error {
ProtocolError::DataContractError(
dpp::data_contract::errors::DataContractError::MissingRequiredKey(_),
) => Error::Query(QuerySyntaxError::Unsupported(
"this indexOnly query's index does not cover every required \
property, so the documents it synthesizes cannot be \
serialized into a non-proof response; query through an \
index covering all properties, or use a proved query"
.to_string(),
)),
other => other.into(),
})
})
.collect::<Result<Vec<_>, Error>>()?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Reject non-covering indexes before examining query results

Index coverage is currently validated only when serializing each synthesized document and converting MissingRequiredKey into QuerySyntaxError::Unsupported. If the storage query returns no documents—because there are no matches, the relevant path is absent, or the offset skips all matches—the iterator is empty and collect() succeeds. The same subset-index query therefore returns an empty successful response until a matching row becomes visible, at which point it starts failing. Validate the selected index's ability to reconstruct every required serializable field independently of the returned rows, and add an empty-result regression case so query validity remains state-independent.

source: ['codex']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants