Skip to content

fix(dpp): make document type index update validation name-order independent - #4291

Merged
QuantumExplorer merged 7 commits into
v4.2-devfrom
claude/practical-poitras-e400e5
Aug 5, 2026
Merged

fix(dpp): make document type index update validation name-order independent#4291
QuantumExplorer merged 7 commits into
v4.2-devfrom
claude/practical-poitras-e400e5

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 5, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

IndexLevel::validate_update compares old/new index trees by level_identifier equality, but identifiers are assigned by an incrementing counter that walks the document type's indices — a BTreeMap keyed by index name. As a result, whether a semantically identical index change was rejected with a proper consensus error or with something opaque depended purely on where the changed index's name sorted relative to the document type's other indexes:

  • A change whose name sorted before an existing index renumbered every level → rejected with a useless "Invalid path" message.
  • The identical change under a late-sorting name passed the tree comparison, then hard-errored in the JSON-schema compatibility check (which has no rule for the indices keyword) → surfaced as an InternalError (code 1) tx result instead of a clean consensus error.
  • Renames of an unchanged index and unique/nullSearchable flips (which the tree comparison is blind to) hit the same lottery.

Notably, no index modification was ever acceptable end-to-end under v0 — everything that survived the tree diff was killed by the indices schema-compatibility hard error. The name only determined which kind of failure you got. Since paid-invalid transitions record their error codes in blocks, the outcome is consensus-visible and the fix must be protocol-gated.

What was done?

  • Added a v1 generation of DocumentTypeRef::validate_update (protocol v14) that replaces the IndexLevel tree comparison with a name-keyed comparison of the parsed index definitions: any added, removed or modified index is rejected with a deterministic DataContractInvalidIndexDefinitionUpdateError whose payload names the offending index (added index 'x' / removed index 'x' / changed index 'x'), independent of name sort order.
  • The accepted set is unchanged: v1 rejects all index modifications, exactly like v0 did end-to-end — only the rejections become deterministic, clean, and correctly labeled. Chain history is unaffected: PV ≤ 13 replays through the byte-for-byte frozen v0 flow, and system-contract index rewrites (e.g. the DPNS v2 update at PV13) bypass validate_update entirely.
  • Gated via new DPP_VALIDATION_VERSIONS_V5 (document_type.validate_update: 1) wired into v14 only; v13 stays on V4.
  • Moved the generation-independent helpers (validate_config, byte-array encoding stability, validate_schema) verbatim to a shared common module, following the try_from_schema extraction precedent (refactor(dpp): extract shared try_from_schema parsing helpers #4276). The v0 flow itself is untouched.

Known residual (kept out of scope, follow-up planned): reordering the indices array without changing the index set still reaches the schema-compat indices hard error, because the parsed definitions compare equal. That belongs in the compatibility validator, not here.

How Has This Been Tested?

  • New v1 tests: identical clean rejection for an added index regardless of whether its name sorts before or after existing indexes (the original repro: old j:[c], k:[a,b], adding [a] as "i" vs "z"); rename, removal, added property, and unique-flip all rejected cleanly; unchanged document type accepted.
  • New v0 pin test at protocol v13 locking in the frozen legacy asymmetry ("a -> Invalid path" vs schema-compat error) for replay safety.
  • Full dpp suite (3889 tests) passes with --all-features; platform-version tests pass; cargo check --workspace --all-targets, cargo clippy on both touched crates, and the verify-feature-gated drive build are clean.

Breaking Changes

None. The change is gated at protocol v14 (unreleased); shipped protocol versions keep byte-identical behavior, and the set of acceptable contract updates does not change at any version.

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

  • Bug Fixes

    • Improved document-type update validation for protocol version 14.
    • Prevented changes to immutable configuration, index definitions, and byte-array encoding layouts.
    • Added JSON-schema compatibility checks for updates.
    • Ensured index validation is consistent regardless of definition order.
    • Improved validation errors for added, removed, renamed, or modified indexes.
  • Tests

    • Added comprehensive coverage for configuration, schema, index, and byte-array encoding changes.

…endent

IndexLevel::validate_update compared index trees by level_identifier
equality, but identifiers are assigned by an incrementing counter that
walks the document type's indices — a BTreeMap keyed by index NAME. An
index change whose name sorted before existing indexes renumbered every
level and was rejected with an opaque "Invalid path", while the
semantically identical change under a late-sorting name passed the tree
comparison and instead hard-errored later in the JSON-schema
compatibility check (which has no rule for the `indices` keyword),
surfacing as an InternalError tx result. Which consensus outcome a
contract owner got therefore depended purely on the index name string.

At protocol v14, validate_update v1 replaces the tree comparison with a
name-keyed comparison of the parsed index definitions: any added,
removed or modified index is rejected with a deterministic
DataContractInvalidIndexDefinitionUpdateError naming the offending
index, independent of name sort order. The accepted set is unchanged —
under v0 no index modification could ever pass the full pipeline, so
this only makes the rejection deterministic and correctly labeled.

Gated per the new-version-module convention: new
DPP_VALIDATION_VERSIONS_V5 (document_type.validate_update: 1) wired
into v14 only; the v0 flow is byte-for-byte frozen for protocol
versions <= 13 and pinned by a regression test. Generation-independent
helpers (validate_config, byte-array encoding stability,
validate_schema) move verbatim to a shared common module, following the
try_from_schema extraction precedent (#4276).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thepastaclaw

thepastaclaw commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit ae98e86)
Stage: Codex precheck starting
ETA: complete ~05:48 UTC (median 23m across 30 recent reviews)
Running 4m · Last checked: 2026-08-05 05:30 UTC

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 46e2e6df-c4e9-4e0e-befc-bd10533163dd

📥 Commits

Reviewing files that changed from the base of the PR and between 5078c6a and ae98e86.

📒 Files selected for processing (2)
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
📝 Walkthrough

Walkthrough

Protocol v14 now uses document-type update validation version 1. Shared validation enforces immutable configuration, byte-array encoding stability, and schema compatibility. Version 1 compares indexes by name and reports deterministic changes.

Changes

Document-type update validation

Layer / File(s) Summary
Shared validation rules
packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs
Adds configuration, byte-array encoding, and schema compatibility validation with tests for accepted and rejected updates.
Version 1 index validation
packages/rs-dpp/src/data_contract/document_type/methods/validate_update/mod.rs, packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
Dispatches validation version 1 and compares index definitions by name. Tests cover additions, removals, renames, property changes, uniqueness, and ranking flags.
Protocol version wiring and v0 coverage
packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs, packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/*, packages/rs-platform-version/src/version/v14.rs
Adds validation-version v5, selects it for protocol v14, and retains v0 index-addition regression coverage.

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

Sequence Diagram(s)

sequenceDiagram
  participant PlatformV14
  participant validate_update
  participant validate_update_v1
  participant DocumentTypeRef
  PlatformV14->>validate_update: select validation version 1
  validate_update->>validate_update_v1: dispatch document-type update
  validate_update_v1->>DocumentTypeRef: validate configuration, encoding, schema, and indexes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 main change: making document-type index update validation independent of index name order.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/practical-poitras-e400e5

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 v4.2.0 milestone Aug 5, 2026
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.47751% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.60%. Comparing base (163b9cd) to head (ae98e86).
⚠️ Report is 2 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
...ocument_type/methods/validate_update/common/mod.rs 98.32% 19 Missing ⚠️
...ct/document_type/methods/validate_update/v1/mod.rs 99.20% 2 Missing ⚠️
...tract/document_type/methods/validate_update/mod.rs 50.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4291      +/-   ##
============================================
+ Coverage     87.57%   87.60%   +0.02%     
============================================
  Files          2700     2702       +2     
  Lines        344254   344605     +351     
============================================
+ Hits         301493   301896     +403     
+ Misses        42761    42709      -52     
Components Coverage Δ
dpp 88.82% <98.47%> (+0.11%) ⬆️
drive 86.25% <ø> (ø)
drive-abci 89.66% <ø> (-0.01%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.02% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

QuantumExplorer and others added 2 commits August 5, 2026 11:18
The ranked aggregate index grammar (#4266) only exists at protocol v14,
where validate_update now dispatches to v1, so the rankedAverageable
update tests added to the v0 module assert the v1 rejection shape
("changed index 'byRestaurant'") and belong in the v1 module. The
ranked flags ride the same name-keyed index-definition comparison as
every other index property.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thepastaclaw

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs (1)

167-199: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for the untested validate_config guards.

The validate_config tests cover 11 of the flags. Five guards have no test: documents_keep_transfer_history, documents_keep_purchase_history, documents_keep_pricing_history, documents_summable, and range_summable. The comment at Lines 312-318 states that a change to the sum configuration invalidates every on-disk sum contribution. A guard without a test can be reordered or removed without a failing test.

Add one test per missing guard, in the same shape as should_return_invalid_result_when_range_countable_is_changed. For the sum guards, keep the doctype-level flag equal across old and new when you test range_summable, so the check below it is reached.

Do you want me to generate these tests?

Also applies to: 319-347

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs`
around lines 167 - 199, Add one `validate_config` test for each missing guard:
`documents_keep_transfer_history`, `documents_keep_purchase_history`,
`documents_keep_pricing_history`, `documents_summable`, and `range_summable`.
Follow the structure of
`should_return_invalid_result_when_range_countable_is_changed`; when testing
`range_summable`, keep the doctype-level sum flag unchanged so validation
reaches that guard.
packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs (1)

143-148: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert which error the late-name addition returns.

expect_err accepts any ProtocolError. The comment states the expected cause is the schema-compatibility check for the indices keyword. If a future change makes this path fail for an unrelated reason, the test still passes and the frozen v0 behavior drifts without a signal. The early-name half of this test already pins an exact string at Line 140, so the two halves are pinned at different strengths.

Bind the error and assert its variant or its message.

♻️ Proposed change to pin the error
         // The identical addition under a late-sorting name passes the tree
         // comparison and instead hard-errors in the schema compatibility
         // check ("schema keyword 'indices' ... is not supported").
-        old.as_ref()
+        let late_error = old
+            .as_ref()
             .validate_update(new_late_name.as_ref(), platform_version)
             .expect_err("late-name addition should error in schema compatibility");
+
+        assert!(
+            late_error.to_string().contains("indices"),
+            "expected the schema compatibility hard error for the `indices` keyword, got {late_error}"
+        );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs`
around lines 143 - 148, Update the late-name addition assertion in the v0
validate-update test to bind the returned ProtocolError and verify it is
specifically the schema-compatibility failure for the unsupported "indices"
keyword, using the existing exact-message assertion pattern or an equivalent
variant match. Keep the current expectation that validation returns an error
while rejecting unrelated failure causes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs`:
- Around line 167-199: Add one `validate_config` test for each missing guard:
`documents_keep_transfer_history`, `documents_keep_purchase_history`,
`documents_keep_pricing_history`, `documents_summable`, and `range_summable`.
Follow the structure of
`should_return_invalid_result_when_range_countable_is_changed`; when testing
`range_summable`, keep the doctype-level sum flag unchanged so validation
reaches that guard.

In
`@packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs`:
- Around line 143-148: Update the late-name addition assertion in the v0
validate-update test to bind the returned ProtocolError and verify it is
specifically the schema-compatibility failure for the unsupported "indices"
keyword, using the existing exact-message assertion pattern or an equivalent
variant match. Keep the current expectation that validation returns an error
while rejecting unrelated failure causes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d14909f8-dbd7-4ab0-8de9-625bd50bd9cc

📥 Commits

Reviewing files that changed from the base of the PR and between 163b9cd and 23f40c6.

📒 Files selected for processing (7)
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/mod.rs
  • packages/rs-platform-version/src/version/dpp_versions/dpp_validation_versions/v5.rs
  • packages/rs-platform-version/src/version/v14.rs

…e error

Adds the five validate_config guard tests that were missing (keeps
transfer/purchase/pricing history, documentsSummable, rangeSummable —
the latter with documentsSummable held equal so the guard below it is
reached), and pins the v0 late-name index addition to the exact
schema-compatibility "indices" keyword error instead of accepting any
ProtocolError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer

Copy link
Copy Markdown
Member Author

Both review nitpicks addressed in 17cd413:

  • Untested validate_config guards (common/mod.rs): added one test per missing guard — keepsTransferHistory, keepsPurchaseHistory, keepsPricingHistory, documentsSummable, and rangeSummable (with documentsSummable held equal across old/new so validation reaches the range_summable guard).
  • Unpinned late-name error (v0/mod.rs): the v0 pin test now binds the returned error and asserts the exact schema-compatibility message (schema keyword 'indices' at path '/indices/2' is not supported) instead of accepting any ProtocolError.

🤖 Addressed by Claude Code

@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 + Sonnet

The latest delta strengthens validate_config and v0 regression coverage and moves ranked-index tests into the correct v1 module; no new latest-delta defects were found. Carried forward: one test-coverage suggestion remains valid because all eight v1 dispatcher tests still use the moving PlatformVersion::latest() target instead of protocol version 14.
Source: reviewers Codex gpt-5.6-sol and Claude claude-sonnet-5; final verifier Codex gpt-5.6-sol as fallback for the Sonnet verifier; openclaw-agent coordinator is orchestration-only.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: gpt-5.6-sol — final-verifier (fallback)
  • Sonnet reviewers: claude-sonnet-5 — general (failed), claude-sonnet-5 — security-auditor (failed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — general (completed), claude-sonnet-5 — security-auditor (completed), claude-sonnet-5 — rust-quality (completed)

🟡 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-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs:175: Pin v1 consensus tests to protocol version 14
  All eight tests in this version-specific module call the public `validate_update` dispatcher with `PlatformVersion::latest()` at lines 175, 229, 258, 284, 314, 341, 429, and 448. `LATEST_VERSION` currently equals protocol version 14, whose validation configuration selects `validate_update_v1`, but a future version selecting v2 would silently redirect these tests and leave the protocol-v14 consensus behavior without permanent regression coverage. Pin each test to version 14, matching the adjacent v0 regression test's explicit version-13 pin and the generation-pinning convention documented by the versioned schema-parser tests.

QuantumExplorer and others added 2 commits August 5, 2026 12:14
PlatformVersion::latest() is a moving target: a future protocol version
selecting a v2 dispatcher would silently redirect these tests and leave
the v14 consensus behavior without permanent regression coverage. Pin
to version 14 explicitly, matching the v0 module's version-13 pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs`:
- Around line 151-156: Update the assertion for late_error in the
validate-update regression test to match the concrete ProtocolError variant and
compare its message exactly, rather than using contains on
late_error.to_string(). Preserve the expected schema-compatibility message for
the indices path and retain useful failure context.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: facf216f-2235-4ff1-8c29-7cfb2bd9bb80

📥 Commits

Reviewing files that changed from the base of the PR and between 23f40c6 and 5078c6a.

📒 Files selected for processing (3)
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs

Comment thread packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v0/mod.rs Outdated
Replaces the contains() check with an assert_matches on the concrete
ProtocolError -> DataContractError::JsonSchema ->
SchemaCompatibilityValidationError chain and the exact inner message,
so the frozen v0 late-name behavior cannot drift to a different error
without a signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit 86db10a into v4.2-dev Aug 5, 2026
33 of 35 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/practical-poitras-e400e5 branch August 5, 2026 05:31
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.

2 participants