Skip to content

fix(platform): reject keep-history document deletes cleanly#3930

Open
thepastaclaw wants to merge 10 commits into
dashpay:v3.1-devfrom
thepastaclaw:fix/document-history-delete-error
Open

fix(platform): reject keep-history document deletes cleanly#3930
thepastaclaw wants to merge 10 commits into
dashpay:v3.1-devfrom
thepastaclaw:fix/document-history-delete-error

Conversation

@thepastaclaw

@thepastaclaw thepastaclaw commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

fix(platform): reject keep-history document deletes cleanly

Issue being fixed or feature implemented

Fixes #3927.

Document types could set both documentsKeepHistory: true and
canBeDeleted: true. That combination advertises deletability even though
Drive refuses to delete keep-history documents, so delete attempts surfaced as
InternalError instead of a clear consensus rejection.

What was done?

  • Reject new document types that resolve to both keep-history and deletable
    during DPP schema parsing.
  • Return that rejection as a consensus error in validator/full-validation mode,
    while preserving protocol/data-contract error behavior for non-validation
    builds.
  • Add a Drive ABCI delete-transition structure-validation guard so
    already-deployed contradictory contracts fail as invalid-paid transitions
    instead of internal errors.
  • Version-gate the delete-transition guard at protocol version 12 so prior
    replay behavior is preserved.
  • Add focused DPP and Drive ABCI regression coverage, including a contradictory
    keep-history/deletable fixture for the existing-contract path.

How Has This Been Tested?

Local worktree on macOS arm64, branch fix/document-history-delete-error.

  • cargo test -p dpp --features all_features data_contract::document_type::class_methods::try_from_schema::v2

    • 13 passed, 0 failed
  • Broader DPP try_from_schema sweep across v0/v1/v2

    • 53 passed, 0 failed
  • cargo test -p dpp --features all_features

    • 3574 passed, 0 failed
  • cargo test -p drive-abci --features mocks test_document_delete_on_document_type_that_keeps_history_is_rejected

    • passed
  • cargo test -p drive-abci --features mocks data_contract_create::state::v0::tests

    • 6 passed, 0 failed
  • cargo test -p drive-abci --features mocks focused batch
    state-transition/deletion coverage

    • 293 passed, 0 failed
  • DPP no-default-features parser regression:

    cargo test -p dpp \
      doctype_keep_history_with_can_be_deleted_rejected \
      --no-default-features
    • 1 passed, 0 failed
  • DPP all-features parser regression:

    cargo test -p dpp --features all_features \
      doctype_keep_history_with_can_be_deleted_rejected
    • 1 passed, 0 failed
  • Pre-commit hook:

    • cargo fmt --all -- --check
    • cargo check workspace

Pre-PR code-review gate was run after fixes and returned ship.

Breaking Changes

None.

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

Summary by CodeRabbit

  • New Features
    • Added full-validation rejection for document-type contracts that specify keep-history while also permitting deletion.
    • Enabled versioned document delete structure validation for the relevant protocol level to enforce the new rule.
  • Bug Fixes
    • Improved error surfacing so contradictory contract/document-type setups fail as consensus/paid validity errors rather than generic contract parsing errors.
  • Tests
    • Updated existing keep-history test contracts/fixtures to set canBeDeleted: false.
    • Added regression tests and a dedicated fixture covering the keep-history + can-be-deleted contradiction (including feature-gated acceptance when full-validation is off).

thepastaclaw and others added 5 commits June 17, 2026 17:12
…schema

documentsKeepHistory: true together with canBeDeleted: true is
self-contradictory — rs-drive's force-delete path unconditionally
refuses to delete documents whose type keeps history
(InvalidDeletionOfDocumentThatKeepsHistory). Catch the combination at
contract creation time in the V2 document-type schema parser, mirroring
the existing cross-flag rule for
ContestedUniqueIndexOnMutableDocumentTypeError, so SDK users get a
clean InvalidContractStructure error instead of a runtime delete
failure.

Gated by full_validation so already-deployed contradictory contracts
continue to load through the V2 parser at v12+ (the rs-drive-abci
delete-transition guard surfaces them as clean invalid-paid
transitions in a follow-up commit).

Refs dashpay#3927

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this guard, a delete state transition against a document type
whose schema sets documentsKeepHistory: true passes the structure
validator (which only checks documents_can_be_deleted()) and reaches
rs-drive's force-delete path, where it triggers
InvalidDeletionOfDocumentThatKeepsHistory. The processor reclassifies
that drive-layer error as ExecutionResult::InternalError — the
transition is neither valid nor invalid-paid, leaving the SDK with no
clean accept/reject signal.

Add a documents_keep_history() check beside the existing
documents_can_be_deleted() check in the delete-transition advanced
structure validator so the contradiction surfaces as a normal invalid
(paid) consensus error. This fixes the classification even for
already-deployed contradictory contracts (e.g. testnet
5CBPiadGmx3Zsjc26g5onopcx7pdxHPbrRAUD2T2yAbC), which the DPP cross-flag
rule from the previous commit cannot retroactively fix.

The new note-contract-keep-history-and-can-be-deleted.json fixture is
loaded with full_validation=false so it bypasses the DPP guard and
exercises this layer directly.

Refs dashpay#3927

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit added the documents_keep_history() check directly
to advanced_structure_v0, which is the dispatched validator for ALL
historical protocol versions (1-11) as well as v12. Mutating v0 would
alter the consensus result of any historical block containing a delete
against a keep-history doctype — pre-fix those returned
ExecutionResult::InternalError (no fees, no state change); post-fix
they would be classified as invalid-paid (fees deducted, state delta
differs), breaking bit-for-bit chain replay at PROTOCOL_VERSION_11
and earlier.

Move the check into a new advanced_structure_v1 module, restore v0 to
its original behavior, extend the trait dispatcher to handle both
versions, and bump document_delete_transition_structure_validation
from 0 to 1 in DRIVE_ABCI_VALIDATION_VERSIONS_V8 (used by
PROTOCOL_VERSION_12, the v3.1 hard fork). V1-V7 still dispatch v0 so
pre-v12 chain history stays reproducible.

Refs dashpay#3927

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrap the v2 DocumentType keep-history + canBeDeleted contradiction
through `consensus_or_protocol_data_contract_error` so that with the
`validation` feature it returns `ProtocolError::ConsensusError` instead
of bare `ProtocolError::DataContractError`. drive-abci's
`transform_into_action_v0` only converts the consensus variant into a
clean invalid (paid) transition with a bump action — the
DataContractError variant propagated as an internal execution error in
validator mode, defeating the parse-time guard's whole purpose for
contract create/update.

Pin the variant in the existing regression test so the classification
can't silently regress.

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

coderabbitai Bot commented Jun 17, 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f78a2466-040a-481a-ae05-34e8230010bf

📥 Commits

Reviewing files that changed from the base of the PR and between 078c233 and 663b50a.

📒 Files selected for processing (2)
  • packages/rs-drive/src/drive/contract/mod.rs
  • packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs
✅ Files skipped from review due to trivial changes (1)
  • packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs

📝 Walkthrough

Walkthrough

Enforces that documentsKeepHistory: true and canBeDeleted: true cannot coexist in a document type. The V2 schema parser now rejects this combination with InvalidContractStructure at contract creation. A new V1 delete-transition structure validator rejects delete attempts against keep-history document types as clean paid consensus errors, gated at PROTOCOL_VERSION_12 in the v3.1 hard fork version table.

Changes

Fix documentsKeepHistory + canBeDeleted contradiction

Layer / File(s) Summary
Schema parse-time contradiction check
packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs
Imports consensus_or_protocol_data_contract_error and adds a full_validation-gated check rejecting documentsKeepHistory && canBeDeleted with InvalidContractStructure, surfaced as ProtocolError::ConsensusError when the validation feature is enabled.
DPP unit test alignment and new regression tests
packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs
Fixes existing keep-history accepted tests to set canBeDeleted: false. Adds new tests for the rejected combination, keep-history with canBeDeleted: false (accepted), and canBeDeleted: true without keep-history (accepted).
V1 delete-transition structure validator and version wiring
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_delete_transition_action/advanced_structure_v1/mod.rs, document_delete_transition_action/mod.rs
Adds DocumentDeleteTransitionActionStructureValidationV1 trait and implementation rejecting deletes when documents_can_be_deleted() is false or documents_keep_history() is true. Wires it into validate_structure dispatch at platform version 1, updating UnknownVersionMismatch to list [0, 1].
Protocol version gating
packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs
Enables document_delete_transition_structure_validation at version 1 starting from PROTOCOL_VERSION_12 in DRIVE_ABCI_VALIDATION_VERSIONS_V8.
Integration test fixture and regression test
packages/rs-drive-abci/tests/supporting_files/contract/note/note-contract-keep-history-and-can-be-deleted.json, packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs
Adds a self-contradictory JSON contract fixture loaded with full_validation: false and a regression test verifying document creation succeeds while deletion yields invalid_paid_count == 1, valid_count == 0, and no InternalError in execution_results.
Test infrastructure updates
packages/rs-drive/src/drive/contract/mod.rs, packages/rs-drive/src/drive/document/insert/add_document_to_primary_storage/v0/mod.rs
Updates existing test contract builders and test schemas to set canBeDeleted: false alongside documentsKeepHistory: true to comply with the new contradiction rule.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • dashpay/platform#3661: Both PRs modify DocumentTypeV2::try_from_schema in packages/rs-dpp/.../try_from_schema/v2/mod.rs, adding separate full_validation-gated contract-structure checks (keep-history vs deletable contradiction in this PR vs summable/range-summable parsing/validation in the related PR).

Suggested labels

ready for final review

Suggested reviewers

  • shumkov
  • QuantumExplorer

Poem

🐇 Two flags at odds, a contract's flaw,
Keep history AND delete — oh raw!
The schema now says "pick just one,"
And delete transitions? Cleanly done.
No more InternalError shame,
Just consensus errors, clear as flame! 🌟

🚥 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 PR title clearly describes the main fix: rejecting keep-history document deletes with a clean consensus error instead of internal errors.
Linked Issues check ✅ Passed The PR successfully implements both objectives from issue #3927: DPP schema validation rejects keep-history+deletable combinations via full_validation gating, and Drive ABCI delete-transition validation adds structure checks that surface as clean consensus errors.
Out of Scope Changes check ✅ Passed All changes directly address issue #3927 requirements: DPP schema validation, Drive ABCI structure validation, version gating, test fixtures, and regression tests for keep-history delete handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 17, 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.

@thepastaclaw thepastaclaw marked this pull request as ready for review June 17, 2026 23:53
@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 17, 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.

@thepastaclaw

thepastaclaw commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

✅ Review complete (commit 663b50a)

@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/class_methods/try_from_schema/v2/mod.rs (1)

986-1019: 💤 Low value

Consider adding a test for full_validation: false to verify the gating.

The validation logic is correctly gated by full_validation, but there's no test verifying that contradictory contracts are accepted when full_validation: false (the scenario for re-parsing already-deployed contracts). A test would confirm the gate works as documented in lines 317-321.

