Skip to content

feat(platform)!: anyOf, allOf and not in propertyConstraints rules (PV14) - #5036

Merged
QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/property-constraints-disjunction-abd48e
Sep 27, 2026
Merged

QuantumExplorer merged 2 commits into
v4.2-devfrom
claude/property-constraints-disjunction-abd48e

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: A contract can already declare propertyConstraints, named rules that a document's integer properties must meet, such as price * quantity <= deposit. Until now a rule could only be a single comparison. This PR adds anyOf (or), allOf (and) and not, so a rule can say things like "a is 0 or b is 4".

Value: Contract authors can state either/or rules in the contract instead of in application code, and consensus enforces them on every create and replace. One workaround existed before: a * (b - 4) == 0 for "a is 0 or b is 4". It only works for equalities and reads poorly.

Risks: Low. It changes consensus validation, but only in protocol version 14, which is not on mainnet. It also only widens what is accepted: every rule that registered before still registers and evaluates exactly as it did. Devnets running 4.2.0-beta.4 would refuse a contract that uses the new keywords, so their nodes need to upgrade together. The error message for a broken rule changes wording; the error code and its encoded data do not.

Issue being fixed or feature implemented

propertyConstraints (#4962) had no way to say "or". The request was to express a == 0 || b == 4 directly.

What was done?

Grammar

A rule is now a condition, an object with one key:

  • one of the six comparisons, as before;
  • anyOf: two or more conditions, at least one holds;
  • allOf: two or more conditions, every one holds;
  • not: one condition, which does not hold.

Conditions nest freely. Before, the rule below was refused when the contract registered (the meta-schema took only the six comparison keys). Now it registers:

"propertyConstraints": {
  "aIsZeroOrBIsFour": { "anyOf": [{ "equal": ["a", 0] }, { "equal": ["b", 4] }] },
  "noFreeLargeOrder": {
    "not": { "allOf": [{ "equal": ["price", 0] }, { "greaterThan": ["quantity", 10] }] }
  }
}

With aIsZeroOrBIsFour, a document { "a": 1, "b": 4 } is accepted. { "a": 1, "b": 5 } is refused with DocumentPropertyConstraintViolatedError (10422), rule aIsZeroOrBIsFour, violation NotMet.

Evaluation order

  • Conditions are checked in declared order and stop once the outcome is known: anyOf at the first that holds, allOf at the first that fails.
  • A fault in a condition that is checked still refuses the document, whatever the others would say. The faults are overflow, zero divisor, negative exponent and a non-integer value. not never turns a fault into a pass.
  • So an earlier condition guards a later one, like || in most languages:
{ "anyOf": [{ "equal": ["b", 0] }, { "equal": [{ "divide": ["a", "b"] }, 2] }] }

For { "a": 6, "b": 0 } this holds without dividing. With the two conditions swapped, the same document is refused with DivisionByZero.

Parse rules (every parse, stored contracts included)

  • anyOf and allOf list two or more conditions.
  • An anyOf may not sit directly in an anyOf, and an allOf may not sit directly in an allOf: one flat list says the same. This matches refersTo expressions. A not may not sit directly in a not.
  • The "reads no property" check moves from the whole rule to each comparison. Otherwise { "equal": [1, 1] } inside an anyOf would make the rule hold for every document.
  • MAX_PROPERTY_CONSTRAINT_PARSE_DEPTH (64) now counts conditions and operands together.

Registration rules (full validation, with the existing limits)

  • max_property_constraint_nodes (32) counts every comparison and logical operator.
  • No anyOf or allOf may list the same condition twice (PropertyConstraint::repeated_condition). Conditions are compared as they parse, so 1 and 1.0 are alike, and so are "price" and { "ifAbsent": ["price", 0] }. The meta-schema's uniqueItems catches identical JSON first. Like refersTo operand uniqueness, this runs only under full validation, where the node limit bounds the lists it compares.
  • Errors name where the problem is. Before: rule "r" must be an object with one key, its comparison: .... After: rule "r" at anyOf[1] must be an object with one key: a comparison (equal, ...), anyOf, allOf or not, or rule "r" at allOf[1].not.lessThan[0].divide divides by 0.

Code

  • property_constraints/mod.rs: PropertyConstraint changes from a struct into an enum: Compare { comparison, left, right }, AnyOf, AllOf, Not. It gains holds() and repeated_condition(), and violation(), node_count() and property_paths() recurse through conditions. parse_rule becomes the recursive parse_condition plus condition_list.
  • apply_property_constraints_v0 (parser generation 3) refuses a repeated condition next to the node limit.
  • Meta-schema v3: the propertyConstraint def gains anyOf/allOf (new propertyConstraintConditions: minItems 2, uniqueItems, no directly nested same operator) and not (no not directly inside). The keyword description is updated.
  • PropertyConstraintViolation::NotMet message. Before: its two sides do not compare as it requires. After: it does not hold. The encoded error is unchanged.
  • Docs: v14 note 39, the book's Property Constraints section and the js-evo-sdk README.

How Has This Been Tested?

  • property_constraints/tests.rs: parsing of all three operators, every new refusal with its error location, the nesting cap (both the operand and the condition guard), finding repeated conditions, short-circuit order and fault behaviour, the a == 0 || b == 4 truth table, node counts and paths.
  • try_from_schema/v3/property_constraints_tests.rs: the meta-schema accepts nested conditions on registration and refuses each malformed shape. Both the full-validation and stored-contract paths are covered. Also covered: a non-integer or transient property read deep inside a condition is refused, logical nodes count against the node limit, and a repeated condition is refused on registration only.
  • drive-abci batch/tests/document/property_constraints.rs: the offer fixture gains feeWaivedOrAtLeastTen (anyOf) and feeWaivedOnlyWithDiscount (not over allOf). The new should_judge_any_of_all_of_and_not runs real create transitions: two refused with 10422, one accepted.
  • cargo test -p dpp --lib (4846 passed), cargo test -p drive-abci --lib property_constraints (9 passed), cargo clippy -p dpp --all-features --all-targets -- -D warnings, cargo clippy -p drive-abci --all-targets -- -D warnings.

Breaking Changes

Consensus, protocol version 14 only. Contracts may now register rules using anyOf, allOf and not, which earlier builds of 4.2 refuse. Meta-schema v3, parse_property_constraints 0 and validate_property_constraints 0 were introduced in protocol version 14, which is not released on mainnet, so they are extended in place. Every declaration valid before parses and evaluates identically. Protocol version 13 and earlier are unaffected: they ignore the keyword.

Rust API: PropertyConstraint is now an enum. Its old fields live in the Compare variant.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

…V14)

