Skip to content

test(drive): pin the batch-transition cap and land the phantom-group evidence - #4383

Merged
shumkov merged 3 commits into
v4.2-devfrom
chore/batch-transition-cap-tripwire
Aug 13, 2026
Merged

test(drive): pin the batch-transition cap and land the phantom-group evidence#4383
shumkov merged 3 commits into
v4.2-devfrom
chore/batch-transition-cap-tripwire

Conversation

@shumkov

@shumkov shumkov commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

max_transitions_in_documents_batch has been 1 at every protocol version since the first mainnet release, and nothing in the codebase said why that matters. It reads as a throughput knob. It is not — it is the only thing keeping a consensus-visible index defect off the network, and raising it (an attractive, plausible future change) would arm that defect silently.

This PR does not fix the defect. It documents the cap, pins it with tests, and lands the evidence so the next person to touch it cannot miss what it is holding up.

What was done?

The mechanism. BatchTransitionAction::into_high_level_drive_operations flattens every transition of a batch into one Vec<DriveOperation>, and apply_drive_operations turns that into a single GroveDB batch. Within that batch the ordinary document Add/Update/Delete conversions are blind to each other: batch_delete_up_tree_while_empty decides whether an index group has become empty from committed state plus only its own operations. Two operations that jointly empty a group each observe the other's document still committed, neither removes the group tree, and on a ranked index the leftover tree is mirrored into the aggregate secondary — so the group keeps ranking at zero with nothing behind it, sorting ahead of every group with a positive aggregate. Primary and secondary agree the empty group exists, so verify_grovedb is clean and the proof reconstructs the live root hash. A wrong ranking that verifies.

Why it is not fixed here. Fixing it at the correct site (the flatten loop in apply_drive_operations_v0, not the five &mut None call sites — those are a no-op) changes the number of emitted GroveDB operations, hence fees, hence identity balances, hence the app hash. It would also break Drive::update_contract_keywords_operations, which concatenates N deletes then M adds over a shared byContractId group and is correct because its deletes are blind — sibling awareness would make the second delete conclude the group is empty and delete it, then the adds would insert into a tree the same batch deleted. That needs a protocol-version gate and its own PR.

Landed instead:

  • The rationale, as a doc comment on SystemLimits::max_transitions_in_documents_batch, with pointers from all three SYSTEM_LIMITS_V* constants and from the one hand-written SystemLimits literal in the mocks. It also names the two other guards holding the same line, because "the cap is the only thing" is not true in general:
    • the keyword path's caller guard (below);
    • MultipleDocumentOperationsForSameContractDocumentType, which does thread accumulated operations and is why the withdrawal paths batch many documents safely — and which is not a drop-in for batch transitions, because it carries no delete variant. That is the detail that makes the obvious fix non-obvious.
  • Cap tripwires: a test asserting the value is 1 across PLATFORM_VERSIONS, and (under mock-versions) across the mock registry, since one mock hand-writes its SystemLimits and the main loop cannot see it.
  • rs-drive-abci reachability coverage (ranked_group_drain.rs): a real signed two-transition BatchTransition is refused by the cap before any drive operation is built, and the reachable near-miss — two transitions draining the same group in one block — produces the correct index, because execute_event calls apply_drive_operations once per state transition.
  • rs-drive mechanism coverage (batched_group_drain.rs): five cases that expose the defect land #[ignore]d rather than deleted or weakened, so raising the cap has something concrete to un-ignore. They fail today, for the right reason. Everything else in the file runs: a characterization test asserting the phantom exactly as it is on all three axes (including that it verifies and proves), the fail-loud inverse shapes, and sequential controls for both the delete and move paths.
  • The keyword path, previously argued safe only by code reading, is now executed. Replacing a whole keyword set keeps the byContractId group holding exactly the new members. Clearing it entirely strands an empty group tree — and the only thing preventing that from the network is !contract.keywords().is_empty() in update_contract_v1. That guard is a shield, not a fix: it also skips the deletes, so a contract that clears its keywords keeps being returned by keyword search under the old ones. Both halves are commented at the guard and pinned by characterization tests, because removing the guard to fix the stale index would silently arm the stranded group tree.