📝 Suggested test addition
/// `full_validation: false` must allow the contradictory combination so
/// already-deployed contracts (e.g. testnet `5CBPiadGmx3Zsjc26g5onopcx7pdxHPbrRAUD2T2yAbC`)
/// continue to load when re-parsed at v12+.
#[test]
fn doctype_keep_history_with_can_be_deleted_accepted_without_full_validation() {
    let schema = platform_value!({
        "type": "object",
        "properties": {
            "label": {
                "type": "string",
                "maxLength": 50,
                "position": 0,
            },
        },
        "additionalProperties": false,
        "documentsKeepHistory": true,
        "canBeDeleted": true,
    });
    let platform_version = PlatformVersion::latest();
    let config = DataContractConfig::default_for_version(platform_version)
        .expect("default config available on latest platform version");
    let result = DocumentTypeV2::try_from_schema(
        Identifier::new([1; 32]),
        1,
        config.version(),
        "test_doc",
        schema,
        None,
        &BTreeMap::new(),
        &config,
        false, // full_validation = false
        &mut vec![],
        platform_version,
    );
    assert!(
        result.is_ok(),
        "contradictory flags must be accepted when full_validation=false \
         to allow already-deployed contracts to load; got {:?}",
        result.unwrap_err()
    );
}
🤖 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/class_methods/try_from_schema/v2/mod.rs`
around lines 986 - 1019, Add a new test function after the existing
doctype_keep_history_with_can_be_deleted_rejected test to verify that when
full_validation is set to false, the contradictory combination of
documentsKeepHistory: true and canBeDeleted: true is accepted. The test should
use the same schema structure as the existing rejection test, call
DocumentTypeV2::try_from_schema with full_validation parameter set to false, and
assert that the result is Ok instead of Err. This confirms the validation gating
mechanism works correctly for re-parsing already-deployed contracts as
documented.
packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs (1)

381-390: ⚡ Quick win

Pin this regression to protocol version 12 to keep the gate test deterministic.

Using current_platform_version() makes this test float with latest, so it can stop validating the exact v12 gate behavior over time. Prefer explicitly setting protocol version 12 here (and optionally add a paired v11 assertion for legacy classification).

Suggested change
-        let mut platform = TestPlatformBuilder::new()
-            .build_with_mock_rpc()
-            .set_initial_state_structure();
+        let protocol_version = 12;
+        let mut platform = TestPlatformBuilder::new()
+            .with_initial_protocol_version(protocol_version)
+            .build_with_mock_rpc()
+            .set_initial_state_structure();

         let contract_path = "tests/supporting_files/contract/note/note-contract-keep-history-and-can-be-deleted.json";

-        let platform_state = platform.state.load();
-        let platform_version = platform_state
-            .current_platform_version()
-            .expect("expected to get current platform version");
+        let platform_state = platform.state.load();
+        let platform_version = PlatformVersion::get(protocol_version)
+            .expect("expected platform version for protocol version 12");
🤖 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/deletion.rs`
around lines 381 - 390, The test is currently using current_platform_version()
to retrieve the platform version, which will always use the latest protocol
version and cause the test to float over time, making it unable to reliably
validate v12 gate behavior. Replace the call to current_platform_version() with
an explicit protocol version 12 assignment, ensuring the test pins to protocol
version 12 to maintain deterministic gate validation. Optionally, add a paired
assertion for legacy v11 classification to provide additional coverage.
🤖 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/class_methods/try_from_schema/v2/mod.rs`:
- Around line 986-1019: Add a new test function after the existing
doctype_keep_history_with_can_be_deleted_rejected test to verify that when
full_validation is set to false, the contradictory combination of
documentsKeepHistory: true and canBeDeleted: true is accepted. The test should
use the same schema structure as the existing rejection test, call
DocumentTypeV2::try_from_schema with full_validation parameter set to false, and
assert that the result is Ok instead of Err. This confirms the validation gating
mechanism works correctly for re-parsing already-deployed contracts as
documented.

In
`@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs`:
- Around line 381-390: The test is currently using current_platform_version() to
retrieve the platform version, which will always use the latest protocol version
and cause the test to float over time, making it unable to reliably validate v12
gate behavior. Replace the call to current_platform_version() with an explicit
protocol version 12 assignment, ensuring the test pins to protocol version 12 to
maintain deterministic gate validation. Optionally, add a paired assertion for
legacy v11 classification to provide additional coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25cb4c10-52f5-41b2-a9ed-9d76c405cfe4

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1bcb8 and ed57f33.

📒 Files selected for processing (6)
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_delete_transition_action/advanced_structure_v1/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_delete_transition_action/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs
  • packages/rs-drive-abci/tests/supporting_files/contract/note/note-contract-keep-history-and-can-be-deleted.json
  • packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs

…tests

- Add DPP test asserting documentsKeepHistory + canBeDeleted is accepted
  when try_from_schema runs with full_validation=false (the restore /
  migration / cache-warmup path), pinning the gating behavior that lets
  already-deployed contradictory contracts continue to load at v12+.
- Pin the drive-abci keep-history delete regression test to protocol
  version 12 via with_initial_protocol_version + PlatformVersion::get
  instead of floating on current_platform_version(), so the assertion
  stays bit-for-bit reproducible as new protocol versions ship.

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

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 18, 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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

Addressed both CodeRabbit nitpicks in 078c233:

  • Added full_validation: false coverage proving contradictory keep-history/deletable doctypes still load for already-deployed contracts.
  • Pinned the keep-history delete regression test to protocol version 12 instead of floating on latest/current.

Focused validation after the commit:

  • cargo test -p dpp doctype_keep_history_with_can_be_deleted_accepted_without_full_validation --features all_features — passed
  • cargo test -p drive-abci --features mocks test_document_delete_on_document_type_that_keeps_history_is_rejected — passed

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review

Targeted fix is correct: DPP parser cross-flag guard is gated by full_validation and uses consensus_or_protocol_data_contract_error to surface as ConsensusError in validator builds; drive-abci v1 validator is wired only at PROTOCOL_VERSION_12 so v11 replay is preserved; tests cover the rejected/accepted combinations. Three minor follow-ups: a misleading docstring about the canBeDeleted config default, a missing regression test for the omitted-default boundary, and a missing pre-v12 paired test for the new delete guard. No blockers.

🟡 2 suggestion(s) | 💬 1 nitpick(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/class_methods/try_from_schema/v2/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs:329-339: Add regression test for omitted `canBeDeleted` (default=true) with `documentsKeepHistory: true`
  The guard fires on resolved `DocumentTypeV1` fields, and `documents_can_be_deleted_contract_default()` is `true`, so a schema with `documentsKeepHistory: true` and `canBeDeleted` omitted is now rejected — the most likely real-world misuse. Every other branch is pinned (true+true rejected, true+false accepted, false+true accepted), but the omitted-defaults case is not. A future refactor that inspects raw schema keys instead of resolved values could pass the current tests while silently reopening the contradiction. Add a `doctype_keep_history_with_can_be_deleted_omitted_rejected` test that omits `canBeDeleted` to pin the resolved-default behavior.

In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs:379-390: Pin pre-v12 keep-history delete classification with a paired test
  The new regression runs only against the current platform version, but this PR's correctness depends on `document_delete_transition_structure_validation` activating at v12 while v11 preserves the v0 path for chain replay. Adjacent version-gated deletion behavior in this file already has paired v11/latest helpers; adding the same v11 case for the contradictory keep-history fixture would catch an accidental change to the v11 version table or to the v0 validator that would otherwise alter consensus replay silently.

Comment on lines +329 to +339
if full_validation && v1.documents_keep_history && v1.documents_can_be_deleted {
return Err(consensus_or_protocol_data_contract_error(
DataContractError::InvalidContractStructure(format!(
"document type \"{}\" sets both `documentsKeepHistory: true` and \
`canBeDeleted: true`, but the storage layer unconditionally refuses to \
delete a document whose type keeps history. Set one of the two flags to \
false (or omit it).",
name,
)),
));
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🟡 Suggestion: Add regression test for omitted canBeDeleted (default=true) with documentsKeepHistory: true

The guard fires on resolved DocumentTypeV1 fields, and documents_can_be_deleted_contract_default() is true, so a schema with documentsKeepHistory: true and canBeDeleted omitted is now rejected — the most likely real-world misuse. Every other branch is pinned (true+true rejected, true+false accepted, false+true accepted), but the omitted-defaults case is not. A future refactor that inspects raw schema keys instead of resolved values could pass the current tests while silently reopening the contradiction. Add a doctype_keep_history_with_can_be_deleted_omitted_rejected test that omits canBeDeleted to pin the resolved-default behavior.

source: ['codex', 'claude']

Comment on lines +379 to +390
#[tokio::test]
async fn test_document_delete_on_document_type_that_keeps_history_is_rejected() {
let mut platform = TestPlatformBuilder::new()
.build_with_mock_rpc()
.set_initial_state_structure();

let contract_path = "tests/supporting_files/contract/note/note-contract-keep-history-and-can-be-deleted.json";

let platform_state = platform.state.load();
let platform_version = platform_state
.current_platform_version()
.expect("expected to get current platform version");

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🟡 Suggestion: Pin pre-v12 keep-history delete classification with a paired test

The new regression runs only against the current platform version, but this PR's correctness depends on document_delete_transition_structure_validation activating at v12 while v11 preserves the v0 path for chain replay. Adjacent version-gated deletion behavior in this file already has paired v11/latest helpers; adding the same v11 case for the contradictory keep-history fixture would catch an accidental change to the v11 version table or to the v0 validator that would otherwise alter consensus replay silently.

source: ['codex']

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Resolved in 078c233Pin pre-v12 keep-history delete classification with a paired test 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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correction: this was auto-resolved incorrectly by the review dedupe/reconciliation step. The latest push does pin the regression test to protocol version 12, but the paired pre-v12/v11 classification coverage is still missing, so this finding remains valid at 078c233. Reopening/keeping this thread active.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

CI note: Rust workspace tests / Tests (macOS) is red on an unrelated pre-existing SDK test, not on the keep-history/delete changes in this PR.

Failure:

  • dash-sdk::main fetch::document_query_v0_v1::sdk_builder_default_seeds_atomic_to_floor
  • assertion at packages/rs-sdk/tests/fetch/document_query_v0_v1.rs:227: left: 11, right: 10

Why unrelated:

  • This PR only changes DPP/drive-abci keep-history delete validation plus focused regression tests and the gated drive-abci validation feature version.
  • The failing SDK test/file is unchanged by this PR.
  • The same expectation exists on upstream/v3.1-dev, while SdkBuilder::new_mock() defaults to mainnet and build() clamps to max(DEFAULT_INITIAL_PROTOCOL_VERSION=10, min_protocol_version(Mainnet)=11), so the observed value 11 follows the current base SDK code.

Local focused checks for this PR still pass:

  • cargo test -p dpp doctype_keep_history_with_can_be_deleted_accepted_without_full_validation --features all_features
  • cargo test -p drive-abci --features mocks test_document_delete_on_document_type_that_keeps_history_is_rejected

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 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.

`SdkBuilder::new_mock()` builds on Mainnet, whose floor (PV_11) dominates
`DEFAULT_INITIAL_PROTOCOL_VERSION` (10). Assert the actual
`max(DEFAULT_INITIAL_PROTOCOL_VERSION, network floor)` the SDK seeds,
not the bare constant.
Resolves a single conflict in
packages/rs-sdk/tests/fetch/document_query_v0_v1.rs:
upstream PR dashpay#3900 ("per-network protocol-version floor + version_pinned
unification") removed the DEFAULT_INITIAL_PROTOCOL_VERSION constant and
replaced it with the per-network min_protocol_version() function. The
network-floor-raised test added in 3ed90bc referenced the removed
constant; resolved by adopting the upstream form, which asserts the same
seeded value (PROTOCOL_VERSION_11 on mainnet) without the deleted
constant. PR intent (unpinned SDK on mainnet seeds to the network floor)
is preserved.

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

codecov Bot commented Jun 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.54%. Comparing base (a0c013f) to head (663b50a).
⚠️ Report is 3 commits behind head on v3.1-dev.

❗ There is a different number of reports uploaded between BASE (a0c013f) and HEAD (663b50a). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (a0c013f) HEAD (663b50a)
rust 3 1
Additional details and impacted files
@@              Coverage Diff              @@
##           v3.1-dev    #3930       +/-   ##
=============================================
- Coverage     87.18%   52.54%   -34.64%     
=============================================
  Files          2632       11     -2621     
  Lines        327563     1707   -325856     
=============================================
- Hits         285592      897   -284695     
+ Misses        41971      810    -41161     
Components Coverage Δ
dpp ∅ <ø> (∅)
drive ∅ <ø> (∅)
drive-abci ∅ <ø> (∅)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value ∅ <ø> (∅)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier ∅ <ø> (∅)
🚀 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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 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.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review

Latest push (3ed90bc) only adjusts an unrelated rs-sdk seed test to assert max(DEFAULT_INITIAL_PROTOCOL_VERSION, network floor); that delta is correct. The cumulative keep-history-delete fix is well-targeted and consensus-safe: DPP parse-time rejection under full_validation, version-gated drive-abci advanced_structure_v1 at PROTOCOL_VERSION_12, and ConsensusError plumbing that yields clean invalid-paid classification. Three prior review items remain unaddressed (omitted-default test gap, paired v11 replay test, docstring misstatement) and one new related nit surfaced about misleading error-message wording around the same omitted default.

💬 1 nitpick(s)

3 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

Comment on lines +332 to +335
"document type \"{}\" sets both `documentsKeepHistory: true` and \
`canBeDeleted: true`, but the storage layer unconditionally refuses to \
delete a document whose type keeps history. Set one of the two flags to \
false (or omit it).",

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

💬 Nitpick: Error message suggests an unsafe 'omit it' fix

The error tells the author to 'Set one of the two flags to false (or omit it).' Omitting does not necessarily fix the contradiction: with the default contract config, canBeDeleted resolves to true when omitted (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED in config/fields.rs:6), so documentsKeepHistory: true plus omitted canBeDeleted still hits this error. The message should direct the user to ensure the resolved value of canBeDeleted is false, either explicitly on the document type or via the contract default — otherwise authors will follow the literal instruction and be surprised that omission doesn't help.

source: ['codex']

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review

Latest push (230eeb1) is a routine merge of upstream/v3.1-dev pulling unrelated rs-sdk/swift-sdk changes; none of the keep-history/delete fix files are modified by the delta. The cumulative fix is correct and consensus-safe: DPP rejects the resolved contradictory flags under full_validation, drive-abci routes v12 delete attempts through advanced_structure_v1 via DRIVE_ABCI_VALIDATION_VERSIONS_V8, and the new error helper produces a clean invalid-paid classification. No new findings; all four prior findings still apply at this SHA and are carried forward.

Reviewed commit: 230eeb1

🟡 2 suggestion(s) | 💬 2 nitpick(s)

Carried-forward prior findings

nitpick: Docstring misstates `canBeDeleted` default and sibling test coverage

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 1069)

This comment claims doctype_keep_history_without_summable_accepted leaves canBeDeleted at its config default (false). Both halves are wrong against the current tree: (1) DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED is true (packages/rs-dpp/src/data_contract/config/fields.rs:6), so the config default is true, not false; (2) the sibling test explicitly sets "canBeDeleted": false — it does not exercise the omitted-default path at all. As written, this misleads future maintainers into thinking the omitted-key case is already covered and benign, when in fact it resolves to documents_can_be_deleted = true and is rejected at v12+.

Suggested fix:

/// Guard against an over-broad fix: `documentsKeepHistory: true` +
/// `canBeDeleted: false` is consistent (the doctype is append-only)
/// and must continue to parse cleanly. NOTE: the omitted-key case is
/// NOT covered here — the contract default for `canBeDeleted` is
/// `true` (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED), so a schema
/// that omits `canBeDeleted` with `documentsKeepHistory: true`
/// resolves to the contradictory state and is rejected. Omitted-key
/// coverage belongs in a separate regression test.
suggestion: Add regression test for omitted `canBeDeleted` (default=true) with `documentsKeepHistory: true`

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 329)

The guard fires on the resolved DocumentTypeV1 booleans, and documents_can_be_deleted resolves to true when the schema omits canBeDeleted (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED = true). So documentsKeepHistory: true with canBeDeleted omitted is rejected at v12+ — the most likely real-world authoring mistake. Current tests pin true+true rejection, true+false acceptance, and false+true acceptance, but not the omitted-default rejection. A future refactor that inspects raw schema keys instead of resolved values could pass the current tests while silently reopening the contradiction. Add a doctype_keep_history_with_can_be_deleted_omitted_rejected test that omits the key and asserts the resolved default still rejects under full_validation: true.

suggestion: Pin pre-v12 keep-history delete classification with a paired v11 test

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs (line 379)

test_document_delete_on_document_type_that_keeps_history_is_rejected is correctly pinned to PROTOCOL_VERSION = 12, but the PR's correctness story explicitly depends on document_delete_transition_structure_validation activating at v12 while v11 preserves the v0 path (and its pre-fix InternalError/replay behavior). Other version-gated deletion tests in this file already use paired v11/latest helpers; adding the same v11 case for the contradictory keep-history fixture would catch any accidental change to the v11 version table or to advanced_structure_v0 that would otherwise alter consensus replay silently. Without it, only the post-fix branch is exercised by CI.

nitpick: Error message suggests an unsafe 'omit it' fix

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 332)

The error tells the author to "Set one of the two flags to false (or omit it)." Omitting does not necessarily fix the contradiction: with the default contract config, canBeDeleted resolves to true when omitted (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED = true), so documentsKeepHistory: true plus omitted canBeDeleted still hits this error. The message should direct the user to ensure the resolved value of canBeDeleted is false (explicitly on the document type or via the contract default), otherwise authors will follow the literal instruction and be surprised that omission doesn't help.

Suggested fix:

                "document type \"{}\" sets both `documentsKeepHistory: true` and \
                 resolves `canBeDeleted` to true, but the storage layer unconditionally \
                 refuses to delete a document whose type keeps history. Ensure \
                 `canBeDeleted` resolves to false (either explicitly on the document \
                 type or through the contract default), or set `documentsKeepHistory: false`.",

No new findings in the latest delta; these are the still-valid prior findings revalidated at this head. Existing inline threads were already open, so this body-only exact-SHA review records coverage without duplicating inline comments.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 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.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review

Latest push (a90cf62) is a one-line test-fixture adjustment in rs-drive that adds canBeDeleted: false alongside documentsKeepHistory: true so an existing ChangingDocumentTypeKeepsHistory regression test reaches its intended assertion under the V2 parser's new cross-flag rule — mechanical, consensus-safe, and contains no new in-scope issues. Cumulative review confirms the version-gated v12 drive-abci delete-transition guard, DPP full_validation rejection, and clean ConsensusError classification remain correct, and all four prior carry-forward findings (documentation, error wording, and test-coverage gaps around the canBeDeleted default and pre-v12 replay) still apply at this SHA.

Reviewed commit: a90cf62

🟡 2 suggestion(s) | 💬 2 nitpick(s)

Prior findings reconciliation

  • prior-1: STILL VALIDdocuments_can_be_deleted_contract_default still defaults to true via DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED (config/fields.rs:6); the new guard at v2/mod.rs:329 fires on resolved booleans, and there is still no test that omits canBeDeleted under documentsKeepHistory:true.
  • prior-2: STILL VALID — deletion.rs test is still pinned to v12 only; no paired v11 case for the contradictory keep-history fixture exists.
  • prior-3: STILL VALID — Docstring at v2/mod.rs:1069-1074 still claims the sibling test leaves canBeDeleted at its config default (false); the default is true and the sibling test was updated this PR to explicitly set canBeDeleted:false.
  • prior-4: STILL VALID — Error message at v2/mod.rs:332-335 still advises 'Set one of the two flags to false (or omit it)', but omitting canBeDeleted resolves to true via the contract default and still trips the same rejection.

Verified findings

nitpick: Docstring misstates `canBeDeleted` default and sibling-test coverage

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 1069)

The docstring asserts that doctype_keep_history_without_summable_accepted covers the omitted-canBeDeleted case by leaving the key at its config default (false). Both halves are wrong against the tree at this SHA: (1) DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED is true (packages/rs-dpp/src/data_contract/config/fields.rs:6), so the resolved default is true, not false; (2) the sibling test was edited in this PR to explicitly set canBeDeleted: false and no longer exercises the omitted-key path at all. As written, the comment misleads maintainers into assuming the omitted-default case is already covered and benign, when in fact omitting canBeDeleted under documentsKeepHistory: true resolves to the contradictory state and is rejected at v12+.

Suggested fix:

/// Guard against an over-broad fix: `documentsKeepHistory: true` +
/// `canBeDeleted: false` is consistent (the doctype is append-only)
/// and must continue to parse cleanly. NOTE: the omitted-key case is
/// NOT covered here — the contract default for `canBeDeleted` is
/// `true` (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED), so a schema
/// that omits `canBeDeleted` with `documentsKeepHistory: true`
/// resolves to the contradictory state and is rejected. Omitted-key
/// coverage belongs in a separate regression test.
nitpick: Error message suggests an unsafe 'omit it' fix

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 332)

The validation error tells the author to "Set one of the two flags to false (or omit it)." Omitting does not necessarily fix the contradiction: with the default contract config, canBeDeleted resolves to true when omitted (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED = true, packages/rs-dpp/src/data_contract/config/fields.rs:6), so documentsKeepHistory: true plus omitted canBeDeleted still hits this exact error. SDK users will follow the literal instruction and be surprised when omission doesn't help. Redirect the user to the resolved value instead.

Suggested fix:

                "document type \"{}\" sets both `documentsKeepHistory: true` and \
                 resolves `canBeDeleted` to true, but the storage layer unconditionally \
                 refuses to delete a document whose type keeps history. Ensure \
                 `canBeDeleted` resolves to false (either explicitly on the document \
                 type or through the contract default), or set `documentsKeepHistory: false`.",
suggestion: Add regression test for omitted `canBeDeleted` (default=true) with `documentsKeepHistory: true`

packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v2/mod.rs (line 329)

The new guard fires on the resolved DocumentTypeV1 booleans, and documents_can_be_deleted resolves to true when the schema omits canBeDeleted (DEFAULT_CONTRACT_DOCUMENTS_CAN_BE_DELETED = true). So documentsKeepHistory: true with canBeDeleted omitted is the most likely real-world authoring path and is rejected at v12+, but the current tests only pin the explicit true+true rejection, explicit true+false acceptance, and full_validation=false bypass. A future refactor that inspects raw schema keys instead of resolved values could pass the existing suite while silently reopening the contradiction. Add a doctype_keep_history_with_can_be_deleted_omitted_rejected test that omits the key and asserts the resolved default still rejects under full_validation: true.

suggestion: Pin pre-v12 keep-history delete classification with a paired v11 test

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/deletion.rs (line 379)

test_document_delete_on_document_type_that_keeps_history_is_rejected is correctly pinned to PROTOCOL_VERSION = 12, but the PR's correctness story explicitly depends on document_delete_transition_structure_validation activating at v12 while v11 preserves the v0 path (and its pre-fix InternalError/replay behavior). Nearby version-gated deletion tests already use paired v11/latest helpers (e.g. run_document_delete_on_document_type_that_is_mutable_and_can_be_deleted_at_protocol_version, run_document_delete_that_does_not_yet_exist_at_protocol_version). Adding the same paired v11 case for the contradictory keep-history fixture would catch any accidental change to the v11 version table or to advanced_structure_v0 that would otherwise alter consensus replay silently. Without it, only the post-fix v12 branch is exercised in CI.

Note: inline posting deduped these carried-forward findings against existing open threads, so this exact-SHA review is posted body-only to preserve the review record without duplicating inline comments.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 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.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review

Cumulative re-review of PR #3930 at 663b50a. The latest delta (a90cf62..663b50a) is a one-line test fixture update in rs-drive that adds canBeDeleted: false to the keep_history_summable_e2e schema so it satisfies the new DPP cross-flag guard — no new issues introduced. All four prior findings reconcile as STILL VALID against current HEAD: the docstring at v2/mod.rs:1069-1074 still misstates both the canBeDeleted default (it is true) and the sibling-test coverage (the sibling now explicitly sets canBeDeleted: false), the error string still suggests an unsafe 'omit it' fix, no omitted-key regression test was added, and the keep-history delete test is still only pinned at v12 with no paired v11 case. All findings carried forward — none new in the delta.

4 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 20, 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.

@thepastaclaw

Copy link
Copy Markdown
Collaborator Author

CI follow-up on the current red checks at 663b50ab83:

  • Rust workspace tests / Tests (macOS) was cancelled by GitHub/the runner while the shielded-test step was still running. The log shows thousands of Rust tests passing and then only ##[error]The operation was canceled; I did not find a Rust panic/assertion failure in the job output.
  • Swift SDK build / Swift SDK build + tests (warnings as errors) passed afterward.
  • codecov/project is red because the run did not complete/upload full coverage after the cancellation; the log also shows llvm-cov warnings from simulator universal binaries, not a source/test failure in this PR.

Leaving the branch unchanged: this looks like CI/runner/coverage plumbing rather than a regression in the keep-history delete validation diff.

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.

Document type allowing documentsKeepHistory: true + canBeDeleted: true: delete fails as an internal error, not a clean rejection

1 participant