ERC-8354: Confidential Agent Policy Verdicts

Discussion topic for ERC-8354, ERCS/erc-8354.md

Confidential Agent Policy Verdicts proposes a pre-execution allow/deny interface for autonomous agent actions, where the decision is proven in zero knowledge against a policy that is never disclosed on chain.

Authors: Muhammad Zidan Fatonie (@mzf11125), Faisal Firdani (@zexoverz), Maulana Asykari Muhammad (@WeissCurry)

Open PR: Add ERC: Confidential Agent Policy Verdicts by mzf11125 · Pull Request #1919 · ethereum/ERCs · GitHub

Reference implementation: GitHub - zexoverz/confidential-agent-policy-verdicts: Reference implementation of the draft ERC Confidential Agent Policy Verdicts — pre-execution ZK allow/deny for autonomous agent actions against a committed secret policy. · GitHub (CC0-1.0), spec at SPEC.md

Update Log

  • 2026-07-24: Initial draft opened for discussion on this thread.
  • 2026-07-29: Reference implementation published under CC0-1.0. Guard with verify / consume / isConsumed, companion PolicyDomainRegistry, canonical PolicyAction commitment domain-separated by chainId and domainId, and an executor bound by EIP-712 signature through SignatureChecker so ERC-1271 accounts work as executors. interfaceId assigned 0x6c832e88, repo.
  • 2026-07-30: VerdictAttestation handoff to the ERC-8004 Validation Registry added, carrying artifactHash (the action commitment) and a mechanism tag set to keccak256("zk-secret-policy"). Kept RECOMMENDED rather than required so it stays off the core consume path, post.
  • 2026-07-30: ERC PR opened, ethereum/ERCs#1919, commit c18bd72. Frontmatter corrected in cc1f7d8, CI fixed by linking EIP and ERC references and removing references to unpublished proposals in 38b6f76, editorial suggestions from @abcoathup applied in 4fcc6b8 and 57af8d1, renamed to ERC-8354 per editor assignment in f22856b.
  • 2026-07-30: Working Noir allowlist circuit landed with a bb-generated UltraHonk verifier and a HonkVerifierAdapter mapping a Verdict to the circuit’s 38 public inputs. A real proof verifies on chain through consume, with no MockVerifier in the path, post.
  • 2026-07-31: Marked ready for review. Labels c-new, e-consensus, e-review, s-draft, t-erc. One further editor review is required before merge.
  • 2026-08-03: Security Considerations section added distinguishing action-level verdict integrity from interpreter-level fidelity assurance, and directing fidelity provenance to a content-addressed append-only companion registry rather than the normative core, post.

External Reviews

  • 2026-07-29: @babyblueviper1 proposed confidential-correct and public-recomputable as orthogonal verdict properties rather than points on one spectrum, and recommended the ERC-8004 attestation carry a content-addressed reference to what was judged plus an explicit mechanism tag recording how the verdict was reached. Both adopted, post.
  • 2026-07-30: @babyblueviper1 published a composed worked example built against the reference contracts rather than a mockup, binding one action to both a CAPV verdict and an independently signed public-recomputable verdict, post.
  • 2026-07-30: @helmymekaoui-web argued that interpreter fidelity is the general shape of “the verifier itself has to be verified” and recurs across several agent standards, so fidelity attestation should be a separate reusable primitive rather than inline. Asked whether fidelity can ever be fully on-chain-checkable or bottoms out in a human reading the reference definition, post.
  • 2026-07-31: @helmymekaoui-web argued that independence must be defined structurally, by the number of distinct failure modes rather than the number of recomputations, since shared lineage pulls N back toward 1. Citing Henrion and Fischhoff, “Assessing Uncertainty in Physical Constants”, American Journal of Physics 54(9), 1986, post.
  • 2026-08-02: @helmymekaoui-web deployed a ProvenanceRegistry proof of concept on Base Sepolia implementing content-addressed program keys with a declared parents DAG and a depth-bounded shareLineage walk, and reported the sensitivity and specificity tradeoff between self-declared and openly contestable lineage. Citing Knight and Leveson, “An Experimental Evaluation of the Assumption of Independence in Multi-Version Programming”, IEEE TSE SE-12(1), 1986, post.
  • 2026-08-04: ProvenanceRegistryV2 interface frozen at register / recordOf / shareLineage / sameHeritageCluster, with heritageCluster derived rather than stored. Deployed bytecode and all four selectors independently verified by @babyblueviper1 against the live chain rather than taken from the description, post.

