Skip to content

feat(platform)!: let an indexOnly type index the elements of a typed array (PV14) - #5390

Merged
QuantumExplorer merged 3 commits into
v5.0-devfrom
claude/index-only-typed-array-index
Oct 12, 2026
Merged

QuantumExplorer merged 3 commits into
v5.0-devfrom
claude/index-only-typed-array-index

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: An index-only document type (one whose documents exist only as index entries, like Yappr's like) can now have an index over the items of a list property, such as a post's tags. A like tagged dash and news gets one entry under dash and one under news, so apps can count and list likes per tag. Until now the contract parser refused this shape.

Value: Yappr is moving a post's single hashtag to a tags list (up to 4). With this, its like type keeps per-tag counts and per-tag trending working, at one entry per tag instead of a separate document per tag.

Risks: Medium. It touches how index-only documents are created, checked on delete and read back, all on protocol version 14, which is unreleased. Every contract valid today parses and behaves the same: no index-only type could name a list before. The main new rule is that another index must keep each document's entries apart, so the list index never becomes an accidental uniqueness rule, and deletes must repeat the exact list (same items, same order) the like was created with. Fee estimates were checked to stay at or above the real fee for 1 and 4 tags, with and without a time window. Every index-only create now also pays a small processing fee for hashing its row commitment once per entry, which was previously unbilled work.

Issue being fixed or feature implemented

Since #5370 (and the SDK side in #5372) a stored document type can index each element of a typed array. An indexOnly type was refused (validate_indexed_array_index: "an indexOnly type's indexes are uniqueness rules over each document's values, and an index of a typed array enters a document once per element"). Yappr's like is indexOnly and needs a row per tag once posts carry tags (typed array, maxItems 4, uniqueItems, immutable), so per-tag counts and trending keep working.

This PR covers the indexOnly per-element index only. Two related pieces are separate work and stay as they are:

  • refersTo where entries over typed arrays;
  • preallocated / summableOffCountIndex through an array-equality where. Both stay refused on an index over a typed array here.

Before

"like": {
  "indexOnly": true, "documentsMutable": false, "canBeDeleted": true,
  "properties": {
    "postId": { "type": "identifier", "position": 0 },
    "tags": { "type": "array", "items": { "type": "string", "maxLength": 61 }, "maxItems": 4, "uniqueItems": true, "position": 1 },
    "postAuthor": { "type": "identifier", "position": 2 }
  },
  "required": ["$createdAt", "postId", "postAuthor"],
  "indices": [
    { "name": "byPost", "properties": [{ "postId": "asc" }], "terminal": "$ownerId", "rangeCountable": true },
    { "name": "byTagPost", "properties": [{ "tags": "asc" }, { "postId": "asc" }], "rangeCountable": true },
    { "name": "byTrendTagPost", "properties": [{ "$createdAt": "asc" }, { "tags": "asc" }, { "postId": "asc" }],
      "terminal": "$ownerId", "rangeCountable": true,
      "timeRange": { "on": "$createdAt", "range": 86400, "step": 21600, "ttl": 604800 }, "outlivesDelete": true },
    { "name": "byAuthorPostTime", "properties": [{ "postAuthor": "asc" }, { "postId": "asc" }, { "$createdAt": "asc" }], "terminal": "$ownerId" }
  ]
}
InvalidContractStructure: index "byTagPost" of document type "like" names the typed array "tags":
an indexOnly type's indexes are uniqueness rules over each document's values, and an index of a
typed array enters a document once per element

After

The same contract registers. A like { postId: P, postAuthor: A, tags: ["dash", "news"] } by owner O writes:

byPost          postId/P/0                      -> O
byTagPost       tags/"dash"/postId/P/0          -> O
                tags/"news"/postId/P/0          -> O
byTrendTagPost  <window>/tags/"dash"/postId/P/0 -> O   (x 4 windows)
                <window>/tags/"news"/postId/P/0 -> O   (x 4 windows)
byAuthorPostTime postAuthor/A/postId/P/$createdAt/T/0 -> O

each entry holding the same row commitment. A like without tags (absent, null or []) is in neither tag index.

like O/P tags ["solo"]          -> DuplicateUniqueIndexError (byPost already holds O's like of P)
indexOnlyDelete tags ["news","dash"] -> DocumentNotFoundError (the commitment holds the list in order)
indexOnlyDelete tags ["dash","news"] -> deleted, every per-tag entry removed

Refused shapes, each with InvalidContractStructure (10231):

{ "name": "byTagPost", "properties": [{ "tags": "asc" }, { "postId": "asc" }] }

with no index whose whole key is inside {postId, $ownerId} (for example only [postAuthor] -> $ownerId beside it): "its key without the array ... holds the whole key of no index that keeps one entry for every document"; with "uniqueItems": false on tags: "must declare uniqueItems: true"; with "preallocated": true, or as a summableOffCountIndex index: refused naming that keyword.

What was done?

Registration (rs-dpp, apply_index_only, which runs on every parse, validating or not). An index of an indexOnly type that names a typed array (Index::indexed_array) is admitted when:

  • another index of the type keeps exactly one entry for every live document (keys_each_live_document_by_its_values: keeps entries, skips no document, does not outlive deletes, involves no $createdAt, and now also names no typed array) and its whole key (properties and terminal components) is inside this index's key without the array. Two live documents could then only share an entry of the array index by sharing that index's entry, which the second create is refused for: the array index is no uniqueness rule of its own, and no entry of it stands for two documents;
  • the array declares uniqueItems: true, so each element of a list has an entry of its own (and an update can not remove it, since every parse runs the rule);
  • the index is neither preallocated nor a summableOffCountIndex index;
  • it names one typed array, a top-level property. The rules every indexed array follows run under full validation only; these two are what the entry probes and the row commitment rely on, so an indexOnly type checks them on every parse too.

The covering check shares one helper (keyed_by_a_document_index) with the existing outlivesDelete rule, which asks the same question.

Required-property coverage no longer counts an index of a typed array for any property but the array itself: it has no entry for a document whose list is empty, so a required postAuthor held only by [tags, postId, postAuthor] would be committed and stored nowhere for an untagged like. Such a type is now refused.

The #5370 rules (one array, top-level, not unique or contested, nullSearchable, no ranking above the array, maxItems at most 16, or 4 under a window, 256 entries per document) still apply. The old indexOnly refusal in validate_indexed_array_index is gone. keys_each_live_document_by_its_values excluding typed arrays also means an array index is never the proof index (it has no entry for an empty list) and never a counter's source.

Row commitment (index_only_row_commitment). A typed array is committed as its whole list in the document's order: name ‖ u32 len ‖ (u32 len ‖ element key)*, each element keyed as its entries are (serialize_value_for_key). A delete carrying the list reordered, shortened, lengthened or changed hashes differently and fails every probe, byPost's included. An empty, null and absent list write the same entries and are committed alike (nothing, name included), so a delete may carry any of the three.

Entry paths (index_only_entry_paths_and_key). The array's level fans out over the distinct sorted elements (get_raw_array_elements_for_document_type, what the write walkers use), times the windows of a bucketed first level; a list without elements gives no paths. The skip set is now judged with the walkers' own definition (takes_part_in_index_by / document_carries). The delete commitment probe, its fee estimate, the batch entry tracker and the client document cost all read these paths, so they see every element. The write and delete walkers already fanned out per element (#5370) and needed no change.

Commitment hash billing (add_reference_for_index_level_for_contract_operations/v0). Every indexOnly entry insert now bills its row commitment's double-SHA256 as a Sha256_2 function operation sized by the real preimage; the dry run bills the widest preimage the type allows (index_only_row_commitment_max_preimage_size), so the estimate stays an upper bound. A typed array makes both the preimage and the number of entries larger, so this is now paid for. This applies to every indexOnly create, with or without a typed array.

Create probe (document_create_transition_action/state_v1). The create's state validation no longer reads an array index's entries: the covering index is probed and refuses any collision first, so the reads per element could never find a duplicate. The storage insert's if-not-exists stays the backstop.

Queries (synthesize_index_only_document). An entry under an element is rebuilt as a document listing that one element (tags: ["dash"]), a list of the array's type rather than a bare element. contains and containsAny route through the existing matchers; containsAny returns a like once per listed tag it holds, each a distinct projection.

Comments and book. Comments that assumed an indexOnly type indexes no typed array now give the actual reason. book/src/contract-keywords/index-only.md gets a "Typed arrays" section with the example and rules; typed-arrays.md, indexes.md and book/src/drive/index-only-document-types.md (commitment encoding, constraint matrix) follow.

In-place changes to shipped generations

No vN was added: protocol version 14 is unreleased, and every edited path is reached only by data that exists from it.

  • try_from_schema/common/mod.rs (shared by document type generations 1 to 3, protocol versions 9 to 14): the removed refusal sits in validate_indexed_array_index, which runs only when admit_indexed_arrays is set (generation 3, protocol version 14). index_only_array_index_error is called only from apply_index_only, which only generation 3 calls. The summable_off_count_index_error wording change is in the same generation 3 path.
  • Index::keys_each_live_document_by_its_values (unversioned): read only by apply_index_only, summable_off_count_index_error and Drive's index_only_proof_index, all indexOnly-only, and indexOnly types exist only from protocol version 14. The new term reads indexed_array, which is None on every index of every earlier generation.
  • Drive's index_only_entry_paths_and_key, index_only_row_commitment and synthesize_index_only_document (unversioned): reached only for indexOnly types (protocol version 14). The new branches fire only for an index or property that names a typed array, which no indexOnly type could do before this PR. The skip-set check moving to the walkers' document_carries differs from the old get_raw_for_document_type(..).is_none() only for a skip value present as null, which the schema refuses on every indexOnly transition.
  • add_reference_for_index_level_for_contract_operations/v0 (the terminal writer): the hash billing sits in add_index_only_terminal_item_operations, reached only for indexOnly types (protocol version 14).
  • document_create_transition_action/state_v1 (document create state validation): the probe filter skips indexes with indexed_array, which only an indexOnly type of protocol version 14 can carry after this PR; the branch is inside if document_type.index_only().

How Has This Been Tested?

New tests:

  • rs-dpp indexed_array_tests (validating and non-validating parses): admits the Yappr shape, with the array in each tag index's skip set and no tag index keeping each document once; refuses an array index no other index keeps apart (an index outside its key, and candidates involving $createdAt); refuses one without uniqueItems; refuses preallocated and summableOffCountIndex on an array index; refuses an indexOnly index naming two arrays or a nested one on every parse; refuses a required property only an array index holds, while a required array its own index holds parses.
  • rs-drive index_only_array_index_e2e_tests (13 tests): every entry's commitment hash is billed, one Sha256_2 per entry (22 for a 4-tag like), by the real preimage when applied and the type's widest in the dry run; an array index sorting first is never the proof index, and a tagged and an untagged like are each proven through byPost; when only the outliving window involves $createdAt, a like deletes by its values without a timestamp, leaves its per-tag window entries, refuses reordered tags and drains later; one entry per tag under byTagPost and per window and tag under byTrendTagPost, all carrying the row commitment, and the probes read exactly those paths; a second like of a post by its owner is refused whatever its tags while another owner's like with the same tags goes through; a delete removes every per-tag entry and prunes a tag no other like holds; deletes with tags reordered, shortened, lengthened, changed, empty, null or absent are refused and remove nothing; absent, null and empty tags enter no tag index and delete in any of the three forms; per-tag window entries outlive a delete and drain with their window, and a like whose cleared windows drained still deletes; dry-run fees upper-bound applied fees (insert storage and processing, delete processing) for 1 and 4 tags with no window, an outliving window and a cleared window; contains, contains + postId, the terminal-bound "did I like this under this tag" read and containsAny prove to the live root hash; per-tag counts, a post's count under a tag, containsAny grouped counts and a window's count under a tag agree unproved and proved (point lookup verifier).
  • rs-drive fixture yappr-likes-tags-contract.json added to fixture_contracts::CONTRACTS, so the layout and client cost checks (document::cost, document::layout) run over it against real inserts.
  • rs-drive-abci contract_structure_error_tests: a contract update dropping uniqueItems from the tags a stored indexOnly index names is refused, paid, in check_tx and the block.
  • rs-drive-abci index_only::array_index_tests: through process_raw_state_transitions, a tagged like is created and proven through the proof index, a second like of the post under another tag is refused with DuplicateUniqueIndexError, a delete with the tags reordered is refused with DocumentNotFoundError, and the tags as created delete and are proven absent.

Targeted runs, all passing:

cargo test -p dpp --lib --features all_features -- index_only summable_off_count outlives_delete preallocat typed_array
cargo test -p drive --lib -- index_only array_index outlives_delete skip_if_absent summable_off_count preallocated document::cost document::layout expiration structure::tests
cargo test -p drive-abci --lib -- batch::tests::document::index_only contract_structure data_contract_update
cargo clippy -p dpp -p drive -p drive-abci --all-features --all-targets -- -D warnings
cargo check -p drive --no-default-features --features verify

Breaking Changes

Consensus: at protocol version 14 (unreleased) a contract with an indexOnly index over a typed array that meets the rules above now registers, where it was refused, and every indexOnly create's processing fee now includes its commitment hashes. No earlier protocol version changes.

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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 82a4d3b

  • Bots — coderabbitai ✓ · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed
  • Build running
  • Approvals — you own every area touched; none needed

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

Summary by CodeRabbit

  • New Features
    • Index-only documents can now index unique typed-array elements, with results available through array-element queries and counts.
    • Added support for combining array-element indexes with time-windowed indexes.
  • Bug Fixes
    • Deletion checks now bind the complete ordered array, so changed or reordered values are rejected; absent, null, and empty arrays are treated consistently.
    • Duplicate entries are checked across array-indexed documents.
  • Documentation
    • Clarified array-index requirements, query behavior, deletion checks, and supported index combinations.

…array (PV14)

An indexOnly type may now name a typed array in an index, as a stored type
can since #5370: a document has one entry per distinct element of its list,
and none for an empty, null or absent list.

Registration (apply_index_only, every parse) admits such an index when
another index keeping one entry per document (no $createdAt, skip,
outlivesDelete or typed array) has its whole key inside the array index's
key without the array, the array declares uniqueItems, and the index is
neither preallocated nor a summableOffCountIndex index. An index naming a
typed array is never the proof index or a counter's source.

The row commitment binds the whole list in its order, so a delete carrying
the tags reordered, shortened or changed is refused; an empty, null and
absent list are committed alike. The entry probes fan out per element, and
synthesis rebuilds an entry as a document listing its one element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: dashpay/platform/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a6b2b277-2fc8-4b25-9551-c8d105783fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 371a3b3 and 82a4d3b.


📒 Files selected for processing (25)
  • book/src/contract-keywords/index-only.md
  • book/src/contract-keywords/indexes.md
  • book/src/contract-keywords/typed-arrays.md
  • book/src/drive/index-only-document-types.md
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/indexed_array_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/index/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/index/preallocation.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_create_transition_action/state_v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/index_only.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_update/contract_structure_error_tests.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_array_index_e2e_tests.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/mod.rs
  • packages/rs-drive/src/drive/document/cost/writes.rs
  • packages/rs-drive/src/drive/document/delete/remove_indices_for_index_level_for_contract_operations/v2/mod.rs
  • packages/rs-drive/src/drive/document/fixture_contracts.rs
  • packages/rs-drive/src/drive/document/index_only.rs
  • packages/rs-drive/src/drive/document/index_only_row_commitment.rs
  • packages/rs-drive/src/drive/document/insert/add_indices_for_index_level_for_contract_operations/v2/mod.rs
  • packages/rs-drive/src/drive/document/insert/add_reference_for_index_level_for_contract_operations/v0/mod.rs
  • packages/rs-drive/src/drive/document/mod.rs
  • packages/rs-drive/src/query/conditions.rs
  • packages/rs-drive/src/query/index_only_synthesis.rs
  • packages/rs-drive/tests/supporting_files/contract/yappr-likes/yappr-likes-tags-contract.json
  • packages/rs-platform-version/src/version/v14.rs

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



📝 Walkthrough

Walkthrough

This change permits constrained typed-array indexes on index-only document types. It adds per-element index paths, ordered array row commitments, schema validation rules, query synthesis, and coverage for creation, deletion, proofs, fees, queries, and counts.

Changes

Index-only typed-array indexing

Layer / File(s) Summary
Typed-array registration rules
book/src/contract-keywords/*, book/src/drive/index-only-document-types.md, packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/..., packages/rs-dpp/src/data_contract/document_type/index/*, packages/rs-platform-version/src/version/v14.rs, packages/rs-drive-abci/.../contract_structure_error_tests.rs
Validation now allows an index-only index to name one top-level typed array when it has uniqueItems: true and a qualifying non-array index. It rejects unsupported array index configurations and prevents array indexes from covering other required properties. Tests cover registration, validation modes, and contract updates.
Entry generation and row commitments
book/src/drive/index-only-document-types.md, packages/rs-drive/src/drive/document/index_only.rs, packages/rs-drive/src/drive/document/index_only_row_commitment.rs, packages/rs-drive/src/drive/document/insert/..., packages/rs-drive/src/drive/document/mod.rs, packages/rs-drive/src/drive/document/fixture_contracts.rs, packages/rs-drive/tests/supporting_files/contract/yappr-likes/*, packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/*
Index paths now expand across array elements and applicable time buckets. Row commitments encode ordered, length-framed array element keys, and insertion records hash costs. Tests cover per-element entries, commitments, and fee estimates.
Duplicate checks and deletion
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/..., packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/index_only.rs, packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_array_index_e2e_tests.rs, book/src/drive/index-only-document-types.md
Create duplicate probes skip array indexes. Tests verify duplicate handling, deletion against ordered array contents, entry removal, and retention of outliving entries.
Array queries and result synthesis
packages/rs-drive/src/query/*, packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_array_index_e2e_tests.rs, book/src/contract-keywords/index-only.md
Query results synthesize the matched array element as a single-element list. Tests cover contains and containsAny queries, counts, and proof checks.

Priority: ➖ Normal

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Contract as Validated index-only contract
  participant Paths as Index path generation
  participant Commitment as Row commitment
  participant GroveDB
  Contract->>Paths: Provide indexed array and time-range rules
  Paths->>GroveDB: Generate one path per array element and applicable time bucket
  Contract->>Commitment: Provide document properties and typed-array definition
  Commitment->>GroveDB: Supply commitment for index entries
Loading

Suggested reviewers: thepastaclaw


Merge Risk: ⚪ Minimal · up to 82a4d

The change lets index-only document types, such as Yappr likes, index each tag in a list. It restricts which indexes may do this, and the review found no open defect in fee charging or required-value storage. Deletes must supply the tags in their original order, which is the stated design. The change is ready to merge after normal checks.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 88 functions across 20 files. (5 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the primary change: allowing index-only document types to index typed-array elements in platform version 14.
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.

Full details: Docstring Coverage

Explanation

Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 88 functions across 20 files. (5 skipped: 5 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions github-actions Bot added this to the v5.0.0 milestone Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-10-12T19:18:34.227Z

@thepastaclaw

thepastaclaw commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit 82a4d3b) · triage: critical
████░░░░░░░░░░░░░░░░ 21% · about 35 min left · running for 9 min
✅ triage → ⏳ Phase 2 (1/5 lanes) → ⏳ Phase 1 (1/2 lanes) → ▫️ verify 2 → ▫️ fresh Phase 2 → ▫️ fresh verify → ▫️ publish
Estimated from recent reviews of this tier · updated 19:29 UTC · live progress

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Oct 12, 2026
- the entry probes judge the skip set with the walkers' own definition
  (`takes_part_in_index_by` / `document_carries`)
- the create's state probe skips an index of a typed array: the covering
  index registration requires is probed and refuses any collision first
- apply_index_only also refuses, on every parse, an indexOnly index naming
  two typed arrays or a nested one
- the covering-key check is shared with the outlivesDelete rule
- the row commitment iterates the properties once
- the ContainsAny doc notes an indexOnly read returns a document per value
- tests: the proof index when a tag index sorts first, a delete without
  $createdAt when only an outliving window involves it, and a contract
  update dropping uniqueItems

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 2 threads unresolved — resolve them. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@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 — Phase 1 + Phase 2

Verified the supplied findings against exact head 4b56ddc. Two blocking issues remain: implicit array sparsity can leave required scalar values unrecoverable, and the newly admitted array commitment work is not charged on insertion; the PV14 snapshot also contains a stale rejection rule. This was static verification only: no builds or tests were run, and Rust workspace CI was queued in the supplied 2026-10-12T18:15:10Z snapshot.

🔴 2 blocking | 🟡 1 suggestion(s)

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate change to consensus-critical document indexing, validation, deletion, querying, storage keys, and fee accounting in the protocol-versioned Drive/DPP state machine, including the functions that add and remove index entries and synthesize index-only results.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer, muse-spark-1.3-contributor — rust-quality (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity below 15% reserve: weekly 14% left, 5h 100% left), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — architecture-layering (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — platform-versioning (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — rust-quality (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 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-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs`:
- [BLOCKING] packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs:4643-4648: Account for implicit array skips in required-property coverage
  The new admission exposes a required-value storage hole. `parse_indices` adds an indexed array to `skip_if_absent_properties` while leaving the declaration boolean `skip_if_absent` false, but required-property coverage below still accepts an index whenever `!index.skip_if_absent && !index.outlives_delete`. A type with required `postId` and `postAuthor`, optional `tags`, `byPost = [postId] -> $ownerId`, and `byTagPostAuthor = [tags, postId, postAuthor] -> $ownerId` therefore passes both the covering-key rule and property coverage. With absent or empty tags, the array index writes nothing and only `byPost` remains: `postAuthor` is hashed into the row commitment but stored in no recoverable entry. A client that loses that value cannot reconstruct the tuple required for deletion. For required scalar coverage, check effective participation rather than the declaration boolean, while retaining the array's empty-list semantics. Add regression coverage for validating and non-validating parses of this shape.
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs:4643-4648: Record the indexOnly typed-array rule in the PV14 version snapshot
  This branch now admits qualifying indexOnly typed-array indexes, but item 92 in `packages/rs-platform-version/src/version/v14.rs` still states that parser generation 3 refuses indexes on an indexOnly type. Amend that item and the `PLATFORM_V14` contract-version annotation to describe the new admission constraints and their PV14 activation boundary. No additional version slot is needed for this finding: the affected indexOnly and typed-array data is unavailable in historical protocol generations, and PV14 is unreleased.

In `packages/rs-drive/src/drive/document/index_only_row_commitment.rs`:
- [BLOCKING] packages/rs-drive/src/drive/document/index_only_row_commitment.rs:230-236: Typed-array row commitment work is not charged on create
  The newly admitted array path serializes every element into the commitment preimage, and the terminal insert builder recomputes and double-hashes that full preimage for every emitted entry, including each array element and time window. In `add_reference_for_index_level_for_contract_operations/v0/mod.rs`, the commitment call at lines 549–553 adds no corresponding `FunctionOperation`; estimation instead constructs a padded placeholder value. GroveDB operations account for storing the fixed-size commitment and maintaining the trees, not this separate hash over the full list. The delete validation path explicitly charges its commitment with `FunctionOp::new_with_byte_count(HashFunction::Sha256_2, preimage_size)`. Although missing scalar-insert hash billing predates this PR, this change newly increases the uncharged work through larger preimages and array fan-out, so the incremental defect is in scope. Account for it in Drive's insertion operation assembly and its estimate, or compute and charge one shared commitment per document. An estimate-versus-applied-fee comparison alone cannot detect work omitted from both fee totals.

…lues out of array-only coverage

- an index of a typed array no longer covers a required property other than
  the array itself: it has no entry for a document whose list is empty, so
  such a property could be committed and stored nowhere
- every indexOnly entry insert bills its row commitment's double-SHA256 by
  its preimage; the dry run bills the widest preimage the type allows
- the PV14 notes (item 92 and the contract versions annotation) describe the
  indexOnly typed array rules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — coderabbitai ✓ · thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@QuantumExplorer
QuantumExplorer merged commit 945fcf6 into v5.0-dev Oct 12, 2026
28 of 30 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/index-only-typed-array-index branch October 12, 2026 19:27
QuantumExplorer added a commit that referenced this pull request Oct 12, 2026
…-where

#5388 and #5390 are merged (squashed, with their review fixes). Resolved as
v5.0-dev plus this branch's own changes: the export list keeps
where_list_elements beside where_lists_elements_read, note 94 keeps the
element billing and points at 95, note 92 now says preallocated and counter
array indexes follow 95, and the book keeps both the commitment billing and
the per-element keying.
@QuantumExplorer QuantumExplorer mentioned this pull request Oct 13, 2026
8 of 26 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants