Skip to content

feat(platform)!: anyOf and allOf reference expressions (PV14) - #4942

Merged
QuantumExplorer merged 3 commits into
v4.2-devfrom
feat/refers-to-any-of
Sep 23, 2026
Merged

QuantumExplorer merged 3 commits into
v4.2-devfrom
feat/refers-to-any-of

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A refersTo names exactly one target today. The moderation charters contract (#4898) needs a reference that holds when one of several targets does: a resignation must come from a member who is either listed in the elected charter or was added later through an addedModerator. This PR lets a refersTo be a small expression at protocol version 14: { "anyOf": [...] } holds if at least one operand holds, { "allOf": [...] } if every operand holds for the same value, and the two nest up to 4 combinators deep. The charters contract itself is not edited here.

What was done?

The declaration

refersTo is one target, as before, or an object whose only key is anyOf or allOf, a list of operands. An operand is a leaf (an ordinary target with its own keys) or an expression of the other combinator. Allowed on an identifier property and on the items of a typed array of identifiers (#4928).

"memberId": {
  "type": "array", "byteArray": true, "minItems": 32, "maxItems": 32,
  "contentMediaType": "application/x.dash.dpp.identifier",
  "refersTo": {
    "anyOf": [
      { "type": "permanentDocument", "documentType": "addedModerator",
        "lookup": { "index": "byModerator", "keys": { "submittedCharterId": "submittedCharterId", "moderatorId": "." } } },
      { "allOf": [
        { "type": "identity" },
        { "type": "permanentDocument", "documentType": "joinRequest",
          "lookup": { "index": "bySubmittedCharter", "keys": { "submittedCharterId": "submittedCharterId", "$ownerId": "." } } }
      ] }
    ]
  }
}
Before: refused by meta-schema v3 ("type" is required; "anyOf"/"allOf" are not properties of refersTo)
After:  IdentifierWithReference(AnyOf([PermanentDocumentLookup{addedModerator}, AllOf([Identity, PermanentDocumentLookup{joinRequest}])]))
        reads: added as a moderator, or an identity that asked to join

Shape rules, refused on every parse by meta-schema v3 and the parser:

Declaration Outcome
{ "anyOf": [ { "type": "identity" } ] } refused: "refersTo anyOf must list at least two operands"
{ "anyOf": [ ..., { "anyOf": [...] } ] } (same combinator directly inside) refused: "refersTo anyOf[1] is an anyOf directly inside an anyOf, which says what one flat list says"
{ "anyOf": [...], "type": "identity" }, a propertyAgreement beside a combinator, or both anyOf and allOf refused: "refersTo anyOf declares nothing beside anyOf"
a contract leaf (at any depth) refused: "refersTo anyOf[1].allOf[1] is a reference of type contract, which a reference expression does not take: ..."
token, deletableDocument, identityPublicKey (either form) leaves refused the same way, each with its reason
an expression on an integer key id property refused: "refersTo allOf is only allowed on identifier properties"

Registration limits, full validation only (like the other reference limits, so a stored contract is never re-judged):

Declaration Outcome
5 combinators on one path refused: "declares a refersTo expression nested 5 deep, above the maximum of 4" (SystemLimits::max_reference_expression_depth)
a list of 5 operands, at any depth refused: "declares a refersTo anyOf[1].allOf of 5 operands, above the maximum of 4" (max_reference_operands)
two alike operands in one list, including a leaf naming the declaring contract explicitly next to one omitting it refused: "declares a refersTo allOf whose operand 2 repeats an earlier one"
typed array of 128 elements, 3 leaves refused: "declares references for up to 384 values" (every leaf counts against max_references_per_document)

Why only identity and permanentDocument leaves (also in the meta-schema's referenceOperands description): both are existence checks against entities that are never deleted, so an expression of them holds for good once it holds and is re-validated on replace only when its value or a property one of its leaves binds changed, exactly like a single target. The other types do not compose with other operands: deletableDocument is re-validated on every replace and may be cleared once its document is deleted (the immutable-property exception assumes the property refers to that one target); identityPublicKey pairs the value with a key id property no other operand reads; a contract target's requirements are gates judged against the block time and the writer rather than an existence check, and a contract or token id is never also an identity or document id. Admitting another leaf type after 4.2 ships takes a new apply_property_reference generation and meta-schema, not an edit to COMBINABLE_REFERENCE_TARGET_TYPES.

Registration

Every leaf is checked exactly as the same declaration alone, at the same places: the contract parse for a lookup into the declaring contract (create_document_types_from_document_schemas v1), the registration state validation for everything that needs state (data_contract_reference_validation v0). Every leaf must pass, since each has to be a declaration that could hold. Errors name the leaf by where it sits:

anyOf [ joinRequest lookup, allOf [ identity, { permanentDocument removedModerator } ] ]
-> ReferencedDocumentTypeNotFoundError (40121), path "resignation.memberId.anyOf[1].allOf[1]"

anyOf [ addedModerator lookup, allOf [ identity, joinRequest lookup on a non-unique index ] ]
-> "document type \"resignation\" property \"memberId\" refersTo anyOf[1].allOf[1] lookup: index \"byMessage\" of \"joinRequest\" is not unique"

Write time

validate_reference_v0 (document reference validation v0) evaluates an expression operand by operand in declared order, a nested expression the same way; a leaf goes to the per-target check it always ran (validate_reference_target_v0, unchanged).

  • anyOf stops at the first operand that holds; when none does, the result is the last operand's.
  • allOf stops at the first operand that fails, with that operand's result.
  • Every read is billed as it is made, the failed operands' included; an allOf whose first operand fails reads nothing more.
member M asked to join charter 1; moderator D was added to charter 1; S is a moderator of charter 2 only

memberId = anyOf(joinRequest, addedModerator)
  M -> accepted (1 query)        D -> accepted (2 queries)
  S -> refused, paid: 40120 for the addedModerator lookup (the LAST operand)

vettedMemberId = allOf(joinRequest, addedModerator)
  M -> refused: 40120 for addedModerator (first failing operand; 2 queries)
  D -> refused: 40120 for joinRequest (first operand fails; 1 query)
  M after being added -> accepted

identifiedMemberId = allOf(identity, anyOf(joinRequest, addedModerator))
  a non-identity id -> refused with the identity leaf's 40120
  S                 -> refused with the nested anyOf's last operand's 40120 (addedModerator)

members = [D, M, S] with the anyOf on the items -> refused at "members[2]"

Decision: a refusal is always a leaf's own error, no new state error. Each leaf's failure already has a precise error with the path and the target embedded; a "no operand held" error would have to nest one consensus error per leaf or drop their reasons; and the author controls which failure a writer sees by ordering the lists (the most general anyOf operand last, the most telling allOf operand first). StateError and the JS error-code surface are unchanged, and a reference error never carries a combinator.

A propertyAgreement belongs to its leaf and is judged only against that leaf's document:

agreedMemberId = anyOf(joinRequest lookup + propertyAgreement { title: message }, addedModerator lookup)
title "let me in",  M -> accepted          title "let me out", M -> refused with addedModerator's 40120
title "let me out", D -> accepted (second leaf declares no agreement)

On replace, the bound-property rule moved into binds_a_changed_property (same arms, same comments); an expression is re-validated as a whole when any of its leaves would be. A replace changing submittedCharterId (a key part the lookups read) re-validates memberId; one changing only title does not.

Model and encoding

  • DocumentPropertyReferenceTarget::AnyOf(ReferenceOperands) (variant 7) and AllOf(ReferenceOperands) (8) are appended, so every existing target keeps its variant and its bytes (should_keep_the_encoding_of_a_reference_error_for_an_id_reference still pins them). Serde writes {"anyOf": [...]} / {"allOf": [...]}.
  • The enum is embedded in consensus errors, which clients decode (untrusted) from bytes a node sends, and it is now recursive. ReferenceOperands decodes through a depth guard refusing a nesting deeper than MAX_REFERENCE_EXPRESSION_DECODE_DEPTH (16), tested with 100,000 claimed levels on every decoder; a test holds every protocol version's max_reference_expression_depth below it so every registrable declaration decodes.
  • leaves() / leaves_with_paths() walk the leaves (a single target is its own leaf at an empty path), expression_depth() and combinator() describe the tree. Every consumer of reference() / as_document_reference() / as_any_document_reference() was audited:
Consumer Expression
validate_reference_lookup_sources (every parse), same-contract lookup check, registration validator walk leaves_with_paths(): each leaf checked as alone, errors name the leaf
validate_reference_expressions (new, full validation) depth, operands per list, alike operands normalized against the declaring contract id
validate_reference_count counts leaves (PropertyReference::max_references)
write-time validator recursive evaluation, a leaf's own error
validate_no_immutable_deletable_element_references, deletable_document_reference_target_is_gone no change: an expression never holds a deletable leaf
chained query join, composite by-id join refused by name ("declares a refersTo anyOf or allOf expression"), like a lookup
preallocated index bindings never bound; comment + test for both combinators
IdentifierWithReference(_) matches (identifier paths, query conditions, typed array element width, random documents) unaffected: still an identifier
wasm-dpp2 documentTypeReferences { path, type: 'anyOf', anyOf: [...] } / { path, type: 'allOf', allOf: [...] }, operands recursive: the list sits under the schema's own key and type tags the union like every other member (wasm-dpp2 CONVENTIONS.md, "Tagged unions")

Update rules

A changed expression is an incompatible schema change: the compatibility rule for refersTo already freezes everything beneath it (the rule walker never descends into anyOf/allOf as JSON Schema keywords). Tests cover single to expression and back, adding, removing, reordering and changing an operand, swapping anyOf for allOf, and nesting deeper, on a property and on typed array elements.

Meta-schema

refersTo picks its form with if/then/else (anyOf present: only that key; else allOf present: only that key; else type required), so an invalid single target keeps its precise error ("type is a required property") rather than an opaque oneOf failure. Both combinators reference a shared $defs/referenceOperands (two or more unique operands, leaf types restricted), and each forbids its own combinator directly inside.

Docs

v14 changelog item 33, book/src/data-model/documents.md ("Reference expressions (anyOf, allOf)"), the meta-schema descriptions, and the wasm-dpp2 TypeScript types (DocumentPropertyReferenceExpression, DocumentPropertyReferenceOperand, tagged type: 'anyOf' | 'allOf').

Before (this PR's first commit): documentTypeReferences('resignation') -> [{ path: 'memberId', anyOf: [...] }]   // no type: switch (ref.type) fell through
After:                           documentTypeReferences('resignation') -> [{ path: 'memberId', type: 'anyOf', anyOf: [...] }]

Test helpers

The parser suites for lookups and expressions share reference_test_helpers.rs (the charter's joinRequest type, identifier properties, the parse and the refusal check), and the drive join refusals for both kinds live in one reference_join_tests.rs on one fixture. The ABCI suite uses the shared reference_test_setup harness.

Siblings

listElement (#4940) and ownerRefersTo (#4941) are open, not merged (v4.2-dev fetched before starting and before pushing, still at af350a9), so there is no composing test for them here. Whichever of these lands after another:

In-place changes to shipped generations

Generation Selected by Why consensus cannot change there
create_document_types_from_document_schemas v1 (same-contract lookup check) every protocol version from the one that introduced token-only contracts (CONTRACT_VERSIONS_V2) The loop walks leaves_with_paths(), which for a single declaration is the declaration itself at an empty path, so the walk and the error text are identical wherever no expression exists; an expression parses only where apply_property_reference is Some(0), PV14. Comment at the edit.
data_contract_reference_validation v0 every table, called only from contract create/update state validation of PV14 The per-leaf checks moved into validate_reference_target_declaration_v0 unchanged (same order, errors, billing, memo); a single declaration is one leaf at the declaration's own path. Comment at the edit.
document_reference_validation v0 every table, called only from document create state validation 2 and replace 1 (PV14) validate_reference_target_v0 is the old per-target body, unchanged; validate_reference_v0 sends a single target straight to it; the bound-property match moved verbatim into binds_a_changed_property. should_validate_no_expression_at_protocol_version_13 runs this generation at protocol version 13 on the fixture: the PV13 parse holds no reference, so it reads and bills nothing. Comments at the edits.

Meta-schema v3, the parser (apply_property_reference 0) and try_from_schema generation 3 are PV14-only and unreleased (edited in place per the book).

How Has This Been Tested?

  • rs-dpp (new, all "should"): parse (anyOf of two lookups in order; allOf; nested to the depth limit; agreement per leaf; typed array elements), refusals by the parser and, asserted separately, by the meta-schema (fewer than two operands at any depth, forbidden leaf types at any depth, same combinator directly nested, keys beside a combinator, non-identifier property), registration limits derived from SystemLimits (depth, operands at the top and nested, alike operands including the own-contract-id spelling, leaf count), each leaf checked as alone with its path in the error, PV13 gate, round trip including a depth-4 expression, model (leaves_with_paths, expression_depth, Display, max_references), decode guard (every decoder, the bound itself, one past it, 100,000 claimed levels, depth restored after a refusal, every version's limit within the bound), preallocation for both combinators, update incompatibility.
  • drive-abci (fixture reference-validation-contract-reference-expression.json): anyOf first/second/none, elements, identity-or-document, agreement per leaf, allOf all/first-fails/second-fails, nested allOf(identity, anyOf(...)), depth-4 evaluation, replace re-validation on a changed key part, billing of failed operands and allOf short-circuit, the PV13 run of generation 0, registration success, registration failure at anyOf[1].allOf[1]. Mutation-checked: making allOf stop on success fails four tests; making an expression never re-validate on replace fails the replace test.
  • drive: chained and composite join refusals for both combinators.
  • wasm-dpp2: JS specs for the tagged { path, type: 'anyOf', anyOf } form, an allOf nested in an anyOf, a refused leaf type and a same-combinator nesting, each with its message; a TypeScript probe compiled with tsc --strict against the generated dpp.d.ts shows the recursive types narrow on type and refuse a contract operand.
  • Ran locally:
    • cargo test -p dpp --all-features --lib: 4750 passed, 0 failed
    • cargo test -p drive-abci --lib -- reference refers_to: 137 passed (the reference suites, including the 14 new tests)
    • cargo test -p drive --lib -- reference_join_tests preallocat: all passed (the 4 join refusals on the shared fixture)
    • cargo test -p json-schema-compatibility-validator, cargo test -p platform-version: all passed
    • cargo clippy -p dpp --all-features --all-targets, cargo clippy -p drive -p drive-abci --all-targets, cargo clippy -p wasm-dpp2 --target wasm32-unknown-unknown, all -D warnings: clean
    • cargo check -p wasm-dpp2 --target wasm32-unknown-unknown: clean; cargo fmt --all --check: clean
    • yarn workspace @dashevo/wasm-dpp2 build, then the wasm-dpp2 mocha unit suite: 1316 passing, 7 pending
    • yarn eslint tests/unit/DocumentPropertyReference.spec.ts: clean
  • Not run locally: the full drive-abci suite (CI) and the karma browser run.

Breaking Changes

Consensus-breaking at protocol version 14 (unreleased): meta-schema v3 and the parser admit reference expressions, and DocumentPropertyReferenceTarget gains two appended variants (existing encodings unchanged). Rust code matching the enum exhaustively needs AnyOf and AllOf arms. SystemLimits gains max_reference_operands and max_reference_expression_depth.

Checklist:

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

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · 853cd59

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs
  • Build running
  • Approvals — you own every area touched; none needed

When every box is checked the PR Hygiene check passes and this can merge.

A refersTo may be { "anyOf": [target, ...] } in place of one target, on
an identifier property or on the elements of a typed array, and holds if
at least one target holds. Targets are identity or permanentDocument (by
id or through a lookup); contract, token, deletableDocument,
identityPublicKey, a nested anyOf, keys beside anyOf and a repeated
target are refused on every parse. Registration caps a list at
SystemLimits::max_any_of_reference_targets (4) and counts every target
against max_references_per_document, and checks each target as the same
declaration alone.

At write time the targets of each value are checked in declared order
and the first that holds ends the check; every read is billed, and when
none holds the write is refused with the last target's error, so no new
StateError exists. A propertyAgreement belongs to its target.

DocumentPropertyReferenceTarget::AnyOf is appended (existing encodings
unchanged) and decodes through a guard that refuses an anyOf inside an
anyOf, so consensus error bytes cannot drive unbounded recursion. Joins
refuse an anyOf join property and preallocated indexes never bind one.

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

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 54 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: 067c9f81-e1c3-49c3-8aba-2bad9e9a216a

📥 Commits

Reviewing files that changed from the base of the PR and between af350a9 and 853cd59.

📒 Files selected for processing (35)
  • book/src/data-model/documents.md
  • packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json
  • packages/rs-dpp/src/data_contract/document_type/class_methods/create_document_types_from_document_schemas/v1/mod.rs
  • 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/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/reference_expression_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/reference_lookup_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/reference_test_helpers.rs
  • packages/rs-dpp/src/data_contract/document_type/index/preallocation.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/reference_expression.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/batch/action_validation/document/document_reference_validation/v0/mod.rs
  • 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/reference_expression.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_common/data_contract_reference_validation/v0/mod.rs
  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/data_contract_create/mod.rs
  • packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-reference-expression-registration-unknown-type.json
  • packages/rs-drive-abci/tests/supporting_files/contract/reference-validation/reference-validation-contract-reference-expression.json
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/mod.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/reference_join_tests.rs
  • packages/rs-drive/src/query/chained_document_query/mod.rs
  • packages/rs-drive/src/query/composite_document_query/mod.rs
  • packages/rs-json-schema-compatibility-validator/src/rules/rule_set.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
  • packages/rs-platform-version/src/version/system_limits/v4.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/wasm-dpp2/src/data_contract/document_type_reference.rs
  • packages/wasm-dpp2/src/data_contract/document_type_typed_arrays.rs
  • packages/wasm-dpp2/tests/unit/DocumentPropertyReference.spec.ts

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

❤️ Share

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

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 23, 2026
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 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-23T13:25:03.148Z

@thepastaclaw

thepastaclaw commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 11th in line, estimated start in ~1.4 h (commit 853cd59)
Estimated review time once started: ~15 min (two-phase automated review; median of recent runs).

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

refersTo expressions: besides anyOf, an allOf holds if every operand
holds for the same value, and the two nest (an operand is a leaf or an
expression of the other combinator) up to
SystemLimits::max_reference_expression_depth (4) combinators, each list
holding at most max_reference_operands (4). Registration refuses two
alike operands of one list, a leaf naming the declaring contract
explicitly counting as one omitting it, and counts every leaf against
max_references_per_document.

At write time an expression is evaluated operand by operand in declared
order: anyOf stops at the first that holds and otherwise refuses with
the last operand's error, allOf stops at the first that fails and
refuses with its error; every read is billed.

AllOf is appended as variant 8. The decode guard now bounds nesting at
MAX_REFERENCE_EXPRESSION_DECODE_DEPTH (16) instead of refusing it. Errors
name a leaf by its path (anyOf[1].allOf[0]) in the parse and at
registration. The meta-schema picks the form with if/then/else so a
single target missing its type keeps a precise error. The ABCI tests
use the shared reference_test_setup helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@QuantumExplorer QuantumExplorer changed the title feat(platform)!: anyOf reference targets (PV14) feat(platform)!: anyOf and allOf reference expressions (PV14) Sep 23, 2026
…lpers

wasm-dpp2 reports a reference expression as { type: 'anyOf', anyOf: [...] }
or { type: 'allOf', allOf: [...] }, so the DocumentPropertyReferenceTarget
union stays internally tagged by `type` (CONVENTIONS.md, "Tagged unions")
and client code switching on `type` sees the combinators.

The lookup and expression parser suites share reference_test_helpers.rs,
and the drive join refusals for lookups and expressions run on one
fixture in reference_join_tests.rs. The malformed-leaf test asserts the
parser's message, and a shadowed name in the JS spec is renamed.

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

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Approved

@QuantumExplorer
QuantumExplorer merged commit 9953165 into v4.2-dev Sep 23, 2026
20 of 21 checks passed
@QuantumExplorer
QuantumExplorer deleted the feat/refers-to-any-of branch September 23, 2026 13:29
QuantumExplorer added a commit that referenced this pull request Sep 23, 2026
…ment

Merges anyOf/allOf (#4942) and the transient-object fixes (#4943, #4948),
and reworks listElement to the shape Sam proposed:

  "refersTo": {
    "type": "listElement",
    "documentType": "electedCharter",
    "propertyAgreement": { "electedCharterId": "$id" },
    "inList": "members"
  }

- The list's document is found by the agreement pair with `$id` on the
  referenced side (exactly one, read from a stored identifier property,
  never $ownerId); `documentProperty`/`list` are gone. `$id` joins $ownerId
  and $creatorId as a referenced-side agreement name for every document
  reference.
- A list element is a document reference: contractId allowed,
  `as_any_document_reference` carries it with `in_list`, so registration
  checks its contract, type and pairs through the shared code; the $id
  property needs no refersTo of its own.
- Write time: the document is fetched by id through a per-write memo shared
  with every by-id reference (one fetch for the charter and its list
  elements); lists are collected once into a set. Replace triggers are the
  agreement's (binds_a_changed_property).
- listElement is a combinable leaf of anyOf/allOf (target variant 9).
- Tests, fixtures, meta-schema, changelog item 34, book and wasm-dpp2
  updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Sep 23, 2026
… the leader

Built on #4940 (listElement), #4941 (ownerRefersTo) and #4942 (anyOf),
merged in from v4.2-dev. The resignation request:

- may be filed only by a member of the seated team: ownerRefersTo holds
  an anyOf of a listElement (the writer is in the elected charter's
  members, the charter found through the electedCharterId $id pair) and
  a lookup of an addedModerator for the charter keyed by the writer; the
  leader is in neither list;
- carries a message encrypted to the leader, with the leader's
  decryption key bound to submittedCharter and the member's encryption
  key bound to joinRequest, the keys join requests already use;
- is deletable (Sam), so it is a request the leader acts on with a
  removal and the member withdraws by deleting it. It changes the team
  by itself no longer: ElectedCharter::active_members drops its
  resignations input.

The charter changelog item is renumbered 36 after the three new items.

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