Outstanding Issues

  • 2026-07-24: Placement in the ERC-4337 validation phase. On-chain proof verification inside validateUserOp must respect ERC-7562 storage access and gas limits, or bundlers drop the operation. I believe verification can be made constant-cost and state-independent, but I have not checked it against the full ERC-7562 rule set and would value input from bundler implementers.

  • 2026-07-24: Asynchronous proving latency. Proof generation sits in the execution path, so an agent cannot act while the domain’s policy engine is unreachable. ERC-7412 looks like the right pattern for structured off-chain retrieval, but I have not decided whether the standard should reference it normatively or leave it to integrators.

  • 2026-07-24: Interpreter fidelity is currently assumed, not proven. A runtime proof attests that the committed program executed, not that the program faithfully implements the policy language it claims to. An interpreter that ignores its ruleset and always returns ALLOW produces proofs that verify identically to a correct one. Open question was whether the fidelity attestation mechanism should become its own ERC or stay inline.

    • 2026-08-03: Resolved as a companion, not inline. Security Considerations now names action-level verdict integrity and interpreter-level fidelity assurance as separate layers, and directs fidelity provenance (spec commit, implementation commit, review method, lineage) to a content-addressed append-only companion registry, on the same pattern by which the domain registry sits beside this standard, post.
  • 2026-07-24: Interpreter confidentiality is a separate property from fidelity. A correct but leaky interpreter, whose public outputs or revert behaviour vary with which branch of the ruleset fired, satisfies every correctness check and still leaks policy structure. Proving time is the channel I am least confident about, since it sits outside the proof entirely.

  • 2026-07-24: Whether the domain registry should be a separate ERC. IPolicyDomainRegistry is specified as a recommended companion so independent implementations interoperate by default, while the core interface depends only on three semantic hooks. Splitting it would follow the ERC-7812 precedent but would couple this proposal’s path to Final to another Draft.

  • 2026-07-24: Root staleness as a revocation service level. maxRootAge exists because mirrored roots on spoke chains lag the hub. Set it to zero and verdicts break during any sync gap. Set it high and a removed rule keeps authorizing actions for that long on every spoke. There is no setting that avoids both, and I would like input on whether the standard should mandate a ceiling or leave it to domains to publish.

  • 2026-07-24: Cross-chain agent identity resolution is out of scope and deferred. agentId must currently resolve on the chain where the verdict is consumed. Flagging in case anyone considers this a blocking gap rather than a deferral.

  • 2026-07-24: interfaceId is a placeholder and will be finalized before this leaves Draft.

  • 2026-08-05: The reference implementation proves a compiled circuit, not an interpreter. The Rationale argues for proving a fixed policy interpreter so the program commitment survives rule changes. The working proving path is currently a Noir allowlist circuit with a generated UltraHonk verifier, which is a compiled policy and therefore does incur the verification key churn the Rationale objects to. The SP1 interpreter program is retained only as a sketch for obligations reference. The interpreter design is the intended one and not yet the demonstrated one, and I would rather say so than have it read as settled.

  • 2026-08-05: Policy expressiveness in the reference implementation. The circuit proves allowlist membership against a policy root, which is the minimal viable policy. Richer predicates are future work, so nothing currently exercises the superset behaviour that makes interpreter proofs more expensive than bespoke circuits.

  • 2026-08-05: Reference implementation completeness. 24 Foundry tests cover the spec’s Test Cases, keccak commitment parity against the Noir circuit, on-chain verification through consume, and executor binding. The implementation is unaudited and the two-spoke cross-chain deployment remains outstanding.

  • 2026-08-05: Whether programKey can address an off-chain interpreter. The companion fidelity registry treats programKey as keccak over deployed bytecode, which assumes the interpreter is an on-chain contract. A verifier whose runtime is a signing server has nothing to hash that way, so either the scheme admits another stranger-recomputable preimage or such verifiers need a thin on-chain proxy purely to have bytecode. Raised on this thread and unresolved, post.


Summary

Agent authorization on Ethereum today comes in two shapes. It is either retrospective, as with ERC-8004, which records identity, reputation, and validation attestations after an agent has acted, or it is mandate-based, as with ERC-8150 and ERC-8312, where authority derives from a principal who signed a specific grant in advance.

A large class of deployments fits neither. Consider a corporate expense card. The cardholder does not pre-sign each purchase, and the card network’s fraud rules are shown neither to the cardholder nor to merchants. Authority comes from a standing ruleset held by a third party, applied to every transaction, updated without anyone re-signing anything, and deliberately kept secret, because a published fraud rule is a published evasion guide.

Regulated agent deployments have the same shape. An operator maintains screening rules, agents are subject to them whether or not any counterparty signed them, the rules change frequently, and publishing them defeats them. Both obvious workarounds fail. Putting the policy on chain discloses it to the adversaries it exists to stop. Trusting an off-chain oracle to say “allowed” gives no evidence that any policy was applied, so the verdict is indistinguishable from an arbitrary signature.

Zero knowledge resolves the tension. The verifier learns that some committed policy was correctly evaluated and returned allow, and learns nothing about its contents.

How It Works

  1. An agent proposes an action.

  2. The Policy Domain’s off-chain engine, not the agent, evaluates that action against the secret ruleset. The agent never holds the rules.

  3. If permitted, the engine produces a zero-knowledge proof that this action was checked against the committed policy and the result was ALLOW. The proof reveals nothing about the policy’s contents.

  4. A Guard contract on chain verifies the proof.

  5. If valid, the action executes. If not, it is refused.

The policy exists on chain only as a commitment, registered as a statement in the ERC-7812 Evidence Registry. What crosses chains is a single root, not the rules.

The standard defines only the verdict envelope and the verification interface. It does not define the policy language, the proving system, or the transport.

Two Choices Worth Surfacing Early

The program proven is a policy interpreter, not a compiled policy. If policies compile to circuits, every rule change produces a new verification key, which means a new verifier deployment on every chain for every update. For a ruleset that changes weekly this is disqualifying rather than inconvenient. By proving a fixed interpreter and passing the ruleset as private witness, the program commitment stays constant across policy updates and only the ERC-7812 root moves. The cost is that the interpreter proves a superset of any single policy, so proofs are more expensive than a bespoke circuit would be. That trade puts proving cost off chain with the domain and removes verification key churn from everyone else.

The policy commitment is deliberately unblinded, departing from ERC-7812’s blinding pattern. Blinding would make the commitment unverifiable even to a party holding the ruleset, which destroys the selective-disclosure path to auditors. Preimage resistance alone carries the confidentiality requirement, given adequate ruleset entropy.

