Skip to main content

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.

Approvals in the Preview

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​

  1. 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.
  2. The mandate, from a person's session, as in the release agent playbook.
  3. An API key for your enforcement point, with the verify scope. 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​

RequestExpectedWhy
Plant 1000, EUR 1,250.00ALLOW, then a redemption that succeedsWithin the mandate
Plant 1000, EUR 38,500.00REQUIRE_APPROVAL, no authorizationAbove the threshold
Plant 1000, EUR 240,000.00DENY, AMOUNT_ABOVE_LIMITAbove the cap
Plant 2000, EUR 3,200.00DENY, NO_COVERING_MANDATEAnother plant
The same authorization redeemed twice409 ALREADY_REDEEMEDIt is spent the first time
Redeemed for another amount or record409 DIGEST_MISMATCHIt authorizes one request
Redeemed after five minutes409 AUTHORIZATION_EXPIREDIt is short-lived
Redeemed after the mandate was revoked409 MANDATE_NOT_ACTIVERevocation 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_REDEEMED or DECISION_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_id with 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.