Integration
Integration model
The interfaces an integrator works with, where the check belongs in an execution path, and how decisions are surfaced.
An integration adds one thing to a system that already works: a point at which a proposed execution is evaluated before it becomes irreversible. Everything else — credential issuance, mandate assembly, adapter coverage — sits behind that point and is invisible to the code path that moves capital. The integration question is therefore narrow and answerable. Where does the check go, what does it return, and what happens to the answer.
The single decision point
An execution path should contain exactly one authoritative decision point. Not zero, and not several.
AGENT / CALLER
|
v
DECISION POINT <- one per execution path
|
+--> preflight --> DECISION (outcome, reason)
|
v
EXECUTE | REFUSE + REASONThe reason for insisting on one is accounting rather than tidiness. A mandate carries stateful limits — in the running example, a $10,000 daily exposure limit alongside a $2,500 maximum transaction. Limits of that kind must be debited once per execution. If two checks in the same path each record consumption, a single $2,500 trade consumes $5,000 of daily capacity and the actor is throttled by an artefact of the wiring. If neither records consumption because each assumes the other did, the limit does not bind at all. One point decides, and that same point accounts.
Where to place it
Three placements are architecturally sound. They differ in how close they sit to the moment capital actually moves, and in how much of the surrounding system they require you to change.
Inside the executing contract. The check runs in the same transaction as the state change it governs. Nothing can be reordered between the decision and its effect, and the transaction's atomicity binds them without further machinery. The cost is intrusiveness: the contract has to call the check, which means you must own or be able to upgrade it. This placement suits venues and execution contracts built under your own control.
In a guard or module in front of an account. A module on a smart account inspects
outgoing calls before they are dispatched. Destination protocols need no modification,
and coverage extends automatically to calls the account has not made yet. The
difficulty is that at this layer you observe calldata rather than meaning, which is
precisely the problem an adapter exists to solve. A guard that
cannot decode a call has not established what the call does, and must refuse it with
INVALID_EXECUTION_CONTEXT rather than pass it through.
At an agent's execution boundary. The check runs offchain, where an autonomous actor turns a plan into a signed instruction. It is the earliest and most informative placement: refusals cost no gas, arrive before signing, and can be explained to an operator or fed back to the agent in time to change the plan. It is also the weakest as an enforcement boundary, because it constrains the agent rather than the key. An actor whose key can sign elsewhere is not bound by a check it can route around.
| Placement | Proximity to irreversibility | Intrusiveness | Constrains |
|---|---|---|---|
| Executing contract | Same transaction as the effect | Requires owning the contract | The state change itself |
| Account guard or module | Same transaction, before dispatch | Account configuration only | Every call the account makes |
| Agent execution boundary | Before signing | None | The agent's own behaviour |
The placements are not exclusive, and the arrangement the architecture anticipates combines them: offchain preflight for the operator experience and for the agent's own planning, plus an onchain check for enforcement. Both evaluate the same policy and return the same Reason Code. Where they disagree, the enforcing layer is authoritative — the offchain answer described a moment that has since passed.
The onchain interface
The evaluation contract exposes a check over a normalized Execution Intent. The intent is what an adapter produces; it is not raw calldata.
interface IPassPolicy {
struct ExecutionIntent {
address actor;
bytes32 subject;
bytes32 action;
address adapter;
address asset;
uint256 value;
bytes32 contextHash;
}
/// Evaluates an intent against the mandate bound to actor.
/// Pure evaluation: records no consumption.
function check(ExecutionIntent calldata intent)
external
view
returns (bool allowed, bytes32 reason);
/// Evaluates and records consumption atomically.
/// Derives the execution identifier from the intent and the nonce.
/// Reverts with the reason code on a blocked intent.
function checkAndConsume(
ExecutionIntent calldata intent,
uint256 nonce
) external returns (bytes32 executionId, bytes32 reason);
}Three properties of this shape are deliberate. A reason is returned on every path,
including the permitted one, where it is AUTHORIZED, so a log of approvals speaks
the same vocabulary as a log of refusals and there is no silent success. Evaluation
and consumption are separated: check is a view and can be called from anywhere,
including by a caller with no intention of executing, and it reports what the answer
is now while saying nothing about whether it will still hold. Only checkAndConsume
debits the daily counter, and only the enforcing layer should call it. Reason Codes
cross the ABI boundary as fixed byte values rather than as enum indices, so adding a
code to the registry never renumbers an existing one, and an integration compiled
against an older copy of the registry misreads none of the codes it already knows.
What the intent carries
| Field | Meaning |
|---|---|
actor | The account proposing execution, bound by a mandate |
subject | The party on whose behalf the actor acts |
action | The normalized operation, such as BUY, SELL or BRIDGE |
adapter | The adapter that decoded the call, which establishes the venue |
asset | The asset the action concerns |
value | The exposure the action creates, in the mandate's accounting unit |
contextHash | A commitment to the execution conditions the adapter observed |
The subject field resolves to the passport holding that party's
credentials, which is how a policy condition on STOCK_TOKEN_ELIGIBLE reaches an
attestation the actor does not hold itself. In the running example the actor is
0x81...29F, the subject is 0x3A...F02, and that subject resolves to passport
0x7F...94A, which carries IDENTITY_VERIFIED, NON_US_PERSON, EEA_ELIGIBLE,
AML_CHECKED and STOCK_TOKEN_ELIGIBLE.
The contextHash field commits to the surrounding conditions the adapter observed:
chain, venue parameters, any deadline. The evaluating contract cannot re-derive those
conditions from the other fields, so the adapter carries them forward as a commitment.
An intent whose context the enforcing layer cannot reproduce is refused with
INVALID_EXECUTION_CONTEXT, which is the mechanism by which an undecodable or
unrecognized call becomes an explicit refusal rather than an unexamined pass.
The offchain shape
Offchain preflight returns the same information in a form that a calling system and a human operator can both read.
type ReasonCode =
| 'AUTHORIZED'
| 'MISSING_CREDENTIAL'
| 'CREDENTIAL_EXPIRED'
| 'CREDENTIAL_REVOKED'
| 'ASSET_NOT_ALLOWED'
| 'ACTION_NOT_ALLOWED'
| 'ADAPTER_NOT_ALLOWED'
| 'TX_LIMIT_EXCEEDED'
| 'DAILY_LIMIT_EXCEEDED'
| 'MANDATE_EXPIRED'
| 'MANDATE_SUSPENDED'
| 'ACTOR_NOT_AUTHORIZED'
| 'INVALID_EXECUTION_CONTEXT';
interface ExecutionIntent {
actor: string;
subject: string;
action: string;
adapter: string;
asset: string;
value: string;
contextHash: string;
}
interface Decision {
outcome: 'AUTHORIZED' | 'BLOCKED';
reason: ReasonCode;
actor: string;
subject: string;
action: string;
asset: string;
value: string;
limits: {
transaction: string;
dailyRemaining: string;
};
evaluatedAt: string;
}
interface Preflight {
evaluate(intent: ExecutionIntent): Promise<Decision>;
}Onchain the decision is returned in reduced form: allowed is true exactly when
outcome is AUTHORIZED. That boolean is redundant with a comparison against the code
and is kept anyway, because control flow branches on the flag while operators, logs and
support tooling read the code. Monetary values cross the boundary as decimal strings
rather than as numbers, because a limit compared in binary floating point is a limit
that occasionally fails to hold at exactly the value where it matters most.
A rendered decision for the running example, where the actor proposes a $4,000 AAPL purchase against a $2,500 maximum transaction, carries enough context to explain itself.
{
"outcome": "BLOCKED",
"reason": "TX_LIMIT_EXCEEDED",
"actor": "0x81...29F",
"subject": "0x3A...F02",
"action": "BUY",
"asset": "AAPL",
"value": "4000.00",
"limits": {
"transaction": "2500.00",
"dailyRemaining": "10000.00"
},
"evaluatedAt": "2026-04-11T09:12:44Z"
}One proposed execution can fail several constraints at once. The order of evaluation
is therefore fixed, so that the same intent produces the same code in every
implementation and on both sides of the boundary: execution context first, because an
intent that cannot be constructed cannot be judged; then actor authority; then mandate
state; then credentials; then permission over action, asset and adapter; then limits.
The first failing check produces the decision. Ordering this way also means a refusal
names the most fundamental obstacle rather than an incidental one. An actor bound to
no mandate is told ACTOR_NOT_AUTHORIZED, not TX_LIMIT_EXCEEDED.
Surfacing the answer
To a human operator, a refusal is useful in proportion to how specifically it names the constraint. "Blocked" is not an answer. Show the code, the limit, the observed value, and the part interfaces often omit: who can change it. In the case above the actor cannot widen its own transaction ceiling; the party who issued the mandate can. Naming that party converts a dead end into a request.
To a calling system, return the code as a stable machine value rather than a rendered
message. Retry behaviour is a property of the reason. A caller that receives
DAILY_LIMIT_EXCEEDED may sensibly try again once the window resets, while a caller
that receives ASSET_NOT_ALLOWED should not retry at all, because nothing about the
passage of time will change the answer. Collapsing every refusal into one generic
error discards the only information that would let the caller behave correctly, and
tends to produce agents that retry blocked instructions indefinitely.
Responding to each family of refusal
| Family | Codes | Party that can change the answer |
|---|---|---|
| Credential | MISSING_CREDENTIAL, CREDENTIAL_EXPIRED, CREDENTIAL_REVOKED | The issuer, by issuance or renewal against the passport |
| Permission | ACTION_NOT_ALLOWED, ASSET_NOT_ALLOWED, ADAPTER_NOT_ALLOWED | The mandate issuer, by deliberate amendment |
| Limit | TX_LIMIT_EXCEEDED, DAILY_LIMIT_EXCEEDED | The actor, by resizing or by waiting for the window; the issuer, by amendment |
| Mandate state | MANDATE_EXPIRED, MANDATE_SUSPENDED | The mandate controller, by reissue or by lifting the suspension |
| Authority | ACTOR_NOT_AUTHORIZED | The mandate issuer, by binding the actor where that is intended |
| Context | INVALID_EXECUTION_CONTEXT | The integrator, by extending adapter coverage |
That table names who can change the answer. What the calling actor should itself do on receiving each code is a separate question, treated in agents.
Credential failures are the most operationally routine. An expired attestation is an administrative condition and clears when the issuer renews it against the passport. A revoked one is not: revocation is a withdrawal someone performed on purpose, and an integration should treat it as terminal until the issuer acts rather than as a transient fault to be retried around.
Permission failures should be slow. If the actor in the running example proposes a
third asset, ASSET_NOT_ALLOWED is the mandate working as specified; the same mandate
blocks bridging, so a proposed bridge returns ACTION_NOT_ALLOWED however small the
amount and however complete the subject's credentials. The remedy is an amendment by
the issuer, and the deliberation that amendment requires is the point of the
mechanism. Automation across these families must therefore be asymmetric. An agent may
reasonably resize a $4,000 order to $2,500 or less in response to
TX_LIMIT_EXCEEDED, because a smaller order stays inside authority already granted.
An agent that responds to ASSET_NOT_ALLOWED by widening its own mandate has reduced
the permission layer to a formality.
Context failures belong to the integrator rather than to any counterparty. They mean
the decision point met a call it could not describe, and refusing is the correct
default: an unrecognized call is not a safe call, it is an unevaluated one. Persistent
INVALID_EXECUTION_CONTEXT at a given venue is a request for adapter coverage, not a
condition for the actor to work around.
Binding a decision to its execution
A decision describes one proposed execution at one moment. If it is not bound to that execution, two failure modes follow. The first is reuse: a single approval at $2,500, replayed four times, moves $10,000 while the accounting records $2,500. The second is drift, where a check and the execution it authorized are separated in time, and in the interval a credential expires or a mandate is suspended.
INTENT
|
v
executionId = hash(intent fields, nonce)
|
v
DECISION recorded against executionId
|
+-- already consumed? --> refuse, no re-check
|
v
EXECUTE --> debit daily exposure onceThe identifier is derived from the content of the intent — actor, subject, action, adapter, asset and value — together with a nonce, so that two economically distinct executions cannot collide and the same execution cannot be presented twice. It must be derived by the evaluating contract rather than supplied by the caller. An identifier the caller chooses establishes nothing, because a caller free to choose one is free to choose a fresh one for a second presentation of the same intent. That is why the interface above takes a nonce and returns the identifier it computed.
The identifier is consumable once. A second presentation observes it consumed and is refused without re-evaluation, which is the behaviour you want, because re-evaluating would sometimes succeed.
Where the decision point sits inside the executing contract, most of this machinery disappears: check and effect share a transaction and cannot be separated. That is the strongest argument for placing the decision point as close to irreversibility as the surrounding system permits.
Accounting follows the same discipline. Offchain preflight should evaluate without recording consumption, because an intent that is authorized and then never submitted, or submitted and reverted, would otherwise burn capacity the actor never used. Preflight predicts; the enforcing layer accounts.
What these definitions are
The interfaces on this page are specification artefacts. They fix the shape of a boundary — what an integrator must be able to construct, what a check must return, and which values stay stable across implementations — so that independent implementations agree on one vocabulary. They describe a boundary, not a distribution. Normative field definitions and the complete Reason Code table live in the specification; implementation-facing material is collected under developers.
It is worth being precise about what a correct integration achieves. A decision point constrains authorization: it establishes that this actor was permitted to take this action, for this subject, within these limits, at this venue. It leaves market, smart-contract, oracle and governance risk untouched, as the security model sets out: an authorized execution is a permitted one, not necessarily a sound one.