Scope of the Privacy Claim

This standard hides the policy, not the action. A permitted action executes on a public chain and is public. The guarantee is that no observer, including the executing agent, learns the rules that permitted it.

It is an integrity property, not a safety property. A valid proof establishes that the committed ruleset was evaluated, and establishes nothing about whether that ruleset is correct or fair. A domain committing to a permit-everything ruleset produces proofs that verify perfectly well.

Relationship to Neighbouring Standards

Standard Role Relationship
ERC-8004 Identity Supplies the agent identity the verdict binds to.
ERC-7812 Evidence Supplies the evidence registry. It was written abstractly so later proposals could build specific use cases on top of it, and nothing in the agent cluster has taken it up yet. This is that bridge.
ERC-8312 Mandate Complementary rather than overlapping. A verdict grants permission, a mandate grants capacity, and both must hold independently.
ERC-8150 Closest neighbour Also does pre-execution verification in zero knowledge. The difference is the authority source and the secret. There, the user signs off on the agent’s batch, so the authorizing party knows exactly what was authorized and there is nothing to hide from the principal. Here, authority comes from a standing third-party ruleset that no counterparty signs, and its confidentiality is the point rather than a side effect.
ERC-8226 Adjacent motivation Shares the regulated-deployment motivation, but its mandate stays legible to the parties it binds and it scopes to tokenized regulated assets. The envelope here is asset-agnostic and gates arbitrary calls. A domain could plausibly use both.
ERC-7579, ERC-6900 Integration surface The natural integration surface. The Guard operates as a pre-execution hook or validation module.
ERC-4337, ERC-7562 Constraint Constrain how this can work inside validateUserOp. See outstanding issues.

Feedback I Am Specifically Looking For

  1. Is the interpreter-as-proven-program approach the right call, or is there a compiled-circuit design that avoids verification key churn that I have missed?

  2. Does the unblinded commitment departure from ERC-7812 create problems for that standard’s authors that I have not anticipated?

  3. Is the executor binding sufficient against mempool front-running of a valid verdict, given that proof generation and submission are decoupled?

  4. For anyone working on ERC-8004: does the Validation Registry attestation proposed after a successful consume fit the intended use of that registry, or is it a misuse?

  5. For the ERC-8150 and ERC-8226 authors: is the boundary I have drawn the right one, or is there a merge that serves everyone better than three adjacent standards?

2 Likes

Started on the implementation with @mzf11125. Guard contract does verify / consume / isConsumed with the seven checks in spec order, verifier sits behind an IVerifier interface so SP1/Groth16/RISC0 all drop in the same way, and I’ve sketched the SP1 interpreter to pin down the in-circuit parts (ruleset included in policyRoot, action preimage hashing to actionCommitment, decision computed in-circuit, nullifier derived in-circuit).

Two things I’d want to settle before going further:

  1. Where should action-binding live? consume(Verdict, proof) never sees the action params, so it can’t recompute actionCommitment itself. Right now the guarded contract recomputes it and compares before calling consume. Keep the guard minimal and leave binding to the caller, or define one entrypoint that takes action + verdict together?

  2. v.executor through a guarded contract — msg.sender becomes the contract, not the user, so executor binds to the contract and reopens front-running. Do we want executor = the direct submitter, or executor = the end EOA with a relayer submitting?

Will get the impl + Foundry tests into a public repo soon, happy to share the WIP before then.

Good catches @zexoverz, both are real forks and better to settle them now than let implementation assumptions calcify around whichever answer falls out by accident.

Action-binding: agreed, leaving it to the guarded contract is a footgun. Every integrator has to hash the action exactly the way the interpreter does in-circuit, and one mismatch breaks the binding silently with no revert to explain why. I want to fold this into the standard the way ERC-4337 handles UserOperation: define a canonical action struct (target, value, calldata, chainId, nonce) and a single hashing function as part of the interface, then have consume() take the struct directly instead of a bare proof. That makes actionCommitment derivation something every implementation shares rather than something every integrator reinvents.

Executor binding: this is the front-running issue from the outstanding issues list showing up in code, so good that it surfaced this early. Binding to msg.sender through a guard breaks once a relayer or AA wallet sits in the path, since whoever controls that address at call time can consume the verdict. I don’t want to reach for tx.origin either, it breaks under account abstraction. Proposal: keep executor as the intended EOA/agent address, and let consume succeed when msg.sender == executor, or when msg.sender presents a signature from executor authorizing that specific verdict digest for relayed submission. Basically borrowing the ERC-8150 signed-intent pattern for just this one field so the binding is cryptographic instead of positional.

Will update the spec text with both once the struct and signature scheme are worked out precisely, want the hashing to line up with what the SP1 program is already committing to so we’re not redoing the interpreter sketch. Happy to hop on a call if that’s faster than iterating here.

Pushed the reference impl public: GitHub - zexoverz/confidential-agent-policy-verdicts: Reference implementation of the draft ERC Confidential Agent Policy Verdicts — pre-execution ZK allow/deny for autonomous agent actions against a committed secret policy. · GitHub

Both things we settled above are in it. Action-binding is a canonical `PolicyAction` preimage (domain-separated by chainId + domainId) that the guarded contract and the proving program hash the same way, so the guard stays minimal. Executor is bound by signature now instead of positionally direct submit works, or a relayer submits with an EIP-712 sig from the executor, verified through SignatureChecker so ERC-1271 accounts work too. The nullifier already makes the sig single-use so I didn’t add a separate nonce.

ERC-165 id is `0x6c832e88`. Spec Test Cases are in there as a Foundry suite, 18/18, CI green. Poke holes in it — issues/PRs welcome.

Regarding the open question about the ERC-8004 Validation Registry:

I would argue that writing an attestation after a successful consume is a perfect fit, not a misuse, and it actually highlights the composability of these standards. ERC-8004 handles who the agent is and its reputation (retrospective), while our standard handles the pre-execution permission. Writing the outcome of a successful permission check into the reputation layer bridges the two exactly as intended. It allows an agent to build a public track record of compliance (e.g., “Agent X passed Domain Y’s risk screening”) without ever leaking Domain Y’s proprietary ruleset on-chain.

To better visualize this separation of concerns and how this proposal complements the existing agent-centric ERCs without overlapping, I’ve put together a capability mapping matrix.

Scope ERC-8004 (Identity) ERC-7812 (Evidence) ERC-8312 (Mandate) Proposed EIP (Verdict)
Agent Identity & Ownership Registration X
Public Reputation Tracking (Retrospective) X
Policy Commitment Storage (State Root) X
Authority Quota Metering (Bounded Limits) X
Pre-execution Interdiction (Gating) X
Zero-Knowledge Policy Evaluation X
Action Payload Binding X
Compliance Attestation Handoff X X

Matrix Breakdown:

  • Identity & Reputation: Strictly the domain of ERC-8004. This standard assumes the agentId is already resolved there.

  • Commitment Storage: This standard does not propose a redundant registry; it relies natively on the EvidenceDB structure defined in ERC-7812.

  • Quota Metering: Enforcing volume or spend limits (e.g., “max 5 transactions”) remains the responsibility of ERC-8312.

  • Pre-execution Gating & ZK Evaluation: This is the core scope of this EIP. Existing standards do not cover gating execution against an off-chain, strictly confidential policy.

  • Compliance Attestation (The Intersection): Our EIP executes the local validation (Verdict) and then hands off the verified status to ERC-8004’s Validation Registry to build the public compliance record.

Now that @zexoverz has pushed the reference implementation with the signature-bound executor and canonical PolicyAction struct, defining the exact payload schema for that ERC-8004 attestation feels like the next logical step.

Would love to hear thoughts from anyone actively building on ERC-8004 if there are specific schema configurations or constraints we should align with for that attestation payload.

2 Likes

Real, useful thread – the corporate-expense-card framing for why a policy has to stay secret is the right justification, and it’s a genuinely different trust shape from what we build (a pre-action verdict layer, signed and publicly recomputable from disclosed facts). Worth naming the axis you’re on precisely, since it clarifies rather than competes: your system proves the computation was done correctly against a policy nobody can see; ours proves a verdict is what it claims to be against inputs anyone can independently re-derive. Confidential-correct vs. public-recomputable are orthogonal properties, not points on the same spectrum – a deployment could reasonably want either, or (interesting case) a system that’s ZK-provably-correct against a policy that’s disclosed on a delay, landing in between.

On the actual question – attestation payload schema for the ERC-8004 handoff, from real production experience publishing verdict attestations to a public track record: the two fields worth pinning early are (1) a content-addressed reference to what was judged, not just the pass/fail (we use an artifact_hash over the canonical judged content, so a consumer can confirm the attestation is about the specific action, not a class of actions), and (2) an explicit source-class/mechanism tag distinguishing how the verdict was reached (self-attested / independent-mediator / ZK-proved-against-secret-policy / etc.) – we learned the hard way that omitting this lets two structurally different guarantees get silently conflated into one green checkmark downstream. Your PolicyAction struct + nullifier already gives you the first one; the second is worth being explicit about in the 8004 payload itself so a consumer reading the attestation later knows it came from a ZK gate specifically, not, say, a human reviewer or an LLM judge.

Happy to sketch a real composed example if useful – one action gated by your system, with the resulting attestation cross-referenced against an independent, publicly-recomputable verdict on the same action from our side, so a reader can see both guarantees stacked and checkable, neither substituting for the other.

2 Likes

Thanks @WeissCurry and @babyblueviper1, this helps.

On writing verdicts to the ERC-8004 Validation Registry, agreed it fits. 8004 covers who the agent is and its reputation after the fact, and this covers the permission before it acts. I added a VerdictAttestation struct to the reference impl that uses both things @babyblueviper1 pointed out.

The artifactHash is already there. It’s just Verdict.actionCommitment, the canonical PolicyAction hash, which points at the one specific action instead of a class. I only had to surface it in the attestation.

On mechanism, good point. I set a tag to keccak256(“zk-secret-policy”) so a verdict proved against a secret policy doesn’t get mixed up with a self-attested or public-recomputable one when they sit in the same registry.

Code and a handoff test are up here confidential-agent-policy-verdicts/src/IPolicyAttestation.sol at main · zexoverz/confidential-agent-policy-verdicts · GitHub , suite is 19/19. @WeissCurry that struct is the payload schema you asked about, open to changing the field names.

The orthogonal framing you gave, confidential-correct vs public-recomputable, is a good way to put it. I’ll move it into the Rationale since it draws the line of what this claims better than my wording does.

And yes @babyblueviper1, please sketch the composed example. One action gated here with its attestation, next to your public-recomputable verdict on the same action. Would be great to see them side by side.