How Has This Been Tested?

Rebased onto v4.2-dev @ f05bf82dc9 and re-run there. No conflicts (this branch touches no Cargo.lock).

Gate Result
cargo clippy --workspace --all-features --all-targets exit 0, no findings
cargo fmt --check --all exit 0
cargo test -p drive --lib 3333 passed, 0 failed, 5 ignored
cargo test -p drive --lib batched_group_drain -- --ignored 5 failed, as designed — the defect is real
cargo test -p drive-abci --lib batch::tests 320 passed, 0 failed
cargo test -p platform-version --lib 17 passed; 18 with --features mock-versions

Not overstating this: the clippy gate is green only after retries. Two third-party build scripts download GitHub artifacts (tenderdash-proto fetching the Tenderdash source zip, grovedb's grovedbg feature fetching a release zip) and both failed repeatedly from this machine with Peer disconnected / HTTP2 REFUSED_STREAM. I retried until the downloads succeeded, verified the grovedbg artifact against the SHA-256 the build script pins, seeded both build scripts' own documented caches, and then got a clean full-workspace run. Nothing was skipped or narrowed to work around it — the green run above is the real full-feature, all-targets workspace lint. Flagging it because it is the exact command a reviewer will re-run, and it may flake for them too.

Three independent review passes were run over the diff (correctness, adversarial test-quality, consensus/version-safety) and their must-fixes are folded in. Two are worth naming because the first version was genuinely weaker than it looked:

  • The two "never lands silently wrong" tests took the rejection branch in all 24 combinations, so they asserted nothing beyond "returned Err". They now assert the refusal kind per batching configuration — Drive's pre-flight check when batching_consistency_verification is on, grovedb's batch applier on the shipped default, which is the only guard a real node has — and that a refused batch leaves state unchanged.
  • The documentation's strongest claim (that the phantom passes integrity verification and proves against the live root hash) was prose only: every ignored test dies at its first assertion on the Count axis and never reaches the proof checks. That claim is now executed by a green test on all three axes.

One verification limit, stated rather than papered over: clearing_a_contracts_keywords_leaves_the_old_ones_indexed drives Drive-layer update_contract, not a signed DataContractUpdate end to end. Reachability of the stale-keyword behaviour from the network is strongly indicated by the call path but is not executed here.

Breaking Changes

None. No wire format, serialization, fee, or state change. Every non-test hunk in this PR is a comment; no value, version mapping, or dispatch entry is touched, and the 1 literals themselves are unchanged.

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 ranked-index consistency when multiple document operations empty the same group.
    • Prevented stale or phantom entries after document deletions, updates, or keyword removal.
    • Preserved accurate ranked queries and proofs across count, sum, and average indexes.
  • Documentation

    • Clarified the single-document batch limit and its role in maintaining state consistency.
  • Tests

    • Added coverage for batched and sequential operations, index draining and refilling, keyword updates, and state integrity.

…evidence

`max_transitions_in_documents_batch` is 1 at every protocol version and has
been since the first mainnet release. Nothing said why that matters.

It is load-bearing for state correctness, not throughput.
`BatchTransitionAction::into_high_level_drive_operations` flattens every
transition of a batch into one `Vec<DriveOperation>` and
`apply_drive_operations` turns that into a single GroveDB batch, where the
ordinary document Add/Update/Delete conversions are blind to each other. Two
operations that jointly empty an index group each observe the other's document
still committed, neither removes the group tree, and on a ranked index the
leftover tree keeps ranking at zero with nothing behind it — internally
consistent, so `verify_grovedb` passes and the proof reconstructs the live root
hash. A wrong ranking that verifies.

The defect is not fixed here. Fixing it at the correct site changes emitted
operation counts, hence fees, hence the app hash, and would break
`update_contract_keywords_operations`, which is correct precisely because its
deletes are blind. That needs a protocol-version gate and its own change.

What lands instead:

- The rationale, on `SystemLimits::max_transitions_in_documents_batch`, with
  pointers from all three `SYSTEM_LIMITS_V*` constants and the one hand-written
  mock literal. It also names the two other guards holding the same line: the
  keyword path's caller guard, and the sibling-aware
  `MultipleDocumentOperationsForSameContractDocumentType` variant, which is not
  a drop-in for batch transitions because it carries no delete variant.
- Tests pinning the cap at 1 across `PLATFORM_VERSIONS` and, under
  `mock-versions`, across the mock registry.
- `rs-drive-abci` coverage proving the cap is enforced end to end: a real signed
  two-transition batch is refused before any drive operation is built, and the
  reachable near-miss — two transitions draining the same group in one block —
  produces the correct index.
- `rs-drive` coverage of the mechanism. Five cases that expose the defect land
  `#[ignore]`d rather than deleted or weakened, so raising the cap has something
  to un-ignore; they fail today for the right reason. The rest run:
  a characterization test asserting the phantom exactly as it is on all three
  axes, the fail-loud inverse shapes, and the sequential controls.
- The keyword path, previously argued safe only by code reading, is now
  executed. Replacing a whole keyword set keeps the `byContractId` group with
  exactly the new members; clearing it entirely strands an empty group tree, and
  the only thing preventing that from the network is the `!keywords.is_empty()`
  guard in `update_contract_v1`. That guard is a shield, not a fix: it also
  skips the deletes, so a contract that clears its keywords keeps advertising
  the old ones through keyword search. Both halves are now commented at the
  guard and pinned by characterization tests, because removing it to fix the
  stale index would silently arm the stranded group tree.

No production behaviour changes: every non-test hunk is a comment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 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: 55727fb5-c35a-483c-9c28-a37030afcad9

📥 Commits

Reviewing files that changed from the base of the PR and between c05b9b3 and e2f4480.

📒 Files selected for processing (1)
  • packages/rs-platform-version/src/version/system_limits/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-platform-version/src/version/system_limits/mod.rs

📝 Walkthrough

Walkthrough

The pull request adds ranked-index group-drain tests for document and keyword operations. It documents and verifies the single-transition batch limit across platform versions, including GroveDB integrity, ranking, proofs, and residual empty-group behavior.

Changes

Ranked-index state consistency

Layer / File(s) Summary
Document transition batch limit
packages/rs-platform-version/src/version/system_limits/*, packages/rs-platform-version/src/version/mocks/v2_test.rs
Documents and tests the requirement that max_transitions_in_documents_batch remains 1.
Batched ranked-index drain coverage
packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/*
Adds count, sum, and average index tests for batched deletes, updates, refills, sequential controls, ranking, proofs, and GroveDB integrity.
State-transition drain integration tests
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/*
Tests rejection of multi-document batches and successful independent deletes in shared and separate blocks.
Keyword-index clearing coverage
packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs, packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs
Adds keyword replacement and clearing tests that inspect indexed documents, groups, and residual empty trees.

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

Mergeability Score: ⚪ Minimal · up to e2f44

This PR documents and tests the existing batch-transition cap without changing runtime behavior, so no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related PRs

Suggested reviewers: quantumexplorer, lklimek

🚥 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 summarizes the main changes: enforcing the batch-transition cap and adding tests for the phantom-group defect.
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 chore/batch-transition-cap-tripwire

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

@thepastaclaw

thepastaclaw commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit e2f4480)

@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 (3)
packages/rs-platform-version/src/version/system_limits/mod.rs (1)

84-87: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deriving the registry-size floor instead of hardcoding 14.

The literal 14 must be bumped by hand whenever a protocol version is added, and a stale value silently weakens the guard. A simple alternative pins the count against the highest registered protocol_version, which self-updates.

♻️ Alternative floor assertion
-        assert!(
-            PLATFORM_VERSIONS.len() >= 14,
-            "the protocol version registry lost entries; this test only covers what it holds"
-        );
+        assert_eq!(
+            PLATFORM_VERSIONS.len(),
+            PlatformVersion::latest().protocol_version as usize,
+            "the protocol version registry lost entries; this test only covers what it holds"
+        );
🤖 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-platform-version/src/version/system_limits/mod.rs` around lines
84 - 87, Update the registry-size assertion in the system-limits test to derive
its minimum from the highest registered protocol_version rather than the
hardcoded 14. Reuse the existing PLATFORM_VERSIONS entries and preserve the
current failure message and guard intent.
packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs (1)

1072-1235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the shared driver for the two "never lands silently wrong" cases.

deleting_a_groups_only_document_and_creating_another_in_one_batch_never_lands_silently_wrong and moving_one_document_out_of_a_group_while_creating_another_into_it_never_lands_silently_wrong share the same triple loop, label construction, report/bad accumulation, and match arms. Only three things differ: the departing operation, the two order labels, and departure_stored in the success expectation. A single driver that accepts those three inputs would keep the two cases in sync as the assertions evolve.

🤖 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-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs`
around lines 1072 - 1235, Extract the duplicated test execution logic from the
two “never_lands_silently_wrong” tests into a shared helper or driver.
Parameterize it with the departing operation, the two order labels, and the
expected departure_stored value, while keeping each test responsible only for
constructing its scenario-specific inputs; preserve the existing loops,
reporting, refusal assertions, and success expectations.
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs (1)

544-586: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider extracting the repeated platform and fixture setup.

The three tests repeat the same block: build the platform, load the state and platform version, set up the identity, register the restaurants contract, resolve the visit doctype, seed the RNG, and create the [(H, 2), (G, 4), (G, 6)] documents. a_multi_document_batch_transition_is_refused_by_the_one_transition_limit and this test create an identical population. One async fixture helper returning the platform, contract, doctype, key, signer, and documents would keep the three cases in sync, matching the setup_g_two_h_one pattern already used in packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs.

🤖 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-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs`
around lines 544 - 586, Extract the duplicated platform and fixture
initialization from the affected tests into one async helper, following the
existing setup_g_two_h_one pattern. Have the helper build the platform,
establish identity and signing data, register the restaurant contract, resolve
the visit document type, seed the RNG, and create the [(H, 2), (G, 4), (G, 6)]
documents, returning all values needed by the tests; update
a_multi_document_batch_transition_is_refused_by_the_one_transition_limit and
deleting_the_same_two_documents_in_separate_blocks_removes_the_group to reuse
it.
🔇 Additional comments (30)
packages/rs-platform-version/src/version/system_limits/mod.rs (2)

41-44: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

⚠️ Unverified finding
Sandbox verification was unavailable.

Verify the keyword-guard claim against the keyword-clearing behavior.

This bullet states that the caller of Drive::update_contract_keywords_operations skips the call when the new keyword set is empty, so "any batch that empties the group also refills it". The PR adds a test named clearing_every_keyword_leaves_an_empty_by_contract_id_group_behind, and batched_group_drain.rs (lines 31-35) points a reader to that test "for what that guard is worth". Those two statements suggest the guard does not fully prevent a residual empty group on the clearing path. If clearing can leave an empty by_contract_id group, soften this bullet so the documentation does not overstate the guard.

Run the following script to inspect the keyword-update path and the clearing test:


73-137: LGTM!

packages/rs-platform-version/src/version/system_limits/v1.rs (1)

17-21: LGTM!

packages/rs-platform-version/src/version/system_limits/v2.rs (1)

14-15: LGTM!

packages/rs-platform-version/src/version/system_limits/v3.rs (1)

16-17: LGTM!

packages/rs-platform-version/src/version/mocks/v2_test.rs (1)

505-507: LGTM!

packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs (7)

1-43: LGTM!

Also applies to: 60-96, 98-167, 173-227


45-58: 🗄️ Data Integrity & Integration

⚠️ Unverified finding
Sandbox verification was unavailable.

Verify that every parent-module helper this file relies on exists.

This module is attached with #[path] to ranked_index_e2e_tests, so use super::* must supply read_grove_element, setup_restaurants, build_doc, insert_doc, count_top_k, sum_top_k, avg_top_k, group_keys, expected_avg_fixed_point, indexed_property_name_tree_path, assert_grovedb_is_consistent, GROUP_PROPERTY, and platform_version. A rename in the parent breaks this file only at compile time in that crate's test profile, which is easy to miss locally.

Run the following script to confirm each symbol is defined in the parent module:


398-468: LGTM!


559-648: LGTM!


650-799: LGTM!

Also applies to: 840-901


1245-1299: LGTM!


497-527: 🩺 Stability & Availability

⚠️ Unverified finding
Sandbox verification was unavailable.

Both new suites load the same restaurants fixture through crate-relative paths. The shared root cause is one assumption: restaurants-contract.json sits at packages/rs-drive/tests/supporting_files/contract/restaurants/, and Cargo runs each crate's unit tests with that crate's root as the working directory. If the fixture moves, both suites break at runtime rather than at compile time.

  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs#L497-L527: confirm "tests/supporting_files/contract/restaurants/restaurants-contract.json" resolves from the rs-drive crate root.
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs#L53-L54: confirm "../rs-drive/tests/supporting_files/contract/restaurants/restaurants-contract.json" resolves from the rs-drive-abci crate root to the same file.
packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/mod.rs (1)

24-27: LGTM!

packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs (2)

49-53: LGTM!


55-58: 📐 Maintainability & Code Quality

No change required. #[path = "batched_group_drain.rs"] resolves relative to the directory containing ranked_index_e2e_tests.rs.

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/mod.rs (1)

5-5: LGTM!

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs (6)

58-120: LGTM!


145-202: LGTM!


206-233: LGTM!


297-403: LGTM!


405-542: LGTM!


588-618: LGTM!

packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs (3)

256-271: LGTM!


471-516: LGTM!


518-589: LGTM!

packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs (4)

183-203: LGTM!


378-398: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

⚠️ Unverified finding
Sandbox verification was unavailable.

Reuse the canonical path helpers instead of hardcoding the subtree markers.

contract_keywords_path and by_contract_id_reference_path encode the storage layout as literals: vec![1] for the document-types subtree and vec![0] for the primary/reference subtree. These literals carry no name and no comment.

This matters for one assertion in particular. subtree_keys returns an empty vector when the path does not resolve. Line 759 asserts that subtree_keys(&drive, storage_path, platform_version) is empty. If the layout markers ever change, that assertion passes without reading the intended subtree. The assertions that compare against sorted_document_keys(&after) do not have this problem, so the doc comment at lines 400-402 is accurate for those callers only.

rs-drive already exposes path constructors for contract document storage. Build these helpers on top of them, or name the markers with the existing layout constants.

Run the following script to find the canonical helpers:


522-680: LGTM!


682-801: LGTM!

🤖 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-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs`:
- Around line 544-586: Extract the duplicated platform and fixture
initialization from the affected tests into one async helper, following the
existing setup_g_two_h_one pattern. Have the helper build the platform,
establish identity and signing data, register the restaurant contract, resolve
the visit document type, seed the RNG, and create the [(H, 2), (G, 4), (G, 6)]
documents, returning all values needed by the tests; update
a_multi_document_batch_transition_is_refused_by_the_one_transition_limit and
deleting_the_same_two_documents_in_separate_blocks_removes_the_group to reuse
it.

In
`@packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs`:
- Around line 1072-1235: Extract the duplicated test execution logic from the
two “never_lands_silently_wrong” tests into a shared helper or driver.
Parameterize it with the departing operation, the two order labels, and the
expected departure_stored value, while keeping each test responsible only for
constructing its scenario-specific inputs; preserve the existing loops,
reporting, refusal assertions, and success expectations.

In `@packages/rs-platform-version/src/version/system_limits/mod.rs`:
- Around line 84-87: Update the registry-size assertion in the system-limits
test to derive its minimum from the highest registered protocol_version rather
than the hardcoded 14. Reuse the existing PLATFORM_VERSIONS entries and preserve
the current failure message and guard intent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f3ba5b36-f193-4ab3-9f91-ffe896753933

📥 Commits

Reviewing files that changed from the base of the PR and between f05bf82 and 8a4f152.

📒 Files selected for processing (12)
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/ranked_group_drain.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/batched_group_drain.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/mod.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs
  • packages/rs-drive/src/drive/contract/update/update_contract/v1/mod.rs
  • packages/rs-drive/src/drive/contract/update/update_keywords/v0/mod.rs
  • packages/rs-platform-version/src/version/mocks/v2_test.rs
  • packages/rs-platform-version/src/version/system_limits/mod.rs
  • packages/rs-platform-version/src/version/system_limits/v1.rs
  • packages/rs-platform-version/src/version/system_limits/v2.rs
  • packages/rs-platform-version/src/version/system_limits/v3.rs

@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 12, 2026
…ust what it prevents

The bullet on max_transitions_in_documents_batch described the empty-keyword-set
skip in update_contract_v1 as though it made the keyword path safe. It does not:
it is a shield, not a fix. Calling the keyword update directly with an empty set
still strands the shared byContractId group, and the skip leaves the previous
keyword documents in place, so a contract that clears its keywords keeps being
returned by keyword search under them. Both halves already had tests and a
comment at the call site; only this bullet still read optimistically.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shumkov

shumkov commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Responses to the three nitpicks and the flagged finding, in order.


system_limits/mod.rs:84 — derive the registry-size floor instead of hardcoding 14

Not applying this one — I think it inverts the guard. PlatformVersion::latest() is PLATFORM_VERSIONS.last() (protocol_version.rs:167), not the LATEST_PLATFORM_VERSION constant, so the proposed assert_eq!(PLATFORM_VERSIONS.len(), PlatformVersion::latest().protocol_version) compares the array against its own last element. Truncate the registry to [V1, V2, V3] and it becomes assert_eq!(3, 3) and passes — which is precisely the case the assertion exists to catch.

The premise is also slightly off: a >= 14 floor doesn't need bumping to stay correct when a protocol version is added, only to stay tight, and the loop below already covers every entry the registry does hold.


batched_group_drain.rs:1072-1235 — extract the shared driver for the two "never lands silently wrong" cases

Leaving these as two explicit tests. They differ in the departing operation, the order labels, and the expected departure_stored, so a single driver ends up taking a boolean that selects the success expectation — which is the thing that makes a failure hard to read at 2am. The drift risk you name is real (both sets of assertions were strengthened together during review), but I'd rather pay it than obscure which case failed.


ranked_group_drain.rs:544-586 — extract the repeated platform and fixture setup

Leaving as is. The substantive helpers are already extracted — register_restaurants, create_visit, process_block, signed_delete_batch. What's left is platform construction plus a seeding loop, and a combined fixture would have to hand back the platform_state guard so each caller can re-derive platform_version from it. That's lifetime plumbing in exchange for a few lines of setup.


system_limits/mod.rs:41-44 — verify the keyword-guard claim against the keyword-clearing behaviour (your flagged "unverified finding")

Good catch, and correct — fixed in c05b9b3. You inferred it from the two documents disagreeing and you were right: the guard-site comment in update_contract_v1 already stated both halves, but this bullet still read as though the guard made the keyword path safe.

It now says that every batch the keyword update actually emits refills the group it empties, but that the skip is a shield rather than a fix — called directly with an empty set it does strand the group, and the skip leaves the previous keyword documents in place, so a contract that clears its keywords keeps being found under them.

@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 PR documents and characterizes the existing batch-index defect without changing consensus behavior, but the new registry-wide cap test does not remain complete when another protocol version is declared. Its independent completeness assertion should use the authoritative latest protocol version rather than a fixed floor.
Source: reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. 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)
  • 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-platform-version/src/version/system_limits/mod.rs`:
- [SUGGESTION] packages/rs-platform-version/src/version/system_limits/mod.rs:87-90: Derive the registry completeness check from LATEST_VERSION
  The fixed `>= 14` floor only detects truncation below the current registry size. If protocol version 15 is declared by updating `LATEST_VERSION` but `PLATFORM_V15` is accidentally omitted from `PLATFORM_VERSIONS`, the registry will still contain 14 entries, this assertion will pass, and the loop will never inspect version 15. Compare the registry length with the independent, authoritative `LATEST_VERSION` constant so the test continues to verify every declared protocol version. Unlike `PlatformVersion::latest()`, that constant is not derived from the registry being checked.

Comment on lines +87 to +90
assert!(
PLATFORM_VERSIONS.len() >= 14,
"the protocol version registry lost entries; this test only covers what it holds"
);

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: Derive the registry completeness check from LATEST_VERSION

The fixed >= 14 floor only detects truncation below the current registry size. If protocol version 15 is declared by updating LATEST_VERSION but PLATFORM_V15 is accidentally omitted from PLATFORM_VERSIONS, the registry will still contain 14 entries, this assertion will pass, and the loop will never inspect version 15. Compare the registry length with the independent, authoritative LATEST_VERSION constant so the test continues to verify every declared protocol version. Unlike PlatformVersion::latest(), that constant is not derived from the registry being checked.

Suggested change
assert!(
PLATFORM_VERSIONS.len() >= 14,
"the protocol version registry lost entries; this test only covers what it holds"
);
assert_eq!(
PLATFORM_VERSIONS.len(),
crate::version::LATEST_VERSION as usize,
"the protocol version registry does not contain every declared protocol version"
);

source: ['codex']

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.

Resolved in e2f4480Derive the registry completeness check from LATEST_VERSION no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.75510% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.75%. Comparing base (f05bf82) to head (e2f4480).
⚠️ Report is 1 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
...rc/drive/contract/update/update_contract/v1/mod.rs 74.03% 27 Missing ⚠️
...rc/drive/contract/update/update_keywords/v0/mod.rs 92.70% 21 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4383      +/-   ##
============================================
- Coverage     87.49%   86.75%   -0.74%     
============================================
  Files          2672     2676       +4     
  Lines        340400   344267    +3867     
============================================
+ Hits         297819   298668     +849     
- Misses        42581    45599    +3018     
Components Coverage Δ
dpp 87.10% <ø> (-1.77%) ⬇️
drive 85.74% <87.75%> (-0.45%) ⬇️
drive-abci 88.76% <ø> (-0.46%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
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.

…SION, not a fixed floor

The registry-completeness guard used a hardcoded ">= 14" floor, which only
detects a registry that shrinks. Declare protocol version 15 by bumping
LATEST_VERSION but forget to add PLATFORM_V15 to PLATFORM_VERSIONS, and the
registry still holds 14 entries, the floor still passes, and the loop never
inspects version 15 — so the batch-transition cap goes unchecked on the newest
version, at exactly the moment someone is editing version machinery.

Comparing the registry length against LATEST_VERSION closes that: it is declared
independently of PLATFORM_VERSIONS, so it catches both a version declared but
omitted and a registry that loses entries. Deriving the expectation from the
registry instead — PlatformVersion::latest() is PLATFORM_VERSIONS.last() — would
pass in both cases and is why that route was not taken.

Verified by construction: with LATEST_VERSION at 15 and the registry holding 14,
the old assertion passes and the new one fails (left: 14, right: 15); with
PLATFORM_V14 dropped from the registry the new one fails (left: 13, right: 14).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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 sole prior suggestion is fixed at the exact head: the cap test now compares PLATFORM_VERSIONS.len() against the independently declared LATEST_VERSION, so a declared but unregistered protocol version fails the completeness guard. No additional in-scope findings were reported or identified, and the PR remains documentation-and-test-only with respect to runtime behavior.
Source: Reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. 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)
  • 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)

@shumkov
shumkov merged commit eaf3a48 into v4.2-dev Aug 13, 2026
37 checks passed
@shumkov
shumkov deleted the chore/batch-transition-cap-tripwire branch August 13, 2026 12:56
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