A propertyConstraints rule is now a condition: a comparison of two
integer expressions, as before, or anyOf / allOf over two or more
conditions, or not over one, nesting freely. Conditions are checked in
declared order and no further than the outcome needs, a fault in one
that is checked refuses the document, and not never turns a fault into
a pass, so an earlier condition guards a later one.

Extended in place in meta-schema v3 and parse_property_constraints 0:
every declaration valid before parses and evaluates as it did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 27, 2026
@coderabbitai

coderabbitai Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: a0bc4e0f-79c8-4369-9e4a-076169592414

📥 Commits

Reviewing files that changed from the base of the PR and between 5febda1 and 6cd24a2.

📒 Files selected for processing (15)
  • book/src/data-model/documents.md
  • packages/js-evo-sdk/README.md
  • packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/property_constraints_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property_constraints/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property_constraints/tests.rs
  • packages/rs-dpp/src/data_contract/document_type/v2/mod.rs
  • packages/rs-dpp/src/errors/consensus/basic/document/document_property_constraint_violated_error.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/tests/document/property_constraints.rs
  • packages/rs-platform-version/src/version/system_limits/mod.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp2/src/consensus_error.rs

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

❤️ Share

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

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

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

Updated at 2026-09-27T06:31:17.348Z

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 27, 2026
@thepastaclaw

thepastaclaw commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 2nd in line, estimated start in ~10 min (commit 2310939)
Estimated review time once started: ~1.0 h (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

…tration, refresh docs

- Move the "no two alike conditions" check from every parse to full
  validation (PropertyConstraint::repeated_condition, called by
  apply_property_constraints_v0 next to the node limit), like refersTo
  operand uniqueness: the node limit bounds the lists it compares.
- Test that a comparison reads a property without allocating a path list.
- Tests: the condition-level depth guard, logical nodes against the node
  limit at registration, a transient read inside a nested condition, and
  repeated conditions on both parse paths.
- Docs that still described every rule as a comparison: wasm-dpp2 10422
  code, SystemLimits::max_property_constraint_nodes, DocumentTypeV2 field,
  PROPERTY_CONSTRAINTS, the drive-abci test module, v14 note, book.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit 8936d44 into v4.2-dev Sep 27, 2026
9 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/property-constraints-disjunction-abd48e branch September 27, 2026 06:31
QuantumExplorer added a commit that referenced this pull request Sep 28, 2026
…rators

- notIn's meta-schema description said what an anyOf of equals says;
  it now says an allOf of notEquals, a not over the in.
- A not directly holding a notIn (an in of the same values, one node
  more) is refused like a not holding a not, by the parser and the
  meta-schema.
- min and max fold from their identities, as add and multiply do: no
  allocation per evaluation and no stand-in error for an empty list.
- abs given a list says it takes a single operand; a notIn over strings
  names itself in its operand error.
- Docs: SystemLimits max_property_constraint_nodes, the PROPERTY_CONSTRAINTS
  constant, the DocumentTypeV2 field and PropertyConstraint::holds cover
  the operators added since #5036, and implies' evaluation order.
- v14 note 39 is back to v4.2-dev's wrapping with only the touched lines
  reflowed, so its diff is the change.
- Tests: notIn and implies over $ownerId and the update and transfer
  times answer to transfers and price updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants