Playbook: Your System Acts
The scenario
Your ERP integration already releases requisitions, through your own middleware, with its own audit and its own connection to the ERP. A procurement agent proposes releases, and the integration should perform only those a mandate allows, on the record as the ERP holds it. Mandaitor does not connect to the ERP here: your integration does, and it is your enforcement point.
The risk
An answer that holds for any matching request can be reused: for another requisition, for a higher amount, by a component that was never meant to act. And if the decision rests on what the agent claims about the requisition, an agent that understates an amount gets a release it should not.
A request-bound decision closes both. It is decided on the facts your enforcement point read from the ERP. The authorization is bound to a digest of exactly that request, for your enforcement point alone, for five minutes, and it is spent the first time it is redeemed. It never passes through the agent.
The mandate design
The same as for the requisition release agent: procurement.requisition.release on procurement:plant:1000/requisition:*, with a cap of EUR 100,000 and an approval threshold of EUR 10,000. A decision counts the same mandates an execution does, and charges the same cumulative limits.
A request above the threshold is answered REQUIRE_APPROVAL, with no authorization, and no approval is raised: in the Preview, nothing above the threshold can be authorized. Treat REQUIRE_APPROVAL as a refusal. And until the procurement taxonomy is loaded, a decision on procurement.requisition.release is denied with UNKNOWN_ACTION.
Set it up
- The agent. Register it in the Agent Identity Registry. Your enforcement point names it by its DID. The agent itself needs no key here, because it never calls Mandaitor.
- The mandate, from a person's session, as in the release agent playbook.
- An API key for your enforcement point, with the
verifyscope. Nothing else asks for decisions with it.
Ask, redeem, then act
Your enforcement point reads the requisition from the ERP, never from the agent, and asks:
import { MandaitorClient } from "@mandaitor/sdk"; // 5.1.0 or later
const client = new MandaitorClient({ apiKey, tenantId });
const requisition = await erp.readRequisition("10004711"); // your system of record
const request = {
agent: "did:web:agents.mandaitor.io:agt_01JZ8Y3S6N0F4K7Q2W9E5R1T8V",
action: "procurement.requisition.release",
resource: `procurement:plant:${requisition.plant}/requisition:${requisition.number}`,
facts: {
plant: requisition.plant,
amount: { currency: requisition.currency, value: requisition.amount }, // a decimal string
},
audience: "https://erp.example.com", // your enforcement point
};
const decision = await client.requestDecision(request);
if (decision.decision !== "ALLOW") {
return { released: false, code: decision.code, reason: decision.reason };
}
// Immediately before the write: spend the authorization on exactly this request.
await client.redeemDecision(decision.decision_id!, request);
await erp.releaseRequisition(requisition.number); // act once
If the redemption is refused, do not act. When the component that writes is not the one that asked, it verifies the authorization against the request first, with verifyAuthorizationForRequest, and then redeems it: see Request-bound Decisions.
What must be refused
| Request | Expected | Why |
|---|---|---|
| Plant 1000, EUR 1,250.00 | ALLOW, then a redemption that succeeds | Within the mandate |
| Plant 1000, EUR 38,500.00 | REQUIRE_APPROVAL, no authorization | Above the threshold |
| Plant 1000, EUR 240,000.00 | DENY, AMOUNT_ABOVE_LIMIT | Above the cap |
| Plant 2000, EUR 3,200.00 | DENY, NO_COVERING_MANDATE | Another plant |
| The same authorization redeemed twice | 409 ALREADY_REDEEMED | It is spent the first time |
| Redeemed for another amount or record | 409 DIGEST_MISMATCH | It authorizes one request |
| Redeemed after five minutes | 409 AUTHORIZATION_EXPIRED | It is short-lived |
| Redeemed after the mandate was revoked | 409 MANDATE_NOT_ACTIVE | Revocation stops what is not yet redeemed |
If the answer to a redemption is lost, a retry is answered ALREADY_REDEEMED when the first attempt went through. Do not act on it: ask for a new decision.
Evidence and review
- On the mandate's audit chain: each decision (
DECISION_ALLOWED,DECISION_DENIED,DECISION_APPROVAL_REQUIRED) with the facts and the digest, and each redemption (DECISION_REDEEMEDorDECISION_REDEMPTION_REFUSED). - In your own system: the release itself. Mandaitor did not write it, so it signs no receipt: your integration's own record of the write is the outcome's evidence. Keep the
decision_idwith it, so that a reviewer can join the two.
Adapt it
- Let Mandaitor act instead. Where Mandaitor can connect to the system, an execution reads the record itself, performs the write, and proves the outcome with a signed receipt. Your integration then needs no enforcement point of its own.
- Several enforcement points. Give each its own
audience. An authorization given for one is refused at another.