This ERC defines a framework through which qualified participants — autonomous agents, humans or contracts — form collective decisions with a defined effect, within explicit authorization, under verifiable and composable rules. It specifies:
a Decision Policy: an immutable, content-addressed rule template that fixes who may vote, how ballots are counted, how several deciding bodies compose, how long each phase lasts and how the procedure ends; policies are versioned through policy families whose update authority alone may publish a new version;
an Issue: the binding of a concrete subject and question to one policy version and to at most one relying contract (the consumer) that has committed, through one of three acceptance modes, to accept the result and enforce its own effects;
a procedure state machine with procedural finality, a three-valued outcome type (none, decided, no decision) kept separate from procedure state and from enactment status, finite appeals, and a hard deadline;
two co-deployable registries — an IACDFPolicyRegistry and an IACDFRegistry — that hold the normative record of policies, issues, ballots, rounds, results and finality, and never execute external effects themselves.
The minimal conforming configuration is a fixed-roster K-of-N vote. Richer configurations compose bodies with ALL, ANY, K-of-M and VETO under four-valued semantics, accept EIP-712 signed ballots with ERC-1271 support, admit results from authorized submitters, and run appeal rounds — all as normative-optional profiles over the same objects and the same state machine.
Motivation
Agents increasingly commit resources on each other’s behalf: a task escrow pays a fulfiller when work is accepted, a marketplace admits or suspends a member, a swarm changes a parameter that every member runs under. Existing building blocks each cover one corner. Token-weighted governors decide protocol parameters but assume one electorate and one weighting; multisignature wallets give a fixed roster a K-of-N threshold but no notion of a question, a result record or a procedure that can end without a decision; arbitration interfaces route a dispute to one arbitrator; escrow and commerce standards such as ERC-8183 deliberately leave the evaluator as a single trusted address and exclude multi-party voting; oracle councils such as ERC-8033 standardize one specific flow — information agents answering, a judge aggregating — for one kind of query.
What is missing is the layer those slots are waiting for: a way to say, in one interoperable record, which rule decided which question about which subject, who had committed in advance to accept that result, whether the procedure is still open, provisional or final, and whether it ended in a decision at all. Without it, every relying contract either hard-codes one voting scheme or trusts one address.
Three design facts drive this ERC.
Authorization precedes eligibility. Credit, reputation or stake may decide whether an agent deserves to sit on a panel; they never decide what the panel may decide. Seven highly reputable agents may be entrusted with reviewing a delivery; their reputation does not entitle them to vote assets out of an unrelated wallet. A result therefore has effect only where a relying contract committed, before the vote, to accept results of that policy for that class of matter, and only within the effects it committed to. Voting forms a collective decision; it creates no power and proves no truth.
The registry is the normative record, not an executor. Whether a result is “passed” is read from the registry; which funds may move, whether execution already happened and whether its conditions still hold are checked by the relying contract within its own authority. Procedure state, outcome type and enactment status are three dimensions, not one enum: an issue can be final with no decision, and the same final decision can be executed successfully by one relying party and refused by another without anyone touching the decision.
Rules are frozen as execution parameters, not as prose. A policy’s identifier is the hash of the very struct the registry executes — roster, thresholds, windows, composition graph, appeal rules — so there is no gap between a rule as published and a rule as applied, and no proxy, admin list or code-hash pin is needed to argue that it stayed the same.
The framework is deliberately agnostic about who the participants are. An eligibility profile may bind rosters to agent identity and trust-assertion registries such as ERC-8004 and the know-your-agent frameworks built over it; the kernel only requires that eligibility be verifiable and frozen at admission. It is equally agnostic about incentives: whether participants are paid, bonded or scored is left to profiles, and “agreeing with the majority” is never defined as correctness.
Illustrative flows (informative):
A token-bound task tender names an adapter contract as its acceptance authority. When a delivery is submitted, anyone opens an issue bound to that exact submission; a 3-of-5 roster decides; the adapter calls the tender’s real accept or reject entry point, and a round that ends without a decision triggers nothing — the tender’s own default (silence pays the fulfiller) governs.
A swarm charter registers a standing acceptance for parameter changes: any member may file; a screening council holds a 12-hour veto; a technical chamber voting by signed ballots and an economic chamber voting on-chain must both approve; one appeal rebuilds every chamber; an executor with a timelock enforces the adopted value.
A party files a dispute and names a counterparty; nothing is voted until the counterparty acknowledges and commits the effects it will honour; if it never does, the issue closes or proceeds as advisory evidence that no one is bound by.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
1. Terminology
Decision Policy (policy) — an immutable, content-addressed PolicySpec fixing bodies, composition, timing, appeals and acceptance rules. Identified by policyId = keccak256(abi.encode(spec)).
Policy family — the lineage a policy version belongs to; the family’s update authority alone may publish its next version.
Issue — one concrete collective decision: a subject, a question, a policy version and at most one consumer binding.
Consumer (relying contract) — the contract or account that commits to accept an issue’s result and to enforce its effects within its own authority.
Acceptance mode — how the consumer’s commitment is established: CONSUMER_FILED, STANDING_ACCEPTANCE or POST_ACK.
Binding / Advisory — an issue with a verified consumer commitment is Binding; an issue with none is Advisory and produces a record without hard effect.
Obligation — the consumer-committed scope within which at most one unfinished Binding issue may exist. It is identified by an opaque obligationId chosen by the consumer; the obligation key is keccak256(abi.encode(consumer, obligationId, question)). The subject identifies what an issue evaluates; the obligation identifies which duty of the consumer the result discharges. The two may coincide by the consumer’s choice, but the registry never equates them on its own.
Body — one deciding unit of a policy: a fixed roster with a K-of-N rule, or a single authorized submitter. A body instance is a body on one issue in one round.
Body decision — Pending, Yes, No or NoDecision(reason).
Composition — the tree of combinators (BODY, ALL, ANY, KOFM, VETO) whose root value is the round’s result.
Round — one run of all bodies; appeals open further rounds under the same policy version.
Procedure state — Filed, Deciding, Provisional, Final or Withdrawn.
Outcome type — None, Decided (with a boolean outcome) or NoDecision (with a reason).
Enactment — a consumer’s execution of an effect; its status (NotEnacted, Enacted, Failed) is authoritative at the consumer and logged informatively by the registry.
Formation instant — the time at which a body or node value became determined by the ballots and the clock; appeal windows run from it, not from the transaction that records it.
2. Conformance
A conforming deployment consists of one IACDFPolicyRegistry and one IACDFRegistry bound to it (Section 11). Both MUST implement ERC-165 and report the interface identifiers of Section 15.
The following are REQUIRED of every conforming deployment: the policy data model and validation rules (Section 3), the three acceptance modes and the obligation rule (Section 5), the procedure state machine (Section 6), the ROSTER_KOFN body with ON_CHAIN_TALLY acceptance (Section 7), single-body composition (the BODY root), the finality rules (Section 9), the result and enactment record (Section 10) and the clock disclosure (Section 12). This is the Minimal profile: a fixed-roster K-of-N vote.
The following are normative-optional profiles: body composition (ALL, ANY, KOFM, VETO, Section 8), signed ballots (SIGNED_BALLOTS, Section 7.3), authorized-submitter bodies (AUTHORIZED_SUBMITTER, Section 7.4) and appeals (maxAppeals > 0, Section 9). A deployment MAY reject policies that use a profile it does not implement; a deployment that accepts such a policy MUST implement the profile exactly as specified here.
Section 13 names extension points that this ERC reserves without specifying. A deployment MUST NOT claim conformance to a reserved extension on the basis of this document.
3. Decision Policy
3.1 Data model
libraryACDFTypes{enumBodyKind{ROSTER_KOFN,AUTHORIZED_SUBMITTER}enumAcceptance{ON_CHAIN_TALLY,SIGNED_BALLOTS,AUTHORIZED_SUBMITTER}enumCombinator{BODY,ALL,ANY,KOFM,VETO}enumVetoSilence{PASS_THROUGH,REQUIRE_CLEARANCE}enumAppealStanding{ANYONE,CONSUMER_OR_FILER}enumAppealMode{PRESERVE_UNLESS_OVERTURNED,REQUIRE_FRESH_DECISION}structBodySpec{BodyKindkind;Acceptanceacceptance;// result-acceptance mode of THIS body
address[]members;// ROSTER_KOFN: unique, non-zero roster; AUTHORIZED_SUBMITTER: exactly one
uint32k;// ROSTER_KOFN: approvals needed, 1 <= k <= N
uint64window;// seconds from round start during which input is accepted; > 0
}structNode{Combinatorop;uint32body;// BODY: index into bodies
uint32k;// KOFM: required child approvals
uint32target;// VETO: guarded node index (> own index)
uint32vetoBody;// VETO: index of the veto body; its window is the veto window
VetoSilencesilence;// VETO: meaning of the veto body's silence at window close
uint32[]children;// ALL / ANY / KOFM: child node indices (> own index, unique)
}structPolicySpec{bytes32family;uint32version;bytes32previous;// previous policyId in the family, or 0 for the first
addressupdateAuthority;// who may publish the family's next version
BodySpec[]bodies;Node[]nodes;// nodes[0] is the root
uint32maxAppeals;// rounds allowed after the first
uint64appealWindow;// seconds after a round's formation instant
uint8appealable;// bit0: Decided appealable; bit1: NoDecision appealable
AppealStandingappealStanding;AppealModeappealMode;// what an appeal round ending in NoDecision means for the decision under appeal
uint64maxTotalDuration;// hard cap from admission
uint64ackWindow;// POST_ACK acknowledgment window; 0 disables POST_ACK
boolallowAdvisory;// may an unacknowledged POST_ACK issue proceed as Advisory
bytes32descriptorHash;// informative reference to an off-chain description
}}
policyId MUST equal keccak256(abi.encode(spec)) over the full PolicySpec. Every field of the struct is part of the identifier; descriptorHash is included so that a published description is bound to the parameters, but it MUST NOT influence execution.
3.2 Validation
registerPolicy MUST reject a specification unless all of the following hold:
Every body has window > 0. A ROSTER_KOFN body has members.length >= 1, 1 <= k <= members.length, pairwise-distinct non-zero members and acceptance in {ON_CHAIN_TALLY, SIGNED_BALLOTS}. An AUTHORIZED_SUBMITTER body has exactly one non-zero member and acceptance == AUTHORIZED_SUBMITTER.
The nodes form a tree rooted at index 0: a BODY node references a valid body and has no children; ALL, ANY and KOFM nodes have at least one child, every child index is strictly greater than the node’s own index and less than nodes.length, children are pairwise distinct, and a KOFM node has 1 <= k <= children.length; a VETO node has no children, a target strictly greater than its own index and less than nodes.length, and a valid vetoBody. Node 0 is referenced by no node, and every other node is referenced exactly once (as a child or as a VETO target). Because every edge points to a larger index, the graph is acyclic by construction.
Each body plays one role: a body index appears as a BODY node at most once in the policy; a body named as the vetoBody of any VETO node appears as a BODY node nowhere in the policy (it MAY be the vetoBody of several VETO nodes); and every body is referenced at least once, as a BODY node or as a vetoBody.
If maxAppeals > 0 then appealWindow > 0 and 1 <= appealable <= 3.
With roundDuration defined as the maximum window over all bodies: maxTotalDuration >= (maxAppeals + 1) * roundDuration + maxAppeals * appealWindow, and maxTotalDuration <= 2^63 - 1.
family != 0, version >= 1, updateAuthority != 0.
A policyId MUST NOT be registered twice.
3.3 Families and versions
The first policy registered under a family MUST have previous == 0; it claims the family and sets its update authority to spec.updateAuthority. Any later version under the same family MUST be registered by the family’s current update authority, MUST carry previous equal to the family’s latest policyId, and MUST carry a version strictly greater than the latest; its updateAuthority becomes the family’s new authority. Registering a new version MUST NOT alter any existing policy, any issue bound to an earlier version, or any standing acceptance that references one.
4. Policy registry interface
interfaceIACDFPolicyRegistry/* is IERC165 */{functionsupportsInterface(bytes4interfaceId)externalviewreturns(bool);structTiming{uint32maxAppeals;uint64appealWindow;uint8appealable;ACDFTypes.AppealStandingappealStanding;ACDFTypes.AppealModeappealMode;uint64maxTotalDuration;uint64ackWindow;boolallowAdvisory;uint64roundDuration;}eventPolicyRegistered(bytes32indexedpolicyId,bytes32indexedfamily,uint32version,addressindexedby);functionpolicyIdOf(ACDFTypes.PolicySpeccalldataspec)externalpurereturns(bytes32);functionregisterPolicy(ACDFTypes.PolicySpeccalldataspec)externalreturns(bytes32policyId);functionpolicyExists(bytes32policyId)externalviewreturns(bool);functionfamilyAuthority(bytes32family)externalviewreturns(address);functionfamilyLatest(bytes32family)externalviewreturns(bytes32policyId,uint32version);functionroundDurationOf(bytes32policyId)externalviewreturns(uint64);functiontimingOf(bytes32policyId)externalviewreturns(Timingmemory);functionpolicyHeader(bytes32policyId)externalviewreturns(bytes32family,uint32version,bytes32previous,addressupdateAuthority,bytes32descriptorHash);functionbodyCount(bytes32policyId)externalviewreturns(uint256);functionbodyOf(bytes32policyId,uint32body)externalviewreturns(ACDFTypes.BodySpecmemory);functionnodeCount(bytes32policyId)externalviewreturns(uint256);functionnodeOf(bytes32policyId,uint32node)externalviewreturns(ACDFTypes.Nodememory);}
registerPolicy is permissionless for new families. The registry MUST store the full specification so that every parameter it executes is readable through bodyOf, nodeOf, timingOf and policyHeader.
5. Issues and authorization
5.1 Issue data
structSubject{uint256chainId;addresstarget;uint256id;bytes32dataHash;}enumAcceptanceMode{CONSUMER_FILED,STANDING_ACCEPTANCE,POST_ACK}enumEffectClass{Advisory,Binding}structIssueInput{bytes32policyId;AcceptanceModemode;bytes32acceptanceId;// STANDING_ACCEPTANCE
addressconsumer;// CONSUMER_FILED: MUST equal msg.sender; POST_ACK: the account whose acknowledgment is awaited
Subjectsubject;bytes32question;bytes32effectYes;// opaque effect identifiers; CONSUMER_FILED: committed; POST_ACK: proposal only
bytes32effectNo;bytes32disposition;// opaque reference to the consumer's committed NoDecision disposition
bytes32obligationId;// consumer-committed obligation scope; CONSUMER_FILED: MUST be non-zero; POST_ACK: proposal only
uint64consumerDeadline;// 0 = none
}structStandingAcceptanceInput{bytes32policyId;// the exact policy version accepted
addresssubjectTarget;// 0 = any
bytes32question;// 0 = any
address[]filers;// empty = anyone
bytes32effectYes;bytes32effectNo;bytes32disposition;bytes32obligationId;// non-zero: one obligation shared by every issue under this acceptance; 0: one obligation per exact subject
uint64validUntil;// 0 = none
}
Effect identifiers, the disposition and the obligation identifier are opaque to the registry. The effects name, for the consumer, the action that each outcome authorizes; the disposition names what the consumer does when no decision forms; the obligation identifier names the duty whose unfinished Binding proceedings are limited to one. The registry never interprets or executes any of them.
5.2 Acceptance modes
An issue is admitted when its consumer binding (if any) has been verified, its parameters are frozen, and its first round starts. A Binding issue has exactly one consumer. Admission MUST:
record admittedAt and hardDeadline = admittedAt + maxTotalDuration;
if consumerDeadline != 0, require hardDeadline <= consumerDeadline (“insufficient window”); the whole procedure including every appeal must fit the consumer’s remaining window;
for a Binding issue, require a non-zero obligationId (“obligation required”), compute the obligation key keccak256(abi.encode(consumer, obligationId, question)) and require that no other issue holds it unless that issue is Final or Withdrawn (“obligation active”), then record this issue as the obligation’s active issue;
set the procedure state to Deciding and open round 1.
CONSUMER_FILED.file MUST require input.consumer == msg.sender, non-zero effectYes and effectNo, and a non-zero obligationId. The caller is the consumer; acceptance is implied and admission is atomic with filing. Calling as the consumer proves only that this account filed; an adapter acting for a business object MUST itself verify that it holds the authority it claims over that object before filing.
STANDING_ACCEPTANCE. A consumer registers, in advance, an acceptance record naming the exact policy version it accepts, an optional subject-target constraint, an optional question constraint, an optional list of permitted filers, the effects and disposition it commits to, and an optional expiry. file in this mode MUST require that the acceptance exists and is not revoked or expired, that input.policyId equals the accepted policy, that the subject target and question satisfy the constraints, and that msg.sender is a permitted filer when the list is non-empty. The issue’s consumer, effects and disposition MUST be taken from the acceptance record; values supplied by the filer MUST be ignored. The issue’s obligationId MUST be the acceptance’s obligationId when that is non-zero, and otherwise keccak256(abi.encode(subject)): either way the scope of exclusivity is committed by the consumer in advance, never chosen by the filer. Admission is atomic with filing. Revoking an acceptance MUST stop new filings only; issues already admitted under it complete under their committed rules.
POST_ACK.file in this mode MUST require that the policy’s ackWindow is non-zero and that input.consumer is non-zero. The issue remains in state Filed; no round is open and no ballot can be cast. Before filedAt + ackWindow the named consumer, and only it, MAY acknowledge, supplying the final effects, disposition, a non-zero obligationId and an optional consumer deadline; the filer’s values are proposals only. Acknowledgment admits the issue as Binding. After the window, if the policy’s allowAdvisory is true, anyone MAY admitAdvisory, which clears the consumer and effects and admits the issue as Advisory; otherwise anyone MAY expireUnacknowledged, which sets the state to Withdrawn with reason NO_ACCEPTANCE. An issue admitted as Advisory MUST NOT be converted to Binding afterwards; a party that later wishes to adopt its result MUST create a new acceptance or a new issue.
Advisory issues run the full procedure and produce a formal record. Their results carry no obligation for any consumer; recordEnactment MUST reject them.
5.3 Freezing
The following MUST be fixed at admission and MUST NOT change afterwards: the policy version, subject, question, effect identifiers, disposition, obligation identifier, consumer, effect class and every window. There is no function that changes them. Parameters whose evaluation legitimately depends on later state are not part of this ERC’s kernel; a profile that introduces them MUST declare the dependency in the policy.
5.4 Withdrawal
withdraw MUST succeed only while the issue is Filed and only for the filer. Once admitted, no party can unilaterally withdraw: the roster, snapshot and windows exist, and withdrawing and refiling would let a filer shop for panels. A relying contract cannot tear up an in-flight binding; revoking a standing acceptance does not affect admitted issues.
6. Procedure state machine
file (CONSUMER_FILED / STANDING) settleRound, no remaining procedure
-------> Filed -----------------------------> Deciding ------------------------------> Final
| acknowledge / admitAdvisory | ^ settleRound, ^
| (POST_ACK) | | appeal possible |
| v | appeal | finalize after window /
+--- withdraw / expire --> Withdrawn Provisional ---------------------------+ enforceHardDeadline
Transition
Caller
Condition
→ Filed
filer
policy exists; mode-specific checks of Section 5.2
Filed → Deciding
atomic in file, or acknowledge, or admitAdvisory
admission checks of Section 5.2
Filed → Withdrawn
filer (withdraw) or anyone (expireUnacknowledged)
see Sections 5.2 and 5.4
Deciding → Provisional
anyone (settleRound)
root value is not Pending; an appeal is still possible: rounds remain, the outcome type is appealable, and formationInstant + appealWindow has not passed
Deciding → Final
anyone (settleRound)
root value is not Pending and no legal procedure remains: appeals exhausted, the outcome type not appealable, or the appeal window already lapsed
Provisional → Deciding
anyone with standing (appeal)
before appealOpenUntil; a new round opens with fresh body instances
Provisional → Final
anyone (finalize)
after appealOpenUntil
Deciding / Provisional → Final
anyone (enforceHardDeadline)
after hardDeadline; a round still Pending is recorded as NoDecision(TOTAL_TIMEOUT)
Final and Withdrawn are terminal. A Final issue MUST NOT accept ballots, submissions, appeals or any further round, and its adopted result MUST NOT change. Evidence (Section 10.3) MAY be attached while the issue is Filed, Deciding or Provisional.
Every round records startedAt and deadline = startedAt + roundDuration. All bodies of a round open at startedAt; body b accepts input through startedAt + bodies[b].window inclusive.
7. Bodies and ballots
7.1 Roster K-of-N body
For a ROSTER_KOFN body with N = members.length and threshold k, in a given round, let yes and no be the counts of accepted approve and block ballots. The body’s status is:
Yes once yes >= k, formed at the instant the k-th approval was accepted;
No once no >= N - k + 1, formed at the instant the (N−k+1)-th block was accepted;
NoDecision(QUORUM_NOT_MET) if neither holds after the body’s window closed, formed at the window close;
Pending otherwise.
The two decided conditions cannot hold together (they would need N + 1 ballots). This is an approval rule for the proposition put to the body: No means the proposition was blocked under this rule; it does not by itself establish the opposite proposition, and relying contracts MUST NOT read it as such unless their own commitment says so.
A ballot MUST be rejected if the caller or signer is not a member, has already voted in this round and body, arrives after the body’s window, or if the body has already reached a decided status. One ballot per member per round per body; the minimal profile allows no change of ballot.
7.2 On-chain tally
For a body with acceptance == ON_CHAIN_TALLY, castBallot(issueId, round, body, approve) accepts a ballot from msg.sender while the issue is Deciding and round equals the issue’s current roundCount; a ballot naming any other round MUST be rejected. The round argument gives an on-chain ballot the same authorization boundary as a signed ballot’s digest: a transaction broadcast for one round that is included only after that round has settled and an appeal has opened the next one is refused rather than counted in a round the voter never addressed. Signed submissions to such a body MUST be rejected.
7.3 Signed ballots (normative-optional)
For a body with acceptance == SIGNED_BALLOTS, submitSignedBallots(issueId, body, voters, approves, signatures) MAY be called by anyone while the issue is Deciding. Direct castBallot calls to such a body MUST be rejected. For each entry the registry MUST verify the signature over the EIP-712 digest of
under the domain EIP712Domain(string name,string version,uint256 chainId,address verifyingContract) with name = "ACDF", version = "1", the executing chain id and the registry address. Signatures of externally owned accounts are verified by ecrecover with s in the lower half of the curve order and v in {27, 28}; signatures of contract accounts are verified through ERC-1271. The batch MUST revert if any signature is invalid or if any ballot would be rejected under Section 7.1. The ballot carries no nonce: the voter’s identity is the only key, so a second signature by the same voter cannot buy a second ballot, and the round and body in the digest make a signature for one round invalid in the next. Acceptance time is the block in which the batch is submitted (arrival, not signing time). Accepted ballots are counted with exactly the semantics of Section 7.1 and MUST NOT be invalidated afterwards.
7.4 Authorized submitter (normative-optional)
For an AUTHORIZED_SUBMITTER body, submitBodyResult(issueId, round, body, status) MUST be accepted only from the pinned member, only for round equal to the issue’s current roundCount, only once per round, only within the body’s window, with status in {Yes, No, NoDecision}. The body’s status is the submitted value, formed at submission; if the window closes without a submission the status is NoDecision(SUBMITTER_SILENT). The registry does not re-tally whatever process the submitter ran; it records only who submitted what, when, and whether that submitter was the one the policy pinned.
8. Composition (normative-optional)
Node values are four-valued: Pending, Yes, No, NoDecision(reason). Values are a pure function of the accepted ballots and submissions and of the current time, so two evaluations at the same block agree and a later evaluation never contradicts an earlier non-Pending one. Each value carries a formation instant.
Let a composite node have M children and define K = M for ALL, K = 1 for ANY and K = node.k for KOFM. With yes, no, pending the counts of children by status:
Yes if yes >= K, formed at the K-th smallest formation instant among Yes children;
else No if no >= M - K + 1, formed at the (M−K+1)-th smallest formation instant among No children;
else Pending if pending > 0;
else NoDecision(NOT_REACHED), formed at the latest child formation instant.
A VETO node guards target with the veto body vetoBody. In the veto body an approve ballot means veto and a decided No means explicit clearance. Its value is:
No(VETOED) if the veto body is Yes, formed at the veto’s formation instant;
Pending if the veto body is Pending, whatever the target’s value — a live veto right is never extinguished by an early settlement;
NoDecision(NO_CLEARANCE) if the veto body is NoDecision and silence == REQUIRE_CLEARANCE, formed at the veto window close;
otherwise (explicit clearance in either mode, or silence under PASS_THROUGH) the target’s value, Pending while the target is Pending, formed at the later of the veto body’s and the target’s formation instants.
Because the veto window is the veto body’s window and the round deadline is the longest body window, every node is non-Pending by the round deadline.
One veto body MAY guard several targets through several VETO nodes (one screening body over separate chambers); the body decides once per round and each VETO node reads that decision. A veto body never appears as a BODY node and no body appears as two BODY nodes (Section 3): composition combines the decisions of distinct bodies, and repeated references would let one decision fill several threshold slots or make a tree that cannot form Yes.
Composition in this ERC combines decisions about the same issue and the same pair of effect candidates. Policies whose bodies decide different magnitudes or different candidate effects are outside this profile; combining such results by taking a minimum or any other arithmetic is NOT RECOMMENDED and is reserved for a typed extension (Section 13).
9. Rounds, appeals and finality
settleRound MUST revert while the root is Pending. When it succeeds it records the round’s status, reason and formation instant at. An appeal is possible if roundCount - 1 < maxAppeals and the bit of appealable for the outcome type (bit 0 for a decided result, bit 1 for NoDecision) is set; then appealOpenUntil = min(at + appealWindow, hardDeadline). If an appeal is possible and the current time is not after appealOpenUntil, the issue becomes Provisional; otherwise it becomes Final. The window runs from the formation instant: a settlement called late cannot extend the procedure.
appeal MUST be accepted only while Provisional, not after appealOpenUntil, and — when appealStanding == CONSUMER_OR_FILER — only from the consumer or the filer. It opens a new round in which every body instance is rebuilt and every body runs again under the same policy version; ballots and submissions of earlier rounds are not carried over, and a signed ballot for an earlier round is invalid (Section 7.3).
finalize MUST be accepted only while Provisional and after appealOpenUntil. enforceHardDeadline MUST be accepted while Deciding or Provisional once the current time is after hardDeadline; if the current round’s root is still Pending it is recorded as NoDecision(TOTAL_TIMEOUT).
Adoption rule. What an appeal round that ends without a decision means is fixed by the policy’s appealMode; the kernel has no default.
PRESERVE_UNLESS_OVERTURNED (challenge-style appeal): on finalization the registry MUST adopt the most recent round whose status is Yes or No: the outcome type becomes Decided, outcomeYes the round’s status, reason the round’s reason, sourceRound that round’s number and decidedAt its formation instant. Only if no round produced Yes or No does the outcome type become NoDecision, with the last round’s reason. A later round that ended without a decision therefore never erases the decision under appeal; it is recorded, and sourceRound may be smaller than roundCount.
REQUIRE_FRESH_DECISION (de-novo appeal): opening an appeal vacates every earlier decision. On finalization the registry MUST adopt the last round’s own status when it is Yes or No, and otherwise MUST record NoDecision with the last round’s reason; sourceRound always equals roundCount.
In both modes Final is terminal: no later round of the same issue can replace the adopted result; a different conclusion requires a new, separately authorized issue.
getResult MUST return the three dimensions separately. A relying contract MUST check state == Final before enforcing an irreversible effect, MUST check outcomeType == Decided before applying either effect, and MUST apply its committed disposition — not a default of its own choosing at that moment — when outcomeType == NoDecision. Reasons identify why no decision formed and are not a verdict on the merits; VETOED is the reason of a decided No.
A Decided result reached because a later appeal round ended in NoDecision and the earlier decision was preserved (finality by adoption) MUST be distinguishable from a Decided result formed by the last round itself (finality by substantive decision): adoptedFromEarlierRound MUST be true exactly when outcomeType == Decided and sourceRound < roundCount. A relying contract MAY commit to treat the two differently; the registry treats them alike.
10.2 Enactment log
recordEnactment(issueId, effectId, status, ref) is an informative log. It MUST be accepted only while the issue is Final, only from the issue’s consumer, only for a Binding issue, only for effectId equal to the issue’s effectYes or effectNo, and only with status != NotEnacted. Once an effect is recorded Enacted it MUST NOT be overwritten; a Failed record MAY later be replaced by Enacted. The log records the real reporter. Execution state is authoritative at the consumer; the registry never modifies a decision because an enactment failed, and never prevents a consumer from retrying an enactment its own rules allow.
10.3 Evidence
submitEvidence(issueId, evidenceURI) emits Evidence(issueId, msg.sender, evidenceURI) while the issue is Filed, Deciding or Provisional. Evidence is data for participants to weigh; the registry attaches no meaning to it.
11. Issue registry interface
interfaceIACDFRegistry/* is IERC165 */{functionsupportsInterface(bytes4interfaceId)externalviewreturns(bool);eventStandingAcceptanceRegistered(bytes32indexedacceptanceId,addressindexedconsumer,bytes32indexedpolicyId);eventStandingAcceptanceRevoked(bytes32indexedacceptanceId,addressindexedconsumer);eventIssueFiled(bytes32indexedissueId,bytes32indexedpolicyId,ACDFTypes.AcceptanceModemode,addressindexedfiler,addressconsumer);eventIssueAdmitted(bytes32indexedissueId,ACDFTypes.EffectClasseffectClass,addressindexedconsumer,uint64admittedAt,uint64hardDeadline);eventIssueAcknowledged(bytes32indexedissueId,addressindexedconsumer);eventIssueWithdrawn(bytes32indexedissueId,ACDFTypes.Reasonreason);eventBallotCast(bytes32indexedissueId,uint32indexedround,uint32indexedbody,addressvoter,boolapprove,boolsigned);eventBodyResultSubmitted(bytes32indexedissueId,uint32indexedround,uint32indexedbody,addresssubmitter,ACDFTypes.NodeStatusstatus);eventRoundSettled(bytes32indexedissueId,uint32indexedround,ACDFTypes.NodeStatusstatus,ACDFTypes.Reasonreason,uint64at,uint64appealOpenUntil);eventAppealed(bytes32indexedissueId,uint32indexednewRound,addressindexedby);eventFinalized(bytes32indexedissueId,ACDFTypes.OutcomeTypeoutcomeType,booloutcomeYes,ACDFTypes.Reasonreason,uint32sourceRound);eventEnactmentRecorded(bytes32indexedissueId,addressindexedconsumer,bytes32indexedeffectId,ACDFTypes.EnactmentStatusstatus,addressreporter,bytes32ref);eventEvidence(bytes32indexedissueId,addressindexedparty,stringevidenceURI);functionpolicies()externalviewreturns(IACDFPolicyRegistry);functionregisterStandingAcceptance(ACDFTypes.StandingAcceptanceInputcalldatainput)externalreturns(bytes32acceptanceId);functionrevokeStandingAcceptance(bytes32acceptanceId)external;functionfile(ACDFTypes.IssueInputcalldatainput)externalreturns(bytes32issueId);functionacknowledge(bytes32issueId,bytes32effectYes,bytes32effectNo,bytes32disposition,bytes32obligationId,uint64consumerDeadline)external;functionadmitAdvisory(bytes32issueId)external;functionexpireUnacknowledged(bytes32issueId)external;functionwithdraw(bytes32issueId)external;functionsubmitEvidence(bytes32issueId,stringcalldataevidenceURI)external;functioncastBallot(bytes32issueId,uint32round,uint32body,boolapprove)external;functionsubmitSignedBallots(bytes32issueId,uint32body,address[]calldatavoters,bool[]calldataapproves,bytes[]calldatasignatures)external;functionsubmitBodyResult(bytes32issueId,uint32round,uint32body,ACDFTypes.NodeStatusstatus)external;functionsettleRound(bytes32issueId)external;functionappeal(bytes32issueId)external;functionfinalize(bytes32issueId)external;functionenforceHardDeadline(bytes32issueId)external;functionrecordEnactment(bytes32issueId,bytes32effectId,ACDFTypes.EnactmentStatusstatus,bytes32ref)external;functiongetResult(bytes32issueId)externalviewreturns(ACDFTypes.Resultmemory);functiongetIssue(bytes32issueId)externalviewreturns(ACDFTypes.Issuememory);functiongetRound(bytes32issueId,uint32round)externalviewreturns(ACDFTypes.RoundStatememory);functiongetBodyState(bytes32issueId,uint32round,uint32body)externalviewreturns(ACDFTypes.BodyStatememory);functionbodyStatus(bytes32issueId,uint32round,uint32body)externalviewreturns(ACDFTypes.NodeStatus,ACDFTypes.Reason,uint64at);functionnodeStatus(bytes32issueId,uint32round,uint32node)externalviewreturns(ACDFTypes.NodeStatus,ACDFTypes.Reason,uint64at);functionhasVoted(bytes32issueId,uint32round,uint32body,addressvoter)externalviewreturns(bool);functionactiveIssueOf(addressconsumer,bytes32obligationKey)externalviewreturns(bytes32);functionobligationKeyOf(addressconsumer,bytes32obligationId,bytes32question)externalpurereturns(bytes32);functiongetEnactment(bytes32issueId,addressconsumer,bytes32effectId)externalviewreturns(ACDFTypes.Enactmentmemory);functionballotDigest(bytes32issueId,uint32round,uint32body,addressvoter,boolapprove)externalviewreturns(bytes32);functionclock()externalviewreturns(uint48);functionCLOCK_MODE()externalviewreturns(stringmemory);}
issueId and acceptanceId MUST be unique within the registry instance; the reference implementation derives them from the chain id, the registry address and a counter. bodyStatus and nodeStatus MUST implement Sections 7 and 8 as pure functions of recorded state and the current time; settleRound, finalize and enforceHardDeadline MUST use them.
12. Clock
An issue registry MUST expose ERC-6372clock() and CLOCK_MODE(). One registry instance uses one clock for every policy it runs; the reference implementation uses mode=timestamp. Windows are seconds of that clock. Durations MUST NOT be converted between clocks (for example block counts into seconds) to claim that a deadline will be met.
13. Profiles and reserved extension points
Profile
Status in this ERC
Minimal (roster K-of-N, on-chain tally, single body, no appeal)
REQUIRED
Body composition (ALL / ANY / KOFM / VETO)
normative-optional, Section 8
Signed ballots
normative-optional, Section 7.3
Authorized submitter
normative-optional, Section 7.4
Appeals
normative-optional, Section 9
The following are reserved: this ERC names them so that implementations and relying contracts leave room for them, but it does not specify them and no conformance may be claimed for them under this document.
Eligibility profiles binding rosters to identity, trust-assertion or reputation registries (for example ERC-8004 identities with asserted trust levels), including how an eligibility snapshot is produced and how conflicts of interest are excluded.
Selection other than a fixed roster: sortition with a verifiable randomness source, nomination with strikes, delegation.
Weight other than equal weight, including per-operator caps and independence conditions.
Per-round timing: appeal rounds with windows or hard caps different from the first round’s; in this version every round of an issue uses the policy’s body windows.
Non-binary outcome spaces (categorical, scalar, ranked) and typed composition over them, including intersection of allowed effect sets.
Ordering dependencies between bodies beyond the veto window.
Executor modules enforcing effects with delays on behalf of consumers; fees and bonds; optimistic / challenge procedures; privacy (zero-knowledge eligibility, anonymous ballots); multi-consumer issues; cross-chain carriage of results.
A policy that would need any of these is outside this ERC until a later document specifies it.
14. Relying-contract integration (informative)
A relying contract names a registry instance and the exact policy versions it accepts, and derives every parameter of an issue from its own state rather than from the caller. The companion adapter for a token-bound task tender kernel (whose vendored interface appears in the reference assets) illustrates the pattern: it is the tender’s acceptance authority; open(tokenId, submissionId) is permissionless but files as the adapter itself, binding the subject to the submission’s result hash and the task version it cited, and passing as consumerDeadline the earlier of the tender’s two clocks — the judgment window that bounds rejection and the settlement deadline that bounds acceptance — minus an execution margin; execute(issueId) reads a Final result and calls the tender’s real accept or reject entry point with no caller-supplied target or calldata, records Enacted or Failed, and does nothing for a NoDecision, leaving the tender’s own default settlement in force. An ERC-8183 job’s evaluator and an arbitration-standard arbitrator can be realized the same way.
An enactment record describes the adapter’s attempt, not the fate of the obligation. If the relying contract’s own default path settled first (the tender’s unjudged claim after its judgment window, for instance), a later execute is recorded as Failed while the submission is already accepted and paid, and repeating execute changes nothing and pays nothing. Integrations that display a payment status read it, and its source, from the relying contract’s records, which Section 10 makes authoritative for execution state; the registry’s Failed says only that this adapter did not enact.
15. Interface identifiers
interface
ERC-165 id
IACDFPolicyRegistry
0xb362eb4e
IACDFRegistry
0xd31aae30
16. Errors
Implementations SHOULD revert with the following reason strings (or equivalent custom errors) so that relying contracts and tests can distinguish causes. Policy validation: ACDF: policy exists, ACDF: zero family, ACDF: zero version, ACDF: zero update authority, ACDF: first version has no previous, ACDF: not family authority, ACDF: previous != latest, ACDF: version not increasing, ACDF: bodies out of range, ACDF: nodes out of range, ACDF: zero window, ACDF: empty roster, ACDF: bad k, ACDF: roster acceptance, ACDF: zero member, ACDF: duplicate member, ACDF: submitter required, ACDF: submitter acceptance, ACDF: body index, ACDF: body node has children, ACDF: veto node has children, ACDF: veto target, ACDF: veto body index, ACDF: no children, ACDF: bad node k, ACDF: child index, ACDF: duplicate child, ACDF: root referenced, ACDF: node not in tree, ACDF: body reused, ACDF: veto body votes, ACDF: body unused, ACDF: zero appeal window, ACDF: appealable mask, ACDF: maxTotalDuration too short, ACDF: maxTotalDuration too long. Issues and acceptance: ACDF: unknown policy, ACDF: effects required, ACDF: consumer must file, ACDF: acceptance unavailable, ACDF: acceptance expired, ACDF: policy not accepted, ACDF: subject not accepted, ACDF: question not accepted, ACDF: filer not accepted, ACDF: not acceptance owner, ACDF: POST_ACK disabled, ACDF: consumer required, ACDF: not POST_ACK, ACDF: not awaiting acknowledgment, ACDF: not the named consumer, ACDF: ack window closed, ACDF: ack window open, ACDF: advisory not allowed, ACDF: must admit as advisory, ACDF: insufficient window, ACDF: obligation required, ACDF: obligation active, ACDF: not withdrawable, ACDF: not filer, ACDF: evidence closed. Voting: ACDF: not deciding, ACDF: round mismatch, ACDF: body not on-chain tally, ACDF: body not signed ballots, ACDF: body not submitter, ACDF: batch shape, ACDF: bad signature, ACDF: body window closed, ACDF: not a member, ACDF: already voted, ACDF: body decided, ACDF: not the submitter, ACDF: status required, ACDF: already submitted. Rounds and finality: ACDF: pending, ACDF: not provisional, ACDF: appeal window closed, ACDF: appeal window open, ACDF: no standing, ACDF: not open, ACDF: before hard deadline, ACDF: round index. Enactment: ACDF: not final, ACDF: not the consumer, ACDF: unknown effect, ACDF: already enacted.
Rationale
Why a policy is a hashed struct rather than a referenced module. The first-order risk in any decision system is that the rule applied is not the rule published. Pinning a module by address and code hash does not close that gap: an ERC-1967 proxy changes its implementation in a storage slot while its code hash stays the same, and even fixed code can read a mutable threshold. Making the registry execute the very struct whose hash is the policy identifier removes the gap entirely, at the cost of restricting the first version to built-in body kinds. Pluggable modules return as reserved extension points that must declare their dynamic dependencies explicitly.
Why authorization is a consumer’s commitment, not a registry object. Earlier drafts carried a stand-alone “mandate” object. Review showed that it conflated two questions — “by which institution is this decided?” (the policy) and “who recognizes that institution’s result for which matters and effects?” (the consumer’s commitment) — and that a registry entry saying “I have power over X” grants nothing unless X’s own code recognizes it. The three acceptance modes are therefore the only authorization primitives, and all of them are acts of the consumer: filing itself, pre-declaring a standing acceptance, or acknowledging before any vote.
Why POST_ACK acknowledges before admission. If a party could file, watch the vote, and acknowledge only when the result favoured it, the “binding” would be an option, not a commitment. Acknowledgment therefore precedes admission and voting, an unacknowledged issue proceeds only as advisory, and an advisory result can never be upgraded in place.
Why one unfinished Binding issue per obligation, and why the obligation is not the subject. Without the obligation rule the same delivery could be sent to several panels and the favourable result executed. Different consumers deciding about the same fact remain separate obligations; the rule forbids verdict shopping on one execution obligation, not the use of one fact as evidence in several domains. The subject answers a different question from the obligation: the subject freezes exactly what a proceeding evaluates (evidence identity), while the obligation names which consumer duty may have at most one unfinished Binding proceeding (authority identity). A generic kernel cannot infer the second from the first, so the consumer commits the obligation scope explicitly — as a fixed identifier, or, for a standing acceptance, by electing the exact subject as the scope — and the ERC never silently makes evidence identity equal authority identity.
Why four-valued composition and no pooling of ballots. “Technical chamber approves” and “economic chamber approves” compose by logic, not by adding votes: pooling lets the larger chamber swamp the smaller and dissolves the check the two chambers were meant to provide. Pending, Yes, No and NoDecision are kept distinct because “the second chamber failed to reach quorum” is neither “both approved” nor “one rejected”, and relying contracts must be able to see which happened.
Why a body plays one role. The tree validation originally guaranteed only that every node is referenced once; two BODY nodes could still name the same body, so one body’s decision filled two slots of a K-of-M node, and a veto body that also voted as a leaf produced a tree that could never form Yes (its approval vetoes, its refusal blocks the ALL). Review of the draft (Ethereum Magicians, topic 29850) raised both. Documenting reuse would have made weight and opposing roles a matter of tree shape, which is what composition by logic was meant to avoid, so the specification forbids repeated BODY references and a voting veto body, and requires every declared body to be used — an idle body would still open ballots and stretch the round to its window. One screening body guarding several targets through several VETO nodes remains allowed: it decides once and each guard reads the decision.
Why the veto window cannot be settled early. If two chambers approve in the first hour of a 12-hour veto window and the result could be finalized at once, the veto right would be hollow. Treating the VETO node as Pending until the window closes or the veto body decides makes the result type a pure function of ballots and time and independent of who calls settleRound when.
Why NoDecision is a first-class outcome and why appeals keep earlier decisions. Many procedures end without a decision: nobody shows up, a chamber misses quorum, a submitter stays silent. Dressing that up as “rejected” or “approved” would hand a default to whichever side benefits from paralysis. The consumer commits its disposition in advance, and the registry only reports why the procedure ended. What an appeal round that fails to decide means is not universal, so the policy says it. Under PRESERVE_UNLESS_OVERTURNED a challenge that fails to overturn leaves the decision under appeal standing and the failed round is recorded beside it; under REQUIRE_FRESH_DECISION opening the appeal vacates the earlier decision and only a fresh substantive decision can be adopted. Both are coherent; a kernel default would pick one for every deployment, and in particular a preserve rule paired with a short appeal round lets the clock, rather than the appeal body, confirm the challenged result. The mode is part of the hashed policy, so every participant sees it before the first ballot. The invariant behind the obligation scope, the appeal mode and the round binding of ballots is the same: evidence may survive a transition, authority crosses it only when the committed procedure says it does.
Why K-of-N is an approval rule. Under 4-of-5, two blocks stop approval; that proves the proposition cannot reach four approvals, not that four members found the opposite true. Reading No as the opposite proposition is a relying-contract decision that must be made in its own commitment, which is why the registry exposes outcomeYes and a reason rather than a verdict on two propositions.
Why signed ballots carry no nonce and share the on-chain tally. A nonce would let one voter produce several “fresh” ballots; keying de-duplication on the voter’s identity per issue, round and body makes additional signatures worthless. Counting signed and on-chain ballots under one rule prevents a relayer from presenting a favourable batch as the round’s result: a batch adds to the same counts and a decided body refuses further ballots.
Why on-chain ballots and submitter reports name their round. A signed ballot is bound to a round by its digest, so a round-1 signature can never count in round 2. Without an explicit round argument an on-chain ballot would lack that boundary: a transaction broadcast during round 1 but included after the round settled and an appeal opened round 2 would be counted in round 2, and since ballots cannot be changed the voter could not undo it. Requiring the round and rejecting a mismatch makes the two acceptance paths equally bound; the cost is one uint32 of calldata and a comparison. The same applies to an authorized submitter’s report.
Why the kernel never executes effects. A decision registry that could move assets would need the authority of every relying contract; separating decision from execution, as governors with timelocks do, keeps the registry’s trust footprint to its records and lets each relying contract decide how much of a result it enforces and when.
Why the appeal window runs from the formation instant. Anchoring it to the settlement transaction would let any party stretch a procedure by not calling settleRound; anchoring it to the instant the ballots determined the result makes the deadline a property of the record, not of who acted when.
Backwards Compatibility
This ERC introduces new contracts and requires no change to existing standards. Relying contracts that already delegate acceptance to a single address — an evaluator, an acceptance authority, an arbitrator — can point that address at an adapter that reads this registry, without changing their own interfaces. Policies, issues and results are new objects; nothing in this ERC reinterprets existing tokens, registries or signatures. ERC-6372 clock disclosure and ERC-165 interface detection are used as specified in those documents.
Test Cases
Deterministic vectors are provided under the assets of this proposal. Each file states how its values were produced and is self-describing:
policy-id.json: three complete PolicySpec values (minimal 3-of-5; two chambers under ALL; a VETO over ALL with one appeal) with their ABI encodings and policyIds, produced by an independent ABI coder and re-derived in Solidity;
ballot-digest.json: EIP-712 ballot digests for a fixed chain id and verifying contract;
kofn-tally.json: 83 rows of the roster K-of-N status rule over (N, k, yes, no, closed);
composition.json: 192 rows of ALL, ANY and 2-of-3 composition over every triple of child values in {Yes, No, Pending, NoDecision};
As informative context, the reference implementation has been exercised by a test suite covering: minimal tally edge cases and policy validation; acceptance modes, freezing, withdrawal and the obligation rule; composition including every VETO branch and the independence of the result from settlement order; rounds, appeals, the adoption rule, hard deadlines and replay of earlier-round signatures; signed-ballot verification including ERC-1271 accounts, malleable signatures and favourable late batches; and an end-to-end adapter run against a task-tender kernel covering acceptance, rejection, no-decision defaults, late execution and retry after a kernel refusal. This document does not depend on that suite; the vectors above are the normative test material.
Reference Implementation
A reference implementation is provided under the assets of this proposal:
Schemas: policy-spec.schema.json mirrors the on-chain PolicySpec for off-chain tooling; result-receipt.schema.json describes an off-chain carrier of a registry record, which is never a substitute for the record.
The issue registry compiles to 23,514 bytes of runtime code under solc 0.8.24 with the IR pipeline, within the EIP-170 limit.
Security Considerations
Authorization boundary. A result binds only the consumer that committed to it, only for the effects it committed to. Relying contracts derive the consumer binding from their own state (the adapter pattern of Section 14) and do not accept a caller’s claim to act for an object they control; Section 5.2 makes this a requirement of an adapter that files as a consumer. Nothing written into the registry grants power over any contract that did not itself file, pre-accept or acknowledge.
Verdict shopping. The obligation rule (one unfinished Binding issue per (consumer, obligationId, question)) prevents filing the same obligation to several panels. The protection is only as good as the consumer’s choice of scope: an obligation identifier that varies with incidental data (a timestamp, a fresh nonce) defeats the rule, and a scope that is too coarse blocks legitimately independent proceedings. Consumers are advised to derive obligationId from the business object whose duty the result discharges, to keep the evidence that the proceeding evaluates (for example a submission’s result hash and cited task version) in subject, and to refuse to reopen an obligation that already reached a decision.
Appeal rounds and the hard cap. Under PRESERVE_UNLESS_OVERTURNED, an appeal round that times out confirms the challenged decision by the clock, not by the appeal body. Policies using that mode are advised to size the appeal round’s windows and maxTotalDuration for the real difficulty of an appeal, which is typically longer than a first round, or to choose REQUIRE_FRESH_DECISION where a timed-out appeal should not confirm anything. Per-round durations are a reserved extension (Section 13); in this version every round uses the same body windows.
Option-like acknowledgment. POST_ACK acknowledgment before admission, and the prohibition on upgrading an advisory issue, prevent a party from binding itself only after seeing how the vote goes. Section 5.2 forbids an upgrade path, and implementations have no legitimate reason to add one.
Panel shopping by withdrawal. Withdrawal is limited to the Filed state because, after admission, the roster and windows are known and a filer allowed to withdraw and refile could wait for a favourable panel.
Frozen rules. Because the policy identifier is the hash of the executed struct, no proxy or admin can change a policy under an open issue. Deployments that add pluggable modules in future profiles are expected to declare, as Section 5.3 requires of any profile introducing later-evaluated parameters, which dependencies may change, when they are evaluated and what happens if they fail; address and code-hash pinning alone does not establish immutability.
Liveness and griefing. Every state has a permissionless exit: settleRound after the round deadline, finalize after the appeal window, enforceHardDeadline after the cap, admitAdvisory or expireUnacknowledged after the acknowledgment window. Appeals are bounded by maxAppeals and the hard cap. A permissionless path is a path, not an executor: someone must call it, and relying contracts are advised to plan for that.
Timing. Deadlines are arrival deadlines: a ballot or result counts only if the registry receives it in time, and an adapter’s enactment counts only if the target contract receives it before the target’s own deadline. Relying contracts are advised to size consumerDeadline with an execution margin and from the earliest of their own clocks. Appeal windows anchored to formation instants cannot be extended by delaying settlement.
Signature handling. Signed ballots bind chain id, registry, issue, round, body, voter and position; they carry no nonce by design (Section 7.3). On-chain ballots and submitter reports are bound to a round by the explicit round argument (Sections 7.2 and 7.4), so a pending transaction cannot drift into a later round. ERC-1271 validity may depend on the signing contract’s state at submission time; a contract voter that changes its validation logic can change whether its ballot is accepted, never whether an already accepted ballot counts. EIP-712 provides no replay protection by itself; the per-voter, per-round, per-body key does.
Correlated participants and Sybil rosters. This ERC does not establish that roster members are independent or that they are who a policy author believes them to be; a roster of N addresses controlled by one operator is one voter with N ballots. Eligibility profiles (reserved) are where identity, trust assertions, per-operator caps and conflict exclusion belong. Policy authors are advised to disclose what their rosters assume.
Evidence and prompts. Evidence is untrusted data. Agent participants are advised to treat evidence URIs and contents as input to evaluate, never as instructions, and to commit to their rationale off-chain where a profile asks for it.
Incentive design. This ERC pays, bonds and scores nobody. Profiles that add incentives are advised to avoid rewarding agreement with the majority as such, since that selects for predicting the panel rather than judging the matter, and to base penalties on provable procedural violations (for example contradictory signed ballots in one round) rather than on dissent.
Registry instance trust. A relying contract binds to a specific registry instance. The instance’s deployment, upgradeability (none, for the reference implementation) and the policy versions accepted are the relying contract’s trust decisions, best disclosed to its users.
Chain finality. A Final state is a procedural fact recorded on-chain; it is not chain finality. Cross-chain readers of a result have to state their own assumptions about the source chain and the bridge they rely on; this ERC reserves cross-chain carriage and makes no claim about it.