ERC-8412: Preregistered Acceptance Criteria

Topic title: [Pre-ERC Discussion] Preregistered Acceptance Criteria
Category: ERCs


Every verification primitive in the agent stack reduces to the same shape:
recompute the result, compare digests. ERC-8274 verifies an
(inputHash, outputHash, proof) triple. ERC-8404 issues a receipt over
REPRODUCED | DIVERGED | CANNOT_RECOMPUTE. ERC-8263 anchors a digest.
Inclusion-proof designs confirm a commitment made earlier. Every one of them
assumes the artifact can be derived a second time.

Almost nothing an agent pays a counterparty for can be derived a second time.

A deliverable generated by a model at temperature above zero does not return the
same bytes twice, and provider-side model revisions break reproduction even at
temperature zero. A document someone had to review. An inspection someone had to
be physically present for. And in the general case, “is this good enough to pay
for” was never a recomputation question — it is a judgement against a standard.

This is not a complaint about ERC-8404. Its own normative text forbids
converting CANNOT_RECOMPUTE into any settleable verdict, which is the correct
guard to have written. The consequence is that every judged outcome parks in
that status permanently, and settlement happens somewhere off the record.

The boundary is not digital versus physical. It is computed versus judged.

What the draft proposes

Before any evidence exists, the paying party registers two things on-chain: a
digest of the acceptance criteria, and a packed list of typed evidence
obligations at two bits each — required, waivable. When the work comes back, the
verifier’s attestation is itemized against that frozen list: for every
obligation, MET, WAIVED, UNMET, or NOT_APPLICABLE.

function preregister(
    bytes32 criteriaDigest,
    bytes32 taskRef,
    uint16  obligationCount,
    bytes calldata obligationFlags,   // 2 bits per obligation, MSB-first
    uint64  expiry
) external returns (bytes32 preregistrationId);

function attestOutcome(
    bytes32 preregistrationId,
    bytes32 bundleDigest,
    bytes32 attestationDigest,
    Verdict verdict,                   // Satisfied | NotSatisfied | Indeterminate | ...
    bytes calldata obligationOutcomes  // 2 bits per obligation, same order
) external;

One rule carries the whole design: an attestation that declares the outcome
satisfied while a required obligation is recorded UNMET is rejected by the
contract. Not flagged, not down-scored — reverted.

Cost is ceil(2n/8) bytes for n obligations. Eight obligations is two bytes.

Why itemizing is the point

Today a verdict is a uint8, a score, or a complete() call. There is nothing
inside it for a third party to contradict. Itemize it against criteria that
provably predate the evidence and a challenger has something concrete to point
at: obligation 3 is recorded unmet, and you passed the outcome anyway.

“But the deliverable is already hashed”

The opening argument was that most work cannot be recomputed. This one is
stronger, because it holds even where hashing works perfectly.

The thing most people have actually built releases escrow when the digest of the
returned artifact matches a commitment made when the order was accepted — payment
against a hash rather than against a promise. It is a good construction and I am
not arguing against it.

It proves identity: the artifact being paid for is the one that was committed to,
and it cannot be swapped afterwards. It proves nothing about adequacy. A digest
is taken over bytes, not over a requirement. The hash of an artifact that misses
the point matches its commitment exactly as cleanly as the hash of one that
doesn’t.

The tell is the challenge window these designs put after the match. Enumerate what
a challenger can actually raise in it. Substitution — already excluded by the
digest. Non-delivery — already visible, no commitment was ever posted. Lateness —
already in the timestamps. What is left is “was this good enough to pay for”, the
one question the digest never answered. The challenge window is the design
conceding that matching the hash did not settle the matter.

And whoever uses that window is arguing against nothing: no part of the record
states what the artifact was required to contain. It is sharper still where a
design slashes a stake instead of merely withholding payment — withholding leaves
both sides where they started, slashing moves value on a finding nothing on the
record can contradict.

So this is not a niche about physical work. A pure byte-string 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 two different acts, and only the first one has a primitive.

Choosing a judge is a different problem from fixing the criteria

The other reply I expect is “use a weighted verification gate, or have a model
grade the output.” ERC-8353 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 never records what the judge was
required to check. A unanimous weighted verdict against unrecorded criteria is
still unfalsifiable.

The off-chain version 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.

Medicine has a name for that. You cannot replay a patient’s course, so a
researcher could report whichever endpoint looked good once the data arrived —
outcome switching. The fix was preregistration, and the ICMJE made trial
registration a condition of publication on 1 July 2005
. But registration
alone did not fix it: the COMPare project at CEBM Oxford,
from October 2015 to January 2016, checked trials in five major journals against
their own registry entries and found switching still widespread. A registry stores
a plan and never compares it to the report. The comparison was left to volunteers
reading papers by hand.

Mechanising that comparison is the contribution here. Outcome switching becomes
a revert instead of misconduct someone might notice later.

Why this needs a chain at all

Exactly one claim here needs consensus: that the criteria could not have been
chosen after seeing the evidence. That is a claim about ordering in time, and
off-chain timestamps are backdatable by whoever holds the key. Clinical
preregistration buys ordering by trusting a registrar; an agent, an operator and
a verifier share no registrar. Block ordering substitutes for one. Nothing else
in the design needs a chain.

What this is not

Not identity (ERC-8004), not agent risk scoring
(ERC-8126), not authorization
(ERC-8196), not anchoring
(ERC-8263, ERC-8281),
not escrow (ERC-8183, ERC-8195), not judge selection
(ERC-8353). It composes with them rather than competing: an ERC-8353 gate can
seat its verifiers over these criteria and set its evidenceHash to the bundle
digest.

Tri-state verdicts are not the novelty either. ERC-8126 is Final and already
defines Passed, Failed, Inconclusive. What is new is the conjunction:
criteria frozen before the evidence exists, obligations typed and itemized, and
a contract-level rule that makes the mismatch unrepresentable rather than merely
detectable.

Questions I would like answered before opening a PR

  1. I have kept Verdict deliberately separate from ERC-8126’s
    VerificationStatus, on the grounds that the two have different subjects:
    Inconclusive reports that a verification layer reached no conclusion about an
    agent, while Indeterminate here reports that evidence did not resolve a
    single outcome against criteria fixed beforehand. The draft forbids mapping
    between them in either direction. Is that boundary drawn in the right place, or
    is there a case where one should be derivable from the other?
  2. Is two bits per obligation the right encoding, or should the flags live
    entirely in the off-chain criteria document with only its digest on-chain?
    The design hinges on that choice, and I can argue it either way.
  3. Should waiver authority be named on-chain, or is naming it inside the criteria
    document sufficient?
  4. Is there prior art I have missed for freezing acceptance criteria before the
    evidence exists, as opposed to attesting after it does?

The draft is written and pre-submission. No number — editors assign those at PR
time against ethereum/ERCs. Happy to paste the
full specification text inline in a reply if that is more useful.

Real, running prior art on the ordering-needs-a-chain claim specifically (not the criteria-freezing part – I don’t have anything closer than what’s below for that): our own verdict ledger anchors committed_at to Bitcoin PoW via OpenTimestamps for exactly the reason you name – an off-chain timestamp is backdatable by whoever holds the key, and an agent/operator/verifier triad shares no registrar. Entry 38 (api.babyblueviper.com/ledger/38): event_id a42205d7e39c684f0aa43f80fe7ea1aa8e180df93ff46e82b5bff95b70c663e9, OTS-stamped pre-outcome, Bitcoin block 955810, precedence:true – a third party recomputes it against any explorer, no trust in us. Same “block ordering substitutes for a registrar” logic you’re using, just applied to when a verdict was issued rather than when acceptance criteria were frozen.

Where it doesn’t answer your actual question: what we commit is a policy_commitment – sha256 over {policy_version, rubric_sha256, conformance_suite_repo, …}, published in the same proof as the verdict, after judgment. That’s a rubric-level commitment (which standard governed the judgment), not a per-task acceptance-criteria digest frozen before evidence exists – adjacent to your obligationFlags, but it doesn’t do what preregister() does.

One question back, since we run the rubric-level commitment and you’re proposing the task-level one: does the design have room for both – a global policy_commitment naming which standard/rubric governs, alongside the per-task criteriaDigest – or does per-task granularity make a separate rubric-level pin redundant?

Author of ERC-8281 here, since it appears in the “what this is not” list — the placement is correct, and I’d draw the boundary the same way: 8281 answers “were these bytes committed before they were revealed,” never “were they adequate,” which is exactly the identity-versus-adequacy split the post is built on. One refinement to the line that freezing the artifact and freezing the criteria are two different acts “and only the first one has a primitive”: a commitment primitive is agnostic to what the digest is over, so committing criteriaDigest before any evidence exists is structurally an observation commitment — the same commit-then-prove-inclusion shape 8281 already standardizes and that block ordering already anchors. That means the freezing act does have a primitive, and it partially answers Question 4: the prior art for freezing-before-evidence is the commitment primitive itself, applied to criteria rather than to a deliverable. What is genuinely new in the draft is what sits on top of the frozen digest — the typed, itemized obligation encoding and the contract-level rule that a Satisfied verdict over a required UNMET is unrepresentable rather than merely detectable — and I’d suggest stating the novelty at that layer, since the conjunction argument is stronger when the freezing step is credited to an existing primitive rather than claimed. Practically, this makes preregister() a natural 8281 integration: the criteria digest can be an 8281 commitment, with the inclusion proof supplying the ordering claim, so 8412 need not carry its own commitment semantics. Supportive of the proposal.

Thanks — this is exactly the boundary I wanted pressure-tested, and I’ve folded it into the draft (PR updated).

Agreed on both points:

  • Freezing criteria is not a new 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 OCP already standardizes, with block ordering supplying the ordering claim.

  • The novelty sits above the frozen digest: the typed, itemized obligation encoding (two bits per obligation, required/waivable) and the contract rule that makes a Satisfied verdict over a required UNMET unrepresentable rather than merely detectable.

Changes made in the draft:

  • Removed the “the stack has a primitive for the artifact but none for the criteria” framing;

  • preregister() is now described as an OCP integration: criteriaDigest MAY be registered as an observation commitment, the inclusion proof carries the ordering claim, and 8412 does not reimplement commitment semantics;

  • OCP added to the Optional bindings table.

That also answers Question 4: the prior art for freezing-before-evidence is the commitment primitive itself, applied to criteria instead of a deliverable — which makes the case stronger, not weaker.

Appreciate the read.