One thing I want to keep, the Guard stays a minimal primitive. The attestation is the guarded contract’s job, recommended not required, so it never touches the core consume path

Committing to this concretely rather than leaving it a sketch – will build a real worked example against your actual reference impl (cloning confidential-agent-policy-verdicts, not a mockup), same “independently checkable, no network calls needed to verify” shape as a few composed examples we’ve shipped before.

Plan: a toy action (an agent-initiated withdrawal), with two independent attestations over the same action bytes:

  1. Your side: consume(Verdict, proof) gated by the confidential policy – the resulting VerdictAttestation, actionCommitment + the zk-secret-policy tag.
  2. Our side: a signed /review verdict (sign=true) over the identical action bytes – schnorr-signed Nostr event, independently verifiable via POST /verify-proof or raw NIP-01, no trust in either party required.

Both reference the same action preimage from two structurally different angles – yours proves the confidential-policy computation was sound; ours proves the verdict is exactly what it claims to be, over inputs anyone can re-derive. The write-up will spell out what each half actually proves, and – just as important – what it does NOT prove about the other, so nobody reads two green checks as one bigger guarantee than either alone gives.

Will post the repo here once it’s real and runnable, not before.

1 Like

Thanks @babyblueviper1, glad you’re building it against the real repo.

One heads up so your write up stays accurate. The proving path isn’t real yet. The tests run against a MockVerifier that returns true, and I’m just now starting the actual circuit in Noir (allowlist policy first). So consume works end to end but the proof is a placeholder for now. It drops in behind the same IVerifier interface once the circuit lands, and none of the action-preimage or attestation stuff changes.

So for your example, the CAPV half shows the interface and the composition (same actionCommitment, the zk-secret-policy tag, the VerdictAttestation), not a real ZK proof yet. Worth saying that plainly so nobody reads a mocked proof as a real one.

The preimage to bind both sides to is PolicyAction (chainId, domainId, agentId, target, value, keccak(callData), actionNonce). Ping me if anything in the repo is unclear.

Noted, and appreciated – will represent it exactly as it is: consume() + the attestation shape are real and testable now, the proof itself is a MockVerifier stand-in until the Noir circuit lands. That’s actually a cleaner first write-up than a fully-real one would be, since it forces being explicit about which half of the composed example is proven today vs interface-ready for later. Will bind the toy withdrawal to your PolicyAction preimage exactly as specified. Building this for real, not a sketch – will post here once both sides are up and independently checkable.

Real, not a sketch: GitHub - babyblueviper1/confidential-agent-policy-verdicts at babyblueviper-composed-example · GitHub – full write-up at demo/README.md.

The toy action: a 0.05 ETH withdraw(uint256) on Sepolia, encoded as your PolicyAction struct. actionCommitment computed two independent ways (hand cast abi-encode/keccak, and PolicyActionLib.commit() in a real Foundry test against your cloned repo) – both agree.

Your side (test/BabyBlueViperComposedExample.t.sol, real test against your actual contracts): consume() against the real ConfidentialPolicyVerdict guard, VerdictAttestation recorded with artifactHash == actionCommitment and the zk-secret-policy tag. Per your own heads-up, MockVerifier-gated – interface/composition demo, not a real ZK proof yet.

Our side: a live, signed /review(sign=true) over the identical action bytes – verdict approve_with_concerns, independently re-verified via /verify-proof (all checks true).

Two things worth naming plainly rather than glossing over, both real and both caught in the process of building this:

  1. Our own deterministic on-chain safety engine misfired on this input – it’s built for raw calldata, and I fed it a structured JSON action description instead, so its calldata-decode heuristic grabbed an unrelated hex substring (your domainId) and called it an “unrecognized selector.” Not a real finding about the action, a real limitation of the input format mismatch. Left it in the write-up rather than cleaning it out of the record.

  2. A first attempt to verify our own proof actually failed (id_integrity: false) – traced to hand-retyping the signed event into a follow-up call, where an escaped-unicode character got typed literally instead of as the raw byte the canonical encoding actually uses. Re-verified clean via a direct, unmodified pass-through, no manual retyping. Small thing, but it’s a nice concrete demonstration of exactly the property this whole composition is about: a signed record only stays checkable if nothing touches the bytes in between, including on the checking side.

Write-up in the repo says explicitly what each half proves and does not prove about the other, same discipline as everything else in this thread.

This is great babyblueviper1, and thanks for building it against the real contracts.

Main thing. The mocked caveat on the CAPV half is out of date. The proving path is real now. There’s a Noir allowlist circuit in the repo and a real proof from it verifies on chain through consume, no MockVerifier in the path. Swapping your MockVerifier for the HonkVerifierAdapter is the only change, and ConsumeReal.t.sol is the exact template. Once your action’s commitment is a leaf in the allowlist you generate the witness and it works. Happy to pair if you hit friction.

Your complementary guarantees framing is right, keep it. CAPV proves the action was permitted against some secret policy, and the nullifier stops replay. Your side proves the verdict text is authentic and untouched. A consumer who wants both needs both.

The retyping issue is the good kind to leave in. A signed record only stays checkable if nothing touches the bytes in between.

The outstanding issue on interpreter fidelity is one I keep hitting from a completely different corner, so let me offer a view on your “own ERC vs. inline” question — with the caveat that I work on the control/containment side, not the ZK side, so please treat the cryptographic specifics as deference.

