--- eip: 8412 title: Preregistered Acceptance Criteria description: Acceptance criteria and typed evidence obligations frozen on-chain before evidence exists, with verdicts itemized against them author: Richard (@richard7463) discussions-to: https://ethereum-magicians.org/t/erc-8412-preregistered-acceptance-criteria/29609 status: Draft type: Standards Track category: ERC created: 2026-09-04 requires: 165 --- ## Abstract Existing agent verification standards establish a verdict by recomputation: a verifier re-derives a digest from known inputs and compares it. That works only where a function maps the artifact to the verdict. Much of what agents pay for is **judged rather than computed** — a deliverable produced by a model at temperature above zero, a human review, an on-site inspection, an identity-bound action only a person can perform. None of these can be re-derived, so recomputation-based verification returns a permanently indeterminate result. This ERC replaces re-derivation with preregistration. The acceptance criteria and the typed evidence obligations that would satisfy a claim are registered and timestamped on-chain **before any evidence exists**. The resulting verdict is **itemized** against those frozen criteria: for every registered obligation, the verifier records whether it was met, waived, or unmet. A third party can then refute the verdict on the record, using criteria that provably predate the evidence. The specification defines one registry interface, three off-chain document schemas, a packed itemized-verdict encoding, contract-enforced consistency invariants, and a mandatory terminal resolution for indeterminate cases. Evidence file formats, forensic methods, payment, and identity are out of scope. ## Motivation ### The stack verifies by recomputation Every verification primitive in the current agent stack reduces to re-deriving a value and comparing it (status as of 2026-09-04): | Standard | Verification primitive | | --- | --- | | OCP — observation commitment protocol | `recompute → compare → confirm inclusion` | | AI inference proof verification | `verify(inputHash, outputHash, metadata, proof)` | | WYRIWE input provenance | `raw_input_hash → sanitization_pipeline_hash → input_hash` | | Onchain proof layer | `anchor(agentIdScheme, agentId, proofHash)` | | Recomputable verification receipts | `REPRODUCED / DIVERGED / CANNOT_RECOMPUTE` | The primitive is sound wherever a verdict is a function of the artifact. The boundary it cannot cross is not digital versus physical — it is **computed versus judged**. Two clear cases sit on the far side of that boundary: - **A singular event.** "The technician was on site at 14:03" cannot be re-executed, and no amount of evidence makes it recomputable. The photograph, the GPS trace and the countersignature are artifacts of one unrepeatable event. - **A generated deliverable.** A model called at temperature above zero does not return the same bytes twice, and provider-side model revisions break reproduction even at temperature zero. Anchoring the record of an inference is possible and already standardised; re-deriving the deliverable is not. More importantly, "does this deliverable satisfy what was asked for" was never a recomputation question in the first place. The recomputable-receipt draft states the consequence in its own normative text: a recomputation status of `CANNOT_RECOMPUTE` **MUST NOT** be converted into `UNVERIFIABLE`, `REFUTED`, `DIVERGED`, or a default false value. Under that rule, every judged outcome remains permanently in `CANNOT_RECOMPUTE` — a state its own specification forbids collapsing into a settleable verdict. The gap is a direct consequence of a correct guard clause, not a defect in it. ### Settlement layers therefore accept an opaque blob Because no standard describes what a judged outcome claim must contain, the layers that move money treat the outcome as one opaque word: - [ERC-8183](./eip-8183.md) (Agentic Commerce, Draft): `submit(uint256 jobId, bytes32 deliverable, bytes optParams)`; `complete(uint256 jobId, bytes32 reason, bytes optParams)` where `reason` MAY be `bytes32(0)`. Decisions are complete-or-reject. - **Task market protocol.** Actor-agnostic — requesters and workers may be humans, agents or devices — but the deliverable is a bare `bytes32` and `DisputeStatus` is `None | Open | Resolved`, with no indeterminate state. - **Staked weighted verification gate.** Weighted third-party verification over an `evidenceHash`, with the format of that hash explicitly out of scope. - [ERC-8004](./eip-8004.md) (Trustless Agents, Draft): `validationResponse` carries a `uint8 response` and a free-form `string tag`. None of these can be disputed. A `uint8`, a score, or a `complete` call carries no statement of what was required, so there is nothing for a challenger to contradict. ### A deliverable digest proves identity, not adequacy The construction settlement layers converge on is to release payment when the digest of the returned artifact matches a commitment recorded when the order was accepted — payment against a hash rather than against a promise. [ERC-8183](./eip-8183.md) and the task-market-protocol draft both reduce the deliverable to a bare `bytes32` for this purpose, and deployed agent-to-agent marketplaces describe their escrow release in those exact terms. The construction is sound and worth having. What it establishes is *identity*: the artifact settled against is the one that was committed to, so it cannot be substituted afterwards. It establishes nothing about *adequacy*. A digest is taken over bytes, not over a requirement. The digest of an artifact that misses the point matches its commitment exactly as cleanly as the digest of one that satisfies it. The designs themselves acknowledge this, because they place a challenge window after the match — the task-market-protocol draft carries a `DisputeStatus`, and deployments add a fixed contest period. It is worth enumerating what a challenger can raise there. Substitution of the artifact is already excluded by the digest. Non-delivery is already visible as an absent commitment. Lateness is already visible from the timestamps. What remains is whether the artifact was good enough to pay for — the one question the digest did not answer. A challenge window is an admission that matching the hash did not settle the matter. And once it opens, the challenger is in the position described in the previous section: nothing on the record states what the artifact was required to contain, so the dispute resolves to whichever party a venue finds more credible. The asymmetry is sharpest where a design does not merely withhold payment but slashes a staked counterparty. Withholding leaves both parties where they started; slashing moves value on the strength of a finding that nothing on the record can contradict. This is also why the boundary above is computed versus judged rather than digital versus physical. A purely digital deliverable can hash exactly, be committed to before it is revealed, and be anchored beyond dispute, and the adequacy question is untouched by all three. Freezing the artifact and freezing the criteria are separate acts, but they share a primitive: a commitment is agnostic to what its digest is over, so committing `criteriaDigest` before any evidence exists is the same commit-then-prove-inclusion shape that observation-commitment standards already standardize. The gap is not the freezing act itself. It is what sits on top of the frozen digest: a typed, itemized obligation encoding and a contract rule that makes a `Satisfied` verdict over a required `UNMET` unrepresentable rather than merely detectable. ### Choosing a judge is not the same as fixing the criteria Where recomputation fails, the prevailing answer is to appoint a judge: weighted third-party verification on-chain, or a model asked to grade the deliverable off-chain. Both are useful and neither addresses this gap. The staked-weighted-verification draft stakes and weights verifiers over an `evidenceHash` and explicitly puts the format of that hash out of scope. It decides *who* judges and how their votes aggregate. It says nothing about what the judge was required to check, so a unanimous weighted pass over criteria nobody recorded is still unfalsifiable. The off-chain pattern is worse in one specific way. A grading rubric held in a prompt can be edited after the output is in hand, and the record retains no trace of the edit. That is outcome switching in the strict sense — selecting the criterion after seeing the result — reintroduced at the exact point where the money moves. Appointing a judge and freezing the criteria are orthogonal; this ERC supplies the second and composes with any implementation of the first. ### The four questions nobody can ask today 1. What was promised, before work started? 2. Is this the evidence that was required, bound to this task? 3. What does the verifier's decision mean when the evidence is ambiguous? 4. Can the basis for settlement be independently reconstructed later? ### The same problem has an institutional precedent Clinical research faced this structure and resolved it. A patient's course cannot be replayed, so a result can be reported against whichever endpoint happened to look favourable once the data were in — *outcome switching*. The answer was preregistration: the primary outcome and the analysis plan are registered before enrolment, and since 1 July 2005 the ICMJE has made registration a precondition of publication. The second half of that history is what this ERC is actually about. Registration alone did not stop outcome switching. The COMPare project (Centre for Evidence-Based Medicine, Oxford, October 2015 – January 2016) checked trial reports in five major journals against their own registry entries and found switching still widespread. The reason is structural: a registry that stores a plan but never compares it against the report leaves the comparison to whoever is willing to do it by hand, one paper at a time. That sets the design target. Registering the criteria is necessary but inert. The contribution here is that the comparison between the frozen criteria and the rendered verdict is **mechanical** (§7): outcome switching stops being misconduct that a diligent reader might catch and becomes a revert. ### Why this needs a chain The entire value of a preregistration is that it predates the evidence. Off-chain, timestamps are forgeable and criteria can be backdated after seeing the photos. Block ordering is the only mechanism that lets an unrelated third party check "these criteria were fixed before this evidence existed." That temporal claim is the sole reason this belongs on-chain, and it is the same argument the OCP draft makes for inclusion proofs. Consequently, `preregister` does not need to carry its own commitment semantics: a registry MAY register `criteriaDigest` as an observation commitment and let the inclusion proof supply the ordering claim. Clinical preregistration buys that property by trusting a registrar. Between an agent, an operator and a verifier who share no institution, there is no registrar to trust; block ordering is what replaces one. ## Specification The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. ### 1. Terms - **Criteria author** — the party that fixes the acceptance criteria. Typically the delegating agent or the task requester. - **Operator** — the party that performs the outcome-bearing step. May be human, agent, or device. This ERC does not distinguish them. - **Verifier** — the party that renders a verdict against the preregistered criteria. - **Obligation** — one typed evidence requirement, identified by its index in the preregistered criteria document. ### 2. Criteria document (off-chain) The criteria document is a canonical JSON object. Its digest is registered on-chain. It MUST contain: ```json { "version": "1", "taskRef": "0x<32 bytes>", "supersedes": "0x | null", "decisionRule": "", "obligations": [ { "index": 0, "type": "PHOTO", "required": true, "waivable": false, "constraints": { "captureMetadata": ["timestamp", "geolocation"], "geofence": { "commitment": "0x" }, "notBefore": 1788000000 } } ], "waiverAuthority": "0x
| null", "verifier": "0x
", "verifierConstraint": { "allowed": ["0x..."], "maxRiskScore": 20 }, "expiry": 1788600000, "terminalOnExpiry": "REFUND_WITH_RECORD | HOLD | REVERIFY" } ``` Rules: - `obligations` MUST be a dense array ordered by `index`, starting at 0. Indices are the identity of an obligation and MUST NOT be reordered. - `decisionRule` MUST be evaluable by a third party over the obligation outcomes alone. Free-form prose MUST NOT appear here; human-readable acceptance text MAY be carried in a separate field whose digest is included in the document. `decisionRule` MUST be one of the standard rules below or a reverse-DNS namespaced identifier (`io.example.rule-v1`) whose definition is published by its namespace owner: - `ALL_REQUIRED` — `Satisfied` if every required obligation is `MET` or `WAIVED`, otherwise `NotSatisfied`. - `ALL_REQUIRED_AND_AT_LEAST(k)` — as `ALL_REQUIRED`, and additionally at least `k` optional obligations are `MET`. A decision rule yields only `Satisfied` or `NotSatisfied`; `Indeterminate` is governed by O4 and §9. - `waiverAuthority` MUST be present and non-null if any obligation has `"waivable": true`. - `verifierConstraint.maxRiskScore`, if present, is an [ERC-8126](./eip-8126.md) ceiling: a verifier whose current score **exceeds** it MUST NOT be accepted. Lower scores are safer under [ERC-8126](./eip-8126.md). - `verifier` MUST equal the `verifier` passed to `preregister`, and `supersedes` MUST equal the `supersedes` passed to `preregister` (`null` if and only if it is `bytes32(0)`). If `verifierConstraint.allowed` is present, it MUST contain `verifier`. The verifier MAY be a contract account (for example a committee, multisig, or appeal contract that attests only once its own process is final). - Consumers MUST ignore top-level fields and constraint keys they do not recognise if those fields use a reverse-DNS namespace. Unnamespaced fields not defined by this ERC are reserved. This lets profiles add requirements (for example a minimum evidence strength per obligation) without changing the `version` of existing documents. - Canonicalization: JCS (RFC 8785). `criteriaDigest = keccak256(jcs(document))`. Documents defined by this ERC MUST NOT contain non-integer numbers, so that canonicalization does not depend on floating-point serialisation. ### 3. Evidence types This ERC defines the following type identifiers and their REQUIRED constraint keys. It does not define file formats or forensic methods. | Type | REQUIRED constraint keys | | --- | --- | | `PHOTO` | `captureMetadata` | | `VIDEO` | `captureMetadata` | | `GPS_TRACE` | `captureMetadata`, `sampleIntervalSeconds` | | `HUMAN_SIGNATURE` | `signerBinding` | | `DOCUMENT` | `mediaType` | | `WITNESS_STATEMENT` | `signerBinding` | | `DEVICE_READING` | `deviceBinding`, `captureMetadata` | | `AGENT_LOG` | `agentBinding` | | `GENERATED_ARTIFACT` | `modelBinding`, `mediaType` | Implementations MAY define additional types using a reverse-DNS namespace (`io.example.customType`). Unnamespaced identifiers are reserved for this ERC. ### 4. Evidence bundle and attestation document (off-chain) The bundle is a canonical JSON object with `version`, `preregistrationId`, and an `items` array. Each item MUST carry `obligationIndex`, `digest`, `mediaType`, a content locator, and a `captureMetadata` object whose `timestamp` is an integer in Unix seconds. Every item MUST bind the `preregistrationId`. For a `GENERATED_ARTIFACT`, `captureMetadata.timestamp` is the time the artifact was produced and `modelBinding` MUST identify the model and the parameters it was produced under. `bundleDigest = keccak256(jcs(bundle))`. The bundle encoding MAY reuse the [ERC-8326](./eip-8326.md) canonical bundle anchor or the recomputable-receipt draft's evidence-set descriptor; this ERC constrains which obligations must be covered, not how bytes are packed. **Attestation document.** `attestationDigest = keccak256(jcs(attestation))`, where the attestation document MUST contain: ```json { "version": "1", "preregistrationId": "0x<32 bytes>", "bundleDigest": "0x<32 bytes>", "verdict": "Satisfied | NotSatisfied | Indeterminate", "obligationOutcomes": "0x", "waivers": [ { "obligationIndex": 1, "reasonDigest": "0x<32 bytes>", "signature": "0x<65 bytes>" } ], "undecided": [0] } ``` **Waiver record.** Each `waivers` entry is an [EIP-712](./eip-712.md) signature by the `waiverAuthority` over ``` Waiver(bytes32 preregistrationId,uint16 obligationIndex,bytes32 reasonDigest) ``` signed against the domain ``` { name: "ERC-8412", version: "1", chainId, verifyingContract } ``` where `chainId` and `verifyingContract` are those of the registry. Binding the registry and the `preregistrationId` prevents a waiver from being replayed onto another task. Where `waiverAuthority` is a contract, the signature MAY instead be validated with [ERC-1271](./eip-1271.md). ### 5. Packed encodings Two bits per obligation, most-significant-bit first. Obligation `i` occupies bits `[2i, 2i+1]` of the byte stream. Length MUST be `ceil(2n/8)` bytes for `n` obligations, and trailing pad bits MUST be zero. **`obligationFlags`** — fixed at preregistration time: | Bits | Meaning | | --- | --- | | `00` | optional, not waivable | | `01` | required, not waivable | | `10` | optional, waivable | | `11` | required, waivable | **`obligationOutcomes`** — recorded at attestation time: | Bits | Meaning | | --- | --- | | `00` | `UNMET` | | `01` | `MET` | | `10` | `WAIVED` | | `11` | `NOT_APPLICABLE` | Registering the flags on-chain is what makes the invariants in §7 machine-checked rather than advisory. ### 6. Registry interface ```solidity interface IPreregisteredCriteria { enum Verdict { None, Satisfied, NotSatisfied, Indeterminate, ExpiredUnresolved } event CriteriaPreregistered( bytes32 indexed preregistrationId, address indexed author, bytes32 criteriaDigest, bytes32 indexed taskRef, uint16 obligationCount, bytes obligationFlags, uint64 expiry, address verifier, bytes32 supersedes ); event OutcomeAttested( bytes32 indexed preregistrationId, address indexed verifier, bytes32 bundleDigest, bytes32 attestationDigest, Verdict verdict, bytes obligationOutcomes ); event ExpiredResolved(bytes32 indexed preregistrationId, address indexed caller); function preregister( bytes32 criteriaDigest, bytes32 taskRef, uint16 obligationCount, bytes calldata obligationFlags, uint64 expiry, address verifier, bytes32 supersedes ) external returns (bytes32 preregistrationId); function attestOutcome( bytes32 preregistrationId, bytes32 bundleDigest, bytes32 attestationDigest, Verdict verdict, bytes calldata obligationOutcomes ) external; function resolveExpired(bytes32 preregistrationId) external; function getPreregistration(bytes32 preregistrationId) external view returns ( address author, bytes32 criteriaDigest, bytes32 taskRef, uint16 obligationCount, bytes memory obligationFlags, uint64 expiry, uint64 registeredAt, address verifier, bytes32 supersedes, bytes32 supersededBy ); function getAttestation(bytes32 preregistrationId) external view returns ( address verifier, bytes32 bundleDigest, bytes32 attestationDigest, Verdict verdict, bytes memory obligationOutcomes, uint64 attestedAt ); } ``` `preregistrationId` MUST equal `keccak256(abi.encode(block.chainid, address(this), msg.sender, criteriaDigest, taskRef))`. A registry MUST revert on a duplicate `preregistrationId`. Implementations MUST support [ERC-165](./eip-165.md) and MUST return `true` for the `IPreregisteredCriteria` interface identifier. ### 7. Contract-enforced invariants A conforming registry MUST enforce all of the following, reverting otherwise. - **E1** `preregister` MUST record `block.timestamp` as `registeredAt` and MUST reject `expiry <= block.timestamp`. - **E2** `obligationFlags` and `obligationOutcomes` MUST be `ceil(2 * obligationCount / 8)` bytes with zero trailing pad bits. - **E3** At most one attestation per `preregistrationId`, and `attestOutcome` MUST revert unless `msg.sender` is the `verifier` recorded at preregistration. - **E4** `verdict == Satisfied` requires every **required** obligation to be `MET` or `WAIVED`. - **E5** `WAIVED` MUST appear only where the **waivable** flag bit is set. - **E6** `NOT_APPLICABLE` MUST NOT appear for a **required** obligation. - **E7** `attestOutcome` MUST revert once `block.timestamp > expiry`. - **E8** `resolveExpired` MUST revert if `block.timestamp <= expiry`, if an attestation exists, or if the preregistration has been superseded, and MUST set the verdict to `ExpiredUnresolved`. It MUST be permissionless. (E7 and E8 are disjoint by construction: at `block.timestamp == expiry` only attestation is possible.) - **E9** `attestOutcome` MUST reject any verdict other than `Satisfied`, `NotSatisfied`, or `Indeterminate`. In particular `None` and `ExpiredUnresolved` cannot be attested; the latter is produced only by `resolveExpired`. - **E10** Once any terminal verdict is recorded for a `preregistrationId`, including `ExpiredUnresolved`, no function may record or replace a verdict for it. `attestOutcome` MUST revert in that case. - **E11** `verdict == NotSatisfied` or `verdict == Indeterminate` requires at least one obligation to be `UNMET`. A work claim with no unmet obligation cannot be recorded as failed or as undecided. - **E12** `preregister` with `supersedes != bytes32(0)` MUST revert unless the named preregistration exists, has the same `author` and `taskRef`, has not already been superseded, and either has no recorded verdict or has verdict `Indeterminate`. On success the registry MUST record `supersededBy` on the prior preregistration. - **E13** `attestOutcome` MUST revert on a preregistration that has been superseded. - **E14** `preregister` MUST reject `verifier == address(0)`. It SHOULD reject `verifier == msg.sender`. ### 8. Off-chain invariants Checkable by any party holding the criteria document and the bundle: - **O1** Every bundle item's `captureMetadata.timestamp` MUST be greater than or equal to the preregistration's `registeredAt`. Evidence MUST NOT predate the criteria that judge it. - **O2** Every obligation marked `MET` MUST be covered by at least one bundle item whose `obligationIndex` equals that obligation's index and whose constraints in §3 are satisfied. At minimum, a checker MUST verify that every key listed in the obligation's `captureMetadata` constraint is present in the item's `captureMetadata`, that `notBefore` (if present) does not exceed the item's timestamp, and that `mediaType` (if present) equals the item's `mediaType`. Salted commitments are opened only by parties holding the salt. - **O3** Every `WAIVED` outcome MUST correspond to exactly one waiver record in the attestation document, signed by the `waiverAuthority` named in the criteria (§4). No waiver record may name an obligation whose outcome is not `WAIVED`. - **O4** If `verdict` is `Satisfied` or `NotSatisfied`, it MUST equal the value produced by applying `decisionRule` to `obligationOutcomes`, and `undecided` MUST be empty. If `verdict` is `Indeterminate`, `decisionRule` MUST NOT yield `Satisfied`, `undecided` MUST be non-empty, and every index in `undecided` MUST be `UNMET` in `obligationOutcomes`. - **O5** The published documents MUST match the chain: `criteriaDigest`, `bundleDigest` and `attestationDigest` MUST be the digests of the published documents; the criteria document's `taskRef`, `expiry`, `verifier` and `supersedes`, its number of obligations, and the `required` / `waivable` flags of each obligation MUST equal the values registered on-chain; the attestation document's `preregistrationId`, `bundleDigest`, `verdict` and `obligationOutcomes` MUST equal the recorded attestation; and `verifier` MUST appear in `verifierConstraint.allowed` if that list is present. A mismatch in the flags matters most: a required obligation registered as optional escapes E4. A verdict violating O1–O4 is **refuted**, not merely disputed: the contradiction is derivable from published artifacts without trusting either party. ### 9. Indeterminacy and terminal resolution `Indeterminate` is a first-class verdict. A verifier facing evidence that neither satisfies nor fails the criteria MUST be able to say so rather than being forced into a binary answer. An obligation the verifier could not decide is encoded `UNMET` in `obligationOutcomes`, since it was not shown to be met, and its index is listed in the attestation document's `undecided` array. This keeps the two-bit encoding unchanged while letting a reader distinguish "shown unmet" from "not decided". `Verdict` is an **outcome-claim state**: its subject is the work, judged against the preregistered criteria. It is not a statement about the verification machinery. In particular, `Indeterminate` MUST NOT be derived from, mapped onto, or collapsed into [ERC-8126](./eip-8126.md) `VerificationStatus.Inconclusive`, which reports that a verification layer reached no conclusion about an *agent*. The two describe different subjects — an outcome and a verifier — and a registry MUST NOT set either from the other. Indeterminacy MUST NOT be able to strand a settlement: - To re-verify, the criteria author preregisters a **new** criteria document whose `supersedes` field names the prior `preregistrationId` (E12). Attestations are immutable; the audit trail is the chain of preregistrations, recorded on-chain in both directions. A superseded preregistration can no longer be attested (E13) or expired (E8), so at most one preregistration per chain is live. - Superseding re-starts the evidence clock: by O1, evidence captured before the new `registeredAt` cannot satisfy the new criteria. Settlement layers SHOULD therefore treat a supersession made before any attestation, without the operator's consent, as a cancellation by the criteria author rather than as a failure by the operator. - If neither an attestation nor a superseding preregistration exists at `expiry`, anyone MAY call `resolveExpired`, producing the terminal `ExpiredUnresolved`. - Settlement layers MUST treat `ExpiredUnresolved` as non-satisfaction for the purpose of releasing funds, and MUST record it distinctly from `NotSatisfied`. "We could not tell" is not "the operator failed", and conflating them poisons any reputation system built on top. A confidence score is deliberately absent. Publishing one invites downstream consumers to threshold it, which reintroduces the forced binary decision that `Indeterminate` exists to prevent. ### 10. Optional bindings These are OPTIONAL. Conformance does not require any other standard beyond [ERC-165](./eip-165.md). | Standard | Binding | | --- | --- | | [ERC-8004](./eip-8004.md) | The attestation MAY be posted via `validationResponse`; the `tag` SHOULD carry the `preregistrationId`. | | [ERC-8126](./eip-8126.md) | `verifierConstraint.maxRiskScore` is a ceiling on the verifier's own score. `VerificationStatus` MUST NOT be mapped to this ERC's `Verdict`, in either direction (§9). | | OCP — observation commitment | `criteriaDigest` MAY be registered as an observation commitment; the inclusion proof then supplies the ordering claim for "criteria predate evidence" without this ERC reimplementing commitment semantics. | | Onchain proof layer | `criteriaDigest` MAY additionally be anchored via `anchor(...)`. | | Staked weighted verification gate | The staked weighted gate MAY judge against this ERC's `criteriaDigest` and set its `evidenceHash` to `bundleDigest`. Selecting judges and fixing criteria are orthogonal. | | Recomputable verification receipts | Where a receipt's recomputation status is `CANNOT_RECOMPUTE`, this ERC supplies the alternative basis for a verdict. The two are complementary. | | [ERC-8183](./eip-8183.md) / Task market protocol | `deliverable` MAY be set to `bundleDigest`; `reason` MAY be set to `attestationDigest`. | | [ERC-8196](./eip-8196.md) | Out of scope. [ERC-8196](./eip-8196.md) stops at authorization time; this ERC begins after execution. | ## Rationale **Why not a recomputable-receipt profile.** The recomputable-receipt draft describes what a receipt looks like after verification happened. It does not, and by its own guard clause cannot, supply a basis for a verdict when recomputation is impossible. The missing artifact is created *before* the work, which is outside a receipt format's remit. The two compose: this ERC produces the basis, the recomputable-receipt draft records the outcome. **Why not an [ERC-8183](./eip-8183.md) or task-market-protocol extension.** Both are escrow state machines. A preregistration must be usable without escrow — audit, insurance and dispute resolution consume it with no funds in play — and both standards deliberately keep the deliverable opaque so as not to constrain applications. Giving `bytes32` meaning is a separate concern from holding money. **Why a deliverable digest is not a substitute.** The obvious objection is that the deliverable is already hashed, so the record is already fixed. It is, but a digest binds the artifact, not the requirement, and the two are independent: an artifact can be committed to perfectly and still be judged against criteria written afterwards. The `criteriaDigest` and the `bundleDigest` are therefore distinct fields with distinct ordering constraints — the first MUST predate the evidence, the second cannot. Collapsing them into one commitment would fix what was delivered while leaving what was owed unrecorded, which is the state the Motivation describes. **Why not an [ERC-8004](./eip-8004.md) content profile.** [ERC-8004](./eip-8004.md)'s Validation Registry answers whether a response authentically came from a validator. It intentionally says nothing about what the response asserts. Adding outcome semantics to a `uint8` plus a free-form `string tag` would either overload the tag or amount to this specification embedded in another one. **Why not a staked-weighted-verification extension.** The staked-weighted-verification draft selects and weights judges. This ERC fixes what they are judging, before there is anything to judge. A weighted gate with unrecorded criteria produces a verdict no one can contradict, and frozen criteria without a gate still leave the choice of verifier open; neither subsumes the other. Composing them is a one-line binding (§10) rather than an extension. **Why obligation flags go on-chain.** Without them, "the verifier declared success while a required obligation was unmet" is only detectable by a human reading two JSON files. With them, it is a revert. The preregistration becomes binding rather than aspirational, at a cost of `ceil(2n/8)` bytes. **Why indices rather than names.** Fixed integer indices make the packed encodings unambiguous and make reordering a detectable change of digest. **What the record is for once a dispute leaves the chain.** Disputes over judged work end up in venues that do not execute Solidity: an insurance claim, a card chargeback, an arbitration, a small-claims filing. Those venues do not need a score, they need an answer to "what was required, and when was that fixed?" An itemized verdict against criteria that provably predate the evidence gives them a set of separately contestable statements with an identified criteria author, an identified verifier, and a named waiver authority. A `uint8` cannot be cross-examined. Liability lands in the same place: because every `WAIVED` outcome must name the authority that signed it (O3), accepting an unmet obligation is a recorded decision by an identified party rather than an anonymous pass. And because `ExpiredUnresolved` is recorded distinctly from `NotSatisfied`, "nobody could tell" does not quietly become "the operator failed" — an allocation of fault the record does not support. **What is not claimed as novel.** Typed evidence containers exist ([ERC-8326](./eip-8326.md), recomputable-receipt draft). Tri-state verdicts exist — [ERC-8126](./eip-8126.md) is Final and defines `VerificationStatus { Passed, Failed, Inconclusive }`. Digest anchoring exists (onchain-proof-layer and observation-commitment drafts), and a commitment is agnostic to what its digest is over — freezing criteria is the same primitive applied to a different object. Escrow exists. The contribution is what sits on the frozen digest: verdicts itemized against typed, preregistered obligations, making a `Satisfied` verdict over a required `UNMET` unrepresentable at the contract layer; and a mandatory terminal state so indeterminacy cannot strand funds. **Why the indeterminate state is defined here rather than added to [ERC-8126](./eip-8126.md).** The obvious economy would be to reuse `VerificationStatus.Inconclusive`. It does not fit, because the two statuses have different subjects. [ERC-8126](./eip-8126.md) reports the result of verifying an *agent*; `Inconclusive` there means the verification layer reached no conclusion. This ERC reports whether *work* met criteria fixed before the work began; `Indeterminate` here means the evidence resolves neither way. A verification layer can be perfectly conclusive about a verifier while the outcome it judged remains indeterminate, and the reverse. Folding an outcome-level state into an agent-level enum would make every consumer guess which subject a given value refers to, so §9 forbids mapping between them. **What this specification does not claim.** It does not verify truth about the world, does not establish that evidence is authentic, does not detect synthetic media, and does not make criteria fair. It makes a judgment *auditable against conditions that could not have been chosen after the fact*. Every remaining assumption stays with the verifier, in the open. ## Backwards Compatibility No backwards compatibility issues. This ERC introduces new interfaces and does not modify existing ones. ## Reference Implementation An open-source implementation of the loop `task → execution → proof → verify → settle` with typed evidence, ex-ante proof obligations and an abstaining verifier is available; one conditional USDC settlement on Base mainnet released after verification: `0xee543bc107b411edd0202131b82172eb6efaf29c10457e33d2900ae890a72cf0`. A reference registry, an executable model, a reference off-chain verifier and conformance vectors are in the [ERC-8412 reference implementation](../assets/eip-8412/README.md): - `contracts/PreregisteredCriteria.sol` — registry implementing §6 and E1–E14. - `vectors/onchain/` — cases for E1–E14, each naming the invariant it exercises; `contracts/test/Vectors.t.sol` runs them on the EVM with Foundry. - `vectors/offchain/` — complete document packages for O1–O5 with real digests and EIP-712 waiver signatures, run against `verifier/verify.py`. - `tools/mutation_test.py` — disables each invariant in turn and confirms at least one vector fails, so every invariant is exercised. The underlying model is described in *When May an Agent Claim Success? Verification Contracts for Open-World Outcomes*. ## Security Considerations **Criteria author as verifier.** If one party writes the criteria and also renders the verdict, preregistration constrains nothing but self-consistency. The registry records `author` and `verifier` separately so the collapse is publicly visible. Implementations SHOULD reject `author == verifier` (E14) and MUST NOT hide it. **Attestation front-running.** Because a preregistration admits exactly one attestation (E3) and attestations are immutable (E10), an unrestricted `attestOutcome` would let any observer occupy the slot first with an arbitrary verdict. Binding the verifier at preregistration and requiring `msg.sender == verifier` removes this. Committees and appeals compose by making `verifier` a contract that attests only once its own process is final. **Asymmetric protection.** E4 stops a false `Satisfied`; E11 stops the mirror case, a `NotSatisfied` recorded while every obligation was met or waived. Both directions matter: the first protects whoever pays, the second protects whoever did the work. **Waiver abuse.** A `waiverAuthority` able to waive every obligation reduces the criteria to that party's discretion. The `waiverAuthority` SHOULD NOT be the `verifier`; otherwise the verifier can convert any unmet waivable obligation into a pass on its own signature. Waivers are itemized and attributable, so the abuse is visible; specifications built on this ERC SHOULD bound how many required obligations may be waivable. **Evidence replay.** O1 makes reuse of pre-existing evidence detectable: capture timestamps must not precede `registeredAt`, and every item binds `preregistrationId`. This bounds replay but does not prove capture time; a lying capture device defeats it. Devices SHOULD attest capture metadata independently. **Verifier collusion and Sybil verifiers.** Out of scope. Verifier bonding and slashing belong to a staking layer; `verifierConstraint` only expresses who is eligible. **Griefing by preregistration.** Preregistrations are cheap and unrestricted. Registries serving a market SHOULD rate-limit or require a deposit at the application layer. **Expiry as denial of service.** A verifier who stays silent forces `ExpiredUnresolved`. That is why terminal resolution is permissionless and why `ExpiredUnresolved` MUST be recorded distinctly: silence is attributable. **Personal data.** Real-world evidence is personal data. Capture metadata routinely contains an operator's location, device identifiers, and timestamps. - Only digests appear on-chain. Criteria and evidence content MUST be stored off-chain. - Personal fields inside the criteria document (geofences, operator identifiers) MUST be represented as salted commitments, never plaintext. - This ERC MUST NOT be implemented in a way that requires plaintext personal data on-chain, and implementations MUST NOT place it in `aux`-style fields. - Digests are permanent. Salts MUST be per-task, since a reused salt makes a location or identity enumerable across every task it appears in. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).