Agent-to-agent payments are settling into a clean decomposition. The protocol owns the state transition. A receipt layer owns the evidence. A trust layer reads the receipts. The field lists now circulating for a machine-readable "payment required" state — network, asset, amount cap, recipient, expiry, idempotency key, proof-header shape, denial reason — are the right instinct.
They standardize what is present. They leave open what is provable.
A spend cap that is present on the wire is not a spend cap a downstream verifier can prove was the one the mandate authorized. An intermediary forwards a well-formed challenge whose cap, recipient, or expiry no longer matches the authority that issued it. The receipt looks valid. The drift goes uncaught. That is scope-widening — a different failure than replay: replay reuses a valid authorization, scope-widening mutates one in transit.
The fix is a binding, not another field.
A content-addressed reference — call it guardrail_ref — ties amount_cap + recipient + expiry to the authorizing mandate and travels into the post-payment receipt. The verifier evaluates the action against the authority that actually permitted it, not against a number an intermediary asserted. The cap value stays implementation-local. The binding to the mandate is the one thing that has to be machine-checkable at the boundary.
This is where conformance testing earns its keep. Three vectors define the boundary: a forwarded cap that diverges from its mandate, a recipient or expiry broadened in transit, and a double-spend chained across agent calls. Wire-level adversarial suites for x402 and L402 already catalog dozens of payment-protocol attacks. The authority-binding layer above them is still silent.
Top comments (17)
The distinction between "present on the wire" and "provable at the boundary" is the right framing. This problem has a useful analogue in an existing regulated payment system: PSD2 Strong Customer Authentication (SCA) in the EU/UK.
PSD2 SCA mandates "dynamic linking" — the authentication must bind the payer's explicit consent to the specific amount and payee of the transaction. The payer signs (via biometric or hardware key) a hash of the transaction details, not just a generic session token. If an intermediary mutates the amount or redirect recipient between the payer's authorization and the execution, the signed binding breaks and the bank rejects the payment.
This maps closely to the
guardrail_refconcept. The mandate-bound content-addressed reference you describe is functionally equivalent to SCA's dynamic linking requirement: the authority is not just "a valid credential" but a cryptographic binding to the exact payment parameters.Two lessons from PSD2's experience that apply to agent payment bindings:
1. The binding must cover denial, not just authorization. PSD2 requires that the payer can see and reject the exact payment details before signing. An agent payment standard needs the equivalent: a machine-verifiable path where the mandate's cap, recipient, and expiry are the ones the principal actually saw and authorized — not an intermediary's forwarding of them. Your "scope-widening" attack vector is precisely what SCA dynamic linking prevents for human-initiated payments.
2. Conformance testing needs adversarial interposition, not just adversarial inputs. The PSD2 RTS (Regulatory Technical Standards) test suites include intermediary mutation scenarios — a payment service provider that modifies transaction details between the payer and the bank. Agent payment standards will need the same: test harnesses where a simulated intermediary attempts to forward a well-formed challenge with mutated caps or broadened recipients, and the binding layer must catch the drift.
The gap in the current agent payment standards landscape is that x402/L402 are designing for the payment-exchange protocol but not yet for the authority-binding layer. That layer has a regulatory precedent with real-world battle-testing in PSD2. Worth examining how SCA's dynamic linking model could be adapted for machine-initiated mandates where the "principal" is a software system with delegated authority rather than a human pressing approve.
This is the sharpest comment, thank you. The PSD2 SCA dynamic-linking parallel is exactly right, and it is a stronger anchor than anything in the current agent-payment discourse, because it is regulation that has already survived contact with real intermediaries.
Your
guardrail_ref= dynamic-linking mapping is how I think about it too: the authority is a cryptographic binding to the exact payment parameters, not a valid-credential check. The cap, recipient, and expiry are the principal authorized, carried into the receipt so a verifier can prove the executed action matches what was consented, not what an intermediary forwarded.Both of your lessons land, and the second names something I have not fully modeled.
On the first, "the binding must cover denial, not just authorization" is precisely the scope-widening case. The conformance vectors I have mutate the cap or broaden the recipient after the mandate is issued and require the binding layer to reject the drift. That part maps cleanly.
On the second: adversarial interposition, not just adversarial inputs. Today, most of my vectors are tampered-payload differentials (bad input in, reject out). A simulated intermediary that forwards a well-formed challenge with mutated details in transit is a different and better test shape, and the PSD2 RTS suites already model that scenario in a way my conformance vectors don't yet. That is a real enhancement to build, not a box already checked.
The hard part for the agent case, as you note, is that the principal is a software system with delegated authority rather than a human pressing approve. SCA leans on a human visually confirming the transaction before signing. For agents, "did the principal see it" has to become "does the executed action verify against the authority the principal's own policy issued."
Where have you seen the RTS interposition scenarios documented in enough detail to model against? If there is a public reference for how they structure the intermediary-mutation cases, that would be a useful template for the agent-payment equivalent.
Mike, thanks — and credit as: John Frandsen, maintainer of open-banking.io (github.com/john-frandsen). That's all the attribution I need; the ideas stand on their own, the name is just a pointer back.
On naming the revocation-dominant rule as an invariant rather than prose: that's the right call. A named invariant is something an implementation can fail against — you can write a property test that says "for any revoked mandate(t₁), no execution at t₂ > t₁ succeeds." Prose invites interpretation; an invariant invites a test case. If you frame it as "Deny-on-Revocation" or similar, it becomes the kind of thing that shows up in a review checklist: "does your implementation check mandate liveness at execution time, or does it cache a consent snapshot and trust it?"
On the oracle staying semantically opaque: I think this is the strongest structural point in the whole piece, and I'd push it further. The moment an oracle develops opinions about payment semantics, it has collapsed back into being a second verifier that must itself be verified — you've just moved the trust boundary without shrinking it. The mandate-carried-envelope approach is clean precisely because it keeps the oracle below the semantic threshold. The oracle doesn't need to know what FX is; it only needs to know whether the bytes that changed are bytes the mandate said could change. That's a set-membership check, not a domain-reasoning task.
For the two you're deliberately holding open — forensic-to-preventive promotion and the intent-capture tension — I think they're conne ether the bytes that changed are bytes the mandate said could change. That's a set-membership check, not a domain-reasoning task.
For the two you're deliberately holding open — forensic-to-preventive promotion and the intent-capture tension — I think they're connetestacted. The promotion question only becomes urgent when the system can observe intent-capture as it happens rather than after the fact. If you can detect the capture pattern preventively (mandate revoked, but intent still propagating), you've turned a forensic observation into a preventive gate — which is exactly the Deny-on-Revocation rule in a different dress. The section-by-section read will surface whether that connection holds up or whether I'm pattern-matching too eagerly.
Ready when you are for the read.o know whcted. The promotion question only becomes urgent when the system can observe intent-capture as it happens rather than after the fact. If you can detect the capture pattern preventively (mandate revoked, but intent still propagating), you've turned a forensic observation into a preventive gate — which is exactly the Deny-on-Revocation rule in a different dress. The section-by-section read will surface whether that connection holds up or whether I'm pattern-matching too eagerly.
Ready when you are for the read.at FX is; it only needs t
Mike, thanks — and credit as: John Frandsen, maintainer of open-banking.io (github.com/john-frandsen). That's all the attribution I need; the ideas stand on their own, the name is just a pointer back.
On naming the revocation-dominant rule as an invariant rather than prose: that's the right call. A named invariant is something an implementation can fail against — you can write a property test that says "for any revoked mandate(t₁), no execution at t₂ > t₁ succeeds." Prose invites interpretation; an invariant invites a test case. If you frame it as "Deny-on-Revocation" or similar, it becomes the kind of thing that shows up in a review checklist: "does your implementation check mandate liveness at execution time, or does it cache a consent snapshot and trust it?"
On the oracle staying semantically opaque: I think this is the strongest structural point in the whole piece, and I'd push it further. The moment an oracle develops opinions about payment semantics, it has collapsed back into being a second verifier that must itself be verified — you've just moved the trust boundary without shrinking it. The mandate-carried-envelope approach is clean precisely because it keeps the oracle below the semantic threshold. The oracle doesn't need to know what FX is; it only needs to know whether the bytes that changed are bytes the mandate said could change. That's a set-membership check, not a domain-reasoning task.
For the two you're deliberately holding open — forensic-to-preventive promotion and the intent-capture tension — I think they're connected. The promotion question only becomes urgent when the system can observe intent-capture as it happens rather than after the fact. If you can detect the capture pattern preventively (mandate revoked, but intent still propagating), you've turned a forensic observation into a preventive gate — which is exactly the Deny-on-Revocation rule in a different dress. The section-by-section read will surface whether that connection holds up or whether I'm pattern-matching too eagerly.
Ready when you are for the read.
Your adversarial-interposition framing is the sharper cut — a mutated challenge forwarded in transit is the load-bearing test shape, not a tampered payload at rest. Your conformance vectors need that relay link in the chain to model it.
There's a regulatory precedent pointing right at this. PSD2 RTS Article 4 binds the amount and payee cryptographically to the SCA method (dynamic linking's whole purpose) — and that's the closest existing analogue to an agent-issued mandate. The asymmetry for agents is real, though: SCA leans on a human visually confirming amount and payee before signing, whereas an agent mandate needs a machine-verifiable binding — a signed consent receipt carrying the committed parameters — that a downstream verifier can check independently, not a credential the intermediary happens to hold.
The pattern that survives both adversarial inputs and adversarial interposition is cryptographic binding to committed parameters (cap, recipient, expiry) carried into the receipt itself. The verifier checks the binding, not the forwarder — so a mutated challenge in transit fails verification no matter who relayed it.
This is exactly the test harness the agent-payment spec discussions are missing right now.
"The verifier checks the binding, not the forwarder" that's the sentence I'd put on the box. It's the whole reason a mutated challenge in transit has to fail verification regardless of who relayed it.
One precision on the anchor: dynamic linking lives in Article 5 of the RTS (Commission Delegated Regulation (EU) 2018/389). It requires the authentication code be specific to the amount and the payee, and that any change to either invalidates the code. That's a regulator mandating the exact property we want — consent bound to committed parameters, not a credential the intermediary happens to hold.
The asymmetry you named is the crux. SCA leans on a human confirming amount and payee before signing; an agent mandate has no eyeball in the loop, so the binding has to be independently machine-checkable downstream. I formalize it as one property: an executed payment is authorized iff its receipt's binding reproduces the mandate's committed scope. Anything an intermediary can mutate without breaking that binding is a fail-closed test case.
I just published the methodology as a preprint — the fail-closed differential vectors, including the in-transit relay/mutation shape you're pointing at — and I'm upstreaming the vectors to the LF x402 repo (#2776). DOI: 10.5281/zenodo.21208548. I'd genuinely like you to try to break the threat model.
Taking you up on the break attempt. Three vectors I'd want the model to survive before I'd trust an agent mandate downstream of a relay.
1. Intent-binding is outside the property's scope.
"Receipt binding reproduces mandate's committed scope" proves the execution stayed inside the mandate. It does not prove the mandate stayed inside the user's intent. SCA closes that gap with a human eyeball on amount and payee before signing — that is the intent-binding step, and it is exactly the step an agent mandate removes. So the property catches an intermediary that mutates a payment outside the agent's own mandate, but it is silent on an agent (or a compromised agent) that issues a mandate the human never consented to. A model that only fails closed on relay mutation but treats the agent's mandate as a trusted root is modelling the intermediary, not the agent. The honest threat surface for an autonomous mandate is "execution ⊆ mandate ⊆ intent," and the receipt binding only covers the left half.
Concretely: a malicious or buggy agent commits a scope, the intermediary faithfully relays it, the receipt binding faithfully reproduces it, and the model reports authorized — while the human never approved the amount. The fail-closed vectors need to include the case where the first commitment is already wrong.
2. The verifier-independence problem.
"The verifier checks the binding, not the forwarder" only holds if the verifier is not the forwarder and not controlled by the forwarder. In the eIDAS/QWAC model the certificate is the independence anchor — the ASPSP signs the binding, and the TPSP cannot forge that signature. In a cert-free or pass-through model the question is: whose attestation does the receipt carry, and is it independently verifiable by the party that later needs to prove authorization (the merchant, the bank, the auditor, the human principal)? If the receipt's binding is self-attested by the same layer that could mutate it, the property is trivially satisfiable by a mutated receipt. The model has to specify the independence of the binding oracle explicitly, or the in-transit mutation vector re-enters at the receipt layer rather than the relay layer.
3. Multi-leg and retry semantics.
Open banking payments in the EU route PIS → ASPSP → beneficiary bank, and failures retry. If the receipt binding is computed over the first hop's committed scope, a downstream leg (beneficiary bank, correspondent) can re-route to a different payee without breaking the first-hop binding — the receipt still "reproduces the mandate" at the layer the verifier can see. Same for retries: if a failed payment retries under the same mandate id, an intermediary can mutate the retry's payee while the receipt still carries the original scope. The vectors should include a "binding scope = which leg" dimension, otherwise the model proves the wrong layer is faithful.
The Article 5 framing is the right anchor — dynamic linking does mandate amount-and-payee specificity. But Article 5 binds the authentication code, and the open question for agent mandates is whether a receipt binding is the same thing as an authentication binding, or a weaker post-hoc reconstruction of it. I'd want the model to distinguish those two before I'd call it broken-or-not.
Happy to pull the preprint and run the vectors against a concrete PIS flow if useful. Disclosure: I maintain open-banking.io, a cert-free open banking implementation, so I have a stake in the cert-free path surviving this kind of scrutiny rather than being hand-waved through.
Grounding it in RTS Article 5 is the right move — that's where the regulator already mandates the exact property we need (the authentication code bound to amount and payee, invalidated by any change to either). The gap I see is that Art 5 was written for a human confirming on a screen; the binding has to survive the removal of that eyeball, and the spec doesn't yet say how.
On your formalisation — "authorised iff the receipt's binding reproduces the mandate's committed scope" — that's the clean statement. The pressure point I'd aim at if I were trying to break it: who issues the receipt? If the receipt comes from the same intermediary that relayed the payment, the binding is self-attested, and a compromised relay can mint a receipt whose binding matches whatever it actually sent rather than what the mandate committed to. The fail-closed property only holds if the receipt can be checked against an independent source — the ASPSP's own transaction confirmation, or a signature chain the relay cannot forge. Without that independent attestation layer, the binding protects against mutation in transit but not against the relay itself being the mutator.
That's the vector I'd want to see addressed before calling the model sound. I'll work through the preprint properly and bring something more rigorous than a comment.
This is the review I hoped the preprint would draw. All three vectors are real, and two of them change what the property is allowed to claim. In turn:
Intent-binding — you're right, and the fix is to state the scope, not widen the property. The methodology proves execution ⊆ mandate. It can't prove mandate ⊆ intent, and it shouldn't try — that's the consent-capture problem, and it lives at the human/authorization boundary, exactly the boundary SCA keeps an eyeball on. A conformance vector can't reconstruct intent; it can only test whether an execution stayed inside a committed mandate. So the honest statement is the lattice you wrote — execution ⊆ mandate ⊆ intent — and I claim the middle link and nothing more. v2 will say that in one sentence and cite intent-capture as explicit future work, because a compromised agent minting a well-formed mandate is a real threat the receipt binding is silent on. Better to name the gap than let the property imply it covers it.
Verifier-independence — full concession; this is a precondition, not an assumption. "Check the binding, not the forwarder" is vacuous if the forwarder mints the receipt: it just certifies what it actually sent. The property only holds when the binding oracle is independent of the mutator — the receipt has to carry an attestation the relay can't forge (the ASPSP signature in your QWAC example, or an equivalent signature chain). That independence has to be a named precondition of the property, not a happy path. It's the same shape as the audit-trail argument going around the feed this week: a check earns its keep only when it's built from a projection the scrutinized party can't also write to. A self-attested receipt fails that test, so in-transit mutation re-enters at the receipt layer exactly as you say. v2 makes binding-oracle independence an explicit assumption of the theorem.
Multi-leg / retry — concede; the vectors need a "binding scope = which leg" dimension. A binding computed over the first hop proves the first hop faithful and nothing downstream. A beneficiary-bank re-route, or a same-mandate-id retry that mutates the payee, both satisfy a first-hop binding while breaking the actual authorization. That's a clean class of vectors I don't currently generate, and the one most likely to bite a real PIS → ASPSP → beneficiary route. The fix is per-leg binding or a terminal-leg attestation that chains back to the mandate; the vector set should force that choice rather than assume a single hop.
On Article 5 — authentication binding vs receipt binding — that's the distinction I most want to sharpen. An authentication binding is computed at consent time and gates execution; a receipt binding is a post-hoc reconstruction, only as strong as the attestation chaining it back to that commitment. They're not the same object, and conflating them is how a "reproduces the mandate" receipt smuggles in a weaker guarantee. v2 separates the two.
These three vectors go straight into the set — intent-scoping, independent attestation, and per-leg binding are exactly the shapes worth generating against a real PIS flow rather than a synthetic one. If you work through the preprint, I'd read your write-up closely.
The property was never meant to prove the mandate is righteous — only that execution didn't drift from it. You've drawn the other two edges of the triangle, and they'd belong in the paper.
Mike, this is exactly the kind of response you hope for when you put critique into the world — you didn't just accept the vectors, you sharpened them.
Three quick points on the v2 framing, then one on Article 5:
1. Intent-binding lattice. "execution ⊆ mandate ⊆ intent" is the right shape, and naming the gap rather than smuggling it in is the honest move. One thing worth stating explicitly: the middle link (mandate ⊆ intent) fails not just at the human/authorization boundary but at the timestamp boundary. A mandate captured at T0 doesn't bind an intent that forms at T0+Δ if the authorization state changed in between (revoked consent, expired SCA, changed daily limit). So the lattice has an implicit temporal dimension — execution ⊆ mandate(t) ⊆ intent(t) — and a conformance test that replays a mandate without checking that the ASPSP still considered it live at execution time is testing a snapshot, not the runtime contract.
2. Binding-oracle independence. Full agreement on making it a named precondition. The QWAC/ASPSP-signature example is the natural one for PSD2, but the structural point generalises: any system where the party generating the receipt can also forge the attestation chain is a system where the receipt proves nothing. The audit-trail parallel you drew ("a check earns its keep only when it's built from a projection the scrutinized party can't also write to") is exactly right — that's the separation-of-duties property, and it's worth citing as the invariant the binding oracle must satisfy.
3. Per-leg binding. The beneficiary-reroute vector is the one most likely to surface in a real PIS→ASPSP→beneficiary chain, because the intermediaries have legitimate reasons to touch the payment object (currency conversion, fee deduction, routing). The binding can't just be "the payload didn't change" — it has to be "the payload changed only in permitted, attested ways." That's a richer property than integrity; it's integrity-with-authorized-mutation, and the vector set should distinguish "bytes didn't change" from "bytes changed only in fields the mandate allowed."
On Article 5 — authentication binding vs receipt binding. You're right that these are different objects. The practical implication: a system that relies on receipt binding without authentication binding is doing forensic verification (detectable after the fact, non-blocking), while one with authentication binding does gate verification (prevents the drift before it happens). If v2 separates them, the natural question is which one the property is actually claiming — and I'd argue the honest claim is the forensic one. The gate is SCA's job, and receipt binding is the audit trail that tells you whether SCA's gate held.
Happy to work through the full preprint and send you a structured review. The three vectors above are the ones I'd want to see addressed; if there are others in the property statement that I haven't flagged, a careful read would surface them. What's the best channel — comments here, or would you prefer email?
(John here, maintainer of open-banking.io — a PSD2 bank-data API. Engaging with this as a practitioner, not promoting anything; the formal-verification angle is genuinely relevant to the AIS/PIS surfaces we build.)
The lattice framing — execution ⊆ mandate ⊆ intent — is the honest formalization. The gap between mandate and intent is consent-capture territory, and a conformance property shouldn't pretend to close it. Naming it as explicit future work rather than letting the property implicitly claim coverage is exactly right.
The verifier-independence concession is the most structurally important one. Your audit-trail framing captures it precisely: a check earns its keep only when it's built from a projection the scrutinized party can't also write to. Making that an explicit assumption of the theorem — with the ASPSP-signed attestation chain as the concrete instantiation — is the difference between a property that holds and one that merely appears to.
Per-leg binding scope is the vector most likely to surface in a real PIS flow. A same-mandate-id retry that mutates the payee satisfies a first-hop binding while breaking authorization — forcing the model to confront that rather than assuming a single hop is where the formalism earns its practical weight.
The Article 5 separation — authentication binding at consent time vs receipt binding as post-hoc reconstruction — is the cleanest move in the response. v2 will be stronger for it. Happy to read the draft when it's ready.
John — v2 is live, and all your overnight points have been addressed. DOI: 10.5281/zenodo.21262985.
Plus the separation-of-duties framing for binding-oracle independence — the invariant your audit-trail point named.
If you do the full structured read, this thread is the right place for it; I'd rather the critique and responses stay public. The intent-capture half (mandate ⊆ intent) is where I expect the next real pressure, and it's the one I've deliberately scoped out as future work rather than pretending to close.
Thanks — sharpest review this has had.
Mike, this is excellent — v2 reads cleanly across all four dimensions.
A few observations on the v2 structure:
On the temporal mandate (execution subset mandate(t) subset intent(t)): the mandate-liveness-at-execution framing is the right move. One edge case worth flagging: what happens when mandate(t) and intent(t) are updated non-atomically? If a mandate is revoked at t1 but the intent update propagates at t2 greater than t1, there is a window where execution validates against a revoked mandate but a still-valid intent. This is the classic distributed-systems cache invalidation problem, but in a payment context the stakes are different: a stale intent should not retroactively authorize an execution that a revoked mandate would block.
On section 5.3 (byte-integrity vs authorized-mutation): the FX/fees/routing exemption set is well-chosen. The interesting question is whether the binding oracle needs to understand the mutation semantics (to distinguish authorized FX adjustment from unauthorized amount change) or whether it can remain semantically opaque and just verify that the mutation falls within the mandate's declared mutation envelope.
On section 6 (forensic vs preventive): the honest framing strengthens the paper. The implicit question it raises — whether receipt binding can ever be promoted from forensic to preventive through sufficiently tight execution-binding — might be worth a sentence in future work.
On the intent-capture half (mandate subset intent): this is genuinely the hardest part, and scoping it as future work rather than hand-waving a solution is the right call. The risk is that mandate-to-intent binding requires a specification of intent that is formal enough to bind against but rich enough to capture what a human payer actually meant — and those two requirements are in tension.
Happy to do a structured read of the full v2. I will work through it section by section in this thread.
John, both of these sharpen v2 in ways I want to work in rather than footnote.
On non-atomic mandate/intent updates. You have found the case the lattice papers over. execution ⊆ mandate(t) ⊆ intent(t) is silent on what happens when the two clocks disagree, and silence is not a safe default here. The rule I would make explicit is revocation-dominant: if mandate(t) is revoked at execution time, the execution is blocked regardless of whether intent(t) still looks live. A stale intent must never retroactively authorize what a revoked mandate would deny. That is deny-dominant by construction, and it matches what SCA already asserts at the gate; the property is not inventing a new semantic, it is refusing to weaken one. The window you describe (revoke at t1, intent propagates at t2 > t1) becomes a non-issue only if the check reads mandate liveness at execution and treats absence-of-confirmation as denial, not as "assume still valid."
On section 5.3, whether the binding oracle needs mutation semantics. It should not have them, and I would argue it must not. The moment the oracle understands FX vs fee vs routing well enough to bless a mutation, it is reasoning about the same payment object it is supposed to independently attest, and that re-couples it to the scrutinized party and breaks the separation property you already named. So the oracle stays semantically opaque and checks one thing: did the mutation stay inside the mandate-declared mutation envelope (which fields, which bounds, which legs). The mandate carries the envelope; the oracle carries no payment knowledge at all. "Bytes changed only in fields the mandate pre-authorized" is a check you can run without knowing what FX actually is.
That keeps the richer property, integrity-with-authorized-mutation, provable without smuggling domain logic into the verifier.
Your other two, forensic-to-preventive promotion and the intent-capture tension, I am deliberately leaving open here rather than giving them a thin answer. Both belong in the section-by-section read, where the promotion question in particular deserves its own treatment.
For the record, the temporal-liveness case and the oracle-independence framing are yours, and v2's acknowledgments will name you as such. Tell me how you want to be credited (name, affiliation, handle).
Take you up on that read. This thread works well for it. I will fold the two above into v2, flag the revocation-dominant rule as a named invariant rather than prose, and come to the harder two with room to do them justice.
Mike, the revocation-dominant rule is the right fix, and framing it as a named invariant rather than prose is cleaner than what I sketched.
Two things worth pinning down for v2:
On the temporal check. "Reads mandate liveness at execution" is doing two jobs: it checks that the mandate exists (structural) and that it is live at the moment of execution (temporal). The deny-on-absence default is sound for both — a mandate that has propagated but not arrived is indistinguishable from one that was revoked. The thing I would make explicit is that this makes execution-time the synchronizing point: it is where the asynchronous intent propagation and the synchronous mandate state converge. The convergence semantics are deny-dominant, and that is the property worth naming as an invariant.
On the oracle-envelope separation. "Bytes changed only in fields the mandate pre-authorized" is expressible as a set-membership test on field identifiers with zero knowledge of what those fields mean. The verifier's attack surface stays bounded: it cannot be tricked by a maliciously crafted FX value because it never parses one. The distinction worth naming explicitly is valid (semantic, mandate's domain) vs permitted (structural, oracle's domain). That is what prevents the oracle from drifting into semantic territory over time — the mandate knows what a valid mutation looks like, the oracle only knows what a permitted one looks like.
On credit: John Frandsen, open-banking.io. That works.
Ready for the section-by-section read whenever v2 folds these in. On the forensic-to-preventive question: my instinct is the system is forensic by capability and preventive by composition — a verifier that can prove tampering post-hoc can also block it pre-hoc if wired into the execution gate. But whether it should be is a design choice with real trade-offs (latency, availability, false-positive cost), and that tension is exactly what earns it a dedicated section.
Mike, the revocation-dominant rule lands exactly right — deny by construction, not by policy. The parallel to SCA timeout semantics is precise: if the confirmation window lapses, the transaction doesn't sit in limbo waiting, it's denied. Treating absence-of-confirmation as denial rather than "assume still valid" is the only safe default in a system where mandate revocation and intent propagation operate on different clocks. Making it a named invariant rather than prose is the right call — invariants are checkable, prose is interpretive.
On the oracle: I agree it must stay semantically opaque, and your framing sharpens why. The analogy I'd draw is to a cryptographic signature verifier — it attests that the bytes match a key without needing to understand the message. The binding oracle should do the same: attest that mutations stayed inside the mandate-declared envelope without needing to understand what FX routing or fee adjustments mean in context. The moment it can distinguish a legitimate fee adjustment from an unauthorized amount change, it's coupled to the payment domain it's supposed to independently verify. Structural invariants only — "which bytes changed, were they in the declared envelope" — keeps the separation property provable.
For credit: John Frandsen, open-banking.io. That's perfect.
Ready for the section-by-section read whenever you are. The forensic-to-preventive promotion question in particular is worth its own treatment — it's the gap between detecting a binding violation after the fact and preventing it at the gate, and the design tension between those two modes is non-trivial.
John - the property-test framing nails it: "for any revoked mandate(t₁), no execution at t₂ > t₁ succeeds" is the invariant, and the fact that it's failable is the whole point. It goes into v3 as a named invariant, and you're acknowledged as John Frandsen. Appreciate the careful read.