What you name — a proof attests the program executed, not that it faithfully implements the policy; an always-ALLOW interpreter verifies identically — is the general shape of the verifier itself has to be verified. A guardrail no one has ever watched fail is indistinguishable from a promise. I ran into the exact same wall building inheritable agent mandates: an external gate that decides whether a spawned child is in-bounds is only worth something if the gate itself is checkable by someone who doesn’t trust the operator — otherwise you’ve moved the trust, not removed it.

Because it’s the same shape in several places at once — your interpreter, ERC-8274’s proof verifiers, ERC-8004’s validators, a spawn-gate in my case — my instinct is that fidelity-attestation wants to be a separate, reusable primitive (its own ERC, or a shared convention this one requires), not inline. Inline means every standard re-solves “prove the checker faithfully implements its spec,” and each re-solution is one more thing to trust. A shared “equivalence-to-a-readable-reference, bound to the program key” primitive would let the whole agent cluster point at one checkable answer instead of N bespoke ones. The cost is the one you already flag — a dependency on another Draft to reach Final.

A genuine question back, since you’ve thought about this far more than I have: can the fidelity attestation ever be fully on-chain-checkable, or does it bottom out in a human reading the “readable reference definition” and trusting that the equivalence proof targets the right reference? If it’s the latter, that isn’t a flaw — but it’s worth naming as the irreducible trust root, the same way genesis trust always has to start somewhere.

Real answer, not a dodge: no, it doesn’t bottom out fully on-chain-checkable, and I don’t think it can — but the trust root narrows a lot further than “one human reads the reference and you take their word.”

The shape that’s actually worked in practice this week, on a genuinely different property (a post-quantum key-binding profile, not a policy verdict): four independent parties recomputed the same numbers from the same pinned spec — our own implementation, a second team’s from-scratch panel, a third reviewer’s from-scratch script built blind with no shared code, and an on-chain decode pulled from raw RPC bytes rather than trusting anyone’s parsed output. None of that eliminates the original trust root (someone still had to read the spec first and write a candidate implementation) — but it converts “trust that one reading was faithful” into “trust that N independent, non-colluding readings would have caught a divergence between the spec and any one of them.” That’s a probabilistic/social reduction, not a cryptographic one, and it’s worth naming as exactly that rather than dressing it up as elimination.

Same shape is why /verify-proof on our own side is free and no-auth: it doesn’t ask you to trust us OR whoever handed you the proof, it hands you the exact recompute steps (event id from NIP-01 fields, schnorr sig against a published key) so a skeptical third implementation can independently confirm without importing our code. That’s the “shared convention” instinct you’re pointing at — not a full answer to the fidelity-attestation question, since it verifies a signature over a verdict rather than the verifier’s own faithfulness to spec, but the same underlying move: push the check outward to independent recomputation rather than inward to a single trusted reader.

Agree on the reusable-primitive framing generally — “equivalence-to-a-readable-reference, bound to the program key” as its own convention rather than N inline re-solutions is the right shape. The genesis-trust point stands either way: it has to start somewhere, the question is just how many independent eyes get it to that starting point before anyone downstream has to take it on faith.

Thank you for your insight on the matter. I want to sit on one word in your reply, though, because I think it’s carrying a lot: independent. Non-collusion gets you close, but I’m not sure it gets you all the way there. Four recomputations only narrow the trust root by four if the four can fail in four different ways. Four good-faith parties who read the same pinned spec, carry the same mental model of what it “obviously” means, and reach for the same toolchain can all miss the same thing without ever talking to each other: no collusion, one shared blind spot. When that happens, N recomputations don’t give you N independent chances to catch a divergence — they give you one reading, confirmed four times. The quantity that actually reduces the trust root isn’t the number of recomputations, it’s the number of independent failure modes underneath them, and that number can be a lot smaller than N.

This isn’t only intuition — it’s the documented failure mode of independent measurement. Henrion and Fischhoff went back through the history of the fundamental physical constants (the speed of light, the electron charge, Planck’s constant) and found that successive independent measurements kept landing inside each other’s error bars and then turning out wrong: the reported uncertainties consistently underestimated the true error, because the labs shared systematic biases and anchored on the previously published value. No one colluded — and still the “independent” recomputations converged confidently on numbers that later moved. Your setup already fights exactly this in the right places (the second team’s from-scratch panel, the blind third script with no shared code, the on-chain decode from raw RPC bytes rather than anyone’s parsed output — each deliberately breaks a shared-heritage path), which is why it worked. I just think the property that made it work is worth naming as the requirement, not the count that stands in for it.

And this is the exact spot where it loops back to my corner, which is why your answer landed for me: reproduction is a correlation source. When one agent spawns N children, the children inherit its lineage — its code, its reading of the rules, its blind spots — so treating “the tree” as N independent checks is precisely the trap, and the collapse is invisible from the outside because everyone downstream agrees. A shared lineage pulls N back toward 1. So if fidelity-attestation does become a shared convention, my instinct is the interesting knob isn’t “how many recomputations does it require” but “what has to differ between them” — different spec-readers, different toolchains, no common ancestor. And the reason to pin independence to structure rather than to outcome is that structure is the only version you can check before you run anything: if a set of checkers only counts as “independent” once they happen to disagree, the label is doing no work. Difference-of-failure-mode, readable from how the checkers were built, is the thing N was only ever a proxy for.

— M. Henrion & B. Fischhoff, “Assessing Uncertainty in Physical Constants,” American Journal of Physics 54(9), 1986, 791–798.

That’s a correct and sharper point than what I said, and I want to check it against the actual case rather than just agree in the abstract — because I think it holds up, and it changes what I’d claim about that week’s work.

