/** * @module executions * @description The execution API, from the agent's side: ask Mandaitor to * perform one typed business operation (`mandaitor.tool-request/v1`), and * read what became of it (`mandaitor.execution/v1`). * * Mandaitor performs the write. The agent names the tool and the record, and * never holds the target system's credential: the decision reads the facts it * turns on from the target. An `ALLOW` is answered once it is recorded, in * `AUTHORIZED_NOT_EXECUTED`, and Mandaitor then rechecks it, writes it once * and reads it back, usually within seconds: `waitForExecution` reads it until * that is done. A denial is an execution in `DENIED`, not an error. * * The agent authenticates as itself: a session from `POST /mcp/token`, opened * with an OAuth `private_key_jwt` client assertion signed by the key it * registered. The session is bound to a second key the client makes and never * lets out (DPoP, RFC 9449), and every request carries a proof signed with * it, so the session token alone is of no use to anyone who takes it. API keys * are refused by the execution API. * * Web Crypto and fetch only. It is for an agent that runs server-side: the API * does not accept the `DPoP` header from a browser page on another origin. * * @example * ```ts * import { MandaitorExecutionsClient, verifyReceiptForExecution } from "@mandaitor/sdk"; * * const executions = new MandaitorExecutionsClient({ * baseUrl: "https://api.mandaitor.io/v1", * agent: { did: agentDid, keyId, privateKey: pkcs8Pem }, * }); * const { execution: requested } = await executions.requestExecution({ * tool: "sap.purchase_requisition.release", * arguments: { purchase_requisition: "10000123" }, * idempotency_key: crypto.randomUUID(), * }); * const execution = await executions.waitForExecution(requested.execution_id); * if (execution.state === "SUCCEEDED") { * const receipt = await verifyReceiptForExecution(execution); * } * ``` */ import { MandaitorApiError } from "./errors.js"; export declare const TOOL_REQUEST_CONTRACT = "mandaitor.tool-request/v1"; export declare const EXECUTION_CONTRACT = "mandaitor.execution/v1"; export type ExecutionState = "DENIED" | "AWAITING_APPROVAL" | "AUTHORIZED_NOT_EXECUTED" | "EXECUTING" | "SUCCEEDED" | "FAILED" | "UNKNOWN"; /** States an execution never leaves. */ export declare const FINAL_EXECUTION_STATES: readonly ExecutionState[]; /** A `mandaitor.tool-request/v1`, without its `contract`, which the client adds. */ export interface ToolRequest { /** The tool's id, e.g. `sap.purchase_requisition.release`. */ tool: string; /** Pins a definition version; it must be the version the tenant enabled. */ tool_version?: number; /** Identifiers only, as the tool's `arguments_schema` (`GET /tools`) allows. */ arguments: Record; /** * 8 to 128 visible ASCII characters. The same key with the same request * returns the same execution, so a retry never writes twice. */ idempotency_key: string; /** The mandate the agent believes covers the call. A hint only. */ mandate_id?: string; /** Shown to an approver. Never used in the decision. */ purpose?: string; } /** `mandaitor.execution/v1`: one requested operation and what became of it. */ export interface Execution { contract: typeof EXECUTION_CONTRACT; execution_id: string; state: ExecutionState; /** Qualifies the state; treat one you do not know as the state's general meaning. */ state_reason: string; tool: { id: string; version: number; }; request_digest: string; decision: { event_id: string; outcome: "ALLOW" | "DENY" | "REQUIRE_APPROVAL"; reason: string; mandate_id?: string; }; /** The approval the decision required, or null. */ approval: { consent_id: string; /** What the approver sends back to approve. */ digest: string; expires_at: string; approved_at?: string; } | null; /** In SUCCEEDED: the record after the write, only the fields the tool declares returnable. */ result: Record | null; /** * In SUCCEEDED, and in FAILED once a read-back proved the write absent: the * signed receipt. None when a person of the tenant resolved an UNKNOWN * execution (`state_reason` `RESOLVED_BY_OPERATOR`): nothing was read back to sign. */ receipt: string | null; created_at: string; updated_at: string; } /** A person's decision on an execution's approval. */ export interface ExecutionApprovalDecision { decision: "APPROVE" | "REJECT"; /** Required to approve: the digest the approver was shown. */ approval_digest?: string; /** The approval the approver was shown; one a newer approval replaced is refused. */ consent_id?: string; comment?: string; } /** * An error from the execution API. Act on `code` (`TOOL_NOT_ENABLED`, * `IDEMPOTENCY_CONFLICT`, `DIGEST_MISMATCH`, …). */ export declare class MandaitorExecutionError extends MandaitorApiError { /** For `INVALID_REQUEST` and `INVALID_ARGUMENTS`: each problem. */ readonly errors: { path: string; message: string; code: string; }[]; /** For `IDEMPOTENCY_IN_PROGRESS`: the execution the key names. */ readonly executionId?: string | undefined; /** For a 409 on an approval: the execution as it is now. */ readonly execution?: Execution | undefined; /** From `Retry-After`, where the answer carried one. */ readonly retryAfterMs?: number | undefined; readonly name: string; constructor(status: number, code: string, message: string, /** For `INVALID_REQUEST` and `INVALID_ARGUMENTS`: each problem. */ errors?: { path: string; message: string; code: string; }[], /** For `IDEMPOTENCY_IN_PROGRESS`: the execution the key names. */ executionId?: string | undefined, /** For a 409 on an approval: the execution as it is now. */ execution?: Execution | undefined, /** From `Retry-After`, where the answer carried one. */ retryAfterMs?: number | undefined, requestId?: string); } /** The agent, as it registered: its DID, its key's id, and the private half of that key. */ export interface AgentCredentials { did: string; /** The `kid` of the agent's active registered key. */ keyId: string; /** ECDSA P-256: a Web Crypto key, a private JWK, or a PKCS #8 PEM. */ privateKey: CryptoKey | JsonWebKey | string; } export interface MandaitorExecutionsClientConfig { agent: AgentCredentials; /** API base URL (defaults to production). */ baseUrl?: string; /** The client assertion's audience; defaults to `${baseUrl}/mcp/token`. */ tokenAudience?: string; /** Mandates to scope the session to; defaults to every mandate bound to the agent. */ mandateIds?: string[]; /** Request timeout in milliseconds (default 30000: an ALLOW is written before the answer). */ timeout?: number; /** Retries of a request the API asks to be retried, or that got no answer (default 2). */ retries?: number; } /** * The execution API for one agent. It opens the agent's session itself, bound * to a key it makes for itself (DPoP), and opens a new one when it expires or * ends. */ export declare class MandaitorExecutionsClient { private readonly config; private readonly baseUrl; private readonly timeout; private readonly retries; private session; private signingKey; private dpopKey; constructor(config: MandaitorExecutionsClientConfig); /** * Asks for an execution. `replayed` is true when the idempotency key had * already named this request, and the answer is that execution as it is now. * * Safe to retry: the API's own retry signals (`IDEMPOTENCY_IN_PROGRESS`, * `RATE_LIMITED`, `AUDIT_UNAVAILABLE`, `TARGET_UNAVAILABLE`) and a request * that got no answer are retried with the same key, which never writes twice. */ requestExecution(request: ToolRequest): Promise<{ execution: Execution; replayed: boolean; }>; /** Reads one of this agent's executions. */ getExecution(executionId: string): Promise; /** * Reads the execution until `until` holds: by default, until it is final, * waits for an approval, or is UNKNOWN. An UNKNOWN execution is not final: * Mandaitor reads it back again for about 41 hours, and a person of the * tenant resolves what that does not settle, so waiting longer here rarely * helps. Throws a MandaitorTimeoutError when `until` does not hold within * `timeoutMs`. */ waitForExecution(executionId: string, options?: { until?: (execution: Execution) => boolean; intervalMs?: number; timeoutMs?: number; }): Promise; private call; /** The session, opening one when there is none or it is about to expire. */ private openSession; /** * A DPoP proof (RFC 9449) for one request: its method and URL, and the * session token it comes with (`ath`), signed with this client's key. */ private dpopProof; /** A `private_key_jwt` client assertion: ES256, single use, valid for five minutes. */ private clientAssertion; } /** * Approves or rejects an execution that waits for the caller's approval. * Only a person's session counts: a dashboard session, or one from the * tenant's identity provider. API keys and agents are refused with 403 * `NOT_A_PERSON`. To approve, send the `approval_digest` the approver was * shown; the answer is the execution after the approved write was performed. */ export declare function decideExecutionApproval(params: { executionId: string; decision: ExecutionApprovalDecision; /** The person's bearer token. */ accessToken: string; baseUrl?: string; timeout?: number; }): Promise; //# sourceMappingURL=executions.d.ts.map