/** * Quattrino TypeScript SDK (thin) - Economic Control Plane for AI agents. * * import { Quattrino } from "@quattrino/sdk"; * const q = new Quattrino({ apiKey: "qtrn_sk_...", baseUrl: "https://quattrino.io" }); * const r = await q.authorize({ amountMinor: 1899, merchant: "Amazon", purpose: "USB-C cable", * task: { max_amount_minor: 2000 } }); * r.decision // AUTHORIZED | APPROVAL_REQUIRED | DENIED | NEEDS_INFORMATION * * No policy logic lives in this file: every decision is made by the Quattrino API. */ export type Decision = "AUTHORIZED" | "APPROVAL_REQUIRED" | "DENIED" | "NEEDS_INFORMATION"; export type Mode = "dry_run" | "binding"; export type Execution = "auto" | "quattrino" | "external"; export interface TaskConstraints { max_amount_minor?: number; total_order_max_minor?: number; quantity_max?: number; merchants?: string[]; categories?: string[]; product?: string; service_id?: string; recurring_allowed?: boolean; subscription_allowed?: boolean; valid_from?: string; valid_until?: string; destination?: string; geography?: string[]; prohibited_side_effects?: string[]; approval_threshold_minor?: number; instruction?: string; } export interface AuthorizeInput { mode?: Mode; actionType?: string; amountMinor?: number; currency?: string; merchant?: string; serviceId?: string; purpose?: string; task?: TaskConstraints | string; taskId?: string; delegatedAuthorityId?: string; category?: string; quantity?: number; recurring?: boolean; disclosures?: string[]; execution?: Execution; ttlSeconds?: number; idempotencyKey?: string; // ---- Phase 30 ---- /** AuthorityEnvelope the action runs under (from compileIntent) - or pass `task` as a string to compile it */ envelopeId?: string; actionClass?: ActionClass; subactions?: { name: string; class: ActionClass; amount_minor?: number }[]; commitment?: Commitment; attributes?: Record; fulfillment?: "physical" | "digital" | "service" | "invoice" | "none"; /** R4: bind the signed portable proof to a known executor / counterparty (defaults to the merchant) */ proofAudience?: string; [extra: string]: unknown; } /** R4: signed portable proof (sandbox Ed25519). Present only on binding + AUTHORIZED + external. */ export interface PortableProof { proof_id: string; version: string; alg: "EdDSA"; kid: string; issuer: string; environment: "sandbox"; audience: string | null; audience_bound: boolean; token: string; issued_at: string; expires_at: string | null; single_use: true; verification_count?: number; [k: string]: unknown; } export interface ProofVerification { valid: boolean; status: "VALID" | "INVALID"; reason_codes: string[]; reasons: { code: string; family: string; severity: string; message: string; remedy: string }[]; checks: Record; proof: { proof_id: string; authorization_id: string; audience: string | null; expires_at: number | null; terms: Record } | null; authorization: { authorization_id: string; status: string; consumed: boolean; revoked: boolean; expires_at: string | null } | null; [k: string]: unknown; } /** Credential-less verifier for an executor / counterparty that only holds the proof token. * NEVER act unless result.valid === true. Sandbox only. */ export async function verifyProof(baseUrl: string, proof: string, opts: { audience?: string; expected?: Record; verifier?: string; fetch?: typeof fetch } = {}): Promise { const f = opts.fetch || (globalThis as any).fetch; let base = baseUrl.replace(/\/$/, ""); if (!base.endsWith("/api/v1")) base += "/api/v1"; const body: Record = { proof }; if (opts.audience !== undefined) body.audience = opts.audience; if (opts.expected !== undefined) body.expected = opts.expected; if (opts.verifier !== undefined) body.verifier = opts.verifier; const res = await f(base + "/authorizations/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); const json: any = await res.json().catch(() => ({})); if (!res.ok) { const err = json.error || {}; throw new QuattrinoError(res.status, err.code || "http_error", err.message || res.statusText, err.details); } return json as ProofVerification; } export type ActionClass = "NON_CONSEQUENTIAL_RESEARCH" | "EXTERNAL_COMMUNICATION" | "DATA_DISCLOSURE" | "ECONOMIC_COMMITMENT" | "PAYMENT" | "DELEGATION" | "ACCOUNT_OR_SUBSCRIPTION_COMMITMENT"; export type Commitment = "ONE_TIME" | "RECURRING" | "SUBSCRIPTION" | "AUTO_RENEWAL" | "ACCOUNT_CREATION" | "CONTRACT" | "WARRANTY" | "TRIAL_THAT_CONVERTS" | "UNKNOWN_COMMITMENT"; export type DisclosureDecision = "RELEASE" | "RELEASE_ONCE" | "RELEASE_REDACTED" | "ASK_OWNER" | "DENY"; export interface MissingAuthority { dimension: string; needed?: unknown; have?: unknown; requested_delta_minor?: number; how_to_obtain?: string; why_needed?: string; [k: string]: unknown; } export interface LatestSafeQuestion { question: string; dimension: string; requested_delta_minor?: number | null; requested_permission?: unknown; why_needed?: string; how_to_obtain?: string; safe_actions_remaining: string[]; } export interface Subaction { name: string; class: ActionClass; verdict: "AUTHORIZED_NOW" | "NOT_YET_AUTHORIZED" | "NEEDS_INFORMATION_BEFORE_CONSEQUENCE" | "BLOCKED"; reason?: string; missing_authority: MissingAuthority[]; [k: string]: unknown; } export interface Reason { code: string; message?: string; [k: string]: unknown } export interface ExecutionPackage { execution_mode: "quattrino" | "external" | "unavailable"; adapter?: "stripe_spt_merchant" | "credits_marketplace" | null; route?: string | null; how?: string; consume_via?: string; service_id?: string; merchant_key?: string; quattrino_moves_money?: boolean; [k: string]: unknown; } export interface AuthorizationResult { authorization_id: string; decision: Decision; status: string; mode: Mode; effective_maximum_minor: number | null; autonomous_maximum_minor: number | null; effective_authority: { binding_layer: string | null; plain_language: string; layers: unknown[] }; denial_reasons: Reason[]; needs_information: Reason[]; approval_reasons: Reason[]; approval: { required: boolean; state: string; approval_id: string | null }; disclosures: { scope: string; decision: DisclosureDecision; reason: string; recipient?: string | null; single_use?: boolean }[]; execution_plan: ExecutionPackage; execution_mode: string; expires_at: string | null; outcome: unknown | null; // ---- Phase 30 ---- envelope?: { envelope_id: string; version: number } | null; commitment?: Commitment; action_class?: ActionClass; subactions?: Subaction[]; authorized_subactions?: string[]; deferred_subactions?: string[]; blocked_subactions?: string[]; missing_authority?: MissingAuthority[]; question_required?: boolean; question?: LatestSafeQuestion | null; safe_actions_remaining?: string[]; compliance?: { status: "COMPLIANT" | "NONCOMPLIANT"; findings: string[] } | null; [k: string]: unknown; } export interface OutcomeInput { success: boolean; provider?: string; protocol?: string; providerReference?: string; actualAmountMinor?: number; currency?: string; merchant?: string; materialResult?: string; recurring?: boolean; sideEffects?: string[]; receipt?: Record; // ---- Phase 30 expanded terms (for compliance) ---- commitment?: Commitment; disclosedFields?: string[]; delegationsPerformed?: string[]; category?: string; actionType?: string; attributes?: Record; // ---- R4: link the outcome to the proof the executor verified ---- proofId?: string; proofVerified?: boolean; } export class QuattrinoError extends Error { status: number; code: string; details: unknown; constructor(status: number, code: string, message: string, details?: unknown) { super(`${status} ${code}: ${message}`); this.status = status; this.code = code; this.details = details; } } function randomKey(prefix: string): string { const c: any = (globalThis as any).crypto; if (c && typeof c.randomUUID === "function") return prefix + c.randomUUID().replace(/-/g, ""); return prefix + Math.random().toString(16).slice(2) + Date.now().toString(16); } export class Quattrino { private base: string; private apiKey: string; private fetchImpl: typeof fetch; constructor(opts: { apiKey: string; baseUrl?: string; fetch?: typeof fetch }) { if (!opts.apiKey) throw new Error("apiKey (agent credential) is required"); this.apiKey = opts.apiKey; this.base = (opts.baseUrl || "https://quattrino.io").replace(/\/$/, "") + "/api/v1"; this.fetchImpl = opts.fetch || (globalThis as any).fetch; if (!this.fetchImpl) throw new Error("A fetch implementation is required (Node 18+ or pass fetch)"); } private async request(method: string, path: string, body?: unknown, headers: Record = {}): Promise { const res = await this.fetchImpl(this.base + path, { method, headers: { Authorization: "Bearer " + this.apiKey, "Content-Type": "application/json", "User-Agent": "quattrino-ts/0.31.0", ...headers }, body: body === undefined ? undefined : JSON.stringify(body), }); const text = await res.text(); let json: any = {}; try { json = text ? JSON.parse(text) : {}; } catch { json = { detail: text }; } if (!res.ok) { const err = json.error || {}; throw new QuattrinoError(res.status, err.code || "http_error", err.message || json.detail || res.statusText, err.details); } return json as T; } /** Ask whether an economic action is authorized. mode="binding" issues a portable EconomicAuthorization. */ async authorize(input: AuthorizeInput): Promise { const { mode = "dry_run", actionType = "purchase", amountMinor, currency = "USD", merchant, serviceId, purpose, task, taskId, delegatedAuthorityId, category, quantity, recurring = false, disclosures, execution = "auto", ttlSeconds, idempotencyKey, envelopeId, actionClass, subactions, commitment, attributes, fulfillment, proofAudience, ...extra } = input; const body: Record = { mode, action_type: actionType, currency, recurring, execution, ...extra }; if (amountMinor !== undefined) body.amount_minor = amountMinor; if (merchant !== undefined) body.merchant = merchant; if (serviceId !== undefined) body.service_id = serviceId; if (purpose !== undefined) body.purpose = purpose; if (typeof task === "string") { // Phase 30: a plain-language task is compiled into an envelope first (meaning only, never permission) const compiled = await this.compileIntent(task); body.envelope_id = compiled.envelope?.envelope_id; } else if (task !== undefined) body.task = task; if (envelopeId !== undefined) body.envelope_id = envelopeId; if (actionClass !== undefined) body.action_class = actionClass; if (subactions !== undefined) body.subactions = subactions; if (commitment !== undefined) body.commitment = commitment; if (attributes !== undefined) body.attributes = attributes; if (fulfillment !== undefined) body.fulfillment = fulfillment; if (proofAudience !== undefined) body.proof_audience = proofAudience; if (taskId !== undefined) body.task_id = taskId; if (delegatedAuthorityId !== undefined) body.delegated_authority_id = delegatedAuthorityId; if (category !== undefined) body.category = category; if (quantity !== undefined) body.quantity = quantity; if (disclosures !== undefined) body.disclosures = disclosures; if (ttlSeconds !== undefined) body.ttl_seconds = ttlSeconds; const headers: Record = {}; if (mode === "binding") headers["Idempotency-Key"] = idempotencyKey || randomKey("qtrn-"); else if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey; return this.request("POST", "/authorize", body, headers); } authorizeBinding(input: AuthorizeInput): Promise { return this.authorize({ ...input, mode: "binding" }); } getAuthorization(id: string): Promise { return this.request("GET", "/authorizations/" + id); } listAuthorizations(opts: { status?: string; limit?: number } = {}): Promise<{ authorizations: AuthorizationResult[]; count: number }> { const q = "?limit=" + (opts.limit || 50) + (opts.status ? "&status=" + encodeURIComponent(opts.status) : ""); return this.request("GET", "/authorizations" + q); } /** Report what YOUR executor did under an authorization Quattrino did not execute. */ recordOutcome(id: string, o: OutcomeInput): Promise { const body: Record = { success: o.success }; if (o.provider !== undefined) body.provider = o.provider; if (o.protocol !== undefined) body.protocol = o.protocol; if (o.providerReference !== undefined) body.provider_reference = o.providerReference; if (o.actualAmountMinor !== undefined) body.actual_amount_minor = o.actualAmountMinor; if (o.currency !== undefined) body.currency = o.currency; if (o.merchant !== undefined) body.merchant = o.merchant; if (o.materialResult !== undefined) body.material_result = o.materialResult; if (o.recurring !== undefined) body.recurring = o.recurring; if (o.sideEffects !== undefined) body.side_effects = o.sideEffects; if (o.receipt !== undefined) body.receipt = o.receipt; if (o.commitment !== undefined) body.commitment = o.commitment; if (o.disclosedFields !== undefined) body.disclosed_fields = o.disclosedFields; if (o.delegationsPerformed !== undefined) body.delegations_performed = o.delegationsPerformed; if (o.category !== undefined) body.category = o.category; if (o.actionType !== undefined) body.action_type = o.actionType; if (o.attributes !== undefined) body.attributes = o.attributes; if (o.proofId !== undefined) body.proof_id = o.proofId; if (o.proofVerified !== undefined) body.proof_verified = o.proofVerified; return this.request("POST", "/authorizations/" + id + "/outcome", body); } // ------------------------------------------------ R4: signed portable proof (sandbox) ---- /** The signed portable proof of a binding AUTHORIZED externally executed authorization (dry runs have none). */ getProof(id: string): Promise<{ authorization_id: string; status: string; portable_proof: PortableProof }> { return this.request("GET", "/authorizations/" + id + "/proof"); } /** Online fail-closed verification (signature, key, expiry, audience, revocation, single use, live authority). */ verifyProof(proof: string, opts: { audience?: string; expected?: Record; verifier?: string } = {}): Promise { return verifyProof(this.base, proof, { ...opts, fetch: this.fetchImpl }); } /** Public JWKS-style sandbox verification keys (offline signature checks only). */ proofKeys(): Promise> { return this.request("GET", "/public/proof-keys"); } // ------------------------------------------------ R7: stable reason-code registry ---- reasonCodes(opts: { family?: string; severity?: string } = {}): Promise> { const q: string[] = []; if (opts.family) q.push("family=" + encodeURIComponent(opts.family)); if (opts.severity) q.push("severity=" + encodeURIComponent(opts.severity)); return this.request("GET", "/public/reason-codes" + (q.length ? "?" + q.join("&") : "")); } describeReason(code: string): Promise> { return this.request("GET", "/public/reason-codes/" + encodeURIComponent(code)); } // ------------------------------------------------ Phase 30: intent / envelope / discovery ---- /** Human instruction (+ optional structured proposal) -> CompiledIntent + AuthorityEnvelope v1. */ compileIntent(statement: string, opts: { proposal?: Record; statementRef?: string; createEnvelope?: boolean; parentEnvelopeId?: string } = {}): Promise { const body: Record = { statement, create_envelope: opts.createEnvelope ?? true }; if (opts.proposal) body.proposal = opts.proposal; if (opts.statementRef) body.statement_ref = opts.statementRef; if (opts.parentEnvelopeId) body.parent_envelope_id = opts.parentEnvelopeId; return this.request("POST", "/intents/compile", body); } getEnvelope(id: string): Promise { return this.request("GET", "/envelopes/" + id); } listEnvelopes(opts: { status?: string; limit?: number } = {}): Promise { return this.request("GET", "/envelopes?limit=" + (opts.limit || 50) + (opts.status ? "&status=" + encodeURIComponent(opts.status) : "")); } createEnvelope(dimensions: Record, opts: { note?: string; compiledIntentId?: string; parentEnvelopeId?: string } = {}): Promise { return this.request("POST", "/envelopes", { dimensions, note: opts.note, compiled_intent_id: opts.compiledIntentId, parent_envelope_id: opts.parentEnvelopeId }); } /** Self-restrict: an agent may only NARROW its envelope. */ narrowEnvelope(id: string, dimensions: Record, note?: string): Promise { return this.request("POST", "/envelopes/" + id + "/narrow", { dimensions, note }); } /** Propose a change (e.g. widening). The owner decides; nothing changes until then. */ proposeEnvelopeChange(id: string, dimensions: Record, reason?: string): Promise { return this.request("POST", "/envelopes/" + id + "/propose", { dimensions, reason }); } /** What may I currently do? (effective capabilities only) */ discover(envelopeId?: string): Promise { return this.request("GET", "/authority/me" + (envelopeId ? "?envelope_id=" + encodeURIComponent(envelopeId) : "")); } /** What additional authority would I need to do X? (nothing recorded) */ gap(action: Record): Promise<{ would_be: Decision; missing_authority: MissingAuthority[]; question: LatestSafeQuestion | null; [k: string]: unknown }> { return this.request("POST", "/authority/gap", action); } /** Consume a RELEASE / RELEASE_ONCE / RELEASE_REDACTED decision bound to the authorization. */ releaseDisclosure(authorizationId: string, scope: string, recipient: string): Promise { return this.request("POST", "/authorizations/" + authorizationId + "/disclosures/" + encodeURIComponent(scope) + "/release", { recipient }); } listDisclosures(authorizationId: string): Promise { return this.request("GET", "/authorizations/" + authorizationId + "/disclosures"); } revoke(id: string, reason = ""): Promise { return this.request("POST", "/authorizations/" + id + "/revoke", { reason }); } explain(id: string): Promise & { plain_language: string }> { return this.request("GET", "/authorizations/" + id + "/explain"); } schema(): Promise> { return this.request("GET", "/authorize/schema"); } /** Live merchant quote for the stripe_spt_merchant adapter. */ quote(merchantKey: string, sku: string, quantity = 1): Promise { return this.request("POST", "/agent/merchant-payments/quote", { merchant_key: merchantKey, sku, quantity }); } /** Consume a binding authorization through the Quattrino adapter named in its execution_package. */ async execute(auth: AuthorizationResult | string, args: Record = {}): Promise { const a = typeof auth === "string" ? await this.getAuthorization(auth) : auth; const plan = a.execution_plan || ({} as ExecutionPackage); if (plan.execution_mode !== "quattrino") { throw new QuattrinoError(400, "authorization_external_only", "This authorization has no Quattrino execution adapter; execute externally and call recordOutcome()."); } const headers = { "Idempotency-Key": args.idempotencyKey || randomKey("qtrn-exec-") }; if (plan.adapter === "credits_marketplace") { return this.request("POST", "/agent/services/" + plan.service_id + "/purchase", { authorization_id: a.authorization_id, quantity: args.quantity || 1 }, headers); } if (plan.adapter === "stripe_spt_merchant") { for (const k of ["sku", "terms_hash", "expected_total_minor"]) { if (args[k] === undefined) throw new QuattrinoError(400, "adapter_args_missing", "stripe_spt_merchant needs a quote first: " + k); } return this.request("POST", "/agent/merchant-payments/purchase", { merchant_key: plan.merchant_key, authorization_id: a.authorization_id, quantity: args.quantity || 1, sku: args.sku, terms_hash: args.terms_hash, expected_total_minor: args.expected_total_minor, category: args.category, purpose: a.purpose }, headers); } throw new QuattrinoError(400, "unknown_adapter", "Unknown execution adapter: " + plan.adapter); } } export default Quattrino;