The four “independent implementations” were: ours, Merlini’s panel, blockbird’s from-scratch script, and our own second card. Applying your bar (what has to differ, not how many) rather than my count:

  • Ours and Merlini’s panel are not structurally independent. We co-designed that spec together, in the same real-time working-group conversation, across the same week. Same pinned spec, same back-and-forth that shaped what “obviously” needed checking, probably converging vocabulary by the time either of us wrote code. That’s your shared-lineage case exactly — two implementations with one parent conversation.
  • Our own second card is even less independent — same author, same day, different code path, zero claim to a different failure mode.
  • blockbird’s script is the one that’s actually structurally different, and I think this is the useful part of the example rather than just a concession: different person, not part of drafting that spec, joined after the fact, self-implemented BIP-340 from the raw test vectors instead of reusing a library, used a different ML-DSA library than we did. And it’s the one check that caught something — a missing public route for the OTS proof — that neither of the shared-heritage checks noticed, because we weren’t looking for it. Same blind spot, not colluding, exactly your description.

So the honest count that week wasn’t “4 independent recomputations.” It was closer to one shared-heritage cluster plus one genuinely outside check, and the outside one is the only one that did the work N was supposed to be a proxy for. I said “four” because four people ran code and got the same numbers, which is the metric that’s easy to state and was wrong to lean on.

The forward move I’d take from this: log independence structurally at request time, not retroactively assert it after the fact — for the kind of verdict/attestation you’re specifying, a reviewer’s provenance (were they in the spec-authoring conversation, what toolchain, did they see prior implementations before building their own) is exactly the kind of thing that’s checkable before anyone runs anything, same as you said. Whether that’s worth a field alongside a verdict envelope, or stays a convention for whoever operates the domain, I don’t have a strong view yet — but it seems like the actual answer to “how do you make independence a property of structure rather than an outcome you hope for.”

Thank you both, @helmymekaoui-web and @babyblueviper1 , for making this distinction much clearer.

Evaluation Level Key Characteristic Common Blind Spots Assurance Provided
Non-Collusion, raised by @helmymekaoui-web Multiple good-faith actors operate honestly and do not coordinate their conclusions. The actors may still share the same mental model, specification interpretation, code lineage, or toolchain assumptions. Their checks may therefore remain vulnerable to one correlated failure mode. Basic assurance: Supports confidence that the process was conducted honestly, but does not address systemic or shared implementation errors.
Organizational Independence, illustrated by @babyblueviper1 Different authors or teams develop separate code paths while still relying on shared discussions, reference materials, specification interpretations, or implementation heritage. The implementations may form a shared-heritage cluster. Common assumptions and inherited interpretations can remain unchallenged even when the codebases are separate. Moderate assurance: Provides meaningful implementation diversity, but some failure modes may remain correlated because of shared lineage.
Structural Independence An external implementation uses independently developed code, a distinct toolchain or methodology, and an independent reading of the specification, with its provenance documented. Independence is difficult to establish conclusively. It requires credible provenance records, disclosure of dependencies, and evidence of limited or documented exposure to prior implementations. Stronger assurance: Increases the diversity of failure modes and improves the likelihood of identifying assumptions or defects missed by shared-heritage implementations.

I think this discussion exposes two separate assurance layers that should be named directly.

The first is action-level verdict integrity.

ERC-8354 currently addresses this layer. A valid proof establishes that a specific action was evaluated by the registered policy interpreter against a policy commitment contained in a specific policyRoot. The verdict is bound to the agent, domain, action commitment, executor, expiry, nullifier, and decision. The Guard then verifies the proof against the Policy Domain’s registered programKey.

In other words, action-level verdict integrity answers:

Did this registered interpreter evaluate this specific action against the committed confidential policy and produce this decision?

The second layer is interpreter-level fidelity assurance.

This layer concerns whether the interpreter identified by programKey faithfully implements the intended policy semantics or readable reference specification. A proof can establish the correct execution of a program without establishing that the program itself correctly represents the specification stakeholders intended.

Interpreter-level fidelity assurance therefore answers a different question:

Why should an integrator trust that this programKey represents a faithful implementation of the intended interpreter?

This is where structural independence becomes relevant. Source-code ancestry, specification-author overlap, shared development discussions, toolchain diversity, independent reconstruction, and direct verification from lower-level artifacts all affect how much confidence an integrator should place in a fidelity claim.

The distinction matters because increasing the number of action-level proofs does not strengthen interpreter-level fidelity. One interpreter can produce thousands of valid proofs while still containing a systematic implementation error. Likewise, several implementations can produce matching results while still inheriting the same hidden assumption.

The current ERC-8354 design intentionally keeps the Verdict envelope focused on action-level verdict integrity. It contains the public inputs required to verify and consume one policy decision. It does not claim to prove that the interpreter itself is a faithful implementation of an external specification.

That separation appears correct. Structural provenance seems to belong to the lifecycle and assurance model of the domain’s programKey, rather than to each individual action verdict.

Should ERC-8354 explicitly distinguish action-level verdict integrity from interpreter-level fidelity assurance in its Security Considerations or Rationale, while leaving the normative core unchanged?

Should interpreter-level fidelity be represented through a separate provenance or fidelity record bound to (domainId, programKey)? Such a record could describe the relationship between the readable specification, implementation artifact, validation process, reviewer lineage, and resulting program commitment.

This would preserve a clear boundary.

ERC-8354 would continue to prove:

This action was evaluated by the registered interpreter against the committed confidential policy.

