@integraledger/agent-guard 0.9.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/CHANGELOG.md +57 -0
- package/LICENSE +202 -0
- package/NOTICE +15 -0
- package/README.md +154 -0
- package/dist/decision.d.ts +26 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +28 -0
- package/dist/decision.js.map +1 -0
- package/dist/evaluate.d.ts +15 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +112 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/fetch.d.ts +61 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +126 -0
- package/dist/fetch.js.map +1 -0
- package/dist/fingerprint.d.ts +14 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +15 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +32 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +8 -0
- package/dist/log.js.map +1 -0
- package/dist/mechanical.d.ts +30 -0
- package/dist/mechanical.d.ts.map +1 -0
- package/dist/mechanical.js +35 -0
- package/dist/mechanical.js.map +1 -0
- package/dist/policy.d.ts +36 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +96 -0
- package/dist/policy.js.map +1 -0
- package/dist/proposal-ack.d.ts +111 -0
- package/dist/proposal-ack.d.ts.map +1 -0
- package/dist/proposal-ack.js +114 -0
- package/dist/proposal-ack.js.map +1 -0
- package/dist/proposal-acp.d.ts +18 -0
- package/dist/proposal-acp.d.ts.map +1 -0
- package/dist/proposal-acp.js +72 -0
- package/dist/proposal-acp.js.map +1 -0
- package/dist/proposal-ap2.d.ts +121 -0
- package/dist/proposal-ap2.d.ts.map +1 -0
- package/dist/proposal-ap2.js +112 -0
- package/dist/proposal-ap2.js.map +1 -0
- package/dist/proposal-mpp.d.ts +44 -0
- package/dist/proposal-mpp.d.ts.map +1 -0
- package/dist/proposal-mpp.js +106 -0
- package/dist/proposal-mpp.js.map +1 -0
- package/dist/proposal-universal.d.ts +239 -0
- package/dist/proposal-universal.d.ts.map +1 -0
- package/dist/proposal-universal.js +470 -0
- package/dist/proposal-universal.js.map +1 -0
- package/dist/proposal.d.ts +33 -0
- package/dist/proposal.d.ts.map +1 -0
- package/dist/proposal.js +107 -0
- package/dist/proposal.js.map +1 -0
- package/dist/transact.d.ts +24 -0
- package/dist/transact.d.ts.map +1 -0
- package/dist/transact.js +11 -0
- package/dist/transact.js.map +1 -0
- package/package.json +81 -0
- package/src/decision.ts +54 -0
- package/src/evaluate.ts +176 -0
- package/src/fetch.ts +169 -0
- package/src/fingerprint.ts +27 -0
- package/src/index.ts +68 -0
- package/src/log.ts +34 -0
- package/src/mechanical.ts +71 -0
- package/src/policy.ts +135 -0
- package/src/proposal-ack.ts +216 -0
- package/src/proposal-acp.ts +89 -0
- package/src/proposal-ap2.ts +195 -0
- package/src/proposal-mpp.ts +125 -0
- package/src/proposal-universal.ts +671 -0
- package/src/proposal.ts +170 -0
- package/src/transact.ts +34 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { decodeDeclaredRead, readAtPath, readDeclaredPaths, readFromContainer, } from "@integraledger/lcp-binding-core";
|
|
2
|
+
import { placementFor, } from "@integraledger/lcp-placements";
|
|
3
|
+
import { parseProposalFromChallenge, } from "./proposal.js";
|
|
4
|
+
import { parseProposalFromAcpCheckout } from "./proposal-acp.js";
|
|
5
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
6
|
+
// Structural predicates. Every read goes through `readAtPath`, never through `doc.x` or `doc["x"]`, so the
|
|
7
|
+
// own-property and array-traversal rules a hostile wire is read under are binding-core's audited ones rather
|
|
8
|
+
// than a second set invented here. A prototype key answering as though it were a declared field is exactly
|
|
9
|
+
// how a detector gets talked into naming the wrong protocol.
|
|
10
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function isStringAt(doc, path) {
|
|
15
|
+
return typeof readAtPath(doc, path) === "string";
|
|
16
|
+
}
|
|
17
|
+
function isArrayAt(doc, path) {
|
|
18
|
+
return Array.isArray(readAtPath(doc, path));
|
|
19
|
+
}
|
|
20
|
+
function stringAtIsOneOf(doc, path, permitted) {
|
|
21
|
+
const value = readAtPath(doc, path);
|
|
22
|
+
// `some` rather than a `typeof` guard plus `includes`: the guard would be a branch no input can take, since
|
|
23
|
+
// a list of strings never contains a non-string, and a branch nothing reaches is one no test can constrain.
|
|
24
|
+
return permitted.some((token) => token === value);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* ACP's `CheckoutSession.status` — the closed eleven-value enum, and the discriminant that separates an ACP
|
|
28
|
+
* session from every other checkout-shaped document in the set.
|
|
29
|
+
*
|
|
30
|
+
* DERIVED, NOT AUTHORED — from the live ACP schema, which `placement-acp` enumerated protocol-side in a
|
|
31
|
+
* write condition it no longer declares (checked 2026-07-30).
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ THIS COPY NO LONGER HAS A DRIFT GUARD. It was pinned equal to the manifest's write condition through
|
|
34
|
+
* the registry, but the protocol line declares no `writeCondition` on any ACP manifest — its `readAlso` aliases
|
|
35
|
+
* carry `{path, encoding}` only — so the enum is not published anywhere to pin against. The pin test was
|
|
36
|
+
* deleted rather than loosened (an assertion over an absent field guards nothing). Re-derive this list
|
|
37
|
+
* against the live ACP schema when touching it; the discriminant tests only prove it matches our fixtures.
|
|
38
|
+
*/
|
|
39
|
+
export const ACP_SESSION_STATUS = [
|
|
40
|
+
"incomplete",
|
|
41
|
+
"not_ready_for_payment",
|
|
42
|
+
"requires_escalation",
|
|
43
|
+
"authentication_required",
|
|
44
|
+
"ready_for_payment",
|
|
45
|
+
"pending_approval",
|
|
46
|
+
"complete_in_progress",
|
|
47
|
+
"completed",
|
|
48
|
+
"canceled",
|
|
49
|
+
"in_progress",
|
|
50
|
+
"expired",
|
|
51
|
+
];
|
|
52
|
+
/**
|
|
53
|
+
* The two Verifiable Intent Autonomous-mode credential types, derived from Mastercard's own open-mandate
|
|
54
|
+
* definitions. Formerly pinned against `placement-mastercard-vi`'s write condition; that placement is now
|
|
55
|
+
* declaration-only (LCP v1.38 §C.7 — an unregistered constraint type gets the WHOLE mandate rejected by a
|
|
56
|
+
* stock verifier), so it declares no write condition and there is nothing to pin against. Same standing as
|
|
57
|
+
* {@link ACP_SESSION_STATUS}: hand-kept, re-derive against the host when touching it.
|
|
58
|
+
*/
|
|
59
|
+
export const VI_OPEN_MANDATE_VCT = [
|
|
60
|
+
"mandate.checkout.open.1",
|
|
61
|
+
"mandate.payment.open.1",
|
|
62
|
+
];
|
|
63
|
+
/** The two AP2 v0.2 mandate DataPart keys. An envelope carrying either is carrying an AP2 mandate. */
|
|
64
|
+
const AP2_MANDATE_DATA_KEYS = [
|
|
65
|
+
"ap2.mandates.CheckoutMandateSdJwt",
|
|
66
|
+
"ap2.mandates.PaymentMandateSdJwt",
|
|
67
|
+
];
|
|
68
|
+
/** Visa TAP's two agent-recognition signature tags, as they appear quoted in an RFC 9421 `Signature-Input`. */
|
|
69
|
+
const TAP_SIGNATURE_TAGS = [
|
|
70
|
+
"agent-browser-auth",
|
|
71
|
+
"agent-payer-auth",
|
|
72
|
+
];
|
|
73
|
+
/** An A2A `Task`: `id` and `status` are the two REQUIRED members (a2a.proto lines 178-190). */
|
|
74
|
+
function isA2aTask(doc) {
|
|
75
|
+
return isStringAt(doc, "id") && isStringAt(doc, "status.state");
|
|
76
|
+
}
|
|
77
|
+
/** An A2A `Message`: `messageId`, `role` and `parts` are its three REQUIRED members (a2a.proto 267-280). */
|
|
78
|
+
function isA2aMessage(doc) {
|
|
79
|
+
return (isStringAt(doc, "messageId") &&
|
|
80
|
+
isStringAt(doc, "role") &&
|
|
81
|
+
isArrayAt(doc, "parts"));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Does this document carry an AP2 mandate DataPart?
|
|
85
|
+
*
|
|
86
|
+
* Reads `parts` itself rather than trusting a caller to have checked it, because the AP2 rule asks this
|
|
87
|
+
* question FIRST — the mandate part is AP2's own half of the discriminant, and the A2A envelope check is a
|
|
88
|
+
* second, weaker fact about it.
|
|
89
|
+
*/
|
|
90
|
+
function carriesAp2Mandate(doc) {
|
|
91
|
+
const parts = readAtPath(doc, "parts");
|
|
92
|
+
if (!Array.isArray(parts))
|
|
93
|
+
return false;
|
|
94
|
+
return parts.some((_, index) => {
|
|
95
|
+
const data = readAtPath(doc, `parts.${index}.data`);
|
|
96
|
+
return (isRecord(data) &&
|
|
97
|
+
AP2_MANDATE_DATA_KEYS.some((key) => Object.hasOwn(data, key)));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Every supported protocol's document discriminant, one row each.
|
|
102
|
+
*
|
|
103
|
+
* DETECTION IS BY DISCRIMINANT, NEVER BY "does the field I want happen to be present": a document is x402
|
|
104
|
+
* because it carries `x402Version`, not because it has an atrHash somewhere. Detecting on the LCP field would
|
|
105
|
+
* make every protocol look alike the moment it carried a reference — which is exactly the situation this table
|
|
106
|
+
* exists to disambiguate.
|
|
107
|
+
*
|
|
108
|
+
* EVERY RULE IS POSITIVE. No row says "and not the other protocol's marker". Documents CAN satisfy two rows,
|
|
109
|
+
* and where they do the answer is AMBIGUITY, which {@link parseProposalUniversal} refuses. A negative term
|
|
110
|
+
* would convert that refusal into a silent choice, and a wrong negative term (one protocol adding a field
|
|
111
|
+
* another already had) converts it into a silent WRONG choice. Refusing is never the wrong answer; guessing
|
|
112
|
+
* sometimes is.
|
|
113
|
+
*
|
|
114
|
+
* THE TWO OVERLAPS ARE NOT THE SAME STRENGTH, and the difference is load-bearing:
|
|
115
|
+
*
|
|
116
|
+
* - ACP/UCP is CONTINGENT. A UCP checkout response that also carries `currency`, `totals` and `line_items`
|
|
117
|
+
* satisfies both rows; one that does not carries only `ucp`, `id`, `status` and `links` and is
|
|
118
|
+
* unambiguously UCP. Either protocol's documents remain individually reachable.
|
|
119
|
+
* - AP2/A2A is TOTAL. `ap2` matches `carriesAp2Mandate(doc) && isA2aMessage(doc)`, and `a2a` matches
|
|
120
|
+
* `isA2aTask(doc) || isA2aMessage(doc)`, so the `ap2` predicate is a strict logical SUBSET of the `a2a`
|
|
121
|
+
* one: every document the AP2 row fires on fires the A2A row too, without exception and by construction.
|
|
122
|
+
* So NO AP2 document is reachable through {@link parseProposalUniversal} — the ambiguity refusal always
|
|
123
|
+
* triggers first, and registering an `ap2` entry in {@link PROPOSAL_PARSERS} would not make one parseable.
|
|
124
|
+
* A caller holding an AP2 envelope names `ap2` and calls its parser directly. That is a consequence of AP2
|
|
125
|
+
* defining no transport of its own (v0.2 rides A2A), not a defect to engineer away: a negative term on the
|
|
126
|
+
* A2A row would be this package asserting that a valid A2A message is not one, which is A2A's call to
|
|
127
|
+
* make rather than ours — the host protocol defines its own documents.
|
|
128
|
+
*
|
|
129
|
+
* ORDERED as `KNOWN_PROTOCOL_IDS` orders the closed set, so `matchProtocols` reports in a stable order rather
|
|
130
|
+
* than in the order units happened to land.
|
|
131
|
+
*/
|
|
132
|
+
export const PROTOCOL_DISCRIMINANTS = [
|
|
133
|
+
{
|
|
134
|
+
protocol: "x402",
|
|
135
|
+
kind: "structural",
|
|
136
|
+
matches: (doc) => typeof readAtPath(doc, "x402Version") === "number" &&
|
|
137
|
+
isArrayAt(doc, "accepts"),
|
|
138
|
+
cite: "x402 v2 PaymentRequired — `x402Version` plus the `accepts` requirement array (coinbase/x402 specs/x402-specification-v2.md §5.1.2, read 2026-07-30)",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
protocol: "mpp",
|
|
142
|
+
kind: "undiscriminable",
|
|
143
|
+
reason: "The document `placement-mpp` operates on is the DECODED `request` auth-param body, whose members are `amount` and `currency` (both REQUIRED strings, §5.1.1) plus optional `recipient`, `description`, `externalId` and `methodDetails` (§5.1.2). Not one of them names MPP, and an amount/currency pair is the shape of almost every payment document there is. MPP's identity lives one layer OUT, in the `WWW-Authenticate: Payment` challenge's auth-params (`realm`, `method`, `id`, `request`, `expires`, `digest`, `opaque`) — which is not the document the placement reads, so a caller holding a decoded request body must name `mpp` itself. Note `expires` is an auth-param ONLY: §5.1.2 states that expiry is conveyed in the challenge and that request objects MUST NOT duplicate it, so a body carrying one is malformed rather than discriminating.",
|
|
144
|
+
cite: "MPP charge intent draft-payment-intent-charge-00 §5.1.1 Table 2 (required: amount, currency) and §5.1.2 Table 3 (optional: recipient, description, externalId, methodDetails) (mpp.dev/intents/charge, read 2026-07-30)",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
protocol: "ap2",
|
|
148
|
+
kind: "structural",
|
|
149
|
+
// AP2's own half first, A2A's second: the mandate DataPart is what makes this AP2 rather than any other
|
|
150
|
+
// A2A message, and asking it first is also what keeps the `parts`-is-an-array guard a reachable branch
|
|
151
|
+
// instead of one another predicate has already made true.
|
|
152
|
+
//
|
|
153
|
+
// This conjunction is a strict subset of the `a2a` row's disjunction, so the two ALWAYS co-fire — see the
|
|
154
|
+
// table docblock. `ap2` is therefore a detect-and-name row, never a dispatchable one.
|
|
155
|
+
matches: (doc) => carriesAp2Mandate(doc) && isA2aMessage(doc),
|
|
156
|
+
cite: "AP2 v0.2 defines no transport; its reference samples carry mandates as A2A DataParts keyed `ap2.mandates.CheckoutMandateSdJwt` / `ap2.mandates.PaymentMandateSdJwt`, and the reference rides the A2A envelope's `metadata` beside them (placement-ap2 specification gate discharged 2026-07-30)",
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
protocol: "ack",
|
|
160
|
+
kind: "structural",
|
|
161
|
+
matches: (doc) => {
|
|
162
|
+
const types = readAtPath(doc, "type");
|
|
163
|
+
return (Array.isArray(types) &&
|
|
164
|
+
types.includes("PaymentReceiptCredential") &&
|
|
165
|
+
isRecord(readAtPath(doc, "credentialSubject")));
|
|
166
|
+
},
|
|
167
|
+
cite: 'ACK-Pay `createPaymentReceipt` mints a W3C credential typed ["VerifiableCredential", "PaymentReceiptCredential"] with a `credentialSubject` (agentcommercekit/ack packages/ack-pay/src/create-payment-receipt.ts + packages/vc/src/create-credential.ts, read 2026-07-30)',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
protocol: "acp",
|
|
171
|
+
kind: "structural",
|
|
172
|
+
matches: (doc) => stringAtIsOneOf(doc, "status", ACP_SESSION_STATUS) &&
|
|
173
|
+
isStringAt(doc, "currency") &&
|
|
174
|
+
isArrayAt(doc, "totals") &&
|
|
175
|
+
isArrayAt(doc, "line_items"),
|
|
176
|
+
cite: "ACP agentic checkout (stable 2026-04-17) — `status` is REQUIRED on `CheckoutSessionBase` and is a closed eleven-value enum absent from all three request schemas; `currency`, `totals` and `line_items` are its siblings (enum enumerated live 2026-07-30, see placement-acp's write condition)",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
protocol: "ucp",
|
|
180
|
+
kind: "structural",
|
|
181
|
+
matches: (doc) => isRecord(readAtPath(doc, "ucp")) &&
|
|
182
|
+
isStringAt(doc, "id") &&
|
|
183
|
+
isStringAt(doc, "status") &&
|
|
184
|
+
isArrayAt(doc, "links"),
|
|
185
|
+
cite: "UCP checkout response — `ucp` (the UCP metadata object), `id`, `status` and `links` are all REQUIRED, and `ucp` is the member no other protocol in the set carries (ucp.dev/latest/specification/checkout, read 2026-07-30)",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
protocol: "visa-tap",
|
|
189
|
+
kind: "structural",
|
|
190
|
+
matches: (doc) => {
|
|
191
|
+
const signatureInput = readFromContainer(doc, { kind: "header-map" }, "headers.signature-input");
|
|
192
|
+
return (typeof signatureInput === "string" &&
|
|
193
|
+
TAP_SIGNATURE_TAGS.some((tag) => signatureInput.includes(`"${tag}"`)));
|
|
194
|
+
},
|
|
195
|
+
cite: "Visa TAP agent recognition signature — an RFC 9421 `Signature-Input` whose `tag` parameter is `agent-browser-auth` or `agent-payer-auth` (placement-visa-tap specification gate discharged 2026-07-30)",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
protocol: "mastercard-vi",
|
|
199
|
+
kind: "structural",
|
|
200
|
+
matches: (doc) => stringAtIsOneOf(doc, "vct", VI_OPEN_MANDATE_VCT) &&
|
|
201
|
+
isArrayAt(doc, "constraints"),
|
|
202
|
+
cite: "Verifiable Intent — `constraints` appears in Autonomous-mode open mandates only, whose `vct` is `mandate.checkout.open.1` or `mandate.payment.open.1` (placement-mastercard-vi specification gate discharged 2026-07-30)",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
protocol: "a2a",
|
|
206
|
+
kind: "structural",
|
|
207
|
+
matches: (doc) => isA2aTask(doc) || isA2aMessage(doc),
|
|
208
|
+
cite: "A2A — `Task` REQUIRES `id` and `status`, `Message` REQUIRES `messageId`, `role` and `parts`; both carry the free-form `metadata` map this placement writes (a2aproject/A2A specification/a2a.proto @ main, read 2026-07-30)",
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
/**
|
|
212
|
+
* Every protocol whose discriminant this document satisfies, in the table's order.
|
|
213
|
+
*
|
|
214
|
+
* ALL matches, never the first. A first-match detector cannot tell a document that belongs to one protocol
|
|
215
|
+
* from a document that belongs to two, and the second case is the one that costs a buyer money: an AP2
|
|
216
|
+
* envelope IS an A2A message, so both rows fire on it, and answering "a2a" because it came first would be a
|
|
217
|
+
* guess wearing an answer's clothes.
|
|
218
|
+
*/
|
|
219
|
+
export function matchProtocols(wire) {
|
|
220
|
+
const matched = [];
|
|
221
|
+
for (const row of PROTOCOL_DISCRIMINANTS) {
|
|
222
|
+
if (row.kind === "structural" && row.matches(wire))
|
|
223
|
+
matched.push(row.protocol);
|
|
224
|
+
}
|
|
225
|
+
return matched;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Identify which commerce protocol a wire document belongs to, structurally.
|
|
229
|
+
*
|
|
230
|
+
* Returns `undefined` unless EXACTLY ONE discriminant fires — so both "nothing matched" and "several matched"
|
|
231
|
+
* answer `undefined`, because a single-valued return cannot honestly distinguish them and inventing a
|
|
232
|
+
* preference is the failure this function exists to prevent. A caller that needs to tell the two apart calls
|
|
233
|
+
* {@link matchProtocols} and reads the length; {@link parseProposalUniversal} does exactly that, and refuses
|
|
234
|
+
* each case with its own message.
|
|
235
|
+
*
|
|
236
|
+
* This function never guesses and never throws.
|
|
237
|
+
*/
|
|
238
|
+
export function detectProtocol(wire) {
|
|
239
|
+
const matched = matchProtocols(wire);
|
|
240
|
+
return matched.length === 1 ? matched[0] : undefined;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The canonical field plus every declared alias, each flattened into a one-field manifest of its own.
|
|
244
|
+
*
|
|
245
|
+
* A SLOT IS A MANIFEST, so the actual reading is `readDeclaredPaths` — the same function, once per declared
|
|
246
|
+
* path, rather than a second decoder that has to be kept honest by review. Only the INHERITANCE rules are
|
|
247
|
+
* restated (an alias takes the manifest's container and encoding unless it declares its own, and is
|
|
248
|
+
* `integrity` unless it says otherwise), because binding-core resolves those inside the first-hit-wins loop a
|
|
249
|
+
* buyer cannot use. Everything downstream of a resolved slot — the `bare-value` type coming from
|
|
250
|
+
* `carrierTypes[0]`, the wrapping into the §8.1 codec, the corrupt-value throw — stays where it was written.
|
|
251
|
+
*
|
|
252
|
+
* A declared `bareType` becomes the slot's WHOLE `carrierTypes`, which is the same thing said in the shape
|
|
253
|
+
* this call takes: a bare value carries no type tag, so its type is whatever its one-field contract fixes.
|
|
254
|
+
*/
|
|
255
|
+
function carrierSlots(manifest) {
|
|
256
|
+
const canonical = {
|
|
257
|
+
path: manifest.field,
|
|
258
|
+
container: manifest.container,
|
|
259
|
+
encoding: manifest.encoding,
|
|
260
|
+
carrierClass: "integrity",
|
|
261
|
+
carrierTypes: manifest.carrierTypes,
|
|
262
|
+
};
|
|
263
|
+
return [
|
|
264
|
+
canonical,
|
|
265
|
+
...(manifest.readAlso ?? []).map((alias) => ({
|
|
266
|
+
path: alias.path,
|
|
267
|
+
container: alias.container ?? manifest.container,
|
|
268
|
+
encoding: alias.encoding ?? manifest.encoding,
|
|
269
|
+
carrierClass: alias.carrierClass ?? "integrity",
|
|
270
|
+
carrierTypes: alias.bareType === undefined
|
|
271
|
+
? manifest.carrierTypes
|
|
272
|
+
: [alias.bareType],
|
|
273
|
+
})),
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The reference each declared INTEGRITY carrier actually holds, with the path it was read from.
|
|
278
|
+
*
|
|
279
|
+
* Discovery-class slots are skipped before they are read, not after: UCP's `links[type=terms_of_service].url`
|
|
280
|
+
* locates the terms without attesting to them — LCP v1.38 §C.3 files UCP's `links` under "discovery without
|
|
281
|
+
* integrity" and says a standing policy page "is not a per-transaction terms record and carries no hash",
|
|
282
|
+
* which is exactly why one cannot stand in for the other. Reading
|
|
283
|
+
* it and discarding it later would also mean decoding a URL under the capability field's `sha256` contract,
|
|
284
|
+
* which is a corrupt carrier and throws.
|
|
285
|
+
*/
|
|
286
|
+
function integrityHits(doc, manifest) {
|
|
287
|
+
const hits = [];
|
|
288
|
+
for (const slot of carrierSlots(manifest)) {
|
|
289
|
+
if (slot.carrierClass !== "integrity")
|
|
290
|
+
continue;
|
|
291
|
+
const read = readDeclaredPaths(doc, {
|
|
292
|
+
field: slot.path,
|
|
293
|
+
container: slot.container,
|
|
294
|
+
encoding: slot.encoding,
|
|
295
|
+
carrierTypes: slot.carrierTypes,
|
|
296
|
+
});
|
|
297
|
+
if (read === undefined)
|
|
298
|
+
continue;
|
|
299
|
+
const ref = decodeDeclaredRead(read);
|
|
300
|
+
if (ref === undefined)
|
|
301
|
+
continue;
|
|
302
|
+
hits.push({ path: slot.path, ref });
|
|
303
|
+
}
|
|
304
|
+
return hits;
|
|
305
|
+
}
|
|
306
|
+
/** Two references are the same reference iff type and value agree — hex compared case-insensitively. */
|
|
307
|
+
function carrierKey(ref) {
|
|
308
|
+
return ref.type === "sha256"
|
|
309
|
+
? `sha256:${ref.value.toLowerCase()}`
|
|
310
|
+
: `${ref.type}:${ref.value}`;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Read what ANY supported protocol document advertises: its ATR hash, and its terms URL where the protocol
|
|
314
|
+
* has room for one.
|
|
315
|
+
*
|
|
316
|
+
* UNIVERSAL BY CONSTRUCTION. The carriers are not listed here — they are read out of the protocol's own
|
|
317
|
+
* `PlacementManifest` through `@integraledger/lcp-placements`, so a protocol this function supports is precisely
|
|
318
|
+
* one the build can also place a reference INTO, and adding a protocol product-side is nothing at all. There
|
|
319
|
+
* is no second place protocols are listed and no path by which a reader and a writer can drift.
|
|
320
|
+
*
|
|
321
|
+
* THE PROTOCOL IS AN ARGUMENT, NOT A DETECTION. Detection is the only part of this seam that can be wrong, so
|
|
322
|
+
* a caller that knows its protocol — which is the ordinary case, since a buyer agent knows which counterparty
|
|
323
|
+
* it dialled — never pays for it. {@link detectProtocol} is available for the caller that genuinely does not.
|
|
324
|
+
*
|
|
325
|
+
* FAIL-FAST, four ways, all loud:
|
|
326
|
+
*
|
|
327
|
+
* - a protocol with no registered placement (`mcp` is the only one, and LCP v1.38 §C.9 makes that terminal:
|
|
328
|
+
* it describes an LCP-aware MCP *server*, which has no document field for a reference to ride in);
|
|
329
|
+
* - a document advertising nothing at any declared carrier;
|
|
330
|
+
* - DISAGREEMENT between two declared carriers. This is the generalization of the x402 two-carrier rule to
|
|
331
|
+
* every protocol in the set, and it is deliberately stricter than the placement adapter's own `extract`,
|
|
332
|
+
* which answers with the canonical field and says nothing (`readDeclaredPaths` returns the first hit, not
|
|
333
|
+
* the set). A placement is structural and does not adjudicate a host's document; a BUYER must, because two
|
|
334
|
+
* different values on one document would let a seller advertise different terms to different readers of it
|
|
335
|
+
* and then disown whichever one it lost by. Preference is not an answer here — refusal is;
|
|
336
|
+
* - a reference that is not a `sha256` carrier. `carrierTypes` permits `url` on several manifests and that is
|
|
337
|
+
* correct for a placement, but the gate compares the advertised value against a RECOMPUTED record hash, and
|
|
338
|
+
* nothing but a hash can be compared to a hash.
|
|
339
|
+
*
|
|
340
|
+
* `deployment` is required only for a protocol whose placement is namespaced — Mastercard VI, whose constraint
|
|
341
|
+
* type is minted under the deployment's own reverse-domain namespace and has no default. Omitting it there
|
|
342
|
+
* throws from the registry rather than answering about some invented namespace.
|
|
343
|
+
*/
|
|
344
|
+
export function readAdvertisedTerms(protocol, wire, deployment) {
|
|
345
|
+
const placement = placementFor(protocol, deployment);
|
|
346
|
+
if (placement === undefined)
|
|
347
|
+
throw new Error(`no placement is registered for ${protocol} — this build cannot read a reference out of its documents`);
|
|
348
|
+
const manifest = placement.manifest;
|
|
349
|
+
const hits = integrityHits(wire, manifest);
|
|
350
|
+
const sole = hits[0];
|
|
351
|
+
if (sole === undefined) {
|
|
352
|
+
const looked = carrierSlots(manifest)
|
|
353
|
+
.filter((slot) => slot.carrierClass === "integrity")
|
|
354
|
+
.map((slot) => slot.path)
|
|
355
|
+
.join(", ");
|
|
356
|
+
throw new Error(`${protocol} document advertises no LCP reference at any declared integrity carrier (${looked})`);
|
|
357
|
+
}
|
|
358
|
+
const distinct = new Set(hits.map((hit) => carrierKey(hit.ref)));
|
|
359
|
+
if (distinct.size > 1)
|
|
360
|
+
throw new Error(`${protocol} carriers disagree — ${hits
|
|
361
|
+
.map((hit) => `${hit.path} advertises lcp:${hit.ref.type}:${hit.ref.value}`)
|
|
362
|
+
.join(", ")}`);
|
|
363
|
+
const ref = sole.ref;
|
|
364
|
+
if (ref.type !== "sha256")
|
|
365
|
+
throw new Error(`${protocol} advertises an lcp:${ref.type}: reference; the gate compares the advertised value against a recomputed record hash, which only a sha256 carrier can be`);
|
|
366
|
+
return {
|
|
367
|
+
protocol,
|
|
368
|
+
advertisedAtrHash: ref.value,
|
|
369
|
+
legalContextUrl: readTermsUrl(protocol, wire, manifest, sole.path),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* The terms URL at the field the manifest DECLARES — or which of the three absences this is.
|
|
374
|
+
*
|
|
375
|
+
* `termsUrlField` is a plain dotted path on the manifest: it declares no container of its own, so it is read
|
|
376
|
+
* with `readAtPath` rather than through the reference carrier's container. A manifest that omits it (A2A, ACK,
|
|
377
|
+
* Visa TAP, Mastercard VI) is stating that its protocol has no room for one — a fact, not a gap.
|
|
378
|
+
*
|
|
379
|
+
* NOTHING AT THE DECLARED PATH IS TWO DIFFERENT FACTS, and which one it is turns on where the reference
|
|
380
|
+
* answered from. `termsUrlField` is declared once, beside the canonical `field`; a `readAlso` alias carries no
|
|
381
|
+
* terms-URL declaration of its own. So when the declared path is nested INSIDE the canonical field — x402's
|
|
382
|
+
* `extensions.legalContext.info.legalContextUrl` inside `extensions.legalContext.info` — and the reference
|
|
383
|
+
* answered from an alias instead, the manifest's declaration was scoped to a carrier this document did not
|
|
384
|
+
* use, and its emptiness says nothing about the document. When the declared path is a SIBLING of the canonical
|
|
385
|
+
* field (ACP's `metadata.legal_context_url`, MPP's `methodDetails.legalContextUrl`), it is reachable whichever
|
|
386
|
+
* carrier answered, and empty means empty.
|
|
387
|
+
*
|
|
388
|
+
* `answeredAt` is the path the reconciled reference was read from. `carrierSlots` puts the canonical field
|
|
389
|
+
* first and `integrityHits` preserves that order, so it differs from `manifest.field` exactly when the
|
|
390
|
+
* canonical carrier did not answer.
|
|
391
|
+
*/
|
|
392
|
+
function readTermsUrl(protocol, doc, manifest, answeredAt) {
|
|
393
|
+
const field = manifest.termsUrlField;
|
|
394
|
+
if (field === undefined)
|
|
395
|
+
return { kind: "no-field-declared" };
|
|
396
|
+
const raw = readAtPath(doc, field);
|
|
397
|
+
if (raw === undefined)
|
|
398
|
+
return answeredAt !== manifest.field &&
|
|
399
|
+
field.startsWith(`${manifest.field}.`)
|
|
400
|
+
? { kind: "undeclared-at-answering-carrier", field, answeredAt }
|
|
401
|
+
: { kind: "declared-field-empty", field };
|
|
402
|
+
if (typeof raw !== "string")
|
|
403
|
+
throw new Error(`${protocol} ${field} is not a string: ${JSON.stringify(raw)}`);
|
|
404
|
+
if (!raw.startsWith("https://"))
|
|
405
|
+
throw new Error(`legalContextUrl must be HTTPS: ${raw}`);
|
|
406
|
+
return { kind: "read", url: raw };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Every protocol this build can turn into a complete `GateProposal`, keyed by `ProtocolId`.
|
|
410
|
+
*
|
|
411
|
+
* SMALLER THAN THE PLACEMENT REGISTRY, and the difference is a fact about the protocols rather than a gap in
|
|
412
|
+
* this package. A `GateProposal` carries an OFFER — an amount and a unit — and an offer is protocol-native
|
|
413
|
+
* economics that no `PlacementManifest` declares and LCP does not standardize: x402 quotes it in
|
|
414
|
+
* `accepts[].amount` with a `network:asset` unit, ACP in the row of `totals` typed `total` with an ISO 4217
|
|
415
|
+
* currency, and the remaining seven each differently again. Inventing an offer-locator axis product-side would
|
|
416
|
+
* put protocol knowledge in a second place and put it there UNGATED, which is the one thing the placement seam
|
|
417
|
+
* was built to stop. {@link readAdvertisedTerms} is universal because the reference is; this is not, because
|
|
418
|
+
* the offer is not.
|
|
419
|
+
*
|
|
420
|
+
* ONE PROTOCOL COULD NOT BE DISPATCHED HERE EVEN IF ITS OFFER WERE READABLE. `ap2`'s discriminant is a strict
|
|
421
|
+
* subset of `a2a`'s, so an AP2 envelope always matches two rows and {@link parseProposalUniversal} refuses it
|
|
422
|
+
* as ambiguous before any lookup in this map. An `ap2` entry added here would be unreachable through the
|
|
423
|
+
* universal door; the parser a future unit writes must be exported and called by name.
|
|
424
|
+
*
|
|
425
|
+
* `Object.freeze` for the same reason `PLACEMENTS` is frozen: a published package is consumed as JavaScript,
|
|
426
|
+
* where the type alone does not stop a consumer swapping the parser that decides what a buyer is agreeing to.
|
|
427
|
+
*/
|
|
428
|
+
export const PROPOSAL_PARSERS = Object.freeze({
|
|
429
|
+
x402: parseProposalFromChallenge,
|
|
430
|
+
acp: parseProposalFromAcpCheckout,
|
|
431
|
+
});
|
|
432
|
+
/** Every protocol this build can parse a complete proposal from, in registration order. */
|
|
433
|
+
export function parseableProtocols() {
|
|
434
|
+
return Object.keys(PROPOSAL_PARSERS);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Parse ANY supported wire into the one `GateProposal` every parser produces.
|
|
438
|
+
*
|
|
439
|
+
* The universal entry point: a buyer no longer has to know which protocol it is on to gate a
|
|
440
|
+
* transaction. Every named parser stays exported — a caller that DOES know its protocol should keep calling
|
|
441
|
+
* the specific one, because a known protocol needs no detection and detection is the only part that can be
|
|
442
|
+
* wrong.
|
|
443
|
+
*
|
|
444
|
+
* Fail-fast on four conditions, all loud: an unidentifiable wire, an AMBIGUOUS wire matching more than one
|
|
445
|
+
* protocol's discriminant, a protocol whose offer this build cannot read, and any refusal from the protocol
|
|
446
|
+
* parser it routes to. There is no "try them all and take the first that works" path — that is a fallback
|
|
447
|
+
* chain, and it would let a malformed document of one protocol be silently reinterpreted as a valid document
|
|
448
|
+
* of another.
|
|
449
|
+
*
|
|
450
|
+
* The named parsers are DELEGATED to rather than reimplemented over the placement manifests, and one of them
|
|
451
|
+
* proves why that is not merely convenient: x402's wire carries `legalContextUrl` in BOTH `accepts[].extra`
|
|
452
|
+
* and `extensions.legalContext.info` — LCP v1.38 §C.4's own illustration uses the first — while
|
|
453
|
+
* `PlacementManifest.termsUrlField` is singular and declares only the second. Routing x402 through the
|
|
454
|
+
* manifest would drop the terms URL from a spec-legal challenge. The reference reconcile generalizes; the
|
|
455
|
+
* terms-URL locator does not yet, so this entry point delegates and {@link readAdvertisedTerms} REPORTS the
|
|
456
|
+
* shortfall as `undeclared-at-answering-carrier` rather than answering an absence it cannot vouch for.
|
|
457
|
+
*/
|
|
458
|
+
export function parseProposalUniversal(wire, ctx) {
|
|
459
|
+
const matched = matchProtocols(wire);
|
|
460
|
+
if (matched.length > 1)
|
|
461
|
+
throw new Error(`ambiguous wire — it matches the discriminants of ${matched.length} protocols (${matched.join(", ")}); a document that is legitimately two protocols' documents is not resolved by preference, so name the protocol and call its parser directly`);
|
|
462
|
+
const protocol = matched[0];
|
|
463
|
+
if (protocol === undefined)
|
|
464
|
+
throw new Error("could not identify which commerce protocol this document belongs to — no declared discriminant matched");
|
|
465
|
+
const parse = PROPOSAL_PARSERS[protocol];
|
|
466
|
+
if (parse === undefined)
|
|
467
|
+
throw new Error(`no buyer proposal parser for ${protocol} — this build parses ${parseableProtocols().join(", ")}. ${protocol} quotes its offer in a shape no placement manifest declares; readAdvertisedTerms("${protocol}", …) reads its LCP reference today`);
|
|
468
|
+
return parse(wire, ctx);
|
|
469
|
+
}
|
|
470
|
+
//# sourceMappingURL=proposal-universal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal-universal.js","sourceRoot":"","sources":["../src/proposal-universal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Assurance } from "@integraledger/lcp-authority";
|
|
2
|
+
/** The TYPED inputs the gate decides on. It CANNOT carry natural-language prose — the prompt-injection
|
|
3
|
+
* boundary is architectural (LCP §12.7). Prose is fetched and retained as evidence, never fed to policy. */
|
|
4
|
+
export interface GateProposal {
|
|
5
|
+
readonly advertisedAtrHash: `0x${string}`;
|
|
6
|
+
readonly legalContextUrl: string;
|
|
7
|
+
readonly level: 1 | 2 | 3 | 4;
|
|
8
|
+
readonly offer: {
|
|
9
|
+
readonly amount: string;
|
|
10
|
+
readonly unit: string;
|
|
11
|
+
};
|
|
12
|
+
readonly sellerAssurance: Assurance;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Context the host challenge does NOT carry — the buyer's client establishes it: the LCP trust level and
|
|
16
|
+
* the seller's stated assurance.
|
|
17
|
+
*
|
|
18
|
+
* **There is deliberately no offer-validity window here.** An earlier shape carried `validFrom`/`validUntil`
|
|
19
|
+
* on every proposal and the gate read neither, which told a reader that expiry was gated when it was not.
|
|
20
|
+
* The window is not a gap to fill: whether a quote has gone stale is AGENT OPERATIONS, and LCP has no
|
|
21
|
+
* opinion on those — its subject is that final terms are provably bound to the payment. This gate's job
|
|
22
|
+
* stops at the binding. A buyer that wants offer-expiry policy holds it in its own client, where the
|
|
23
|
+
* decision belongs, and reaches `transact` only when it still intends to pay.
|
|
24
|
+
*/
|
|
25
|
+
export interface ProposalContext {
|
|
26
|
+
readonly level: 1 | 2 | 3 | 4;
|
|
27
|
+
readonly sellerAssurance: Assurance;
|
|
28
|
+
}
|
|
29
|
+
/** Parse an x402 402 challenge into a typed GateProposal (the LCP §12.7 boundary — no prose field on the type).
|
|
30
|
+
* Fail-fast (throws) on a malformed challenge, a non-0x-32-byte atrHash, a non-HTTPS terms URL, or a
|
|
31
|
+
* non-base-unit-integer amount (this closes the empty/decimal-amount crack at the trust boundary). */
|
|
32
|
+
export declare function parseProposalFromChallenge(challenge: unknown, ctx: ProposalContext): GateProposal;
|
|
33
|
+
//# sourceMappingURL=proposal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal.d.ts","sourceRoot":"","sources":["../src/proposal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAG9D;6GAC6G;AAC7G,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,KAAK,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;CACrC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;CACrC;AA8ED;;uGAEuG;AACvG,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,OAAO,EAClB,GAAG,EAAE,eAAe,GACnB,YAAY,CAuDd"}
|
package/dist/proposal.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Module-internal structural view of the x402 402 wire format (NOT the seller's type — buyer ≠ seller).
|
|
3
|
+
// z.object strips unknown keys, so a real challenge's other fields (scheme/payTo/x402Version) are harmlessly
|
|
4
|
+
// ignored. Kept internal + not z.infer-exported (isolatedDeclarations).
|
|
5
|
+
//
|
|
6
|
+
// x402 v2 carries the reference in TWO Tier A places and BOTH are optional here: the per-requirement
|
|
7
|
+
// `accepts[].extra` and the challenge-level `extensions.legalContext` map. Requiring `extra` — as this
|
|
8
|
+
// schema did — rejected a spec-legal seller that advertises only in `extensions`, which is a carrier shape
|
|
9
|
+
// real seller implementations emit. Which one is present is resolved below, not here:
|
|
10
|
+
// Zod's job is the shape, and "at least one of two carriers, agreeing if both" is a rule about meaning.
|
|
11
|
+
const X402ChallengeSchema = z.object({
|
|
12
|
+
accepts: z
|
|
13
|
+
.array(z.object({
|
|
14
|
+
amount: z.string(),
|
|
15
|
+
network: z.string(),
|
|
16
|
+
asset: z.string(),
|
|
17
|
+
extra: z
|
|
18
|
+
.object({
|
|
19
|
+
atrHash: z.string().optional(),
|
|
20
|
+
legalContextUrl: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.optional(),
|
|
23
|
+
}))
|
|
24
|
+
.min(1),
|
|
25
|
+
extensions: z
|
|
26
|
+
.object({
|
|
27
|
+
legalContext: z.object({
|
|
28
|
+
info: z.object({
|
|
29
|
+
type: z.string(),
|
|
30
|
+
value: z.string(),
|
|
31
|
+
legalContextUrl: z.string().optional(),
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
})
|
|
35
|
+
.optional(),
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Reconcile ONE field across x402's two Tier A carriers.
|
|
39
|
+
*
|
|
40
|
+
* Field-by-field, not carrier-by-carrier, because the two carriers are not required to be symmetric. LCP
|
|
41
|
+
* v1.38 §C.4's own illustration puts `atrHash` + `legalContextUrl` in `accepts[].extra` while
|
|
42
|
+
* `extensions.legalContext.info` carries only `type` + `value` — so treating each carrier as an atomic
|
|
43
|
+
* {hash, url} pair rejects the spec's canonical example.
|
|
44
|
+
*
|
|
45
|
+
* `accepts[].extra` wins when both agree, because it is the per-requirement carrier and binds to the
|
|
46
|
+
* requirement actually being paid. Disagreement is NOT resolved by preference: two different values on one
|
|
47
|
+
* challenge would let a seller advertise different terms to different readers of the same document, and a
|
|
48
|
+
* buyer that quietly picked one would gate against terms the seller can later disown. That refuses.
|
|
49
|
+
*/
|
|
50
|
+
function reconcileField(field, fromExtra, fromExtensions, equal) {
|
|
51
|
+
if (fromExtra !== undefined && fromExtensions !== undefined) {
|
|
52
|
+
if (!equal(fromExtra, fromExtensions))
|
|
53
|
+
throw new Error(`x402 carriers disagree on ${field} — accepts[].extra advertises "${fromExtra}", ` +
|
|
54
|
+
`extensions.legalContext advertises "${fromExtensions}"`);
|
|
55
|
+
return fromExtra;
|
|
56
|
+
}
|
|
57
|
+
const only = fromExtra ?? fromExtensions;
|
|
58
|
+
if (only === undefined)
|
|
59
|
+
throw new Error(`x402 challenge advertises no ${field} — neither accepts[].extra nor extensions.legalContext carries one`);
|
|
60
|
+
return only;
|
|
61
|
+
}
|
|
62
|
+
const ATR_HASH = /^0x[0-9a-fA-F]{64}$/;
|
|
63
|
+
const BASE_UNIT_INT = /^[0-9]+$/; // decimal base-unit integer — no sign, no decimal point, non-empty
|
|
64
|
+
/** Parse an x402 402 challenge into a typed GateProposal (the LCP §12.7 boundary — no prose field on the type).
|
|
65
|
+
* Fail-fast (throws) on a malformed challenge, a non-0x-32-byte atrHash, a non-HTTPS terms URL, or a
|
|
66
|
+
* non-base-unit-integer amount (this closes the empty/decimal-amount crack at the trust boundary). */
|
|
67
|
+
export function parseProposalFromChallenge(challenge, ctx) {
|
|
68
|
+
const parsed = X402ChallengeSchema.parse(challenge);
|
|
69
|
+
// THE FIRST REQUIREMENT, DELIBERATELY, AND THE GATE DOES NOT CHOOSE.
|
|
70
|
+
//
|
|
71
|
+
// x402's `accepts` is a list of ALTERNATIVE payment requirements. Which one to pay is the agent's own
|
|
72
|
+
// decision — a matter of rails, balances and preference — and this gate has no opinion on it, because
|
|
73
|
+
// choosing how to pay is agent operations rather than binding terms to a payment. A caller that wants a
|
|
74
|
+
// different requirement narrows `accepts` to it BEFORE calling, and gets a proposal gated against that
|
|
75
|
+
// one; the amount checked against the buyer's cap is always the amount on the requirement passed in.
|
|
76
|
+
//
|
|
77
|
+
// What this must never become is a preference rule invented here. Reconciling the reference across two
|
|
78
|
+
// carriers refuses on disagreement precisely because a silent choice lets a seller disown whichever
|
|
79
|
+
// reading lost; a silent choice of REQUIREMENT would gate the buyer against a price it did not pick.
|
|
80
|
+
const req = parsed.accepts[0];
|
|
81
|
+
if (req === undefined)
|
|
82
|
+
throw new Error("x402 challenge has no accepted requirement");
|
|
83
|
+
// The extensions carrier states its own carrier type; anything but sha256 is refused rather than read,
|
|
84
|
+
// because `advertisedAtrHash` is compared against a recomputed record hash and nothing else can be.
|
|
85
|
+
const info = parsed.extensions?.legalContext.info;
|
|
86
|
+
if (info !== undefined && info.type !== "sha256")
|
|
87
|
+
throw new Error(`x402 extensions.legalContext.info.type must be sha256, got "${info.type}"`);
|
|
88
|
+
const atrHash = reconcileField("atrHash", req.extra?.atrHash, info?.value, (a, b) => a.toLowerCase() === b.toLowerCase());
|
|
89
|
+
const legalContextUrl = reconcileField("legalContextUrl", req.extra?.legalContextUrl, info?.legalContextUrl, (a, b) => a === b);
|
|
90
|
+
if (!ATR_HASH.test(atrHash))
|
|
91
|
+
throw new Error(`advertised atrHash is not a 0x-prefixed 32-byte hex: ${atrHash}`);
|
|
92
|
+
if (!legalContextUrl.startsWith("https://"))
|
|
93
|
+
throw new Error(`legalContextUrl must be HTTPS: ${legalContextUrl}`);
|
|
94
|
+
if (!BASE_UNIT_INT.test(req.amount))
|
|
95
|
+
throw new Error(`offer amount must be a base-unit integer string: "${req.amount}"`);
|
|
96
|
+
return {
|
|
97
|
+
advertisedAtrHash: atrHash,
|
|
98
|
+
legalContextUrl,
|
|
99
|
+
level: ctx.level,
|
|
100
|
+
offer: {
|
|
101
|
+
amount: req.amount,
|
|
102
|
+
unit: `${req.network}:${req.asset}`,
|
|
103
|
+
},
|
|
104
|
+
sellerAssurance: ctx.sellerAssurance,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=proposal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposal.js","sourceRoot":"","sources":["../src/proposal.ts"],"names":[],"mappings":"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"}
|