Hold is not a third decision
I argued a confidential policy verdict was missing a third state. One of the authors corrected me, and the correction is a better result than the claim: enforcement is one bit, and everything interesting lives in the reason code.
Two weeks ago I made an argument on the ERC-8354 thread and one of the authors told me I had put the state in the wrong place. He was right. The correction is more useful than the claim was, so this is the writeup of being wrong in a specific way.
ERC-8354 proves, in zero knowledge, that an allow or deny verdict was computed faithfully against a policy that is never disclosed on chain. It is a good design and a narrow one: the relying party learns that the verdict follows from a committed policy, without learning the policy.
My objection was about the verdict set. An agent signer routinely hits actions that are neither a clean allow nor a clean deny, and the canonical case is an ERC-20 approval for a new spender. No value moves now. The permission granted may be unlimited. Refusing it outright makes the agent useless for most of what people want it to do; allowing it silently is how you find out later that the approval was the whole exploit. That action wants a third answer: held, pending a human-issued release bound to the exact thing being approved.
The correction
The answer from the thread was that the four states already exist, and that they do not sit on decision:
decisionis one bit because the Guard gates execution on it, and a hold does not authorize execution, so it isdecision == 0. The separable part lives onpolicyKind, which today carries 1 DENIED, 2 NOT_PERMITTED and 3 COULD_NOT_EVALUATE alongside 0 ALLOWED. A hold is a fifth kind on that table, not a third decision.
This is correct, and the reason it is correct generalizes past this ERC.
The type of a field follows its consumer. The consumer of decision is a guard contract, and a guard is a gate. A gate has two positions. Widening decision to three values forces every relying party to interpret a value which, for the only decision that party actually makes, is identical to deny. A held action does not execute. Neither does a denied one. At the enforcement point they are the same, and a field read by the enforcement point should say so.
The distinction between them is entirely real. It is just not a distinction the gate makes. It is a distinction the human workflow makes, downstream, and modelling it in the gate’s control bit puts it in the one place that cannot use it.
This is a recurring error and I walked straight into it: conflating how many outcomes a system has with how many branches its enforcement point takes. Enforcement is binary in most places worth guarding. Richness belongs in the reason code that travels alongside the bit, not in the bit.
What survived
The part of my argument that held up was not about the verdict set at all. It was about what happens to the refused attempt:
Without that anchor your probing attack works, and it works against any design where a rejected attempt leaves nothing behind.
The attack is the one I keep coming back to. If a refused action leaves no durable record, the policy is an oracle. The agent proposes, reads the answer, adjusts, and proposes again. It does not need to see a single rule. Enough allow and deny answers over inputs it chooses will map the boundary of the policy from the outside, and a planning loop will do this by accident while simply trying to get its task done.
Zero knowledge protects the content of the policy. It does not protect the shape of it. A hidden policy queried without limit and without memory discloses itself anyway, just more slowly and to whoever is patient. Confidentiality plus amnesia is delayed disclosure, not prevention.
Which means the fifth policyKind is necessary and not sufficient. A kind that exists inside the envelope and gets flattened into a generic denial by the time a relying party reads it buys nothing at all. The requirement is that refusals get anchored somewhere durable, and that the anchor carries policyKind through to the read surface rather than collapsing it. Rate-limiting the probe, spotting the third variant of one refused action inside ten minutes, auditing what a gate stopped rather than only what it passed: all of it needs the reason to survive, not just the bit.
The release object is the underspecified half
A hold is only worth having if something can lift it, and that something is the most dangerous object in the design, because it is by construction an authorized exception to the policy.
Four properties, and the ordering matters:
Bound to the exact action. Not to the spender, not to the token, not to a class of transaction: to the specific action commitment that was held. Anything looser is a standing bypass. A release scoped to a spender is a re-approval of exactly the thing the policy stopped, issued in a form nobody will audit again.
Single use. Spent once, and the second presentation fails closed. This is the same discipline ERC-8380 applies to execution credentials, and it applies here for the same reason.
Expiring. A release issued against a market state, a gas price or a counterparty reputation that held an hour ago should not be redeemable next week.
Receipted. Who released it, against which held action, and when. The release is the point where a human takes responsibility, and it is the record most likely to matter after an incident. It should not be the only step in the chain with no receipt, which was the argument of an earlier piece and is the reason I keep pushing on this one.
Where this leaves the spec
8354 is merged. This is not a case for reopening it, and I would not argue for that. A fifth kind on policyKind is additive and cheap, and the natural home is a companion that anchors refusals or a follow-on that specifies the release object properly.
The thing I would most like a follow-on to get right is the release, precisely because it is the part that looks like an implementation detail and is not. Verdict schemes attract careful specification because they are the interesting cryptography. The release is boring plumbing that hands a human the ability to overrule the policy, and boring plumbing is where the exploitable looseness usually lives.
The honest limit
I run this shape locally rather than at scale: a gate in front of an agent signer that writes allow, hold and block to one hash-chained log (pi-crypto-gate). That is enough to know the three-state workflow is operationally necessary. It is not evidence about how a fifth policyKind behaves across independent implementations, and I would not claim otherwise.
What I am more confident about after the exchange than before it: the interesting design pressure in agent policy is not on the verdict. It is on everything the verdict leaves behind. The bit is easy. The reason code, the anchor, and the release are where an agent that holds a signer is actually made safe or not, and they are the parts still least specified.
I was wrong about where hold goes. I would rather be corrected on a public thread than be right in a private one.