@devlab.group/agent-commerce 0.1.0-alpha.0

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/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ Full text: https://www.apache.org/licenses/LICENSE-2.0.txt
package/README.md ADDED
@@ -0,0 +1,324 @@
1
+ <h1 align="center">Agent Commerce Gateway</h1>
2
+
3
+ <p align="center">
4
+ <strong>One backend. Agent-commerce protocols on the front. No proprietary middleman.</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="#quickstart"><img alt="Quickstart" src="https://img.shields.io/badge/quickstart-5%20minutes-2ea44f"></a>
9
+ <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"></a>
10
+ <img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178c6">
11
+ <img alt="MCP" src="https://img.shields.io/badge/MCP-supported-6b4fbb">
12
+ <img alt="x402" src="https://img.shields.io/badge/x402-supported-0052ff">
13
+ <img alt="Status" src="https://img.shields.io/badge/status-alpha-orange">
14
+ </p>
15
+
16
+ > **Alpha.** `v0.1.0-alpha` is experimental. Do not use it with production funds
17
+ > without an independent review. See [SECURITY.md](SECURITY.md).
18
+
19
+ ---
20
+
21
+ ## What it is, in ten seconds
22
+
23
+ You already have an HTTP API. AI agents want to **discover** it, **call** it and
24
+ **pay** for it — over protocols you did not write and do not want to maintain.
25
+
26
+ Agent Commerce Gateway sits in front of your existing API, in **your**
27
+ infrastructure, and does that for you. You describe an endpoint in a YAML file;
28
+ agents get an MCP tool and an x402 paywall. The money goes straight to your
29
+ wallet — the gateway never holds it, and never holds your keys.
30
+
31
+ ```text
32
+ Your existing API → Agent Commerce Gateway → AI Agent
33
+ MCP · x402 · receipts · doctor
34
+ ```
35
+
36
+ ## Demo
37
+
38
+ <!-- TODO(release): 15–30s GIF — left: buyer agent terminal, right: dashboard,
39
+ overlay: on-chain settlement. Replace this block before tagging. -->
40
+
41
+ ```text
42
+ [agent] Discovering resources over MCP...
43
+ [agent] Found: market_report — Premium Market Report (0.01 USDC)
44
+ [agent] Requesting resource...
45
+
46
+ [gateway] Payment required: 0.01 USDC → 0x7099…79C8
47
+ [buyer] Signing x402 authorisation...
48
+ [gateway] Payment verified
49
+ [gateway] Payment settled tx 0x4f2c…9ab1
50
+ [gateway] Calling merchant backend...
51
+ [gateway] Resource delivered
52
+
53
+ [receipt] payment: settled
54
+ [receipt] amount: 0.01 USDC
55
+ [receipt] merchant: 0x7099…79C8
56
+ [receipt] buyer balance 100.00 → 99.99 mUSDC
57
+ [receipt] merchant balance 0.00 → 0.01 mUSDC
58
+ ```
59
+
60
+ The dashboard at <http://localhost:5173> shows the same request as it happens.
61
+ It polls the authenticated events route on a short interval rather than
62
+ streaming: a browser `EventSource` cannot send the admin token, and the operator
63
+ routes are closed without one — so the SSE endpoint is reachable by a
64
+ header-capable client, never by a browser. Polling is the dashboard's intended
65
+ path, not a degraded mode.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ npx @devlab.group/agent-commerce --help # no install needed
71
+ npm install -g @devlab.group/agent-commerce # or install the `agent-commerce` binary
72
+ agent-commerce doctor
73
+ ```
74
+
75
+ Requires **Node >= 22**. One package ships two things: the `agent-commerce`
76
+ CLI (`init`, `validate`, `doctor`, `demo`) and a library for embedding the
77
+ gateway in your own process. A default install is ~49 MB and pulls no
78
+ blockchain or wallet dependencies at all.
79
+
80
+ ```ts
81
+ import { createGateway, loadConfig, receipts } from '@devlab.group/agent-commerce';
82
+
83
+ const config = await loadConfig({ path: 'config.yaml' });
84
+ const gateway = await createGateway({
85
+ config,
86
+ store: receipts({ path: './receipts.sqlite' }),
87
+ paymentProviders: [],
88
+ protocolAdapters: [],
89
+ });
90
+ const { url } = await gateway.listen;
91
+ ```
92
+
93
+ ### Optional peers — install only the rails you use
94
+
95
+ The MCP adapter and the x402 provider live on their own subpaths, because each
96
+ needs a dependency the rest of the package does not. x402 alone pulls a browser
97
+ wallet stack (wagmi, WalletConnect, Reown) worth ~572 MB, which a gateway
98
+ serving a free HTTP resource has no business installing.
99
+
100
+ | You want | Install | Import |
101
+ | ------------------------------ | ------------------------------ | ------------------------------------------ |
102
+ | gateway, config, receipts, CLI | `@devlab.group/agent-commerce` | `from '@devlab.group/agent-commerce'` |
103
+ | expose resources as MCP tools | `+ @modelcontextprotocol/sdk` | `from '@devlab.group/agent-commerce/mcp'` |
104
+ | accept x402 payments | `+ x402 viem` | `from '@devlab.group/agent-commerce/x402'` |
105
+
106
+ ```bash
107
+ npm install @devlab.group/agent-commerce @modelcontextprotocol/sdk x402 viem
108
+ ```
109
+
110
+ ```ts
111
+ import { mcp } from '@devlab.group/agent-commerce/mcp';
112
+ import { x402 } from '@devlab.group/agent-commerce/x402';
113
+ ```
114
+
115
+ Peers are pinned exactly: x402's
116
+ schemas and EIP-712 domains cross this boundary, so a version skew is a
117
+ correctness problem rather than a convenience one. Import a subpath without its
118
+ peer installed and Node fails at load naming the missing package — deliberately,
119
+ rather than starting a gateway that silently serves nothing.
120
+
121
+ ## Quickstart
122
+
123
+ Requirements: **Node >= 22**, **npm 10**, **Docker**. Nothing else — no API
124
+ keys, no real money, no manual blockchain setup.
125
+
126
+ ```bash
127
+ git clone <repo> && cd agent-commerce
128
+ npm install
129
+ docker compose up
130
+ ```
131
+
132
+ Then, in a second terminal:
133
+
134
+ ```bash
135
+ npm run agent-commerce -- doctor --config config-demo.yaml # verify the whole stack
136
+ npm run demo:agent # watch an agent buy something
137
+ ```
138
+
139
+ <sub><b>Linux only</b>, and only if your user is not UID/GID 1000 (check with
140
+ <code>id -u && id -g</code>): export <code>DOCKER_UID=$(id -u) DOCKER_GID=$(id -g)</code>
141
+ before <code>docker compose up</code>. The chain-deploy step runs as that user so the
142
+ deployment manifest it writes stays host-writable rather than root-owned. Docker
143
+ Desktop on macOS and Windows translates permissions through its VM and does not
144
+ need this.</sub>
145
+
146
+ That is the whole thing. The stack is a private Anvil chain, a mock USDC token,
147
+ a demo merchant API, the gateway and a dashboard — all local and disposable.
148
+
149
+ To stop and wipe state: `docker compose down -v`.
150
+
151
+ ## How it works
152
+
153
+ ```text
154
+ ┌──────────────────────────────────────────────────────┐
155
+ │ AI Agent │
156
+ └──────────────┬───────────────────────────────────────┘
157
+ │ MCP · HTTP + X-PAYMENT
158
+ ┌──────────────▼───────────────────────────────────────┐
159
+ │ Agent Commerce Gateway (yours) │
160
+ │ │
161
+ │ protocol adapters → ExecutionPipeline → … │
162
+ │ │ │
163
+ │ ┌─────────────────────┼──────────────┐ │
164
+ │ ▼ ▼ ▼ │
165
+ │ PaymentProvider BackendExecutor ReceiptStore │
166
+ │ (x402) (bounded HTTP) (SQLite) │
167
+ └────────┬─────────────────────┬───────────────────────┘
168
+ │ │
169
+ buyer → merchant ┌──────▼───────────────┐
170
+ (never through us) │ Your backend API │
171
+ └───────────────────────┘
172
+ ```
173
+
174
+ Every protocol adapter converges on **one execution pipeline**. That is what
175
+ makes payment enforcement a property of the system rather than something each
176
+ adapter has to remember. Full detail in
177
+ [docs/architecture.md](docs/architecture.md).
178
+
179
+ ## Configure a resource
180
+
181
+ ```yaml
182
+ resources:
183
+ market_report:
184
+ name: Premium Market Report
185
+ backend:
186
+ type: http
187
+ method: GET
188
+ url: ${MERCHANT_API_BASE_URL}/api/report
189
+ timeoutMs: 10000
190
+ pricing:
191
+ type: fixed
192
+ amount: "0.01"
193
+ currency: USDC
194
+ expose: [http, mcp]
195
+ payments: [x402]
196
+ ```
197
+
198
+ That is the integration. No SDK in your backend, no rewrite.
199
+
200
+ ```bash
201
+ npm run agent-commerce -- init # generate a config interactively
202
+ npm run agent-commerce -- validate # fails loudly, exits non-zero
203
+ ```
204
+
205
+ See [docs/configuration.md](docs/configuration.md).
206
+
207
+ ## Protocol support
208
+
209
+ | Protocol | Status | Pinned revision |
210
+ | --------------------- | --------- | ---------------------------------- |
211
+ | **MCP** | Supported | `@modelcontextprotocol/sdk@1.30.0` |
212
+ | **x402** | Supported | `x402@1.2.0`, scheme `exact`, EVM |
213
+ | **HTTP** | Supported | native routes |
214
+ | UCP | Planned | — |
215
+ | ACP · MPP · A2A · AP2 | Planned | — |
216
+
217
+ "Planned" means **no code ships for it**. Each adapter reports its own
218
+ `supportedSpec`, `capabilities` and `unsupported` list at runtime via
219
+ `GET /.well-known/agent-commerce` and `agent-commerce doctor` — so the claim is
220
+ checkable, not marketing. Detail: [docs/protocols.md](docs/protocols.md).
221
+
222
+ ## Payment model
223
+
224
+ - **Non-custodial.** The gateway never holds funds, and never asks for a
225
+ merchant or buyer private key. `payTo` is your address.
226
+ - **Fail closed.** Missing, malformed, expired, replayed, wrong-amount,
227
+ wrong-recipient, wrong-network and wrong-asset payments all fail — each with a
228
+ test.
229
+ - **Replay-safe twice over.** EIP-3009 stops a double spend on-chain; the
230
+ gateway additionally reserves a `replayKey` derived from the authorisation
231
+ before it settles anything.
232
+ - **Real settlement in CI.** The end-to-end test asserts the buyer's balance
233
+ falls and the merchant's rises by exactly the price, with a real transaction
234
+ hash in the receipt. A log line saying "payment successful" would not count.
235
+
236
+ Detail: [docs/payment-flow.md](docs/payment-flow.md).
237
+
238
+ ## Diagnostics
239
+
240
+ ```console
241
+ $ npm run agent-commerce -- doctor --config config-demo.yaml
242
+
243
+ PASS Config valid — 2 resource(s), merchant "Demo Data Store"
244
+ PASS Gateway healthy and ready at http://127.0.0.1:8080
245
+ PASS Backend 2/2 backend host(s) reachable
246
+ PASS Protocols http=on mcp=on (/mcp)
247
+ PASS Payments x402 enabled — network=base-sepolia, destination=0x7099…79C8, facilitator=local
248
+ INFO Payments (MPP) planned — not implemented in v0.1
249
+ PASS Storage sqlite schema v1 writable; receipts=2
250
+ PASS Protocol versions reported by gateway /.well-known/agent-commerce
251
+
252
+ Score: 7/7 checks passed
253
+ ```
254
+
255
+ That is real output, not an illustration. `doctor` also cross-checks the
256
+ gateway's *live* settlement configuration against what your local config
257
+ resolves to, and fails if they disagree — a diagnostic that passes while the
258
+ system is misconfigured is worse than none.
259
+
260
+ Exits non-zero if anything fails. `--json` for machines.
261
+
262
+ ## Exposure and access
263
+
264
+ The demo binds everything to `127.0.0.1`. Before putting the gateway anywhere
265
+ reachable by anyone else, know the split:
266
+
267
+ - **Agent routes** (`/api/resources/:id/invoke`, `/mcp`) are unauthenticated by
268
+ design — paid resources are protected by payment, not by a password.
269
+ - **Operator routes** (`/api/receipts`, `/api/events`, `/api/events/stream`) are
270
+ the merchant's commerce ledger: payer addresses, amounts, settlement hashes.
271
+ They require `server.adminToken`, and return **404** if none is configured.
272
+ - **Browsers** are governed by `server.allowedOrigins`, an explicit allowlist
273
+ that defaults to empty.
274
+ - **There is no rate limiting.** A free resource is an unauthenticated proxy to
275
+ your backend at whatever rate a caller chooses. Quotas and abuse controls
276
+ belong in your API or your edge.
277
+
278
+ [SECURITY.md](SECURITY.md) states plainly what this does and does not protect.
279
+
280
+ ## Live settlement — not in this release
281
+
282
+ **v0.1.0-alpha settles only against the local deterministic chain** (Anvil +
283
+ MockUSDC). There is no live mode, no flag to enable one, and no partial path
284
+ toward one: `facilitator.mode: "remote"` is rejected at config load, and the
285
+ x402 provider's health check requires an Anvil-only RPC method, so `/ready`
286
+ returns 503 against a real network. Settling real value is **planned**, not
287
+ shipped — see [docs/payment-flow.md](docs/payment-flow.md).
288
+
289
+ ## Development
290
+
291
+ ```bash
292
+ npm run verify # contract + lint + typecheck + test
293
+ npm run test:e2e # deterministic end-to-end, boots its own chain
294
+ ```
295
+
296
+ Foundry (`anvil`, `forge`, `cast`) is needed for the chain work.
297
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
298
+
299
+ ## Roadmap
300
+
301
+ **Now (v0.1.0-alpha)** — MCP, x402, receipts, doctor, deterministic demo.
302
+
303
+ **Next** — OpenAPI import · a stronger conformance suite · a `doctor` GitHub
304
+ Action · UCP · MPP · ACP · A2A · AP2 · Shopify and WooCommerce examples ·
305
+ PostgreSQL · richer observability.
306
+
307
+ New protocols land only after the adapter model survives real use. Scope
308
+ discipline is a release requirement, not a mood.
309
+
310
+ ## Documentation
311
+
312
+ | | |
313
+ | ---------------------------------------------- | ------------------------------------------- |
314
+ | [Architecture](docs/architecture.md) | how the pieces fit |
315
+ | [Payment flow](docs/payment-flow.md) | the paid round trip, and every way it fails |
316
+ | [Protocols](docs/protocols.md) | exactly what is and is not supported |
317
+ | [Configuration](docs/configuration.md) | `config.yaml` reference |
318
+ | [Security model](docs/security.md) | trust boundaries, and what we do not defend |
319
+ | [Contracts](docs/contracts.md) | the frozen cross-package contract |
320
+ | [Adapter guide](docs/contributing-adapters.md) | add a protocol or a payment rail |
321
+
322
+ ## Licence
323
+
324
+ [Apache-2.0](LICENSE).
@@ -0,0 +1,192 @@
1
+ import 'module';
2
+
3
+ // src/core/domain/event.ts
4
+ var COMMERCE_EVENT_TYPES = [
5
+ "resource.discovered",
6
+ "resource.requested",
7
+ "payment.required",
8
+ "payment.rejected",
9
+ "payment.verified",
10
+ "payment.settled",
11
+ "backend.called",
12
+ "backend.failed",
13
+ "resource.delivered"
14
+ ];
15
+
16
+ // src/core/domain/resource.ts
17
+ var DEFAULT_BACKEND_TIMEOUT_MS = 1e4;
18
+
19
+ // src/core/domain/wire.ts
20
+ var PAYMENT_INPUT_FIELD = "_payment";
21
+ var PAYMENT_HEADER = "x-payment";
22
+ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
23
+ var DELIVERY_SUMMARY_META_KEY = "agent-commerce/delivery";
24
+ function toDeliverySummary(outcome) {
25
+ const p = outcome.payment;
26
+ return {
27
+ requestId: outcome.requestId,
28
+ resourceId: outcome.resourceId,
29
+ receiptId: outcome.receipt.id,
30
+ deliveredAt: outcome.receipt.deliveredAt,
31
+ ...p !== void 0 ? {
32
+ payment: {
33
+ status: p.status,
34
+ amount: p.amount,
35
+ currency: p.currency,
36
+ ...p.externalReference !== void 0 ? { externalReference: p.externalReference } : {},
37
+ ...p.network !== void 0 ? { network: p.network } : {}
38
+ }
39
+ } : {}
40
+ };
41
+ }
42
+ function toPaymentRequiredEnvelope(outcome) {
43
+ const r = outcome.requirement;
44
+ return {
45
+ status: "payment-required",
46
+ code: "PAYMENT_REQUIRED",
47
+ requestId: outcome.requestId,
48
+ resourceId: outcome.resourceId,
49
+ message: `Payment of ${r.amount} ${r.currency} is required for resource "${outcome.resourceId}". Retry with a ${r.provider} payment proof.`,
50
+ payment: {
51
+ provider: r.provider,
52
+ version: r.challenge.version,
53
+ amount: r.amount,
54
+ currency: r.currency,
55
+ destination: r.destination,
56
+ ...r.network !== void 0 ? { network: r.network } : {},
57
+ ...r.asset !== void 0 ? { asset: r.asset } : {},
58
+ ...r.expiresAt !== void 0 ? { expiresAt: r.expiresAt } : {},
59
+ accepts: r.challenge.accepts
60
+ }
61
+ };
62
+ }
63
+ function toErrorEnvelope(error) {
64
+ const info = error.toInfo();
65
+ return {
66
+ status: "error",
67
+ code: info.code,
68
+ message: info.message,
69
+ retryable: info.retryable,
70
+ ...info.requestId !== void 0 ? { requestId: info.requestId } : {},
71
+ ...info.resourceId !== void 0 ? { resourceId: info.resourceId } : {},
72
+ ...info.details !== void 0 ? { details: info.details } : {}
73
+ };
74
+ }
75
+ function isPaymentRequiredEnvelope(value) {
76
+ return typeof value === "object" && value !== null && value.status === "payment-required" && typeof value.payment === "object";
77
+ }
78
+
79
+ // src/core/errors/codes.ts
80
+ var COMMERCE_ERROR_CODES = [
81
+ "CONFIG_INVALID",
82
+ "RESOURCE_NOT_FOUND",
83
+ "INPUT_INVALID",
84
+ "PAYMENT_REQUIRED",
85
+ "PAYMENT_INVALID",
86
+ "PAYMENT_REPLAYED",
87
+ "PAYMENT_PROVIDER_UNAVAILABLE",
88
+ "PAYMENT_SETTLEMENT_FAILED",
89
+ "BACKEND_TIMEOUT",
90
+ "BACKEND_ERROR",
91
+ "PROTOCOL_UNSUPPORTED",
92
+ "GATEWAY_BUSY",
93
+ "STORAGE_ERROR",
94
+ "INTERNAL_ERROR"
95
+ ];
96
+ var COMMERCE_ERROR_HTTP_STATUS = {
97
+ CONFIG_INVALID: 500,
98
+ RESOURCE_NOT_FOUND: 404,
99
+ INPUT_INVALID: 400,
100
+ PAYMENT_REQUIRED: 402,
101
+ PAYMENT_INVALID: 402,
102
+ PAYMENT_REPLAYED: 409,
103
+ PAYMENT_PROVIDER_UNAVAILABLE: 503,
104
+ PAYMENT_SETTLEMENT_FAILED: 502,
105
+ BACKEND_TIMEOUT: 504,
106
+ BACKEND_ERROR: 502,
107
+ PROTOCOL_UNSUPPORTED: 501,
108
+ GATEWAY_BUSY: 503,
109
+ STORAGE_ERROR: 500,
110
+ INTERNAL_ERROR: 500
111
+ };
112
+ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set([
113
+ "PAYMENT_PROVIDER_UNAVAILABLE",
114
+ "BACKEND_TIMEOUT",
115
+ // Load shedding is transient by definition: the caller should back off and
116
+ // try again. Before this code existed the MCP adapter's queue-full path threw
117
+ // PROTOCOL_UNSUPPORTED — HTTP 501, retryable:false — so an agent framework
118
+ // keyed on the machine-readable flag (which exists precisely so clients need
119
+ // not parse prose) would read a momentary throttle as a permanent capability
120
+ // gap and stop calling the tool.
121
+ "GATEWAY_BUSY"
122
+ ]);
123
+
124
+ // src/core/errors/commerce-error.ts
125
+ var CommerceError = class _CommerceError extends Error {
126
+ code;
127
+ httpStatus;
128
+ retryable;
129
+ details;
130
+ requestId;
131
+ resourceId;
132
+ constructor(code, message, options = {}) {
133
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
134
+ this.name = "CommerceError";
135
+ this.code = code;
136
+ this.httpStatus = options.httpStatus ?? COMMERCE_ERROR_HTTP_STATUS[code];
137
+ this.retryable = RETRYABLE_ERROR_CODES.has(code);
138
+ if (options.details !== void 0) this.details = options.details;
139
+ if (options.requestId !== void 0) this.requestId = options.requestId;
140
+ if (options.resourceId !== void 0) this.resourceId = options.resourceId;
141
+ Error.captureStackTrace?.(this, _CommerceError);
142
+ }
143
+ /** Non-secret, client-safe representation. */
144
+ toInfo() {
145
+ return {
146
+ code: this.code,
147
+ message: this.message,
148
+ httpStatus: this.httpStatus,
149
+ retryable: this.retryable,
150
+ ...this.requestId !== void 0 ? { requestId: this.requestId } : {},
151
+ ...this.resourceId !== void 0 ? { resourceId: this.resourceId } : {},
152
+ ...this.details !== void 0 ? { details: this.details } : {}
153
+ };
154
+ }
155
+ };
156
+ function isCommerceError(value) {
157
+ return value instanceof CommerceError;
158
+ }
159
+ function toCommerceError(value, fallbackCode = "INTERNAL_ERROR", fallbackMessage = "Unexpected internal error") {
160
+ if (isCommerceError(value)) return value;
161
+ return new CommerceError(fallbackCode, fallbackMessage, { cause: value });
162
+ }
163
+
164
+ // src/core/interfaces/logger.ts
165
+ var NOOP_LOGGER = {
166
+ debug: () => {
167
+ },
168
+ info: () => {
169
+ },
170
+ warn: () => {
171
+ },
172
+ error: () => {
173
+ },
174
+ child: () => NOOP_LOGGER
175
+ };
176
+
177
+ // src/core/interfaces/protocol-adapter.ts
178
+ function isHttpProtocolAdapter(adapter) {
179
+ return typeof adapter.handleHttp === "function";
180
+ }
181
+
182
+ // src/core/interfaces/runtime.ts
183
+ var systemClock = {
184
+ now: () => /* @__PURE__ */ new Date(),
185
+ nowIso: () => (/* @__PURE__ */ new Date()).toISOString(),
186
+ monotonicMs: () => performance.now()
187
+ };
188
+ var PACKAGE_VERSION = "0.1.0-alpha.0" ;
189
+
190
+ export { COMMERCE_ERROR_CODES, COMMERCE_ERROR_HTTP_STATUS, COMMERCE_EVENT_TYPES, CommerceError, DEFAULT_BACKEND_TIMEOUT_MS, DELIVERY_SUMMARY_META_KEY, NOOP_LOGGER, PACKAGE_VERSION, PAYMENT_HEADER, PAYMENT_INPUT_FIELD, PAYMENT_RESPONSE_HEADER, RETRYABLE_ERROR_CODES, isCommerceError, isHttpProtocolAdapter, isPaymentRequiredEnvelope, systemClock, toCommerceError, toDeliverySummary, toErrorEnvelope, toPaymentRequiredEnvelope };
191
+ //# sourceMappingURL=chunk-CZ6ZDAYD.js.map
192
+ //# sourceMappingURL=chunk-CZ6ZDAYD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/domain/event.ts","../src/core/domain/resource.ts","../src/core/domain/wire.ts","../src/core/errors/codes.ts","../src/core/errors/commerce-error.ts","../src/core/interfaces/logger.ts","../src/core/interfaces/protocol-adapter.ts","../src/core/interfaces/runtime.ts","../src/version.ts"],"names":[],"mappings":"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","file":"chunk-CZ6ZDAYD.js","sourcesContent":["/**\n * Canonical commerce events.\n *\n * FROZEN CONTRACT. Every event in a single flow shares one `requestId` so that\n * logs, payment attempts, backend calls and receipts are correlatable\n *.\n */\nimport type { IsoTimestamp } from './common.js';\n\nexport const COMMERCE_EVENT_TYPES = [\n 'resource.discovered',\n 'resource.requested',\n 'payment.required',\n 'payment.rejected',\n 'payment.verified',\n 'payment.settled',\n 'backend.called',\n 'backend.failed',\n 'resource.delivered',\n] as const;\n\nexport type CommerceEventType = (typeof COMMERCE_EVENT_TYPES)[number];\n\nexport interface CommerceEvent {\n readonly id: string;\n readonly type: CommerceEventType;\n readonly requestId: string;\n readonly resourceId?: string;\n readonly at: IsoTimestamp;\n /** Protocol adapter that originated the request, e.g. 'mcp' or 'http'. */\n readonly adapter?: string;\n readonly paymentProvider?: string;\n readonly durationMs?: number;\n readonly status?: 'ok' | 'error';\n /** Non-secret, structured detail. Must never contain keys, headers or proofs. */\n readonly data?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Sink for canonical events.\n *\n * Implementations must be non-throwing from the caller's perspective: an event\n * sink failure must never fail an otherwise successful commerce flow.\n */\nexport interface EventSink {\n emit(event: CommerceEvent): Promise<void>;\n}\n","/**\n * Canonical resource model.\n *\n * FROZEN CONTRACT.\n */\nimport type { DecimalAmount, JsonSchema, PaymentMethodName, ProtocolName } from './common.js';\n\n/** HTTP methods a backend handler may use. */\nexport type BackendMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/**\n * Describes how the gateway calls the merchant's existing backend.\n *\n * Backend URLs are administrator-controlled configuration only. User- or\n * agent-controlled backend URLs are forbidden (see docs/security.md, SSRF).\n * Secret header values must come from `${ENV_VAR}` placeholders resolved by\n * the config loader, never from plaintext configuration.\n */\nexport interface BackendHandler {\n readonly type: 'http';\n readonly method: BackendMethod;\n readonly url: string;\n readonly headers?: Readonly<Record<string, string>>;\n /** Hard upper bound on the backend call. Defaults to DEFAULT_BACKEND_TIMEOUT_MS. */\n readonly timeoutMs?: number;\n}\n\n/** Default backend timeout when a resource does not specify one. */\nexport const DEFAULT_BACKEND_TIMEOUT_MS = 10_000;\n\n/**\n * Canonical pricing.\n *\n * `dynamic` exists in the type system for forward compatibility but is\n * REJECTED by config validation in v0.1.0-alpha.\n */\nexport type Pricing =\n | { readonly type: 'free' }\n | {\n readonly type: 'fixed';\n readonly amount: DecimalAmount;\n readonly currency: string;\n }\n | {\n readonly type: 'dynamic';\n readonly resolver: string;\n };\n\n/**\n * A merchant capability exposed to agents.\n *\n * The canonical resource is the single source of truth: protocol adapters map\n * it outward, they never define their own parallel notion of a resource.\n */\nexport interface CommerceResource {\n readonly id: string;\n readonly name: string;\n readonly description?: string;\n readonly inputSchema?: JsonSchema;\n readonly outputSchema?: JsonSchema;\n readonly handler: BackendHandler;\n readonly pricing: Pricing;\n readonly exposedVia: readonly ProtocolName[];\n readonly paymentMethods: readonly PaymentMethodName[];\n}\n\n/** Read-only view of every configured resource. */\nexport interface ResourceRegistry {\n get(id: string): CommerceResource | undefined;\n list(): readonly CommerceResource[];\n /** Resources exposed through a given protocol surface. */\n listExposedVia(protocol: ProtocolName): readonly CommerceResource[];\n has(id: string): boolean;\n}\n","/**\n * Gateway-defined wire envelopes.\n *\n * FROZEN CONTRACT. These are the *only* representations of \"payment required\"\n * and \"error\" that leave the gateway, whichever protocol carries them. The HTTP\n * routes, the MCP adapter and the demo buyer agent all use these helpers so the\n * three can never drift apart.\n */\n\nimport type { CommerceError, CommerceErrorCode } from '../errors/index.js';\nimport type { DecimalAmount, IsoTimestamp, PaymentMethodName } from './common.js';\nimport type { DeliveredOutcome, PaymentRequiredOutcome } from './request.js';\n\n/**\n * Reserved input property carrying a payment proof on protocols that have no\n * header channel (MCP tool calls). Over HTTP the `X-PAYMENT` header is used.\n */\nexport const PAYMENT_INPUT_FIELD = '_payment';\n\n/** HTTP header carrying the payment proof. */\nexport const PAYMENT_HEADER = 'x-payment';\n\n/** HTTP response header carrying the settlement reference. */\nexport const PAYMENT_RESPONSE_HEADER = 'x-payment-response';\n\n/**\n * Key under which a protocol adapter attaches a {@link DeliverySummary} to its\n * result metadata — MCP's `CallToolResult._meta`, and anywhere else a protocol\n * offers an out-of-band metadata channel.\n *\n * Frozen deliberately. The bug this whole mechanism exists to fix was two\n * surfaces disagreeing about what a payer gets back; a key name enforced only\n * by one side's tests would reintroduce exactly that drift. Producer and\n * consumer both import this constant.\n *\n * Follows MCP's `<namespace>/<name>` convention for `_meta` keys.\n */\nexport const DELIVERY_SUMMARY_META_KEY = 'agent-commerce/delivery';\n\nexport interface PaymentRequiredEnvelope {\n readonly status: 'payment-required';\n readonly code: 'PAYMENT_REQUIRED';\n readonly requestId: string;\n readonly resourceId: string;\n readonly message: string;\n readonly payment: {\n readonly provider: PaymentMethodName;\n readonly version: string;\n readonly amount: DecimalAmount;\n readonly currency: string;\n readonly destination: string;\n readonly network?: string;\n readonly asset?: string;\n readonly expiresAt?: IsoTimestamp;\n /** Provider-native requirement objects, passed through verbatim. */\n readonly accepts: readonly Readonly<Record<string, unknown>>[];\n };\n}\n\nexport interface ErrorEnvelope {\n readonly status: 'error';\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * What a buyer learns about their own completed purchase.\n *\n * A payer is entitled to the record of their own transaction — the request it\n * belonged to, whether it settled, and the settlement reference they can check\n * on-chain. They are NOT entitled to the merchant's ledger, which is why\n * `/api/receipts` is an operator route behind `server.adminToken`.\n *\n * Before this existed the two surfaces disagreed: HTTP callers received a\n * settlement summary in `X-PAYMENT-RESPONSE` while MCP callers received\n * nothing, so an MCP buyer's only route to their own receipt was the merchant's\n * ledger. Both surfaces now emit this same shape.\n */\nexport interface DeliverySummary {\n readonly requestId: string;\n readonly resourceId: string;\n readonly receiptId: string;\n readonly deliveredAt: IsoTimestamp;\n readonly payment?: {\n readonly status: 'verified' | 'settled' | 'rejected';\n readonly amount: DecimalAmount;\n readonly currency: string;\n /** Settlement reference, e.g. an on-chain transaction hash. */\n readonly externalReference?: string;\n readonly network?: string;\n };\n}\n\n/** Build the payer-facing summary of a delivered outcome. */\nexport function toDeliverySummary(outcome: DeliveredOutcome): DeliverySummary {\n const p = outcome.payment;\n return {\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n receiptId: outcome.receipt.id,\n deliveredAt: outcome.receipt.deliveredAt,\n ...(p !== undefined\n ? {\n payment: {\n status: p.status,\n amount: p.amount,\n currency: p.currency,\n ...(p.externalReference !== undefined\n ? { externalReference: p.externalReference }\n : {}),\n ...(p.network !== undefined ? { network: p.network } : {}),\n },\n }\n : {}),\n };\n}\n\nexport function toPaymentRequiredEnvelope(\n outcome: PaymentRequiredOutcome,\n): PaymentRequiredEnvelope {\n const r = outcome.requirement;\n return {\n status: 'payment-required',\n code: 'PAYMENT_REQUIRED',\n requestId: outcome.requestId,\n resourceId: outcome.resourceId,\n message: `Payment of ${r.amount} ${r.currency} is required for resource \"${outcome.resourceId}\". Retry with a ${r.provider} payment proof.`,\n payment: {\n provider: r.provider,\n version: r.challenge.version,\n amount: r.amount,\n currency: r.currency,\n destination: r.destination,\n ...(r.network !== undefined ? { network: r.network } : {}),\n ...(r.asset !== undefined ? { asset: r.asset } : {}),\n ...(r.expiresAt !== undefined ? { expiresAt: r.expiresAt } : {}),\n accepts: r.challenge.accepts,\n },\n };\n}\n\nexport function toErrorEnvelope(error: CommerceError): ErrorEnvelope {\n const info = error.toInfo();\n return {\n status: 'error',\n code: info.code,\n message: info.message,\n retryable: info.retryable,\n ...(info.requestId !== undefined ? { requestId: info.requestId } : {}),\n ...(info.resourceId !== undefined ? { resourceId: info.resourceId } : {}),\n ...(info.details !== undefined ? { details: info.details } : {}),\n };\n}\n\nexport function isPaymentRequiredEnvelope(value: unknown): value is PaymentRequiredEnvelope {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as PaymentRequiredEnvelope).status === 'payment-required' &&\n typeof (value as PaymentRequiredEnvelope).payment === 'object'\n );\n}\n","/**\n * Typed domain error codes.\n *\n * FROZEN CONTRACT. Protocol adapters map these codes into their own response\n * format. Never throw a bare string or untyped Error across a package boundary.\n */\nexport const COMMERCE_ERROR_CODES = [\n 'CONFIG_INVALID',\n 'RESOURCE_NOT_FOUND',\n 'INPUT_INVALID',\n 'PAYMENT_REQUIRED',\n 'PAYMENT_INVALID',\n 'PAYMENT_REPLAYED',\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'PAYMENT_SETTLEMENT_FAILED',\n 'BACKEND_TIMEOUT',\n 'BACKEND_ERROR',\n 'PROTOCOL_UNSUPPORTED',\n 'GATEWAY_BUSY',\n 'STORAGE_ERROR',\n 'INTERNAL_ERROR',\n] as const;\n\nexport type CommerceErrorCode = (typeof COMMERCE_ERROR_CODES)[number];\n\n/**\n * Canonical HTTP status for each error code.\n *\n * Paid resources fail closed: every payment failure maps to a 4xx/5xx status\n * and never to a successful delivery.\n */\nexport const COMMERCE_ERROR_HTTP_STATUS: Readonly<Record<CommerceErrorCode, number>> = {\n CONFIG_INVALID: 500,\n RESOURCE_NOT_FOUND: 404,\n INPUT_INVALID: 400,\n PAYMENT_REQUIRED: 402,\n PAYMENT_INVALID: 402,\n PAYMENT_REPLAYED: 409,\n PAYMENT_PROVIDER_UNAVAILABLE: 503,\n PAYMENT_SETTLEMENT_FAILED: 502,\n BACKEND_TIMEOUT: 504,\n BACKEND_ERROR: 502,\n PROTOCOL_UNSUPPORTED: 501,\n GATEWAY_BUSY: 503,\n STORAGE_ERROR: 500,\n INTERNAL_ERROR: 500,\n};\n\n/** Codes for which a client may reasonably retry the same request. */\nexport const RETRYABLE_ERROR_CODES: ReadonlySet<CommerceErrorCode> = new Set([\n 'PAYMENT_PROVIDER_UNAVAILABLE',\n 'BACKEND_TIMEOUT',\n // Load shedding is transient by definition: the caller should back off and\n // try again. Before this code existed the MCP adapter's queue-full path threw\n // PROTOCOL_UNSUPPORTED — HTTP 501, retryable:false — so an agent framework\n // keyed on the machine-readable flag (which exists precisely so clients need\n // not parse prose) would read a momentary throttle as a permanent capability\n // gap and stop calling the tool.\n 'GATEWAY_BUSY',\n]);\n","/**\n * The single error type crossing package boundaries.\n *\n * FROZEN CONTRACT.\n */\nimport {\n COMMERCE_ERROR_HTTP_STATUS,\n type CommerceErrorCode,\n RETRYABLE_ERROR_CODES,\n} from './codes.js';\n\nexport interface CommerceErrorOptions {\n /** Structured, non-secret detail safe to return to a client. */\n readonly details?: Readonly<Record<string, unknown>>;\n readonly cause?: unknown;\n readonly requestId?: string;\n readonly resourceId?: string;\n /** Override the default HTTP status for this code. */\n readonly httpStatus?: number;\n}\n\n/** Serialisable wire form of a CommerceError. */\nexport interface CommerceErrorInfo {\n readonly code: CommerceErrorCode;\n readonly message: string;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly requestId?: string;\n readonly resourceId?: string;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\nexport class CommerceError extends Error {\n readonly code: CommerceErrorCode;\n readonly httpStatus: number;\n readonly retryable: boolean;\n readonly details?: Readonly<Record<string, unknown>>;\n readonly requestId?: string;\n readonly resourceId?: string;\n\n constructor(code: CommerceErrorCode, message: string, options: CommerceErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'CommerceError';\n this.code = code;\n this.httpStatus = options.httpStatus ?? COMMERCE_ERROR_HTTP_STATUS[code];\n this.retryable = RETRYABLE_ERROR_CODES.has(code);\n if (options.details !== undefined) this.details = options.details;\n if (options.requestId !== undefined) this.requestId = options.requestId;\n if (options.resourceId !== undefined) this.resourceId = options.resourceId;\n Error.captureStackTrace?.(this, CommerceError);\n }\n\n /** Non-secret, client-safe representation. */\n toInfo(): CommerceErrorInfo {\n return {\n code: this.code,\n message: this.message,\n httpStatus: this.httpStatus,\n retryable: this.retryable,\n ...(this.requestId !== undefined ? { requestId: this.requestId } : {}),\n ...(this.resourceId !== undefined ? { resourceId: this.resourceId } : {}),\n ...(this.details !== undefined ? { details: this.details } : {}),\n };\n }\n}\n\nexport function isCommerceError(value: unknown): value is CommerceError {\n return value instanceof CommerceError;\n}\n\n/**\n * Normalise any thrown value into a CommerceError.\n *\n * Used at package boundaries so an unexpected failure never escapes untyped.\n *\n * SECURITY — do not \"improve\" this by propagating `value.message`.\n * `CommerceError.message` is client-visible: it is serialised by `toInfo()`\n * and `toErrorEnvelope()` and returned over HTTP and MCP. An arbitrary thrown\n * Error's message routinely carries internal detail — database connection\n * strings, internal hostnames, filesystem paths, backend URLs. Copying it into\n * a client-facing field turns every unexpected exception into an information\n * disclosure.\n *\n * The original value is preserved on `cause`, which is never serialised, so\n * logs and diagnostics keep the full detail while clients get only the\n * caller's deliberate, reviewed `fallbackMessage`.\n *\n * A caller that *wants* a specific client-facing message passes one:\n * toCommerceError(err, 'BACKEND_ERROR', 'Backend call failed')\n */\nexport function toCommerceError(\n value: unknown,\n fallbackCode: CommerceErrorCode = 'INTERNAL_ERROR',\n fallbackMessage = 'Unexpected internal error',\n): CommerceError {\n if (isCommerceError(value)) return value;\n return new CommerceError(fallbackCode, fallbackMessage, { cause: value });\n}\n","/**\n * Minimal structural logger interface.\n *\n * Core must not depend on pino directly; the gateway injects a pino instance\n * that satisfies this shape. Never log secrets: private keys, seed phrases,\n * Authorization headers or payment authorisation payloads.\n */\nexport interface Logger {\n debug(obj: Record<string, unknown>, msg?: string): void;\n info(obj: Record<string, unknown>, msg?: string): void;\n warn(obj: Record<string, unknown>, msg?: string): void;\n error(obj: Record<string, unknown>, msg?: string): void;\n child(bindings: Record<string, unknown>): Logger;\n}\n\n/** A logger that discards everything. Useful in unit tests. */\nexport const NOOP_LOGGER: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n child: () => NOOP_LOGGER,\n};\n","/**\n * Protocol adapter boundary.\n *\n * FROZEN CONTRACT. An adapter translates a protocol's wire format into a\n * `CanonicalRequest`, hands it to the execution pipeline and maps the outcome\n * back. It must not call merchant backends, implement payment logic, or hold\n * its own copy of the canonical model.\n *\n * Adapter isolation: a failure while starting or serving one adapter must not\n * prevent the others from operating.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { AdapterDescriptor, AdapterHealth, ProtocolName } from '../domain/common.js';\nimport type { EventSink } from '../domain/event.js';\nimport type { ExecutionPipeline } from '../domain/request.js';\nimport type { ResourceRegistry } from '../domain/resource.js';\nimport type { Logger } from './logger.js';\nimport type { Clock, IdGenerator } from './runtime.js';\n\n/** Everything an adapter is given. Nothing else is available to it. */\nexport interface ProtocolAdapterContext {\n readonly pipeline: ExecutionPipeline;\n readonly resources: ResourceRegistry;\n readonly events: EventSink;\n readonly logger: Logger;\n readonly clock: Clock;\n readonly ids: IdGenerator;\n /** Externally reachable base URL of the gateway, from config. */\n readonly publicBaseUrl: string;\n}\n\nexport interface ProtocolAdapter {\n readonly name: ProtocolName;\n readonly descriptor: AdapterDescriptor;\n\n start(context: ProtocolAdapterContext): Promise<void>;\n health(): Promise<AdapterHealth>;\n stop(): Promise<void>;\n}\n\n/**\n * A protocol adapter served over HTTP.\n *\n * The gateway mounts `handleHttp` at `mountPath` using raw Node request and\n * response objects, so adapters stay independent of the HTTP framework.\n *\n * GUARANTEE — the request body is **unconsumed**.\n *\n * `req` is handed over with its stream unread: the gateway suppresses every\n * body parser on this mount, so an adapter whose SDK reads the body itself\n * (MCP's Streamable HTTP transport does) can do so. The adapter owns reading,\n * size-limiting beyond the gateway's own cap, and parsing.\n *\n * This is load-bearing and easy to break silently. Fastify pre-registers\n * exact-match parsers for `application/json` and `text/plain` and prefers them\n * over a `'*'` wildcard, so registering only a wildcard no-op leaves the\n * built-in JSON parser draining the body first — the adapter then reads an\n * empty stream and every request fails to parse. That exact regression shipped\n * once and no unit test caught it, because adapter conformance suites drive\n * `handleHttp` over a bare `node:http` server and never traverse the gateway.\n *\n * Any change to how adapters are mounted must be covered by a test that POSTs\n * a real JSON body through the real gateway and asserts the adapter received\n * it intact.\n */\nexport interface HttpProtocolAdapter extends ProtocolAdapter {\n /** Path prefix the gateway mounts this adapter at, e.g. '/mcp'. */\n readonly mountPath: string;\n handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void>;\n}\n\nexport function isHttpProtocolAdapter(adapter: ProtocolAdapter): adapter is HttpProtocolAdapter {\n return typeof (adapter as HttpProtocolAdapter).handleHttp === 'function';\n}\n","/**\n * Injected runtime primitives.\n *\n * Everything non-deterministic (time, ids) is injected so tests can be exact.\n */\nimport type { IsoTimestamp } from '../domain/common.js';\n\nexport interface Clock {\n now(): Date;\n nowIso(): IsoTimestamp;\n /** Milliseconds since an arbitrary origin; use for durations only. */\n monotonicMs(): number;\n}\n\nexport interface IdGenerator {\n /** Opaque, collision-resistant identifier. */\n next(prefix?: string): string;\n}\n\nexport const systemClock: Clock = {\n now: () => new Date(),\n nowIso: () => new Date().toISOString(),\n monotonicMs: () => performance.now(),\n};\n","/**\n * The package version, resolved once for the whole package.\n *\n * Two paths, deliberately:\n *\n * - **Development** reads the root manifest, so the number is always live.\n * - **The published bundle cannot.** tsup collapses `src/**` into\n * `dist/index.js` and `dist/cli/index.js`, so any `require('../../package.json')`\n * written against a source path resolves from the wrong depth once bundled —\n * which is exactly how this broke during the single-package refactor, and how\n * it earlier produced a CLI reporting `v0.0.0-unknown`. The build injects\n * `__OAC_PACKAGE_VERSION__` instead (see tsup.config.ts).\n *\n * `typeof` on an undeclared identifier is safe in JS, so the dev path is\n * unaffected by the constant not existing.\n */\nimport { createRequire } from 'node:module';\n\ndeclare const __OAC_PACKAGE_VERSION__: string | undefined;\n\nfunction readFromManifest(): string {\n try {\n const require = createRequire(import.meta.url);\n // src/version.ts -> repository root\n return (require('../package.json') as { version?: string }).version ?? '0.0.0-unknown';\n } catch {\n return '0.0.0-unknown';\n }\n}\n\nexport const PACKAGE_VERSION: string =\n typeof __OAC_PACKAGE_VERSION__ === 'string' ? __OAC_PACKAGE_VERSION__ : readFromManifest();\n"]}