@integraledger/lcp-placement-x402 0.9.0 → 0.11.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 +119 -0
- package/README.md +53 -52
- package/dist/manifest.d.ts +66 -42
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +85 -52
- package/dist/manifest.js.map +1 -1
- package/dist/placement.d.ts +23 -10
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +41 -81
- package/dist/placement.js.map +1 -1
- package/package.json +2 -2
- package/src/manifest.ts +87 -53
- package/src/placement.ts +50 -98
package/dist/manifest.js
CHANGED
|
@@ -1,46 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The carrier's JSON Schema, INLINED rather than referenced
|
|
2
|
+
* The carrier's JSON Schema, INLINED rather than referenced — and equal, member for member, to the
|
|
3
|
+
* AUTHORITY document at `https://integraledger.com/lcp/x402/legal-context/v1.schema.json` minus its `$id`
|
|
4
|
+
* and `$defs`. That equality is load-bearing and drift-gated (the conformance suite compares this literal
|
|
5
|
+
* to `@integraledger/lcp-discovery`'s shipped copy of the authority file), because its absence was a
|
|
6
|
+
* published defect: from 0.10.1's release until this version, this schema said `required: ["type",
|
|
7
|
+
* "value"]` while the authority document said `required: ["type", "value", "legalContextUrl"]` — two
|
|
8
|
+
* definitions of the same `info` in the published ecosystem, self-consistent halves, no document valid
|
|
9
|
+
* against both (integra-protocol#8). Both being conformant to the schema each carried is exactly why no
|
|
10
|
+
* package's own tests could catch it; only comparing the two could, and now something does.
|
|
3
11
|
*
|
|
4
|
-
* x402 makes `schema` a REQUIRED member of an extension entry — "JSON Schema defining
|
|
5
|
-
* structure of info" — so whatever goes here is on the wire of every challenge.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
12
|
+
* WHY INLINE AT ALL: x402 makes `schema` a REQUIRED member of an extension entry — "JSON Schema defining
|
|
13
|
+
* the expected structure of info" — so whatever goes here is on the wire of every challenge. **All nine
|
|
14
|
+
* extensions published in the x402 repository inline a complete JSON Schema** rather than referencing an
|
|
15
|
+
* external document (`x402-foundation/x402` HEAD, read 2026-08-11), and one of the nine makes it a rule:
|
|
16
|
+
* the Bazaar extension requires a `schema`'s `$ref`/`$id` values to be "same-document JSON Pointer
|
|
17
|
+
* fragments (starting with `#`); external references … are not allowed", and says a facilitator "must not
|
|
18
|
+
* resolve external `$ref`/`$id` values … when validating an untrusted `schema`". So an external `$ref`
|
|
19
|
+
* here would not merely be unresolvable to a counterparty that declines to fetch — wherever Bazaar
|
|
20
|
+
* governs, it is rejected outright. LCP v1.38 §C.4 draws the same conclusion ("publish a resolvable
|
|
21
|
+
* schema or inline it — and inlining is the safer of the two"). Dropping `$id` and `$defs` from the
|
|
22
|
+
* inlined form is that rule applied: the authority document's `$id` is an absolute URL, and its `$defs`
|
|
23
|
+
* carries the RECEIPT-time definition, which is not this challenge-time `info` and would bloat every 402.
|
|
9
24
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
25
|
+
* The shape is the §8.1 reference object PLUS the locator the reference is verified through:
|
|
26
|
+
* `legalContextUrl` is REQUIRED here because `value` is a digest — a buyer verifies the terms by fetching
|
|
27
|
+
* the document and hashing it, so a challenge advertising the hash without the locator advertises
|
|
28
|
+
* something no counterparty who lacks the document can check. Every shipped buyer parser already refuses
|
|
29
|
+
* such a challenge; the schema now says on the wire what the readers always demanded. It describes the
|
|
30
|
+
* carrier, not the terms behind it, and asserts nothing about any agreement's lawfulness — the
|
|
31
|
+
* description says so in as many words because the schema travels alone.
|
|
17
32
|
*
|
|
18
33
|
* Changing this is a WIRE change — it appears in every challenge — so it is a frozen literal rather than a
|
|
19
|
-
* value assembled at call time.
|
|
34
|
+
* value assembled at call time, and the drift gate is what keeps the frozen copy honest.
|
|
20
35
|
*/
|
|
21
36
|
export const LEGAL_CONTEXT_SCHEMA = Object.freeze({
|
|
22
37
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
23
|
-
title: "
|
|
24
|
-
description: "
|
|
38
|
+
title: "legalContext — x402 extension info",
|
|
39
|
+
description: "The `info` payload of the `legalContext` x402 extension at challenge time: a Legal Context Protocol reference to the terms governing this transaction, plus the URL the terms document can be fetched from. The reference identifies the exact terms document; it is not the terms. This describes a technology harness and asserts nothing about whether any agreement is lawful, sound or enforceable.",
|
|
25
40
|
type: "object",
|
|
26
|
-
required: ["type", "value"],
|
|
27
41
|
additionalProperties: false,
|
|
42
|
+
required: ["type", "value", "legalContextUrl"],
|
|
28
43
|
properties: {
|
|
29
44
|
type: {
|
|
45
|
+
description: "The digest algorithm over the terms document. `sha256` is the only value this version defines.",
|
|
30
46
|
type: "string",
|
|
31
|
-
|
|
32
|
-
description: "Carrier type. sha256, ipfs and ar are content-addressed and bear integrity; url only locates a document.",
|
|
47
|
+
const: "sha256",
|
|
33
48
|
},
|
|
34
49
|
value: {
|
|
50
|
+
description: "The atrHash — SHA-256 of the terms document, lowercase hex with an 0x prefix.",
|
|
51
|
+
type: "string",
|
|
52
|
+
pattern: "^0x[0-9a-f]{64}$",
|
|
53
|
+
},
|
|
54
|
+
legalContextUrl: {
|
|
55
|
+
description: "Where the terms document this hash covers can be fetched. A reader verifies the document against `value`; the URL is a locator and never the authority.",
|
|
35
56
|
type: "string",
|
|
36
|
-
|
|
37
|
-
description: "The reference itself — for sha256, a 0x-prefixed lowercase 32-byte hex digest of the complete ATR file.",
|
|
57
|
+
format: "uri",
|
|
38
58
|
},
|
|
39
59
|
},
|
|
40
60
|
});
|
|
41
61
|
/**
|
|
42
62
|
* x402 reference placement — the HTTP-layer carrier, cut against the live x402 v2 specification
|
|
43
|
-
* (`x402-foundation/x402@
|
|
63
|
+
* (`x402-foundation/x402@db5da2e65952`, `specs/x402-specification-v2.md`, read 2026-08-11; gate discharged
|
|
64
|
+
* in the README).
|
|
44
65
|
*
|
|
45
66
|
* **THE CANONICAL REPOSITORY IS THE FOUNDATION'S, and the citation moved on 2026-08-08.** Every
|
|
46
67
|
* reference here named `coinbase/x402`, which the GitHub API reports as `"fork": true`;
|
|
@@ -83,39 +104,47 @@ export const LEGAL_CONTEXT_SCHEMA = Object.freeze({
|
|
|
83
104
|
* would emit something no x402 counterparty parses. This is the reason an alias declares its own `encoding`
|
|
84
105
|
* at all.
|
|
85
106
|
*
|
|
86
|
-
* **The alias
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
107
|
+
* **The alias IS WRITTEN, and this reverses a recorded stance — deliberately, on three grounds.** The
|
|
108
|
+
* predecessor declared no `write` on the reasoning that `extra` is "Scheme-specific additional information"
|
|
109
|
+
* and writing into another party's namespace is not our act. That reasoning has been overtaken. First, the
|
|
110
|
+
* host itself no longer treats `extra` as wholly scheme-private: §6.1 reserves `assetTransferMethod` and
|
|
111
|
+
* `paymentFlow` inside it as protocol-governed names, so `extra` is a host-managed extension surface with
|
|
112
|
+
* scheme-specific residue, not a foreign namespace. Second, LCP v1.38 §C.4's own Tier A illustration puts
|
|
113
|
+
* `atrHash` AND `legalContextUrl` in `accepts[].extra` — a third-party reader built from the spec's example
|
|
114
|
+
* reads `extra` first, and a challenge that leaves it empty is invisible to that reader. Third, the shipped
|
|
115
|
+
* buyer parser reads BOTH carriers and reconciles, refusing disagreement — so the mirror cannot drift
|
|
116
|
+
* silently: two slots either agree or the document refuses at the counterparty. The write lands only in
|
|
117
|
+
* `accepts[0]`, the requirement buyer parsers read (see the index-0 rule below), and never touches the
|
|
118
|
+
* reserved names.
|
|
90
119
|
*
|
|
91
120
|
* **The alias is index 0 only.** A locator names one path. `accepts[0]` is what buyer parsers read, and the
|
|
92
121
|
* reason is substantive: the reference must bind to the requirement actually being paid, and searching every
|
|
93
122
|
* requirement would let a seller park a second set of terms on an alternative it never expects to be chosen.
|
|
94
123
|
*
|
|
95
|
-
* **`
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
124
|
+
* **`termsUrlFields` declares BOTH slots the wire carries, and both are written.** The predecessor member
|
|
125
|
+
* (`termsUrlField`, singular) named only the `extensions` slot, and it was read-only in every published
|
|
126
|
+
* package — the write path did not exist anywhere, so a seller assembling from published parts emitted a
|
|
127
|
+
* challenge advertising a hash with no locator, which the published buyer refuses
|
|
128
|
+
* (integra-protocol#8). Declaring both slots makes the manifest state what actually lands on the wire:
|
|
129
|
+
* `place` writes the URL beside the reference in `info` (where the authority schema requires it) and
|
|
130
|
+
* mirrors it at `accepts[0].extra.legalContextUrl` (where §C.4's illustration carries it), and `extract`
|
|
131
|
+
* reconciles the two, refusing disagreement. The kit REQUIRES the URL of any integrity-bearing
|
|
132
|
+
* advertisement on this manifest — a hash no counterparty can resolve is unverifiable by construction,
|
|
133
|
+
* which is the defect the readers always guarded against and the emitters never did.
|
|
103
134
|
*
|
|
104
|
-
* **`carrierTypes`
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* rather than narrowed away here, because narrowing the reference field to one type is a change to what the
|
|
118
|
-
* SLOT may hold across the set, not a fact about x402.
|
|
135
|
+
* **`carrierTypes` is `sha256` alone, and the `url` admission is WITHDRAWN — a defect resolved, not a
|
|
136
|
+
* preference.** The predecessor admitted `url` as the §8.1 discovery form and recorded, in the same
|
|
137
|
+
* docblock, that the permission was "WIDER than any shipped x402 reader": a buyer parser that requires an
|
|
138
|
+
* integrity carrier refuses `info.type !== "sha256"` outright, so a `url` placed in this slot was
|
|
139
|
+
* well-formed against the manifest and rejected at read time — a permission no reader accepts, which is a
|
|
140
|
+
* claim about the ecosystem rather than a description of it. The withdrawal ground is now structural: the
|
|
141
|
+
* `schema` member this package puts on the wire is the AUTHORITY document's shape, whose `type` is
|
|
142
|
+
* `const: "sha256"`, so a `url` reference would emit a challenge that violates its own adjacent schema.
|
|
143
|
+
* The predecessor declined to narrow because "narrowing the reference field to one type is a change to
|
|
144
|
+
* what the SLOT may hold across the set" — that set-wide decision has since been made, by the authority
|
|
145
|
+
* document. `ipfs`/`ar` remain excluded on the original ground: alternative integrity carriers add no
|
|
146
|
+
* capability `sha256` does not already discharge while advertising a content-addressed transport no x402
|
|
147
|
+
* counterparty resolves.
|
|
119
148
|
*/
|
|
120
149
|
export const X402_PLACEMENT = {
|
|
121
150
|
protocol: "x402",
|
|
@@ -129,10 +158,14 @@ export const X402_PLACEMENT = {
|
|
|
129
158
|
path: "accepts.0.extra.atrHash",
|
|
130
159
|
encoding: "bare-value",
|
|
131
160
|
bareType: "sha256",
|
|
161
|
+
write: true,
|
|
132
162
|
},
|
|
133
163
|
],
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
164
|
+
termsUrlFields: [
|
|
165
|
+
"extensions.legalContext.info.legalContextUrl",
|
|
166
|
+
"accepts.0.extra.legalContextUrl",
|
|
167
|
+
],
|
|
168
|
+
carrierTypes: ["sha256"],
|
|
169
|
+
specRef: "x402 v2 (x402-foundation/x402@db5da2e65952 specs/x402-specification-v2.md, read 2026-08-11) — top-level extensions map carried on PaymentRequired/PaymentPayload/SettlementResponse, each entry {info, schema}; accepts[].extra carries the §C.4 mirror (reserved names per §6.1 untouched; gate discharged: see README)",
|
|
137
170
|
};
|
|
138
171
|
//# sourceMappingURL=manifest.js.map
|
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,MAAM,CAAC,MAAM,CAAC;IACZ,OAAO,EAAE,8CAA8C;IACvD,KAAK,EAAE,oCAAoC;IAC3C,WAAW,EACT,0YAA0Y;IAC5Y,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,KAAK;IAC3B,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC;IAC9C,UAAU,EAAE;QACV,IAAI,EAAE;YACJ,WAAW,EACT,gGAAgG;YAClG,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;SAChB;QACD,KAAK,EAAE;YACL,WAAW,EACT,+EAA+E;YACjF,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,kBAAkB;SAC5B;QACD,eAAe,EAAE;YACf,WAAW,EACT,yJAAyJ;YAC3J,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,KAAK;SACd;KACF;CACF,CAAC,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,QAAQ,EAAE,MAAM;IAChB,OAAO,EAAE,eAAe;IACxB,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;IAClC,KAAK,EAAE,8BAA8B;IACrC,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,yBAAyB;YAC/B,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,QAAQ;YAClB,KAAK,EAAE,IAAI;SACZ;KACF;IACD,cAAc,EAAE;QACd,8CAA8C;QAC9C,iCAAiC;KAClC;IACD,YAAY,EAAE,CAAC,QAAQ,CAAC;IACxB,OAAO,EACL,0TAA0T;CAC7T,CAAC"}
|
package/dist/placement.d.ts
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { type ReferencePlacementAdapter } from "@integraledger/lcp-binding-core";
|
|
2
2
|
/**
|
|
3
|
-
* The x402 reference placement — the kit
|
|
4
|
-
* supply.
|
|
3
|
+
* The x402 reference placement — the kit, plus the ONE wrapper the kit cannot know.
|
|
5
4
|
*
|
|
6
|
-
* x402's slot does not hold the reference directly: it holds `{ info, schema }`, where `info` is the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
5
|
+
* x402's slot does not hold the reference directly: it holds `{ info, schema }`, where `info` is the
|
|
6
|
+
* carrier payload and `schema` is the inlined JSON Schema describing it (a REQUIRED member per x402
|
|
7
|
+
* §5.1.2). That wrapper is a protocol fact no container kind models, so `place` is overridden — but as
|
|
8
|
+
* COMPOSITION over `base.place`, not as a reimplementation beside it. The kit performs the entire
|
|
9
|
+
* placement first: the advertisement rules (terms URL demanded of an integrity-bearing reference,
|
|
10
|
+
* refused where malformed), the canonical write into `extensions.legalContext.info`, the bare-hash
|
|
11
|
+
* mirror into `accepts[0].extra.atrHash`, the terms-URL writes into BOTH declared slots, every
|
|
12
|
+
* malformed-container refusal, and purity. The override then does exactly one thing: rebuilds our entry
|
|
13
|
+
* as `{ info, schema }` so the wrapper's second member lands beside the payload the kit just wrote.
|
|
14
|
+
*
|
|
15
|
+
* The predecessor override reimplemented the write wholesale — sibling preservation, own-property
|
|
16
|
+
* walks, the malformed-`extensions` refusal, all restated beside the kit's copies — which is how it
|
|
17
|
+
* could drift: it froze a schema and never wrote a terms URL while the kit's manifest declared one
|
|
18
|
+
* (integra-protocol#8). A one-member rebuild has no room to disagree with the manifest, because
|
|
19
|
+
* everything the manifest declares is discharged before it runs.
|
|
20
|
+
*
|
|
21
|
+
* Rebuilding the ENTRY wholesale (rather than merging `schema` into whatever sits there) is the
|
|
22
|
+
* predecessor's ratified behaviour, kept: our entry is the direct holder and is REPLACED, so junk a
|
|
23
|
+
* counterparty parked inside `extensions.legalContext` does not ride our wire; sibling entries in
|
|
24
|
+
* `extensions` survive untouched, per the host's own echo rule. `extract` is the kit's, unchanged —
|
|
25
|
+
* reading `info` is an ordinary object-path read, the alias and both terms-URL slots are declared data,
|
|
26
|
+
* and reconciliation lives where every protocol shares it.
|
|
14
27
|
*/
|
|
15
28
|
export declare const x402Placement: ReferencePlacementAdapter;
|
|
16
29
|
//# sourceMappingURL=placement.d.ts.map
|
package/dist/placement.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,yBAAyB,EAI/B,MAAM,iCAAiC,CAAC;AAKzC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,aAAa,EAAE,yBA4B3B,CAAC"}
|
package/dist/placement.js
CHANGED
|
@@ -1,91 +1,51 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { makePlacement, readAtPath, requireWritten, writeToContainer, } from "@integraledger/lcp-binding-core";
|
|
2
2
|
import { LEGAL_CONTEXT_SCHEMA, X402_PLACEMENT } from "./manifest.js";
|
|
3
3
|
const base = makePlacement(X402_PLACEMENT);
|
|
4
|
-
/** Refusals namespaced from the protocol id, exactly as the kit does — so `x402/document-malformed` means the
|
|
5
|
-
* same thing whether it came from the override or from the generic read path. */
|
|
6
|
-
function refuse(code, detail) {
|
|
7
|
-
return {
|
|
8
|
-
refused: true,
|
|
9
|
-
haltClass: "verification-failure",
|
|
10
|
-
code: `x402/${code}`,
|
|
11
|
-
detail,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
4
|
/**
|
|
15
|
-
* The x402 reference placement — the kit
|
|
16
|
-
* supply.
|
|
5
|
+
* The x402 reference placement — the kit, plus the ONE wrapper the kit cannot know.
|
|
17
6
|
*
|
|
18
|
-
* x402's slot does not hold the reference directly: it holds `{ info, schema }`, where `info` is the
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
7
|
+
* x402's slot does not hold the reference directly: it holds `{ info, schema }`, where `info` is the
|
|
8
|
+
* carrier payload and `schema` is the inlined JSON Schema describing it (a REQUIRED member per x402
|
|
9
|
+
* §5.1.2). That wrapper is a protocol fact no container kind models, so `place` is overridden — but as
|
|
10
|
+
* COMPOSITION over `base.place`, not as a reimplementation beside it. The kit performs the entire
|
|
11
|
+
* placement first: the advertisement rules (terms URL demanded of an integrity-bearing reference,
|
|
12
|
+
* refused where malformed), the canonical write into `extensions.legalContext.info`, the bare-hash
|
|
13
|
+
* mirror into `accepts[0].extra.atrHash`, the terms-URL writes into BOTH declared slots, every
|
|
14
|
+
* malformed-container refusal, and purity. The override then does exactly one thing: rebuilds our entry
|
|
15
|
+
* as `{ info, schema }` so the wrapper's second member lands beside the payload the kit just wrote.
|
|
16
|
+
*
|
|
17
|
+
* The predecessor override reimplemented the write wholesale — sibling preservation, own-property
|
|
18
|
+
* walks, the malformed-`extensions` refusal, all restated beside the kit's copies — which is how it
|
|
19
|
+
* could drift: it froze a schema and never wrote a terms URL while the kit's manifest declared one
|
|
20
|
+
* (integra-protocol#8). A one-member rebuild has no room to disagree with the manifest, because
|
|
21
|
+
* everything the manifest declares is discharged before it runs.
|
|
22
|
+
*
|
|
23
|
+
* Rebuilding the ENTRY wholesale (rather than merging `schema` into whatever sits there) is the
|
|
24
|
+
* predecessor's ratified behaviour, kept: our entry is the direct holder and is REPLACED, so junk a
|
|
25
|
+
* counterparty parked inside `extensions.legalContext` does not ride our wire; sibling entries in
|
|
26
|
+
* `extensions` survive untouched, per the host's own echo rule. `extract` is the kit's, unchanged —
|
|
27
|
+
* reading `info` is an ordinary object-path read, the alias and both terms-URL slots are declared data,
|
|
28
|
+
* and reconciliation lives where every protocol shares it.
|
|
26
29
|
*/
|
|
27
30
|
export const x402Placement = {
|
|
28
31
|
...base,
|
|
29
|
-
place(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// with ZERO own properties must not walk into its prototype's `extensions` and put those entries on the
|
|
47
|
-
// wire — `extract` reports that document as `reference-absent`, and the two halves have to agree about
|
|
48
|
-
// what is present. binding-core's own writer states the rule and proves it for the generic path; the
|
|
49
|
-
// override is the one write path that has to restate it. The DECLARED view rather than a
|
|
50
|
-
// `Record<string, unknown>` is deliberate: reading `extensions` off an index signature is TS4111, and the
|
|
51
|
-
// bracket form biome would then ask for is the fix it classes as unsafe. The document is still spread
|
|
52
|
-
// wholesale below, so nothing is narrowed away.
|
|
53
|
-
const ext = Object.hasOwn(doc, "extensions")
|
|
54
|
-
? doc.extensions
|
|
55
|
-
: undefined;
|
|
56
|
-
// Sibling extensions are PRESERVED: the live x402 v2 spec states a client "must include at least the info
|
|
57
|
-
// received; it may append additional info but cannot delete or overwrite existing info". That is the host
|
|
58
|
-
// protocol's rule about its own map, and a placement that pruned a sibling would make the client
|
|
59
|
-
// using it non-conformant.
|
|
60
|
-
//
|
|
61
|
-
// An `extensions` that is PRESENT but cannot be merged into REFUSES, and that is binding-core's ratified
|
|
62
|
-
// malformed-container rule read against the manifest this package PUBLISHES rather than against the
|
|
63
|
-
// granularity the override happens to write at: `field` is `extensions.legalContext.info`, so `legalContext`
|
|
64
|
-
// is the field's direct holder and `extensions` sits one level ABOVE it — replaced at the holder, refused
|
|
65
|
-
// above it, because replacing an intermediate discards everything beneath it. A stranger holding only the
|
|
66
|
-
// manifest and the kit computes that refusal for the same document, and an override that emitted a
|
|
67
|
-
// challenge instead would make the manifest a description rather than an artifact. ABSENT is still
|
|
68
|
-
// created; our own entry, being the direct holder, is still replaced.
|
|
69
|
-
const siblings = ext === undefined
|
|
70
|
-
? {}
|
|
71
|
-
: typeof ext === "object" && ext !== null && !Array.isArray(ext)
|
|
72
|
-
? ext
|
|
73
|
-
: undefined;
|
|
74
|
-
if (siblings === undefined)
|
|
75
|
-
return refuse("document-malformed", `extensions is present and is not a map, so ${X402_PLACEMENT.field} has no holder to write into: ${JSON.stringify(ext)}`);
|
|
76
|
-
return {
|
|
77
|
-
ok: true,
|
|
78
|
-
value: {
|
|
79
|
-
...doc,
|
|
80
|
-
extensions: {
|
|
81
|
-
...siblings,
|
|
82
|
-
legalContext: {
|
|
83
|
-
info: encoded,
|
|
84
|
-
schema: LEGAL_CONTEXT_SCHEMA,
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
};
|
|
32
|
+
place(ad, doc) {
|
|
33
|
+
const placed = base.place(ad, doc);
|
|
34
|
+
if ("refused" in placed)
|
|
35
|
+
return placed;
|
|
36
|
+
// The kit's write just created this path on its own output; reading it back — rather than re-encoding
|
|
37
|
+
// the reference a second time — keeps one codepath responsible for what `info` contains.
|
|
38
|
+
const info = readAtPath(placed.value, X402_PLACEMENT.field);
|
|
39
|
+
// TOTAL by the kit's own postconditions: `place` succeeded, so `placed.value` is a record; the write's
|
|
40
|
+
// parent path is the one-segment `extensions`, and a direct holder that is present-but-unmergeable is
|
|
41
|
+
// REPLACED under the ratified container rule, never refused. `requireWritten` states that rather than
|
|
42
|
+
// asserting it with a cast — if the postcondition ever breaks, this throws instead of returning a
|
|
43
|
+
// success carrying no document.
|
|
44
|
+
const wrapped = requireWritten(writeToContainer(placed.value, X402_PLACEMENT.container, "extensions.legalContext", {
|
|
45
|
+
info,
|
|
46
|
+
schema: LEGAL_CONTEXT_SCHEMA,
|
|
47
|
+
}), "x402Placement.place");
|
|
48
|
+
return { ok: true, value: wrapped };
|
|
89
49
|
},
|
|
90
50
|
};
|
|
91
51
|
//# sourceMappingURL=placement.js.map
|
package/dist/placement.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAGb,UAAU,EACV,cAAc,EACd,gBAAgB,GACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAErE,MAAM,IAAI,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,MAAM,aAAa,GAA8B;IACtD,GAAG,IAAI;IAEP,KAAK,CAAC,EAA6B,EAAE,GAAY;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACnC,IAAI,SAAS,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QACvC,sGAAsG;QACtG,yFAAyF;QACzF,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC;QAC5D,uGAAuG;QACvG,sGAAsG;QACtG,sGAAsG;QACtG,kGAAkG;QAClG,gCAAgC;QAChC,MAAM,OAAO,GAAG,cAAc,CAC5B,gBAAgB,CACd,MAAM,CAAC,KAAK,EACZ,cAAc,CAAC,SAAS,EACxB,yBAAyB,EACzB;YACE,IAAI;YACJ,MAAM,EAAE,oBAAoB;SAC7B,CACF,EACD,qBAAqB,CACtB,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integraledger/lcp-placement-x402",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Places an LCP reference into an x402 v2 payment challenge, and reads it back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lcp",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/IntegraLedger/integra-protocol/tree/main/packages/placement-x402#readme",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@integraledger/lcp-binding-core": "0.
|
|
43
|
+
"@integraledger/lcp-binding-core": "0.11.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@cfworker/json-schema": "4.1.1",
|