diff --git a/packages/python/src/codespar/__init__.py b/packages/python/src/codespar/__init__.py index 01c45f6..3c385c8 100644 --- a/packages/python/src/codespar/__init__.py +++ b/packages/python/src/codespar/__init__.py @@ -58,6 +58,7 @@ is_tool_not_mocked, ) from .types import ( + AgenticReceipt, AssistantTextEvent, AuthConfig, AuthResult, @@ -84,6 +85,8 @@ LedgerAction, LedgerArgs, LedgerLeg, + LedgerReceiptResult, + LedgerReceiptsResult, LedgerResult, ManageConnections, MockObject, @@ -94,6 +97,11 @@ Preset, ProxyRequest, ProxyResult, + ReceiptDelivery, + ReceiptException, + ReceiptMandate, + ReceiptPayment, + ReceiptQuote, RequiredSecret, SearchStrategy, SendResult, @@ -141,6 +149,8 @@ "POLICY_DENIED", "TOOL_NOT_MOCKED", "TOOL_RESULT_CODES", + # Agentic receipt (the Control Record) + "AgenticReceipt", "ApiError", "ApprovalRequiredOutput", "AssistantTextEvent", @@ -182,6 +192,8 @@ "LedgerAction", "LedgerArgs", "LedgerLeg", + "LedgerReceiptResult", + "LedgerReceiptsResult", "LedgerResult", "ManageConnections", # Test-mode mocks @@ -199,6 +211,11 @@ # Proxy "ProxyRequest", "ProxyResult", + "ReceiptDelivery", + "ReceiptException", + "ReceiptMandate", + "ReceiptPayment", + "ReceiptQuote", "RequiredSecret", "SearchStrategy", "SendResult", diff --git a/packages/python/src/codespar/types.py b/packages/python/src/codespar/types.py index 53d9271..59ae952 100644 --- a/packages/python/src/codespar/types.py +++ b/packages/python/src/codespar/types.py @@ -462,7 +462,7 @@ class ShipResult: # ── codespar_ledger wire shape ───────────────────────────────────── -LedgerAction = Literal["entry", "balance", "account"] +LedgerAction = Literal["entry", "balance", "account", "receipt", "receipts"] @dataclass(slots=True) @@ -499,6 +499,9 @@ class LedgerArgs: alias: str | None = None name: str | None = None type: str | None = None + receipt_id: str | None = None + consumer_id: str | None = None + limit: int | None = None metadata: dict[str, Any] | None = None @@ -512,6 +515,100 @@ class LedgerResult: raw: Any = None +# ── Agentic receipt (the Control Record) ─────────────────────────── + + +@dataclass(slots=True) +class ReceiptException: + """A settle-time policy divergence recorded on a receipt (OBSERVE mode) — + e.g. the settled amount/payee diverging from the bound quote.""" + + code: str + detail: str + at: str + + +@dataclass(slots=True) +class ReceiptMandate: + id: str + nonce: str + scope: str + currency: str + sig: str + + +@dataclass(slots=True) +class ReceiptQuote: + seller: str | None = None + resource: str | None = None + price_minor: int | None = None + payee: str | None = None + session_id: str | None = None + sig: str | None = None + at: str | None = None + + +@dataclass(slots=True) +class ReceiptPayment: + rail: str + amount_minor: int + attempt_id: str + money_moved: bool + provider: str | None = None + tx_id: str | None = None + at: str | None = None + + +@dataclass(slots=True) +class ReceiptDelivery: + result: str | None = None + proof: str | None = None + kind: str | None = None + nfe_chave: str | None = None + at: str | None = None + + +@dataclass(slots=True) +class AgenticReceipt: + """The canonical agentic receipt — the Control Record of one spend. + + Binds mandate -> quote -> payment -> delivery into a tamper-evident + chain (SHA-256 over the RFC 8785 canonical JSON of the records + present); ``receipt_sig`` is the consumer HMAC over ``chain``. The + structure is rail-agnostic; only the delivery proof artifact differs + (NF-e + Pix endToEndId for the fiscal path, the x402 resource + response for crypto). Returned by ``codespar_ledger`` action=receipt. + """ + + receipt_id: str + state: Literal["paid", "exception", "delivered", "voided"] + mandate: ReceiptMandate + payment: ReceiptPayment + chain: str + receipt_sig: str + quote: ReceiptQuote | None = None + delivery: ReceiptDelivery | None = None + exceptions: list[ReceiptException] = field(default_factory=list) + + +@dataclass(slots=True) +class LedgerReceiptResult: + """Result of ``codespar_ledger`` action=receipt. A missing receipt + returns ``found=False`` (not an error).""" + + found: bool + receipt: AgenticReceipt | None = None + receipt_id: str | None = None + + +@dataclass(slots=True) +class LedgerReceiptsResult: + """Result of ``codespar_ledger`` action=receipts.""" + + receipts: list[AgenticReceipt] = field(default_factory=list) + count: int = 0 + + # ── codespar_issue wire shape ────────────────────────────────────── diff --git a/packages/types/package.json b/packages/types/package.json index da499c1..b310c4d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@codespar/types", - "version": "0.10.14", + "version": "0.10.15", "description": "Shared session interface contract for codespar runtimes", "type": "module", "main": "./dist/index.js", diff --git a/packages/types/src/testing/scenario-manifest.ts b/packages/types/src/testing/scenario-manifest.ts index f0f2108..ea920c5 100644 --- a/packages/types/src/testing/scenario-manifest.ts +++ b/packages/types/src/testing/scenario-manifest.ts @@ -31,7 +31,7 @@ * union, and the per-example groups a consumer drives completely. */ export const DEMO_SCENARIO_MANIFEST = { /** Must equal this package's published version (see the lockstep test). */ - version: "0.10.14", + version: "0.10.15", /** Every shipped scenario `name` — the union of all `groups` (see the test). */ scenarios: [ "customer-data-rejection", diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 3b4bb30..55d6394 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -378,8 +378,8 @@ export interface LedgerLeg { } export interface LedgerArgs { - /** entry | balance | account. */ - action: "entry" | "balance" | "account"; + /** entry | balance | account | receipt | receipts. */ + action: "entry" | "balance" | "account" | "receipt" | "receipts"; /** Asset / currency code for entry + account (BRL, USD, USDC, ...). */ asset?: string; /** Decimal places for the asset. Default 2 (fiat); JPY=0, most @@ -400,6 +400,12 @@ export interface LedgerArgs { /** Midaz account type (deposit, savings, external). Default deposit. * action=account only. */ type?: string; + /** The agentic receipt id (rcpt_...) to read. Required for action=receipt. */ + receipt_id?: string; + /** Whose receipts to list. Defaults to the session user. action=receipts. */ + consumer_id?: string; + /** Max receipts to list (action=receipts, default 50). */ + limit?: number; /** Free-form metadata stored on the entry / account. */ metadata?: Record; } @@ -416,6 +422,101 @@ export interface LedgerResult { raw?: unknown; } +/* ── Agentic receipt (the Control Record) ────────────────────── */ + +/** One settle-time policy divergence recorded on a receipt (OBSERVE mode) — + * e.g. the settled amount/payee diverging from the bound quote. */ +export interface ReceiptException { + code: string; + detail: string; + at: string; +} + +/** + * The canonical agentic receipt — the Control Record of one agentic spend. + * Binds four linked records into a tamper-evident chain so the spend proves + * not just that money moved, but that the agent APPROVED X, PAID X, and the + * order/resource was DELIVERED. Returned by codespar_ledger action=receipt. + * The receipt STRUCTURE is rail-agnostic; only the delivery proof artifact + * differs (NF-e + Pix endToEndId for the fiscal path, the x402 resource + * response + on-chain finality for crypto). `chain` is SHA-256 over the RFC + * 8785 canonical JSON of the records present; `receipt_sig` is the consumer + * HMAC over `chain` (the same trust root that signed the mandate). + */ +export interface AgenticReceipt { + receipt_id: string; + /** paid: payment sealed, no delivery yet. exception: a settle-time quote + * divergence was observed (still settled). delivered: a delivery record was + * folded in. voided: reversed. */ + state: "paid" | "exception" | "delivered" | "voided"; + mandate: { + id: string; + nonce: string; + /** The mandate purpose the spend was bound to. */ + scope: string; + currency: string; + /** The consumer HMAC that signed the mandate. */ + sig: string; + }; + /** The offer the agent approved (seller / resource / price), bound + signed + * into the receipt. Null when no quote was provided. */ + quote: { + seller: string | null; + resource: string | null; + price_minor: number | null; + payee: string | null; + session_id: string | null; + sig: string | null; + at: string | null; + } | null; + payment: { + /** pix | card | usdc-onchain | ... */ + rail: string; + provider: string | null; + /** PSP tx id / Pix endToEndId / on-chain hash. */ + tx_id: string | null; + amount_minor: number; + attempt_id: string; + /** True when real money moved (e.g. a Celcoin cash-out); false for a + * pending charge (e.g. a Mercado Pago QR). */ + money_moved: boolean; + at: string | null; + }; + /** Proof of what was received. Rail-specific: an order confirmation / NF-e + * for the fiscal (Pix) path, the x402 resource response for crypto. Null + * until a delivery record is folded in. */ + delivery: { + /** confirmed | pending | failed. */ + result: string | null; + proof: string | null; + /** order_confirmation | nfe | nfse | tracking | resource. */ + kind: string | null; + nfe_chave: string | null; + at: string | null; + } | null; + /** SHA-256 over the canonical JSON of the records present: + * hash(mandate -> quote -> payment -> delivery). */ + chain: string; + /** Consumer HMAC over `chain`. */ + receipt_sig: string; + exceptions: ReceiptException[]; +} + +/** Result of codespar_ledger action=receipt (read one by receipt_id). A + * missing receipt returns found:false (not an error). */ +export interface LedgerReceiptResult { + found: boolean; + receipt?: AgenticReceipt; + /** Echoed back when found is false. */ + receipt_id?: string; +} + +/** Result of codespar_ledger action=receipts (list a consumer's receipts). */ +export interface LedgerReceiptsResult { + receipts: AgenticReceipt[]; + count: number; +} + /* ── codespar_issue wire shape ───────────────────────────────── */ /**