@onchaindiligence/sdk 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A small, typed client for the [OnchainDiligence](https://onchaindiligence.com) compliance API. It hides the HTTP `402` pay-per-call flow entirely: you configure a funded account once, and every method transparently answers the payment challenge, settles on Tempo, and returns a typed, signed result.
4
4
 
5
+ A separate `@onchaindiligence/sdk/commerce` export orchestrates an *agent's own merchant payment lifecycle* (open → preflight → execute → observe/finalize) against `mcp.onchaindiligence.com` — see [Commerce client](#commerce-client-agent-payments) below.
6
+
5
7
  ```bash
6
8
  npm install @onchaindiligence/sdk mppx viem
7
9
  ```
@@ -37,8 +39,8 @@ const both = await od.diligence({
37
39
  company: '00000006',
38
40
  })
39
41
 
40
- // Anchor an attestation on Tempo, then verify it (free)
41
- const anchored = await od.anchor(wallet.data ? wallet.attestation.signature! : '')
42
+ // Anchor the complete authentic attestation envelope, then check it (free)
43
+ const anchored = await od.anchor(wallet)
42
44
  const status = await od.anchored(wallet.attestation.signature!)
43
45
  ```
44
46
 
@@ -47,7 +49,17 @@ Every paid response is a `Signed<T>` — the result plus an `attestation` you ca
47
49
  ```ts
48
50
  {
49
51
  data: { address: '0x…', sanctioned: false, /* … */ },
50
- attestation: { signed: true, key_id: 'ed25519-…', signature: '…' }
52
+ attestation: {
53
+ signed: true,
54
+ schema_version: 'onchaindiligence.attestation.v2',
55
+ issuer: 'https://api.onchaindiligence.com',
56
+ purpose: 'compliance-screening-result',
57
+ issued_at: '…',
58
+ key_id: 'ed25519-…',
59
+ algorithm: 'ed25519',
60
+ canonicalization: 'RFC8785',
61
+ signature: '…'
62
+ }
51
63
  }
52
64
  ```
53
65
 
@@ -59,21 +71,96 @@ Every paid response is a `Signed<T>` — the result plus an `attestation` you ca
59
71
  | `screenName(name, { threshold? })` | `Signed<NameScreenResult>` | yes |
60
72
  | `verifyCompany(number)` | `Signed<CompanyResult>` | yes |
61
73
  | `diligence({ wallet?, company? })` | `Signed<DiligenceResult>` | yes |
62
- | `anchor(signature)` | `Signed<AnchorResult>` | yes |
74
+ | `anchor(envelope)` | `Signed<AnchorResult>` | yes |
63
75
  | `anchored(signature)` | `AnchorStatus` | free |
64
76
  | `health()` | service status | free |
65
77
 
66
78
  Errors throw `OnchainDiligenceError` with the HTTP `status` and a message.
67
79
 
68
- ## Verifying an attestation
80
+ ## Commerce client (agent payments)
81
+
82
+ ```bash
83
+ npm install @onchaindiligence/sdk
84
+ ```
85
+
86
+ A separate, `@onchaindiligence/sdk/commerce` export orchestrates an agent's
87
+ merchant payment lifecycle against `mcp.onchaindiligence.com`:
88
+
89
+ > **Keep your wallet. Keep your payment provider. Add OCD once.**
69
90
 
70
- The `signature` is an Ed25519 signature over the response data plus issue metadata. Off-chain verification is straightforward with the public key at `/.well-known/attestation-key`:
91
+ OCD evaluates a proposed payment against your policy and observes/reconciles
92
+ what actually settled. It never holds a key, never authorizes a payment, and
93
+ never replaces your executor's own authorization — see
94
+ [`CommerceExecutor`](src/commerce/executor.ts).
71
95
 
72
96
  ```ts
73
- import { verify } from '@noble/ed25519'
74
- // fetch the public key, reconstruct the signed bytes, then verify(signature, message, pubKey)
97
+ import { createCommerceClient, MockCommerceExecutor, apiPurchasePolicy } from '@onchaindiligence/sdk/commerce'
98
+ import { NodeFileRecoveryStore } from '@onchaindiligence/sdk/commerce/node' // Node-only; browser code implements CommerceRecoveryStore itself
99
+
100
+ const ocd = createCommerceClient({ recovery: new NodeFileRecoveryStore('./ocd-recovery') })
101
+
102
+ const { policy } = apiPurchasePolicy({ maxAmount: '1.00', allowedNetwork: 'eip155:8453', allowedAsset: BASE_USDC })
103
+ const op = await ocd.open({ action: proposedPayment, policy })
104
+
105
+ const evaluation = await op.preflight()
106
+ if (evaluation.kind === 'blocked' || evaluation.kind === 'approval-required') return handleThat(evaluation)
107
+
108
+ const execution = await op.execute({ executor: myExecutor }) // e.g. new X402BaseUsdcExecutor({ signer: toClientEvmSigner(account) })
109
+ if (execution.kind !== 'execution-recorded') return handleThat(execution)
110
+
111
+ const result = await op.observeAndFinalize() // safe to retry while kind === 'pending'
112
+ console.log(result.receipt.receipt.execution.status) // read the fact, never infer "success" from existence
75
113
  ```
76
114
 
115
+ See **[examples/quickstart.ts](examples/quickstart.ts)** for the complete,
116
+ runnable, ~20-line integration (uses a mocked executor and an in-process demo
117
+ server — `npx tsx examples/quickstart.ts` costs nothing and needs no wallet).
118
+
119
+ Key pieces:
120
+
121
+ | Export | What it is |
122
+ |---|---|
123
+ | `createCommerceClient` / `CommerceOperation` | Orchestrates open → preflight → execute → observe/finalize. |
124
+ | `CommerceExecutor` | The contract your wallet/payment provider implements: `prepare` → `submit` → `resume`. Independent of OCD's policy decision by construction. |
125
+ | `X402BaseUsdcExecutor` | The one production executor: Base mainnet, USDC, x402 v2 exact. Its `recoveryMode` is honestly `'manual'` — see the file's own header for why. |
126
+ | `MockCommerceExecutor` | Deterministic, no-network executor for tests/docs. |
127
+ | `CommerceRecoveryStore` | Durable identity storage — required, no safe default. `NodeFileRecoveryStore` (`@onchaindiligence/sdk/commerce/node` — Node-only, wraps `node:fs`) survives a restart; implement the interface against your own database for a multi-instance deployment, or proxy it through your own local server for a browser UI (never store the secret fields in browser storage). `InMemoryRecoveryStore` is test-only. |
128
+ | `apiPurchasePolicy` / `approvalAboveThresholdPolicy` / `fixedRecipientPolicy` | Three starter policy templates — ordinary strict policy objects, no new semantics. |
129
+ | `buildEvidenceExport` | A minimal, deterministic, secret-free evidence manifest. |
130
+ | `client.getReceipt()` / `client.verifyReceipt()` | Free, structured, reuse OCD's converged verification contract — a convenience, not a stronger trust model than verifying offline yourself. |
131
+
132
+ Every lifecycle result is a discriminated union (`evaluation.kind`,
133
+ `execution.kind`, `result.kind`) — `'pending'` states carry a machine-readable
134
+ `safeNextAction`, `retryAfterSeconds`, and `mayAlreadyHavePaid`, so a `pending`
135
+ outcome is never confused with failure or with "safe to pay again."
136
+
137
+ ## Offline verification
138
+
139
+ The security-sensitive verifier performs zero network access. Supply key records
140
+ you independently chose to trust; merely downloading a public key does not make
141
+ it a trusted publisher identity. Version 2 uses domain-separated RFC 8785
142
+ canonical JSON, while explicit legacy v1 attestations retain their original
143
+ verification path.
144
+
145
+ ```ts
146
+ import { verifyAttestationOffline } from '@onchaindiligence/sdk'
147
+
148
+ const result = await verifyAttestationOffline(signed, trustedRegistry)
149
+ // result.state is exactly VALID, INVALID, or UNVERIFIABLE
150
+ console.log(result.components.signature, result.components.key_window)
151
+ ```
152
+
153
+ `verifyAttestationOnline()` is a separate convenience wrapper. It only treats
154
+ the discovered registry as identity authority when the caller explicitly sets
155
+ `trustRegistry: true` (or supplies an approval callback). The class method
156
+ `od.verifyAttestation()` remains an online compatibility wrapper for existing
157
+ clients; new security-sensitive code should use the standalone offline API.
158
+
159
+ The signature authenticates the signer's `issued_at` assertion. It does not by
160
+ itself prove objective time. A separately verified on-chain anchor can establish
161
+ an external “existed no later than” bound; freshness, key validity, signature
162
+ validity, and anchor status remain separate checks.
163
+
77
164
  **On-chain verification — read this first.** The EVM has no native Ed25519 precompile (only `ecrecover` for ECDSA), so verifying an Ed25519 signature *inside* a Solidity contract is expensive and non-trivial — it requires a full Ed25519 implementation in the contract. For most use cases, verify off-chain. If you need on-chain proof that a check happened, prefer the **anchoring** flow (`anchor()` / `anchored()`), which records the attestation hash on Tempo so a contract can check a `bytes32` rather than recover an Ed25519 signature. That's the cheaper, EVM-friendly path to on-chain verifiability.
78
165
 
79
166
  ## License
@@ -0,0 +1,98 @@
1
+ {
2
+ "corpus_version": 1,
3
+ "description": "Test-only OnChainDiligence v1/v2 trust-foundation fixtures. No production key material.",
4
+ "verifier_options": { "now": "2026-08-27T12:01:00.000Z" },
5
+ "trust_records": {
6
+ "active": {
7
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
8
+ "algorithm": "ed25519",
9
+ "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=\n-----END PUBLIC KEY-----\n",
10
+ "status": "active",
11
+ "valid_from": "2026-08-27T00:00:00.000Z",
12
+ "valid_until": null,
13
+ "status_changed_at": "2026-08-27T00:00:00.000Z"
14
+ },
15
+ "retired": {
16
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
17
+ "algorithm": "ed25519",
18
+ "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=\n-----END PUBLIC KEY-----\n",
19
+ "status": "retired",
20
+ "valid_from": "2026-08-27T00:00:00.000Z",
21
+ "valid_until": "2026-08-27T12:30:00.000Z",
22
+ "status_changed_at": "2026-08-27T12:30:00.000Z"
23
+ },
24
+ "compromised": {
25
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
26
+ "algorithm": "ed25519",
27
+ "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=\n-----END PUBLIC KEY-----\n",
28
+ "status": "compromised",
29
+ "valid_from": "2026-08-27T00:00:00.000Z",
30
+ "valid_until": null,
31
+ "status_changed_at": "2026-08-27T12:30:00.000Z",
32
+ "compromised_at": "2026-08-27T12:30:00.000Z"
33
+ }
34
+ },
35
+ "envelopes": {
36
+ "valid_v2": {
37
+ "data": { "address": "0x0000000000000000000000000000000000000001", "nested": { "empty": [], "unicode": "€ 😀" }, "sanctioned": false },
38
+ "attestation": {
39
+ "signed": true,
40
+ "schema_version": "onchaindiligence.attestation.v2",
41
+ "issuer": "https://api.onchaindiligence.com",
42
+ "purpose": "compliance-screening-result",
43
+ "issued_at": "2026-08-27T12:00:00.000Z",
44
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
45
+ "algorithm": "ed25519",
46
+ "canonicalization": "RFC8785",
47
+ "signature": "Q6znt7824vydlzqVP702c_U8krT3eqc30mmBtp2QYmfDC2vVj5LabHdRjeGbfE65aJGny3B_M6SzhcmSasADDQ"
48
+ }
49
+ },
50
+ "tampered_payload": {
51
+ "data": { "address": "0x0000000000000000000000000000000000000001", "nested": { "empty": [], "unicode": "€ 😀" }, "sanctioned": true },
52
+ "attestation": {
53
+ "signed": true,
54
+ "schema_version": "onchaindiligence.attestation.v2",
55
+ "issuer": "https://api.onchaindiligence.com",
56
+ "purpose": "compliance-screening-result",
57
+ "issued_at": "2026-08-27T12:00:00.000Z",
58
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
59
+ "algorithm": "ed25519",
60
+ "canonicalization": "RFC8785",
61
+ "signature": "Q6znt7824vydlzqVP702c_U8krT3eqc30mmBtp2QYmfDC2vVj5LabHdRjeGbfE65aJGny3B_M6SzhcmSasADDQ"
62
+ }
63
+ },
64
+ "valid_v1": {
65
+ "data": { "address": "0x0000000000000000000000000000000000000001", "nested": { "empty": [], "unicode": "€ 😀" }, "sanctioned": false },
66
+ "attestation": {
67
+ "signed": true,
68
+ "issued_at": "2026-08-27T12:00:00.000Z",
69
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
70
+ "algorithm": "ed25519",
71
+ "signature": "XOqtW1NgrkkPTIYmcHEEv86WRrQGzK4nEkf7qfXFVRrCwhlEVtffFFeawmaoLHQP5qdPJLugP_-8-OQJQqooDQ"
72
+ }
73
+ },
74
+ "unsupported_version": {
75
+ "data": { "address": "0x0000000000000000000000000000000000000001", "nested": { "empty": [], "unicode": "€ 😀" }, "sanctioned": false },
76
+ "attestation": {
77
+ "signed": true,
78
+ "schema_version": "onchaindiligence.attestation.v9",
79
+ "issuer": "https://api.onchaindiligence.com",
80
+ "purpose": "compliance-screening-result",
81
+ "issued_at": "2026-08-27T12:00:00.000Z",
82
+ "key_id": "ed25519-BuP9j9opu2CrWVV9",
83
+ "algorithm": "ed25519",
84
+ "canonicalization": "RFC8785",
85
+ "signature": "eZ6re44eR3zy8PXp93WJ7PDPRy-bovg5LNzh4uxjJpPIlhJIvOnWkSpTZIOPIJT9QG4GMRCiPL_jg9CHYtgtDQ"
86
+ }
87
+ }
88
+ },
89
+ "fixtures": [
90
+ { "id": "valid-v2-active", "expected_state": "VALID", "envelope": "valid_v2", "trust_records": ["active"] },
91
+ { "id": "valid-v2-retired", "expected_state": "VALID", "envelope": "valid_v2", "trust_records": ["retired"] },
92
+ { "id": "compromised-key", "expected_state": "INVALID", "envelope": "valid_v2", "trust_records": ["compromised"] },
93
+ { "id": "tampered-payload", "expected_state": "INVALID", "envelope": "tampered_payload", "trust_records": ["active"] },
94
+ { "id": "unknown-key", "expected_state": "UNVERIFIABLE", "envelope": "valid_v2", "trust_records": [] },
95
+ { "id": "valid-v1-legacy", "expected_state": "VALID", "envelope": "valid_v1", "trust_records": ["active"] },
96
+ { "id": "unsupported-version", "expected_state": "UNVERIFIABLE", "envelope": "unsupported_version", "trust_records": ["active"] }
97
+ ]
98
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "corpus_version": 1,
3
+ "source": "RFC 8785 sections 3.2.2 and 3.2.3, plus OnChainDiligence parser policy",
4
+ "canonicalization": [
5
+ {
6
+ "id": "recursive-order-arrays-empty-negative-zero",
7
+ "input": { "z": [], "n": -0, "a": { "y": true, "x": ["b", "a"], "empty": {} } },
8
+ "expected": "{\"a\":{\"empty\":{},\"x\":[\"b\",\"a\"],\"y\":true},\"n\":0,\"z\":[]}"
9
+ },
10
+ {
11
+ "id": "rfc8785-unicode-property-order",
12
+ "input": { "€": "Euro Sign", "\r": "Carriage Return", "דּ": "Hebrew Letter Dalet With Dagesh", "1": "One", "😀": "Emoji: Grinning Face", "€": "Control", "ö": "Latin Small Letter O With Diaeresis" },
13
+ "expected": "{\"\\r\":\"Carriage Return\",\"1\":\"One\",\"€\":\"Control\",\"ö\":\"Latin Small Letter O With Diaeresis\",\"€\":\"Euro Sign\",\"😀\":\"Emoji: Grinning Face\",\"דּ\":\"Hebrew Letter Dalet With Dagesh\"}"
14
+ },
15
+ {
16
+ "id": "rfc8785-numbers-and-escapes",
17
+ "input": { "numbers": [333333333.3333333, 1e30, 4.5, 0.002, 1e-27], "text": "line\n\"\\" },
18
+ "expected": "{\"numbers\":[333333333.3333333,1e+30,4.5,0.002,1e-27],\"text\":\"line\\n\\\"\\\\\"}"
19
+ },
20
+ {
21
+ "id": "boundary-safe-integers",
22
+ "input": [-9007199254740991, 9007199254740991],
23
+ "expected": "[-9007199254740991,9007199254740991]"
24
+ }
25
+ ],
26
+ "invalid_json": [
27
+ { "id": "duplicate-key", "input": "{\"data\":1,\"data\":2}", "error_code": "duplicate_key" },
28
+ { "id": "unsafe-positive-integer", "input": "{\"n\":9007199254740992}", "error_code": "unsafe_integer" },
29
+ { "id": "unsafe-negative-integer", "input": "{\"n\":-9007199254740992}", "error_code": "unsafe_integer" }
30
+ ]
31
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * client.ts — createCommerceClient (D2.5, Section 1).
3
+ *
4
+ * Orchestrates: open/resume -> preflight -> execute -> observe/finalize,
5
+ * calling onchaindiligence-mcp's D2.4 HTTP surface (POST /operations,
6
+ * POST /x402/lifecycle/preflight-payment, POST /operations/:id/execution-bindings(+/state),
7
+ * POST /operations/:id/finalize) while persisting every durable identity to
8
+ * the caller-supplied CommerceRecoveryStore BEFORE the network call that
9
+ * could make it ambiguous — never after.
10
+ *
11
+ * This class does NOT reimplement any D2.4 guarantee (idempotency, binding
12
+ * strength, finality) — it is a thin, honest orchestrator over the service
13
+ * primitives that already provide them. See each method for exactly which
14
+ * server endpoint it calls and why the local persistence is ordered the way
15
+ * it is.
16
+ */
17
+ import type { CommerceAction, CommercePolicy, CommercePublication, OperationStatus, ReceiptEnvelope } from './types.js';
18
+ import type { CommerceExecutor } from './executor.js';
19
+ import type { CommerceRecoveryStore, CommerceRecoveryRecord } from './recoveryStore.js';
20
+ import { type PreflightEvaluation, type ExecutionRecord, type FinalizeResult, type ResumeResult } from './results.js';
21
+ import { type EvidenceExportManifest } from './evidenceExport.js';
22
+ export declare class RecoveryRequiredError extends Error {
23
+ constructor(operationId: string);
24
+ }
25
+ export interface CreateCommerceClientOptions {
26
+ /** Base URL of the OCD MCP/x402 server. Defaults to production. */
27
+ endpoint?: string;
28
+ /** Durable recovery store. Required -- see recoveryStore.ts. There is no safe default. */
29
+ recovery: CommerceRecoveryStore;
30
+ /** When verifyReceipts is true, receipts returned by preflight/finalize are additionally checked via the free /verify-receipt endpoint (D2.5 Section 7) before being surfaced. Off by default: verification is a distinct concern a caller can invoke on its own via client.verifyReceipt(). */
31
+ trust?: {
32
+ verifyReceipts?: boolean;
33
+ };
34
+ fetch?: typeof globalThis.fetch;
35
+ }
36
+ export interface OpenParams {
37
+ /** If set and a local recovery record already exists for it, resumes that operation instead of creating a new one -- see this file's header. */
38
+ operationId?: string;
39
+ /** Developer-facing label for logs/UI only; never sent to OCD. */
40
+ intent?: string;
41
+ action: CommerceAction;
42
+ policy: CommercePolicy;
43
+ publication?: CommercePublication;
44
+ }
45
+ export declare class OnchainDiligenceCommerceClient {
46
+ private readonly endpoint;
47
+ private readonly recovery;
48
+ private readonly fetchImpl;
49
+ private readonly trust;
50
+ constructor(options: CreateCommerceClientOptions);
51
+ /** @internal */
52
+ apiFetch(path: string, init?: RequestInit): Promise<Response>;
53
+ /** @internal */
54
+ readError(res: Response): Promise<string>;
55
+ /** @internal */
56
+ recoveryStore(): CommerceRecoveryStore;
57
+ /** @internal */
58
+ trustOptions(): {
59
+ verifyReceipts?: boolean;
60
+ };
61
+ /** Opens a new operation, or resumes one already known locally by operationId. Never silently creates a second operation for an id that exists locally with different intent. */
62
+ open(params: OpenParams): Promise<CommerceOperation>;
63
+ /** Explicit resume after restart/lost-response, per D2.5 Section 6. Returns recovery-failed rather than throwing, since "the credential turned out to be wrong" is an expected, handleable outcome, not a programming error. */
64
+ resume(operationId: string, recoveryCredential: string): Promise<ResumeResult>;
65
+ /** Returns a CommerceOperation for an operation already known to the recovery store, without any network call. Use after resume() or across a process restart. */
66
+ load(operationId: string): Promise<CommerceOperation | null>;
67
+ /** D2.5 Section 7: free, structured, reuses the server's converged verifier -- no local re-implementation. */
68
+ verifyReceipt(receiptIdOrEnvelope: string | ReceiptEnvelope): Promise<{
69
+ state: 'VALID' | 'INVALID' | 'UNVERIFIABLE';
70
+ code: string;
71
+ message: string;
72
+ }>;
73
+ /** D2.5 Section 7: free, structured lookup by exact receipt id. */
74
+ getReceipt(receiptId: string): Promise<ReceiptEnvelope | null>;
75
+ }
76
+ export declare function createCommerceClient(options: CreateCommerceClientOptions): OnchainDiligenceCommerceClient;
77
+ export declare class CommerceOperation {
78
+ readonly operationId: string;
79
+ private readonly client;
80
+ private record;
81
+ private pendingPreflightInput;
82
+ private lastCommerceReceiptId;
83
+ private lastLifecycleEvidence;
84
+ /** Serializes execute() calls against THIS operation instance -- see execute()'s header comment for why. */
85
+ private executeQueue;
86
+ constructor(client: OnchainDiligenceCommerceClient, record: CommerceRecoveryRecord);
87
+ /** @internal */
88
+ setPendingPreflightInput(action: CommerceAction, policy: CommercePolicy, publication?: CommercePublication): void;
89
+ /** @internal -- exposed for evidence export and tests. */
90
+ currentRecord(): CommerceRecoveryRecord;
91
+ private reload;
92
+ private casUpdate;
93
+ /**
94
+ * Claims `clientSubmissionKey` for a fresh submission attempt -- but
95
+ * NEVER by blindly overwriting a value a concurrent claimant already won.
96
+ * Unlike casUpdate (which re-applies the SAME patch after a conflict,
97
+ * correct for "set this field to this exact value regardless"), a claim
98
+ * is "set this field to MY value ONLY IF NO ONE ELSE HAS ALREADY SET IT"
99
+ * -- so a conflict here means re-reading and checking on which value
100
+ * actually won, not retrying with a new one. This is what closes the race
101
+ * two concurrent execute() calls (in-process, via a shared store across
102
+ * processes, or across a restart) would otherwise have on this field.
103
+ */
104
+ private claimSubmissionSlot;
105
+ status(): Promise<OperationStatus>;
106
+ preflight(): Promise<PreflightEvaluation>;
107
+ private evaluationFromReceipt;
108
+ /**
109
+ * Serialized per operation instance (Section 15 test #9: "concurrent
110
+ * calls cannot cause duplicate submit"). Two overlapping execute() calls
111
+ * against the SAME CommerceOperation object run one after the other, so
112
+ * the second always observes the first's already-persisted
113
+ * clientSubmissionKey/executionRequestId/transactionHash and resumes
114
+ * instead of racing to claim a fresh identity. Cross-PROCESS concurrency
115
+ * is a different, already-covered case: the server's execution-bindings
116
+ * endpoint is idempotent by client_submission_key (D2.4), and a correctly
117
+ * implemented executor (see MockCommerceExecutor, X402BaseUsdcExecutor)
118
+ * refuses to submit twice for the same key on its own.
119
+ */
120
+ execute(params: {
121
+ executor: CommerceExecutor;
122
+ }): Promise<ExecutionRecord>;
123
+ private executeLocked;
124
+ private applyExecutionOutcome;
125
+ private updateBindingState;
126
+ observeAndFinalize(): Promise<FinalizeResult>;
127
+ /**
128
+ * Builds a minimal, deterministic evidence manifest from PUBLIC artifacts
129
+ * only (fetched fresh via the client's public receipt/status calls) —
130
+ * never touches this.record's secret fields (recoveryCredential,
131
+ * finalizationCapability), so there is no field here to forget to redact.
132
+ */
133
+ exportEvidence(): Promise<EvidenceExportManifest>;
134
+ }