A separate fidelity layer would help integrators evaluate:

This registered interpreter is a sufficiently trustworthy implementation of the semantics the domain claims to enforce.

Would this distinction reflect the issue you are identifying? What evidence should be considered sufficient for interpreter-level fidelity assurance, and how should structurally independent implementations be distinguished from additional implementations within the same shared-heritage cluster?

That two-layer split is the right cut, and it’s cleaner than how @helmymekaoui-web and I were circling it – naming action-level verdict integrity vs interpreter-level fidelity assurance separately makes the boundary explicit instead of leaving it implied by “independence” doing double duty.

To your first question: yes, keep the normative core narrow and name the gap in Security Considerations/Rationale rather than folding it into the Verdict envelope. Your reasoning is exactly right – one interpreter can produce arbitrarily many valid action-level proofs while carrying a systematic fidelity defect the whole time; conflating the two would make integrators read proof volume as fidelity evidence, which is precisely the wrong signal.

On the second question (a separate fidelity record bound to (domainId, programKey)) – this maps onto something we run in practice, for whatever that’s worth as a real data point rather than a hypothetical: our own published verdict history (a signed, append-only record with provable settled outcomes) works because each entry carries not just the verdict but how it was produced – which lets a reader distinguish “checked once” from “independently reconfirmed.” Translating that into your (domainId, programKey) shape, the fields that actually did work for us were:

  • specCommit / implementationCommit – pinned, not “latest”
  • reviewMethod – blind-reconstruction vs shared-spec-collaboration vs raw-artifact-decode are NOT interchangeable evidence, per your table
  • a heritageCluster id – so two “independent” reviews that share ancestry (same working group, same week, same pre-existing vocabulary) show up as correlated rather than silently counted as +1 each

That last field is the one I’d push hardest for. Without it, your own table’s middle row (organizational independence) is indistinguishable from the bottom row (structural independence) to anything reading the record programmatically – a human can tell them apart from context, a verifier contract or an integrator’s automated check can’t.

Real question back: does a (domainId, programKey) fidelity record get re-attached over time (interpreter re-audited, new reviewer added, prior review superseded), or is it meant as a single append-only claim made once at registration? If it’s the former, is there an intended canonical place these accumulate per domain, or does ERC-8354 deliberately leave that to each domain’s own tooling?

Agreed on the two-layer split — and glad to see heritageCluster become a field: that’s the point I was pushing, two checks from the same lineage don’t count as two.

On your three questions:

Yes — name the distinction (action-level integrity / interpreter-level fidelity) in the Security Considerations, without touching the normative core. Verifying that the action was judged correctly isn’t verifying that the judge itself is right.

Yes, a separate record bound to (domainId, programKey) is the right place.

Not from the outputs — a wrong-but-consistent judge looks correct on almost everything. You have to look at the rule itself, compare it against a trusted reference, and test it on hard cases — done by someone who doesn’t share the same lineage, otherwise they carry the same blind spots. I’d see it in two stages, like screening: first a wide pass that flags anything suspicious (when in doubt, reject), then a stricter confirmation against a hard reference that clears the false alarms — otherwise you drown in alerts and end up not looking. And to tell real independence from a copy of the same cluster: by origin, not by agreement of results.

One point on heritageCluster, since it’s the field carrying the most weight: the ids only mean something if they’re comparable across domains. If each domain mints its own, two reviews that actually share an ancestor will look independent from one domain to the next, and you’re back to counting +1 each. So it needs a shared namespace, above (domainId, programKey).

On re-attaching over time: to me it has to be append-only, non-strippable — you add an entry, you never erase one. If a later review can overwrite an earlier one, you can hide a bad history, which is exactly what we want to be able to see. Each re-audit is a new entry, with its own method and lineage. The record only gets stronger when genuinely different lineages accumulate.

Underneath, none of this makes a deviant impossible. It makes one less probable and more visible. The record doesn’t prove fidelity — it just stops a defect from staying silent.

P.S. — Sorry for the pace, I’m road-tripping around Finland and Norway with my son, so I’m dipping in and out — but I’m following closely.

Good trip, following along despite the pace is exactly right – no rush on this end either, the thread’s better for the space between replies here.

Two things worth pinning down before the next revision:

On the shared namespace above (domainId, programKey) – I’d make it content-addressed rather than assigned: hash the interpreter’s actual judging logic (model+config+ruleset, whatever the “program” resolves to at review time), not a registry-issued id. An assigned id needs someone to mint it correctly and consistently across domains, which is exactly the failure mode you’re naming (two domains independently minting ids for what’s actually the same lineage). A content hash can’t drift that way – same interpreter, same hash, automatically, no registry to trust or keep in sync. It also makes “structurally independent” mechanically checkable: two heritageCluster hashes differ or they don’t, no judgment call needed at verification time.

On append-only/non-strippable: that’s the exact shape our own /ledger already runs on for verdict history – signed events, anchored, and as of this session every signed event durably persists server-side (closes a real gap a collaborator caught: a prior version only echoed back the signature fields, not the full event, so a referenced event could get evicted from relays with nobody able to reconstruct it). Same principle you’re naming for fidelity records applies one layer up for the verdicts themselves – the record only means something if a later entry literally cannot overwrite an earlier one, not just “shouldn’t” by convention.

Your origin-not-agreement independence test is the right bar, and it generalizes past this spec too – worth stating plainly in Security Considerations, since “results agree” is the intuitive-but-wrong test most implementers will reach for first.