@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 CHANGED
@@ -1,5 +1,124 @@
1
1
  # @integraledger/lcp-placement-x402
2
2
 
3
+ ## 0.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 822190a: Give the terms URL the write path the published set never had, and certify the composition that broke
8
+ without it (integra-protocol#8).
9
+
10
+ A third party assembling a seller from published parts emitted a 402 the published buyer refuses: every
11
+ published reader demanded `legalContextUrl` and no published writer placed it, and the schema
12
+ `placement-x402` inlined onto the wire (`required: ["type","value"]`, closed) contradicted the authority
13
+ document integraledger.com serves (`required: ["type","value","legalContextUrl"]`, closed) — two
14
+ definitions of one `info`, no document valid against both, each package self-consistent. Three structural
15
+ gaps let it ship: the manifest's `termsUrlField` was singular and read-only (declared, hygiene-checked,
16
+ never written — and x402's wire carries the URL in two slots, so one path could not even name the shape),
17
+ nothing compared the inlined schema to the authority document, and the corpus certified `place` and
18
+ `extract` separately but never fed one to the other.
19
+
20
+ `binding-core` — the placement seam now moves an ADVERTISEMENT, not a bare reference. `place` takes
21
+ `{ ref, termsUrl? }` and writes the URL at every slot the manifest's new `termsUrlFields` (plural,
22
+ replacing `termsUrlField`) declares; it REFUSES an integrity-bearing advertisement with no URL where slots
23
+ are declared (a hash no counterparty can resolve is unverifiable by construction), a URL where no slot
24
+ exists (silent dropping is fail-open), and a non-https URL on either side of the seam. `extract` returns
25
+ `{ ref, termsUrl }` with absence as a typed value — `no-field-declared` is a fact about the protocol,
26
+ `declared-fields-empty` a fact about the document, and the gate decides what an absence means — while two
27
+ slots that disagree, or a malformed value in either, refuse. The object-path writer learned to descend
28
+ into an EXISTING array element (never minting one, never extending a list, refusing an index segment it
29
+ would have to create), which is what lets x402's `accepts[0].extra` mirrors land.
30
+
31
+ `placement-x402` — the inlined wire schema now IS the authority document minus `$id` and `$defs`
32
+ (Bazaar forbids both on the wire), drift-gated in `lcp-conformance` where the two packages meet.
33
+ `termsUrlFields` declares both slots the wire carries; the bare-hash alias is written (`extra` stopped
34
+ being wholly scheme-private when x402 §6.1 reserved names inside it, and LCP v1.38 §C.4's own Tier A
35
+ illustration carries the pair there); the `url` carrier admission is withdrawn (`carrierTypes` is
36
+ `sha256` alone — the schema on the wire is `const: "sha256"`, and no shipped reader ever accepted a url
37
+ in this slot). The `place` override shrinks to composition: the kit performs the whole placement and the
38
+ override adds only the `{info, schema}` wrapper.
39
+
40
+ `placement-mpp` / `placement-acp` — the singular member becomes the one-entry `termsUrlFields`; the kit
41
+ now writes the slot their buyer parsers always demanded and refuses first at the seller.
42
+
43
+ `lcp-conformance` — the corpus grows 812 → 844: a `roundtrip` op (place then extract in one case, the
44
+ composition certification whose absence let two separately-conformant halves ship jointly broken),
45
+ advertisement-rule refusals for every manifest, and the authority↔wire drift gate. Extract expectations
46
+ across every placement area become the extracted advertisement.
47
+
48
+ `lcp-verify` — `referencePlacementStep` reads the advertisement (`extracted.ref.value`) and deliberately
49
+ ignores `termsUrl`: where the terms live is the gate's fetch concern, not a fact the record can
50
+ contradict.
51
+
52
+ `lcp-discovery` — the x402 authority document restates the atrHash pattern inline in both definitions
53
+ (no `$defs` indirection the wire copy would have to rewrite) and moves the two-definitions rationale into
54
+ `$defs.receipt`, so the challenge-time root is byte-derivable for the wire.
55
+
56
+ ### Patch Changes
57
+
58
+ - b2ffecc: Report the class a record actually supports, place a terms URL on UCP, and stop refusing a conformant UCP
59
+ profile — the remediation of the 2026-08-19 conformance re-audit.
60
+
61
+ **`verify` now computes `supportedClass` instead of echoing the claim.** It was `anyFailed ? "TC-0" :
62
+ claimedClass`, so a record proving nothing — no settlement, no acceptance, no authority chain — reported
63
+ whatever class the caller named, while the field's own published docblock promised "what the record
64
+ honestly supports, not what the caller asked for". It is now the highest class every one of whose required
65
+ steps is `proved`, `TC-0` on any failure, computed from the steps alone: neither capped by the claim (rungs
66
+ that reach TC-3 read TC-3 where the caller claimed TC-2) nor lifted by it. White paper #4 §5 defines the
67
+ class of a transaction as "the highest class whose criteria it fully meets", and this is that.
68
+
69
+ The claim is not discarded — the report gains **`claimedClass`**, a required member, because `verified`
70
+ answers "did the record reach the class it claimed?" and cannot be read without it. The two fields are the
71
+ report's two halves: an input echoed, and a finding computed. Where they differ, the record did not reach
72
+ its own shape. An out-of-taxonomy claim now lands only in the echo and can no longer masquerade as a
73
+ finding.
74
+
75
+ **UCP can advertise a terms URL.** Its policy object declares `url` — "Optional link to the full policy
76
+ document", `format: uri` — on the very entry this placement writes, and §C.3's illustration carries `url`
77
+ and `atrHash` side by side there. The manifest previously said the protocol had no slot, citing `links[]`,
78
+ which §C.3 separates as "a standing page, not a per-transaction record". The obstacle was mechanical:
79
+ `termsUrlFields` addresses document paths, and a tagged-array entry's index is chosen at write time. The
80
+ `tagged-array` container therefore gains `termsUrlField`, written onto the same entry in the same write, and
81
+ read back through the same first-match rule. UCP was the last shipped protocol that refused an
82
+ advertisement carrying its own locator (integra-protocol#8).
83
+
84
+ **`readUcpProfile` no longer refuses a conformant business profile.** `requireHttps` mapped an ABSENT `spec`
85
+ to the same branch as a malformed one, and the live host requires `spec` only of a platform declaration —
86
+ as this repository's own README already said. It is now `requireHttpsIfDeclared`: absence is absence, and a
87
+ declared value is still held to the host's https MUST.
88
+
89
+ Also: `requireWritten` replaces an unchecked cast in the x402 override, so a broken postcondition throws
90
+ instead of returning a success carrying no document; six x402 citations move to the revision that actually
91
+ touches the file they name, and a new gate refuses any `owner/repo@sha` in source that `spec-pins.json` does
92
+ not record; four spec citations move from line anchors to section anchors; the escrow binding states why it
93
+ declares no §8.3.1 off-canonical variant, and asserts it; and §C.3's `policies[]` illustration is recorded as
94
+ invalid against the live UCP schema, which shows `description` as a bare string where the host requires an
95
+ object — owed upstream, not a defect here.
96
+
97
+ Corpus 844 → 847, root `ec4ad1b02a81538b…`.
98
+
99
+ - Updated dependencies [b2ffecc]
100
+ - Updated dependencies [822190a]
101
+ - @integraledger/lcp-binding-core@0.11.0
102
+
103
+ ## 0.10.1
104
+
105
+ **0.10.0 was staged and withdrawn before approval; this is that release, re-cut.** The conformance corpus
106
+ was re-sealed after 0.10.0 was staged — its root moved `32fa90a6…` → `28bbf4ef…` when the vector tree was
107
+ brought inside the prose gates — so the staged `lcp-conformance` tarball carried a seal that no longer
108
+ matched the repository. The seal is what proves corpus authenticity to an independent implementer, and a
109
+ published version cannot be replaced, so the whole set was rejected and re-cut rather than shipping one
110
+ package that disagreed with its own source. No version 0.10.0 exists on the registry.
111
+
112
+ Released as part of a flat `0.10.1` across the whole suite. One number describes the set that was built,
113
+ tested and sealed together, so a consumer never has to work out which combination of versions was verified.
114
+
115
+ This package has no source change of its own in this release. What moved across the suite: three exported
116
+ names were corrected before anyone depends on them — `USDC_DECIMALS` became rail-qualified after it was
117
+ found to mean 6 on three rails and 7 on Stellar under one name, `VISA_TAP_PLACEMENT_TIER_A` lost the tier
118
+ it had baked into an identifier, and the memo codecs were named for the carrier they encode. Two wire
119
+ identities were respelled to match the vocabulary they are written into. Five gates were added or widened,
120
+ and a large number of documentation claims were corrected against the host specifications at HEAD.
121
+
3
122
  ## 0.9.0
4
123
 
5
124
  First public release.
package/README.md CHANGED
@@ -19,10 +19,10 @@ npm install @integraledger/lcp-placement-x402
19
19
  | **Chain** | none here — settlement is `binding-evm-x402`'s, on whichever EVM rail the scheme selects |
20
20
  | **Pattern** | `http-advisory` (LCP §8.3.7, Tier A) |
21
21
  | **Field** | `extensions.legalContext.info` — the top-level extensions map, the carrier x402 protects |
22
- | **Read also** | `accepts.0.extra.atrHash` — a **bare** hash, its own encoding (integrity) |
23
- | **Terms URL** | `extensions.legalContext.info.legalContextUrl` — declared, never written by `place` |
24
- | **Carrier types** | `sha256`, `url` |
25
- | **Spec** | x402 v2 (`x402-foundation/x402@1fec3aa04e41`, `specs/x402-specification-v2.md`), gate discharged **2026-07-30** |
22
+ | **Read also** | `accepts.0.extra.atrHash` — a **bare** hash, its own encoding (integrity), **written** by `place` |
23
+ | **Terms URL** | `extensions.legalContext.info.legalContextUrl` and `accepts.0.extra.legalContextUrl` both written by `place` |
24
+ | **Carrier types** | `sha256` |
25
+ | **Spec** | x402 v2 (`x402-foundation/x402@db5da2e65952`, `specs/x402-specification-v2.md`, read **2026-08-11**), gate discharged **2026-07-30** |
26
26
 
27
27
  ## Use
28
28
 
@@ -31,7 +31,9 @@ import { X402_PLACEMENT, x402Placement } from "@integraledger/lcp-placement-x402
31
31
 
32
32
  declare const challenge: unknown; // the x402 402 challenge document, as received
33
33
 
34
- const placed = x402Placement.place({ type: "sha256", value: "0x…" }, challenge);
34
+ const placed = x402Placement.place(
35
+ { ref: { type: "sha256", value: "0x…" }, termsUrl: "https://seller.example/.well-known/legal-context.json" },
36
+ challenge);
35
37
  const ref = x402Placement.extract(challenge); // reads either carrier, canonical first
36
38
  ```
37
39
 
@@ -55,18 +57,21 @@ and the PaymentRequirements table), four facts were confirmed and each one decid
55
57
  cannot delete or overwrite existing info." This is the carrier the **protocol itself protects**, which is
56
58
  why it is canonical here rather than the per-requirement object.
57
59
  4. **`extra` is "Scheme-specific additional information"** on a `PaymentRequirements` entry — the payment
58
- scheme's object, whose contents that scheme defines. So `place` **never writes there**; the alias is
59
- read-only, and that is a decision about whose namespace it is, not an omission.
60
+ scheme's object, whose contents that scheme defines. `place` writes the §C.4 mirror and the terms URL
61
+ there because §6.1 reserves those names for exactly this use, and writing only the extensions map left
62
+ a challenge the published buyer parsers refuse (integra-protocol#8).
60
63
 
61
64
  Extension identifiers are implementation-defined strings — no registry, no reverse-domain rule — so the
62
65
  `legalContext` key is available today and this placement is Tier A on the wire.
63
66
 
64
- ### Drift from LCP v1.37 §C.4, recorded rather than absorbed
67
+ ### Drift from LCP v1.37 §C.4 recorded then adopted by v1.38
65
68
 
66
69
  The host governs: its live specification is binding and LCP's Appendix C is an illustration.
67
70
  Two differences, neither of which changes the design:
68
71
 
69
- - v1.37 renders the echo rule in RFC-2119 capitals ("MAY append to but **MUST NOT** delete or overwrite")
72
+ - v1.37 rendered the echo rule in RFC-2119 capitals ("MAY append to but **MUST NOT** delete or overwrite")
73
+ where the host states it in lower case and about the data rather than the map. **v1.38 §C.4 now says
74
+ exactly that**, so this item is discharged; it is kept because it is why the package reads the way it does
70
75
  and states it about the extensions map. The live spec states it in lower case and about the `info` payload.
71
76
  Same rule, weaker modality, one level lower.
72
77
  - The live spec also carries `extra` on `PaymentPayload.accepted`, which §C.4 does not mention.
@@ -148,52 +153,48 @@ canonical non-negative integer segment only, so `length` and every other array p
148
153
  That relaxation lives in the kit rather than in a private loop here, because a read rule nobody can find is
149
154
  the same defect as a carrier nobody can find.
150
155
 
151
- ## Known limitations
152
-
153
- - **`place` writes the hash, never the terms URL.** `place(ref, doc)` holds one reference; the terms URL is a
154
- second datum. `termsUrlField` is declared so a parser can find the URL our seller does emit, the same
155
- division `placement-acp` draws with `metadata.legal_context_url`.
156
- - **The shipped carrier repeats the URL at `accepts[0].extra.legalContextUrl`.** A single `termsUrlField`
157
- cannot express two spellings, so that one is recorded here rather than half-declared in the manifest.
158
- - **A `url` carrier is placeable here and no shipped x402 reader accepts one.** `carrierTypes` permits
159
- `sha256` and `url` the §8.1 integrity form and the §8.1 discovery form, the same pair `placement-acp` and
160
- `placement-ucp` permit while a strict buyer gate refuses any `info.type` but `sha256`. The permission is not
161
- narrowed away here because narrowing the reference field to one type is a statement about what the slot may
162
- hold across the whole set, not a fact about x402. **Related and not ours to fix in this package:**
163
- `readDeclaredPaths` labels a canonical-field hit `carrierClass: "integrity"` unconditionally, so a `url`
164
- read from `extensions.legalContext.info` is labelled `integrity` and passes `requireIntegrity()` (from
165
- [`@integraledger/lcp-binding-core`](../binding-core#readme), like `makePlacement`). That is
166
- `binding-core` behaviour that `placement-acp` and `placement-ucp` already share on `main`; this manifest
167
- only adds a third reference field with the same property, and `requireIntegrity` has no production caller
168
- yet.
169
- - **Our own `legalContext` entry is replaced whole, not merged — and the key that loses is `legalContextUrl`,
170
- the one `termsUrlField` points at.** `place` writes `{info, schema}`, so nothing previously inside our entry
171
- survives, and on the document sellers actually emit that is concretely the terms-URL half: measured on
172
- the vector case for the long-standing shipped carrier, `termsUrlField` reads
173
- `https://seller.example/.well-known/legal-context.json` before `place` and `undefined` after. This is the
174
- kit's leaf-write semantics, not an override defect `makePlacement(X402_PLACEMENT).place` drops it
175
- identically — and it is why the previous bullet says `place` holds one reference: a caller that needs both
176
- halves on the wire writes the URL itself.
177
- **Consequence for a buyer.** Do not build a `PaymentPayload` echo with `place`. x402's rule is that the
178
- client "cannot delete or overwrite existing `info`", and re-placing over a server-sent entry deletes the
179
- URL the server put there. Echo the received entry verbatim and place only where no entry exists yet; the
180
- reconciled read/echo path belongs to a universal buyer parser, not this package.
181
- x402 defines only `info` and `schema` for an entry and the `legalContext` key is ours, so owning the entry is
182
- the point for the *seller* direction; entries *beside* ours in the `extensions` map are preserved
183
- unconditionally.
184
- - **`LEGAL_CONTEXT_SCHEMA_REF` points at a document the LCP TSC has not published yet**
185
- (`https://legalcontextprotocol.org/schemas/lcp-extension.json`). It is byte-identical to what emitters
186
- emits, and it is never fetched by anything in x402. Publishing it — or repointing it — is a **wire change
187
- and must be made in both repos at once**; repointing it here alone would recreate the drift this package
188
- exists to close.
156
+ ## Limitations, and the three this version resolved
157
+
158
+ The predecessor recorded four limitations here. Three were one defect wearing different clothes
159
+ integra-protocol#8, a seller built on this package emitting a challenge the published buyer refuses and
160
+ are resolved rather than recorded:
161
+
162
+ - **`place` now writes the whole advertisement.** `place({ ref, termsUrl }, doc)` puts the reference and
163
+ the terms URL on the wire together, at every slot `termsUrlFields` declares the `info` member the
164
+ authority schema requires and the `accepts[0].extra` mirror §C.4's illustration carries and REFUSES an
165
+ integrity-bearing advertisement with no URL, because a hash no counterparty can resolve is unverifiable
166
+ by construction. The predecessor's "place writes the hash, never the terms URL" division put the URL in
167
+ no published writer at all while every published reader demanded it.
168
+ - **Both URL spellings are declared, and both are written.** `termsUrlFields` is plural; the singular
169
+ member could not express the second spelling and the shipped carrier's `extra`-side URL lived only in
170
+ this README.
171
+ - **Our own entry is still replaced whole — and no longer costs the URL.** `place` rebuilds
172
+ `{info, schema}` wholesale (junk a counterparty parked inside our entry does not ride our wire), and the
173
+ URL survives because the rebuild happens after the kit has written it into `info`. The predecessor
174
+ measured the opposite: the leaf-write dropped `legalContextUrl` on the very document sellers emit.
175
+
176
+ One limitation is withdrawn rather than resolved: **the `url` carrier admission is gone.**
177
+ `carrierTypes` is `sha256` alone. A `url` here was well-formed against the manifest and refused by every
178
+ shipped reader a permission no reader accepts is a claim about the ecosystem, not a description of it —
179
+ and the schema this package itself puts on the wire (the authority document's shape, drift-gated in
180
+ `@integraledger/lcp-conformance`) is `const: "sha256"`, so a `url` reference would emit a challenge that
181
+ violates its own adjacent schema.
182
+
183
+ What remains, and is a fact rather than a defect: **`readDeclaredPaths` labels a canonical-field hit
184
+ `carrierClass: "integrity"` unconditionally.** That is `binding-core` behaviour shared by every placement,
185
+ and `requireIntegrity()` (from
186
+ [`@integraledger/lcp-binding-core`](../binding-core#readme), like `makePlacement`) is where a caller that
187
+ needs a content-addressed value says so it checks the decoded type as well as the slot's label.
189
188
 
190
189
  ## Provenance
191
190
 
192
- Cut against x402 v2 (`x402-foundation/x402@1fec3aa04e41`, `specs/x402-specification-v2.md`, read 2026-07-30) and reconciled
193
- against LCP v1.37 §C.4 the same day. The **paths and shapes** are matched field-for-field against the shipped
194
- shipped seller carrier and the buyer parsers that read it; the **resolution semantics diverge from a strict
195
- buyer gate in three recorded ways**, and *Drift from a strict buyer-side reader*
196
- above states each one and who closes it. Both drift sections are the disclosure, not a to-do list — a
191
+ Cut against x402 v2 and reconciled against LCP v1.37 §C.4 on 2026-07-30, and re-read against **v1.38 §C.4**
192
+ on 2026-08-12 which adopted the drift items below and added §6.1's reserved keys. The host revision is
193
+ `x402-foundation/x402@db5da2e65952` (`specs/x402-specification-v2.md`), which is what `spec-pins.json`
194
+ records this tree read on 2026-08-11 and what `pnpm spec-drift` watches. The **paths and shapes** are
195
+ matched field-for-field against the shipped seller carrier and the buyer parsers that read it; the
196
+ **resolution semantics diverge from a strict buyer gate in three recorded ways**, and *Drift from a strict
197
+ buyer-side reader* above states each one and who closes it. Both drift sections are the disclosure, not a to-do list — a
197
198
  divergence written down is governed; the same divergence unwritten is the undisclosed-drift defect wearing
198
199
  a new name.
199
200
 
@@ -1,28 +1,44 @@
1
1
  import type { PlacementManifest } from "@integraledger/lcp-binding-core";
2
2
  /**
3
- * The carrier's JSON Schema, INLINED rather than referenced.
3
+ * The carrier's JSON Schema, INLINED rather than referenced — and equal, member for member, to the
4
+ * AUTHORITY document at `https://integraledger.com/lcp/x402/legal-context/v1.schema.json` minus its `$id`
5
+ * and `$defs`. That equality is load-bearing and drift-gated (the conformance suite compares this literal
6
+ * to `@integraledger/lcp-discovery`'s shipped copy of the authority file), because its absence was a
7
+ * published defect: from 0.10.1's release until this version, this schema said `required: ["type",
8
+ * "value"]` while the authority document said `required: ["type", "value", "legalContextUrl"]` — two
9
+ * definitions of the same `info` in the published ecosystem, self-consistent halves, no document valid
10
+ * against both (integra-protocol#8). Both being conformant to the schema each carried is exactly why no
11
+ * package's own tests could catch it; only comparing the two could, and now something does.
4
12
  *
5
- * x402 makes `schema` a REQUIRED member of an extension entry — "JSON Schema defining the expected
6
- * structure of info" — so whatever goes here is on the wire of every challenge. A `$ref` to a URL nobody
7
- * serves would be a required member no counterparty can resolve, and "x402 never fetches it" is a reason
8
- * that does not break, not a reason to ship it. (`https://legalcontextprotocol.org/schemas/lcp-extension.json`
9
- * returns **404**, measured 2026-08-08 the schema is inlined instead.)
13
+ * WHY INLINE AT ALL: x402 makes `schema` a REQUIRED member of an extension entry — "JSON Schema defining
14
+ * the expected structure of info" — so whatever goes here is on the wire of every challenge. **All nine
15
+ * extensions published in the x402 repository inline a complete JSON Schema** rather than referencing an
16
+ * external document (`x402-foundation/x402` HEAD, read 2026-08-11), and one of the nine makes it a rule:
17
+ * the Bazaar extension requires a `schema`'s `$ref`/`$id` values to be "same-document JSON Pointer
18
+ * fragments (starting with `#`); external references … are not allowed", and says a facilitator "must not
19
+ * resolve external `$ref`/`$id` values … when validating an untrusted `schema`". So an external `$ref`
20
+ * here would not merely be unresolvable to a counterparty that declines to fetch — wherever Bazaar
21
+ * governs, it is rejected outright. LCP v1.38 §C.4 draws the same conclusion ("publish a resolvable
22
+ * schema or inline it — and inlining is the safer of the two"). Dropping `$id` and `$defs` from the
23
+ * inlined form is that rule applied: the authority document's `$id` is an absolute URL, and its `$defs`
24
+ * carries the RECEIPT-time definition, which is not this challenge-time `info` and would bloat every 402.
10
25
  *
11
- * **Both extensions published in the x402 repository inline a complete JSON Schema** rather than
12
- * referencing an external document, and LCP v1.38 §C.4 says to do one or the other: "Because `schema` is a
13
- * REQUIRED member, publish a resolvable schema or inline it." Inlining also removes a hosting dependency
14
- * the deployment does not currently meet the same dependency the UCP capability still owes.
15
- *
16
- * The shape is the §8.1 reference object this placement writes into `info`, and nothing more: it describes
17
- * the carrier, not the terms behind it.
26
+ * The shape is the §8.1 reference object PLUS the locator the reference is verified through:
27
+ * `legalContextUrl` is REQUIRED here because `value` is a digest a buyer verifies the terms by fetching
28
+ * the document and hashing it, so a challenge advertising the hash without the locator advertises
29
+ * something no counterparty who lacks the document can check. Every shipped buyer parser already refuses
30
+ * such a challenge; the schema now says on the wire what the readers always demanded. It describes the
31
+ * carrier, not the terms behind it, and asserts nothing about any agreement's lawfulness the
32
+ * description says so in as many words because the schema travels alone.
18
33
  *
19
34
  * Changing this is a WIRE change — it appears in every challenge — so it is a frozen literal rather than a
20
- * value assembled at call time.
35
+ * value assembled at call time, and the drift gate is what keeps the frozen copy honest.
21
36
  */
22
37
  export declare const LEGAL_CONTEXT_SCHEMA: Readonly<Record<string, unknown>>;
23
38
  /**
24
39
  * x402 reference placement — the HTTP-layer carrier, cut against the live x402 v2 specification
25
- * (`x402-foundation/x402@1fec3aa04e41`, `specs/x402-specification-v2.md`; gate discharged in the README).
40
+ * (`x402-foundation/x402@db5da2e65952`, `specs/x402-specification-v2.md`, read 2026-08-11; gate discharged
41
+ * in the README).
26
42
  *
27
43
  * **THE CANONICAL REPOSITORY IS THE FOUNDATION'S, and the citation moved on 2026-08-08.** Every
28
44
  * reference here named `coinbase/x402`, which the GitHub API reports as `"fork": true`;
@@ -65,39 +81,47 @@ export declare const LEGAL_CONTEXT_SCHEMA: Readonly<Record<string, unknown>>;
65
81
  * would emit something no x402 counterparty parses. This is the reason an alias declares its own `encoding`
66
82
  * at all.
67
83
  *
68
- * **The alias declares no `write`, and the live spec is the reason.** `extra` is "Scheme-specific additional
69
- * information" the payment scheme's object, whose contents that scheme defines. An `atrHash` is READ there
70
- * because sellers put one there; writing into another party's namespace is not the same act. The
71
- * `extensions` map is the protocol's own declared extension point and is where `place` writes.
84
+ * **The alias IS WRITTEN, and this reverses a recorded stance deliberately, on three grounds.** The
85
+ * predecessor declared no `write` on the reasoning that `extra` is "Scheme-specific additional information"
86
+ * and writing into another party's namespace is not our act. That reasoning has been overtaken. First, the
87
+ * host itself no longer treats `extra` as wholly scheme-private: §6.1 reserves `assetTransferMethod` and
88
+ * `paymentFlow` inside it as protocol-governed names, so `extra` is a host-managed extension surface with
89
+ * scheme-specific residue, not a foreign namespace. Second, LCP v1.38 §C.4's own Tier A illustration puts
90
+ * `atrHash` AND `legalContextUrl` in `accepts[].extra` — a third-party reader built from the spec's example
91
+ * reads `extra` first, and a challenge that leaves it empty is invisible to that reader. Third, the shipped
92
+ * buyer parser reads BOTH carriers and reconciles, refusing disagreement — so the mirror cannot drift
93
+ * silently: two slots either agree or the document refuses at the counterparty. The write lands only in
94
+ * `accepts[0]`, the requirement buyer parsers read (see the index-0 rule below), and never touches the
95
+ * reserved names.
72
96
  *
73
97
  * **The alias is index 0 only.** A locator names one path. `accepts[0]` is what buyer parsers read, and the
74
98
  * reason is substantive: the reference must bind to the requirement actually being paid, and searching every
75
99
  * requirement would let a seller park a second set of terms on an alternative it never expects to be chosen.
76
100
  *
77
- * **`termsUrlField` is DECLARED this is the protocol whose wire carries both halves.** `binding-core`'s own
78
- * contract cites x402 for exactly that: a buyer-side parser may demand the URL because x402 carries it, and
79
- * emitters put `legalContextUrl` inside `info` beside `type`/`value`. Declaring the path
80
- * makes that half machine-readable instead of a second private convention; `place` never writes it, because
81
- * `place(ref, doc)` holds one reference and the terms URL is a different datum (the same division ACP draws
82
- * with `metadata.legal_context_url`). The shipped carrier repeats the URL at `accepts[0].extra.legalContextUrl`
83
- * too, which a single `termsUrlField` cannot express recorded in the README as a known limitation rather
84
- * than half-declared here.
85
- *
86
- * **`carrierTypes` permits `sha256` and `url`, and the two are admitted on DIFFERENT grounds** one reason
87
- * cannot cover both. `sha256` is the integrity carrier: §C.4's illustration carries one, emitters carry one,
88
- * and the bare alias is fixed to it. `url` is the §8.1 discovery form, admitted because the canonical
89
- * slot is a general reference-object slot and the kit puts the integrity-versus-discovery decision at the
90
- * READER — `carrierClass` plus `requireIntegrity` — not in the permission list; ACP and UCP permit it for the
91
- * same reason. `ipfs`/`ar` are excluded on a ground that does NOT apply to `url`: they are ALTERNATIVE
92
- * integrity carriers, so admitting one adds no capability `sha256` does not already discharge while
93
- * advertising a content-addressed transport no x402 counterparty resolves — a claim about the ecosystem
94
- * rather than a description of it.
101
+ * **`termsUrlFields` declares BOTH slots the wire carries, and both are written.** The predecessor member
102
+ * (`termsUrlField`, singular) named only the `extensions` slot, and it was read-only in every published
103
+ * package the write path did not exist anywhere, so a seller assembling from published parts emitted a
104
+ * challenge advertising a hash with no locator, which the published buyer refuses
105
+ * (integra-protocol#8). Declaring both slots makes the manifest state what actually lands on the wire:
106
+ * `place` writes the URL beside the reference in `info` (where the authority schema requires it) and
107
+ * mirrors it at `accepts[0].extra.legalContextUrl` (where §C.4's illustration carries it), and `extract`
108
+ * reconciles the two, refusing disagreement. The kit REQUIRES the URL of any integrity-bearing
109
+ * advertisement on this manifest — a hash no counterparty can resolve is unverifiable by construction,
110
+ * which is the defect the readers always guarded against and the emitters never did.
95
111
  *
96
- * The `url` permission is nonetheless WIDER than any shipped x402 reader: a buyer parser that requires an
97
- * integrity carrier refuses `info.type !== "sha256"` outright, so a `url` placed in this slot is well-formed
98
- * against this manifest and would still be rejected at read time. Recorded in the README as a limitation
99
- * rather than narrowed away here, because narrowing the reference field to one type is a change to what the
100
- * SLOT may hold across the set, not a fact about x402.
112
+ * **`carrierTypes` is `sha256` alone, and the `url` admission is WITHDRAWN a defect resolved, not a
113
+ * preference.** The predecessor admitted `url` as the §8.1 discovery form and recorded, in the same
114
+ * docblock, that the permission was "WIDER than any shipped x402 reader": a buyer parser that requires an
115
+ * integrity carrier refuses `info.type !== "sha256"` outright, so a `url` placed in this slot was
116
+ * well-formed against the manifest and rejected at read time — a permission no reader accepts, which is a
117
+ * claim about the ecosystem rather than a description of it. The withdrawal ground is now structural: the
118
+ * `schema` member this package puts on the wire is the AUTHORITY document's shape, whose `type` is
119
+ * `const: "sha256"`, so a `url` reference would emit a challenge that violates its own adjacent schema.
120
+ * The predecessor declined to narrow because "narrowing the reference field to one type is a change to
121
+ * what the SLOT may hold across the set" — that set-wide decision has since been made, by the authority
122
+ * document. `ipfs`/`ar` remain excluded on the original ground: alternative integrity carriers add no
123
+ * capability `sha256` does not already discharge while advertising a content-addressed transport no x402
124
+ * counterparty resolves.
101
125
  */
102
126
  export declare const X402_PLACEMENT: PlacementManifest;
103
127
  //# sourceMappingURL=manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuB/D,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,eAAO,MAAM,cAAc,EAAE,iBAkB5B,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA6B/D,CAAC;AAEL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,eAAO,MAAM,cAAc,EAAE,iBAsB5B,CAAC"}