@integraledger/agent-guard 0.10.0 → 0.10.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/CHANGELOG.md +37 -0
- package/README.md +28 -12
- package/dist/proposal-acp.d.ts +1 -1
- package/dist/proposal-acp.js +1 -1
- package/dist/proposal-ap2.d.ts +1 -1
- package/dist/proposal-mpp.d.ts +1 -1
- package/dist/proposal-mpp.js +1 -1
- package/package.json +12 -11
- package/src/proposal-acp.ts +1 -1
- package/src/proposal-ap2.ts +1 -1
- package/src/proposal-mpp.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @integraledger/agent-guard
|
|
2
2
|
|
|
3
|
+
## 0.10.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5154cfb: Install one copy of the protocol packages, not two.
|
|
8
|
+
|
|
9
|
+
`lcp-mcp-server` pinned its `@integraledger/lcp-*` dependencies to an exact version while `agent-guard`
|
|
10
|
+
— which it depends on — declares the same packages as peers at a caret range. Those two declarations
|
|
11
|
+
cannot be satisfied by a single copy, so installing both packages resolved the protocol line **twice**:
|
|
12
|
+
once hoisted to satisfy the caret, and once nested under `lcp-mcp-server` to satisfy the exact pin. The
|
|
13
|
+
two halves of one install then read different protocol code, with `instanceof` failing across the seam
|
|
14
|
+
between them.
|
|
15
|
+
|
|
16
|
+
`lcp-mcp-server` now declares those dependencies as a caret at the minor line's zero patch, matching its
|
|
17
|
+
sibling. A tree holding both resolves a single copy, and `npx lcp-mcp` still installs standalone.
|
|
18
|
+
|
|
19
|
+
Nothing about either package's API, behaviour or guarantees changes. Both were correct in isolation; only
|
|
20
|
+
a tree holding both was affected.
|
|
21
|
+
|
|
22
|
+
- ccf4bb4: The runtime table is measured now, not argued.
|
|
23
|
+
|
|
24
|
+
It used to assert that this package works on Bun and Deno because of what the import graph implies — the
|
|
25
|
+
one Node built-in that arrives, `node:crypto`, is polyfilled on both. That was a sound argument and it was
|
|
26
|
+
only ever an argument; nothing ran.
|
|
27
|
+
|
|
28
|
+
Every release now drives the guard's whole decision on Node, Bun and Deno, against the packed tarball
|
|
29
|
+
installed the way you would install it, with the protocol line resolved from npmjs. Both halves are
|
|
30
|
+
asserted on each runtime, because a runtime where the guard refused everything would pass a check that only
|
|
31
|
+
looked for the halt: tampered terms must halt with the signer never reached, and matching terms must sign
|
|
32
|
+
with the signer reached exactly once.
|
|
33
|
+
|
|
34
|
+
Measured on this release: Node 24.19.0, Bun 1.4.0, Deno 2.9.5.
|
|
35
|
+
|
|
36
|
+
The table now separates what is measured from what is reasoned. Workers with `nodejs_compat` and the
|
|
37
|
+
bundler row remain arguments from the import graph and say so, rather than sitting in the same column as
|
|
38
|
+
the rows that ran.
|
|
39
|
+
|
|
3
40
|
## 0.10.0
|
|
4
41
|
|
|
5
42
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -59,12 +59,20 @@ no DNS module at all. That much is enforced by the code and is the part worth re
|
|
|
59
59
|
ours: `@integraledger/lcp-evidence` re-exports its CAR support from its index, which pulls `multiformats`'
|
|
60
60
|
Node SHA-2 build, which statically imports `node:crypto`. So:
|
|
61
61
|
|
|
62
|
-
| Target | Works |
|
|
63
|
-
|
|
64
|
-
| Node | yes |
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
62
|
+
| Target | Works | How we know |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| Node | yes | **measured** — every release runs the guard's whole decision here |
|
|
65
|
+
| Bun | yes | **measured** — same run, same assertions |
|
|
66
|
+
| Deno | yes | **measured** — same run, same assertions |
|
|
67
|
+
| Workers with `nodejs_compat` | yes — `node:crypto` is polyfilled | reasoned from the import graph, not measured |
|
|
68
|
+
| A bundler honouring `multiformats`' `browser` export condition | yes — it maps to the browser SHA-2 build | reasoned from the export conditions, not measured |
|
|
69
|
+
| A plain unbundled ESM import in a browser or service worker | **no** | reasoned — `node:crypto` has nothing to resolve to |
|
|
70
|
+
|
|
71
|
+
The measured rows run against **the packed tarball**, installed the way you would install it, with the
|
|
72
|
+
protocol line resolved from npmjs — not against this repository's own tree. Both halves of the guarantee are
|
|
73
|
+
asserted on each runtime, because a runtime where the guard refused everything would pass a check that only
|
|
74
|
+
looked for the halt: tampered terms must halt with the signer never reached, and matching terms must sign
|
|
75
|
+
with the signer reached exactly once.
|
|
68
76
|
|
|
69
77
|
The guard needs `fetch` and Web Crypto and nothing else of its own. Removing the last hop is an upstream
|
|
70
78
|
change — a subpath export on `lcp-evidence` so importing one predicate does not drag CAR and `multiformats`
|
|
@@ -132,12 +140,20 @@ Four answers are worth stating plainly, because each is a place a friendlier lib
|
|
|
132
140
|
ACP/UCP one is contingent on the document, while `ap2`'s rule is a strict subset of `a2a`'s, so **every**
|
|
133
141
|
AP2 envelope matches both and none is reachable through `parseProposalUniversal`. AP2 is a detect-and-name
|
|
134
142
|
protocol here; `parseProposalFromAp2Envelope` exists and is called by name.
|
|
135
|
-
- **An
|
|
136
|
-
`no-field-declared`, `declared-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
- **An absent terms URL says WHICH absence it is.** `legalContextUrl` is a union — `read`,
|
|
144
|
+
`no-field-declared`, or `declared-fields-empty` — because a bare `undefined` conflates two different
|
|
145
|
+
facts. `no-field-declared` is a fact about the PROTOCOL: it has nowhere to put a locator, and no document
|
|
146
|
+
of that protocol can be faulted for lacking one. `declared-fields-empty` is a fact about THIS DOCUMENT:
|
|
147
|
+
the protocol has room and this seller left every declared slot empty. Reporting the second as the first
|
|
148
|
+
would blame a protocol for a seller's silence.
|
|
149
|
+
|
|
150
|
+
It used to carry a fourth state, `undeclared-at-answering-carrier`, and that state is gone because the
|
|
151
|
+
defect requiring it is fixed. The manifest's terms-URL member was singular, so x402 could declare only one
|
|
152
|
+
of its two slots: a §C.4-illustrated challenge advertising in `accepts[].extra` really does carry a terms
|
|
153
|
+
URL, while the single declared path sat empty inside `extensions` — and calling that "no terms advertised"
|
|
154
|
+
would have asserted a silence this reader could not see. The member is plural now, every declared slot is
|
|
155
|
+
read and reconciled, and a slot riding a container the placement owns is declared on that container. There
|
|
156
|
+
is no carrier a declaration fails to reach, so the state is unreachable rather than merely unused.
|
|
141
157
|
- **Carrier disagreement refuses.** Where a protocol declares more than one carrier, all of them are read
|
|
142
158
|
and compared. Two different hashes on one document would let a seller advertise different terms to
|
|
143
159
|
different readers of it, so this is deliberately stricter than the placement adapter's own `extract`,
|
package/dist/proposal-acp.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { GateProposal, ProposalContext } from "./proposal.js";
|
|
|
6
6
|
* non-integer minor-unit amount.
|
|
7
7
|
*
|
|
8
8
|
* `legal_context_url` is required, and the ACP placement manifest DECLARES it as
|
|
9
|
-
* `
|
|
9
|
+
* `termsUrlFields: ["metadata.legal_context_url"]` — the field this parser demands is a field the placement
|
|
10
10
|
* names, which is what makes the round-trip compose. It is deliberately NOT read from ACP's native
|
|
11
11
|
* `links[type=terms_of_use]`: that link is the merchant's standing policy page, while this names the ATR
|
|
12
12
|
* terms document for this transaction, and falling back to it would substitute one for the other.
|
package/dist/proposal-acp.js
CHANGED
|
@@ -20,7 +20,7 @@ const AcpSessionSchema = z.object({
|
|
|
20
20
|
* non-integer minor-unit amount.
|
|
21
21
|
*
|
|
22
22
|
* `legal_context_url` is required, and the ACP placement manifest DECLARES it as
|
|
23
|
-
* `
|
|
23
|
+
* `termsUrlFields: ["metadata.legal_context_url"]` — the field this parser demands is a field the placement
|
|
24
24
|
* names, which is what makes the round-trip compose. It is deliberately NOT read from ACP's native
|
|
25
25
|
* `links[type=terms_of_use]`: that link is the merchant's standing policy page, while this names the ATR
|
|
26
26
|
* terms document for this transaction, and falling back to it would substitute one for the other.
|
package/dist/proposal-ap2.d.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface Ap2ProposalContext extends ProposalContext {
|
|
|
81
81
|
/**
|
|
82
82
|
* The terms document's locator.
|
|
83
83
|
*
|
|
84
|
-
* AP2's placement is INTEGRITY-ONLY: `AP2_PLACEMENT` declares no `
|
|
84
|
+
* AP2's placement is INTEGRITY-ONLY: `AP2_PLACEMENT` declares no `termsUrlFields`, because the A2A
|
|
85
85
|
* `Message.metadata` map holds the reference and AP2 models no terms-URL field anywhere. So unlike ACP
|
|
86
86
|
* (`metadata.legal_context_url`) and x402 (`extra.legalContextUrl`), the locator cannot come off the wire.
|
|
87
87
|
* It comes from LCP §2 discovery — the seller's `/.well-known/legal-context.json` — resolved by the
|
package/dist/proposal-mpp.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { GateProposal, ProposalContext } from "./proposal.js";
|
|
|
24
24
|
* restating its regex keeps one definition of what an ATR hash is.
|
|
25
25
|
*
|
|
26
26
|
* **`legalContextUrl` is the field the placement names.** `placement-mpp` declares
|
|
27
|
-
* `
|
|
27
|
+
* `termsUrlFields: ["methodDetails.legalContextUrl"]`, so the field this parser demands is the field the
|
|
28
28
|
* manifest names — which is what makes the seller's write and this buyer's read compose. There is no
|
|
29
29
|
* fallback to any other member: MPP defines none that means "these terms", and inventing one would gate the
|
|
30
30
|
* buyer against a document nobody pointed at.
|
package/dist/proposal-mpp.js
CHANGED
|
@@ -53,7 +53,7 @@ const BASE_UNIT_INT = /^[0-9]+$/;
|
|
|
53
53
|
* restating its regex keeps one definition of what an ATR hash is.
|
|
54
54
|
*
|
|
55
55
|
* **`legalContextUrl` is the field the placement names.** `placement-mpp` declares
|
|
56
|
-
* `
|
|
56
|
+
* `termsUrlFields: ["methodDetails.legalContextUrl"]`, so the field this parser demands is the field the
|
|
57
57
|
* manifest names — which is what makes the seller's write and this buyer's read compose. There is no
|
|
58
58
|
* fallback to any other member: MPP defines none that means "these terms", and inventing one would gate the
|
|
59
59
|
* buyer against a document nobody pointed at.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integraledger/agent-guard",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -39,17 +39,18 @@
|
|
|
39
39
|
"@integraledger/lcp-verify": "^0.12.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@integraledger/lcp-authority": "0.12.
|
|
43
|
-
"@integraledger/lcp-binding-core": "0.12.
|
|
44
|
-
"@integraledger/lcp-discovery": "0.12.
|
|
45
|
-
"@integraledger/lcp-evidence": "0.12.
|
|
46
|
-
"@integraledger/lcp-kernel": "0.12.
|
|
47
|
-
"@integraledger/lcp-placement-ack": "0.12.
|
|
48
|
-
"@integraledger/lcp-placement-acp": "0.12.
|
|
49
|
-
"@integraledger/lcp-placement-ap2": "0.12.
|
|
50
|
-
"@integraledger/lcp-placements": "0.12.
|
|
51
|
-
"@integraledger/lcp-verify": "0.12.
|
|
42
|
+
"@integraledger/lcp-authority": "0.12.2",
|
|
43
|
+
"@integraledger/lcp-binding-core": "0.12.2",
|
|
44
|
+
"@integraledger/lcp-discovery": "0.12.2",
|
|
45
|
+
"@integraledger/lcp-evidence": "0.12.2",
|
|
46
|
+
"@integraledger/lcp-kernel": "0.12.2",
|
|
47
|
+
"@integraledger/lcp-placement-ack": "0.12.2",
|
|
48
|
+
"@integraledger/lcp-placement-acp": "0.12.2",
|
|
49
|
+
"@integraledger/lcp-placement-ap2": "0.12.2",
|
|
50
|
+
"@integraledger/lcp-placements": "0.12.2",
|
|
51
|
+
"@integraledger/lcp-verify": "0.12.2",
|
|
52
52
|
"@types/node": "24.13.3",
|
|
53
|
+
"fast-check": "4.9.0",
|
|
53
54
|
"vitest": "4.1.10"
|
|
54
55
|
},
|
|
55
56
|
"license": "Apache-2.0",
|
package/src/proposal-acp.ts
CHANGED
|
@@ -23,7 +23,7 @@ const AcpSessionSchema = z.object({
|
|
|
23
23
|
* non-integer minor-unit amount.
|
|
24
24
|
*
|
|
25
25
|
* `legal_context_url` is required, and the ACP placement manifest DECLARES it as
|
|
26
|
-
* `
|
|
26
|
+
* `termsUrlFields: ["metadata.legal_context_url"]` — the field this parser demands is a field the placement
|
|
27
27
|
* names, which is what makes the round-trip compose. It is deliberately NOT read from ACP's native
|
|
28
28
|
* `links[type=terms_of_use]`: that link is the merchant's standing policy page, while this names the ATR
|
|
29
29
|
* terms document for this transaction, and falling back to it would substitute one for the other.
|
package/src/proposal-ap2.ts
CHANGED
|
@@ -114,7 +114,7 @@ export interface Ap2ProposalContext extends ProposalContext {
|
|
|
114
114
|
/**
|
|
115
115
|
* The terms document's locator.
|
|
116
116
|
*
|
|
117
|
-
* AP2's placement is INTEGRITY-ONLY: `AP2_PLACEMENT` declares no `
|
|
117
|
+
* AP2's placement is INTEGRITY-ONLY: `AP2_PLACEMENT` declares no `termsUrlFields`, because the A2A
|
|
118
118
|
* `Message.metadata` map holds the reference and AP2 models no terms-URL field anywhere. So unlike ACP
|
|
119
119
|
* (`metadata.legal_context_url`) and x402 (`extra.legalContextUrl`), the locator cannot come off the wire.
|
|
120
120
|
* It comes from LCP §2 discovery — the seller's `/.well-known/legal-context.json` — resolved by the
|
package/src/proposal-mpp.ts
CHANGED
|
@@ -57,7 +57,7 @@ const BASE_UNIT_INT = /^[0-9]+$/;
|
|
|
57
57
|
* restating its regex keeps one definition of what an ATR hash is.
|
|
58
58
|
*
|
|
59
59
|
* **`legalContextUrl` is the field the placement names.** `placement-mpp` declares
|
|
60
|
-
* `
|
|
60
|
+
* `termsUrlFields: ["methodDetails.legalContextUrl"]`, so the field this parser demands is the field the
|
|
61
61
|
* manifest names — which is what makes the seller's write and this buyer's read compose. There is no
|
|
62
62
|
* fallback to any other member: MPP defines none that means "these terms", and inventing one would gate the
|
|
63
63
|
* buyer against a document nobody pointed at.
|