@integraledger/lcp-placement-ucp 0.10.1 → 0.12.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 +106 -0
- package/README.md +2 -2
- package/dist/manifest.d.ts +12 -6
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +16 -6
- package/dist/manifest.js.map +1 -1
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +3 -2
- package/dist/placement.js.map +1 -1
- package/package.json +2 -2
- package/src/manifest.ts +16 -6
- package/src/placement.ts +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,111 @@
|
|
|
1
1
|
# @integraledger/lcp-placement-ucp
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @integraledger/lcp-binding-core@0.12.0
|
|
8
|
+
|
|
9
|
+
## 0.11.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- b2ffecc: Report the class a record actually supports, place a terms URL on UCP, and stop refusing a conformant UCP
|
|
14
|
+
profile — the remediation of the 2026-08-19 conformance re-audit.
|
|
15
|
+
|
|
16
|
+
**`verify` now computes `supportedClass` instead of echoing the claim.** It was `anyFailed ? "TC-0" :
|
|
17
|
+
claimedClass`, so a record proving nothing — no settlement, no acceptance, no authority chain — reported
|
|
18
|
+
whatever class the caller named, while the field's own published docblock promised "what the record
|
|
19
|
+
honestly supports, not what the caller asked for". It is now the highest class every one of whose required
|
|
20
|
+
steps is `proved`, `TC-0` on any failure, computed from the steps alone: neither capped by the claim (rungs
|
|
21
|
+
that reach TC-3 read TC-3 where the caller claimed TC-2) nor lifted by it. White paper #4 §5 defines the
|
|
22
|
+
class of a transaction as "the highest class whose criteria it fully meets", and this is that.
|
|
23
|
+
|
|
24
|
+
The claim is not discarded — the report gains **`claimedClass`**, a required member, because `verified`
|
|
25
|
+
answers "did the record reach the class it claimed?" and cannot be read without it. The two fields are the
|
|
26
|
+
report's two halves: an input echoed, and a finding computed. Where they differ, the record did not reach
|
|
27
|
+
its own shape. An out-of-taxonomy claim now lands only in the echo and can no longer masquerade as a
|
|
28
|
+
finding.
|
|
29
|
+
|
|
30
|
+
**UCP can advertise a terms URL.** Its policy object declares `url` — "Optional link to the full policy
|
|
31
|
+
document", `format: uri` — on the very entry this placement writes, and §C.3's illustration carries `url`
|
|
32
|
+
and `atrHash` side by side there. The manifest previously said the protocol had no slot, citing `links[]`,
|
|
33
|
+
which §C.3 separates as "a standing page, not a per-transaction record". The obstacle was mechanical:
|
|
34
|
+
`termsUrlFields` addresses document paths, and a tagged-array entry's index is chosen at write time. The
|
|
35
|
+
`tagged-array` container therefore gains `termsUrlField`, written onto the same entry in the same write, and
|
|
36
|
+
read back through the same first-match rule. UCP was the last shipped protocol that refused an
|
|
37
|
+
advertisement carrying its own locator (integra-protocol#8).
|
|
38
|
+
|
|
39
|
+
**`readUcpProfile` no longer refuses a conformant business profile.** `requireHttps` mapped an ABSENT `spec`
|
|
40
|
+
to the same branch as a malformed one, and the live host requires `spec` only of a platform declaration —
|
|
41
|
+
as this repository's own README already said. It is now `requireHttpsIfDeclared`: absence is absence, and a
|
|
42
|
+
declared value is still held to the host's https MUST.
|
|
43
|
+
|
|
44
|
+
Also: `requireWritten` replaces an unchecked cast in the x402 override, so a broken postcondition throws
|
|
45
|
+
instead of returning a success carrying no document; six x402 citations move to the revision that actually
|
|
46
|
+
touches the file they name, and a new gate refuses any `owner/repo@sha` in source that `spec-pins.json` does
|
|
47
|
+
not record; four spec citations move from line anchors to section anchors; the escrow binding states why it
|
|
48
|
+
declares no §8.3.1 off-canonical variant, and asserts it; and §C.3's `policies[]` illustration is recorded as
|
|
49
|
+
invalid against the live UCP schema, which shows `description` as a bare string where the host requires an
|
|
50
|
+
object — owed upstream, not a defect here.
|
|
51
|
+
|
|
52
|
+
Corpus 844 → 847, root `ec4ad1b02a81538b…`.
|
|
53
|
+
|
|
54
|
+
- 822190a: Give the terms URL the write path the published set never had, and certify the composition that broke
|
|
55
|
+
without it (integra-protocol#8).
|
|
56
|
+
|
|
57
|
+
A third party assembling a seller from published parts emitted a 402 the published buyer refuses: every
|
|
58
|
+
published reader demanded `legalContextUrl` and no published writer placed it, and the schema
|
|
59
|
+
`placement-x402` inlined onto the wire (`required: ["type","value"]`, closed) contradicted the authority
|
|
60
|
+
document integraledger.com serves (`required: ["type","value","legalContextUrl"]`, closed) — two
|
|
61
|
+
definitions of one `info`, no document valid against both, each package self-consistent. Three structural
|
|
62
|
+
gaps let it ship: the manifest's `termsUrlField` was singular and read-only (declared, hygiene-checked,
|
|
63
|
+
never written — and x402's wire carries the URL in two slots, so one path could not even name the shape),
|
|
64
|
+
nothing compared the inlined schema to the authority document, and the corpus certified `place` and
|
|
65
|
+
`extract` separately but never fed one to the other.
|
|
66
|
+
|
|
67
|
+
`binding-core` — the placement seam now moves an ADVERTISEMENT, not a bare reference. `place` takes
|
|
68
|
+
`{ ref, termsUrl? }` and writes the URL at every slot the manifest's new `termsUrlFields` (plural,
|
|
69
|
+
replacing `termsUrlField`) declares; it REFUSES an integrity-bearing advertisement with no URL where slots
|
|
70
|
+
are declared (a hash no counterparty can resolve is unverifiable by construction), a URL where no slot
|
|
71
|
+
exists (silent dropping is fail-open), and a non-https URL on either side of the seam. `extract` returns
|
|
72
|
+
`{ ref, termsUrl }` with absence as a typed value — `no-field-declared` is a fact about the protocol,
|
|
73
|
+
`declared-fields-empty` a fact about the document, and the gate decides what an absence means — while two
|
|
74
|
+
slots that disagree, or a malformed value in either, refuse. The object-path writer learned to descend
|
|
75
|
+
into an EXISTING array element (never minting one, never extending a list, refusing an index segment it
|
|
76
|
+
would have to create), which is what lets x402's `accepts[0].extra` mirrors land.
|
|
77
|
+
|
|
78
|
+
`placement-x402` — the inlined wire schema now IS the authority document minus `$id` and `$defs`
|
|
79
|
+
(Bazaar forbids both on the wire), drift-gated in `lcp-conformance` where the two packages meet.
|
|
80
|
+
`termsUrlFields` declares both slots the wire carries; the bare-hash alias is written (`extra` stopped
|
|
81
|
+
being wholly scheme-private when x402 §6.1 reserved names inside it, and LCP v1.38 §C.4's own Tier A
|
|
82
|
+
illustration carries the pair there); the `url` carrier admission is withdrawn (`carrierTypes` is
|
|
83
|
+
`sha256` alone — the schema on the wire is `const: "sha256"`, and no shipped reader ever accepted a url
|
|
84
|
+
in this slot). The `place` override shrinks to composition: the kit performs the whole placement and the
|
|
85
|
+
override adds only the `{info, schema}` wrapper.
|
|
86
|
+
|
|
87
|
+
`placement-mpp` / `placement-acp` — the singular member becomes the one-entry `termsUrlFields`; the kit
|
|
88
|
+
now writes the slot their buyer parsers always demanded and refuses first at the seller.
|
|
89
|
+
|
|
90
|
+
`lcp-conformance` — the corpus grows 812 → 844: a `roundtrip` op (place then extract in one case, the
|
|
91
|
+
composition certification whose absence let two separately-conformant halves ship jointly broken),
|
|
92
|
+
advertisement-rule refusals for every manifest, and the authority↔wire drift gate. Extract expectations
|
|
93
|
+
across every placement area become the extracted advertisement.
|
|
94
|
+
|
|
95
|
+
`lcp-verify` — `referencePlacementStep` reads the advertisement (`extracted.ref.value`) and deliberately
|
|
96
|
+
ignores `termsUrl`: where the terms live is the gate's fetch concern, not a fact the record can
|
|
97
|
+
contradict.
|
|
98
|
+
|
|
99
|
+
`lcp-discovery` — the x402 authority document restates the atrHash pattern inline in both definitions
|
|
100
|
+
(no `$defs` indirection the wire copy would have to rewrite) and moves the two-definitions rationale into
|
|
101
|
+
`$defs.receipt`, so the challenge-time root is byte-derivable for the wire.
|
|
102
|
+
|
|
103
|
+
### Patch Changes
|
|
104
|
+
|
|
105
|
+
- Updated dependencies [b2ffecc]
|
|
106
|
+
- Updated dependencies [822190a]
|
|
107
|
+
- @integraledger/lcp-binding-core@0.11.0
|
|
108
|
+
|
|
3
109
|
## 0.10.1
|
|
4
110
|
|
|
5
111
|
**0.10.0 was staged and withdrawn before approval; this is that release, re-cut.** The conformance corpus
|
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ import { UCP_PLACEMENT, ucpPlacement } from "@integraledger/lcp-placement-ucp";
|
|
|
37
37
|
|
|
38
38
|
declare const checkout: unknown; // the UCP checkout response, as received
|
|
39
39
|
|
|
40
|
-
const placed = ucpPlacement.place({ type: "sha256", value: "0x…" }, checkout);
|
|
40
|
+
const placed = ucpPlacement.place({ ref: { type: "sha256", value: "0x…" } }, checkout);
|
|
41
41
|
const ref = ucpPlacement.extract(checkout);
|
|
42
42
|
```
|
|
43
43
|
|
|
@@ -108,7 +108,7 @@ alone. Both arms are pinned as vectors.
|
|
|
108
108
|
|
|
109
109
|
## Why the links alias has no `write` flag
|
|
110
110
|
|
|
111
|
-
The links entry carries the terms **URL** — a different datum than the atrHash — and `place(
|
|
111
|
+
The links entry carries the terms **URL** — a different datum than the atrHash — and `place(ad, doc)`
|
|
112
112
|
holds one reference, not two data: writing `ref.value` into `links[].url` would put a bare hash where every
|
|
113
113
|
UCP client expects a URL. Publishing the links entry is the deployment's act (UCP already makes `links[]`
|
|
114
114
|
required on checkout responses), and this placement **reads** it: `extract` falls to the alias when no policy entry is
|
package/dist/manifest.d.ts
CHANGED
|
@@ -66,12 +66,18 @@ import type { PlacementManifest } from "@integraledger/lcp-binding-core";
|
|
|
66
66
|
* `links[].type` is an OPEN set ("Businesses MAY define custom types"); the well-known `terms_of_service`
|
|
67
67
|
* tag is used because it is the spelling counterparties already read, not because it is the only one legal.
|
|
68
68
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
69
|
+
* **The terms URL rides the policies entry, declared as the container's `termsUrlField`.** UCP's policy
|
|
70
|
+
* object is `additionalProperties: true` and declares `url` outright — "Optional link to the full policy
|
|
71
|
+
* document", `format: uri` (re-verified at UCP HEAD, `source/schemas/shopping/types/policy.json`) — and
|
|
72
|
+
* §C.3's own illustration carries `url` and `atrHash` side by side in ONE entry. So the per-transaction
|
|
73
|
+
* locator belongs on the per-transaction record, which is the entry this placement writes.
|
|
74
|
+
*
|
|
75
|
+
* It is declared container-relative rather than in `termsUrlFields` because the entry's index is chosen at
|
|
76
|
+
* write time (replace-by-tag, else append), so no dotted path names it — not because the host lacks a slot.
|
|
77
|
+
* `links[]` is NOT that slot and never was: §C.3 separates the two explicitly, `links[]` being "a standing
|
|
78
|
+
* page, not a per-transaction record". A predecessor of this manifest gave the links entry as the reason
|
|
79
|
+
* this placement could advertise no terms URL at all, which read the wrong carrier and left UCP the one
|
|
80
|
+
* shipped protocol that refused an advertisement carrying its locator (integra-protocol#8).
|
|
75
81
|
*
|
|
76
82
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
77
83
|
* reference against this list, so the discovery alias's url-typed hits would refuse
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -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
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AACH,eAAO,MAAM,aAAa,EAAE,iBA6C3B,CAAC"}
|
package/dist/manifest.js
CHANGED
|
@@ -65,12 +65,18 @@
|
|
|
65
65
|
* `links[].type` is an OPEN set ("Businesses MAY define custom types"); the well-known `terms_of_service`
|
|
66
66
|
* tag is used because it is the spelling counterparties already read, not because it is the only one legal.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
68
|
+
* **The terms URL rides the policies entry, declared as the container's `termsUrlField`.** UCP's policy
|
|
69
|
+
* object is `additionalProperties: true` and declares `url` outright — "Optional link to the full policy
|
|
70
|
+
* document", `format: uri` (re-verified at UCP HEAD, `source/schemas/shopping/types/policy.json`) — and
|
|
71
|
+
* §C.3's own illustration carries `url` and `atrHash` side by side in ONE entry. So the per-transaction
|
|
72
|
+
* locator belongs on the per-transaction record, which is the entry this placement writes.
|
|
73
|
+
*
|
|
74
|
+
* It is declared container-relative rather than in `termsUrlFields` because the entry's index is chosen at
|
|
75
|
+
* write time (replace-by-tag, else append), so no dotted path names it — not because the host lacks a slot.
|
|
76
|
+
* `links[]` is NOT that slot and never was: §C.3 separates the two explicitly, `links[]` being "a standing
|
|
77
|
+
* page, not a per-transaction record". A predecessor of this manifest gave the links entry as the reason
|
|
78
|
+
* this placement could advertise no terms URL at all, which read the wrong carrier and left UCP the one
|
|
79
|
+
* shipped protocol that refused an advertisement carrying its locator (integra-protocol#8).
|
|
74
80
|
*
|
|
75
81
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
76
82
|
* reference against this list, so the discovery alias's url-typed hits would refuse
|
|
@@ -104,6 +110,10 @@ export const UCP_PLACEMENT = {
|
|
|
104
110
|
plain: "Terms of sale for this order, identified by a Legal Context Protocol reference. The reference identifies the exact terms document; it is not itself the terms.",
|
|
105
111
|
},
|
|
106
112
|
},
|
|
113
|
+
// `url` is declared on this very object by the host — "Optional link to the full policy document",
|
|
114
|
+
// format uri — and §C.3's illustration carries it beside `atrHash` in one entry. Verified at UCP HEAD
|
|
115
|
+
// (source/schemas/shopping/types/policy.json).
|
|
116
|
+
termsUrlField: "url",
|
|
107
117
|
},
|
|
108
118
|
field: "policies[type=com.integraledger.policy.legal_context]",
|
|
109
119
|
readAlso: [
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AACH,MAAM,CAAC,MAAM,aAAa,GAAsB;IAC9C,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,eAAe;IACxB,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,kBAAkB;IAC5B,SAAS,EAAE;QACT,IAAI,EAAE,cAAc;QACpB,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,MAAM;QAChB,GAAG,EAAE,wCAAwC;QAC7C,UAAU,EAAE,iCAAiC;QAC7C,mGAAmG;QACnG,iGAAiG;QACjG,yGAAyG;QACzG,uCAAuC;QACvC,SAAS,EAAE;YACT,WAAW,EAAE;gBACX,KAAK,EACH,gKAAgK;aACnK;SACF;QACD,mGAAmG;QACnG,sGAAsG;QACtG,+CAA+C;QAC/C,aAAa,EAAE,KAAK;KACrB;IACD,KAAK,EAAE,uDAAuD;IAC9D,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,kCAAkC;YACxC,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE,WAAW;YACzB,SAAS,EAAE;gBACT,IAAI,EAAE,cAAc;gBACpB,EAAE,EAAE,OAAO;gBACX,QAAQ,EAAE,MAAM;gBAChB,GAAG,EAAE,kBAAkB;gBACvB,UAAU,EAAE,KAAK;aAClB;SACF;KACF;IACD,YAAY,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;IAC/B,OAAO,EACL,sWAAsW;CACzW,CAAC"}
|
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,EAEL,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AAKzC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"placement.d.ts","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,iCAAiC,CAAC;AAKzC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,YAAY,EAAE,yBAe1B,CAAC"}
|
package/dist/placement.js
CHANGED
|
@@ -30,12 +30,13 @@ export const ucpPlacement = {
|
|
|
30
30
|
const out = base.extract(doc);
|
|
31
31
|
if ("refused" in out)
|
|
32
32
|
return out;
|
|
33
|
-
|
|
33
|
+
const { ref } = out.value;
|
|
34
|
+
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
34
35
|
return {
|
|
35
36
|
refused: true,
|
|
36
37
|
haltClass: "verification-failure",
|
|
37
38
|
code: "ucp/insecure-terms-url",
|
|
38
|
-
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${
|
|
39
|
+
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
39
40
|
};
|
|
40
41
|
return out;
|
|
41
42
|
},
|
package/dist/placement.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAEd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,IAAI,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,YAAY,GAA8B;IACrD,GAAG,IAAI;IACP,OAAO,CAAC,GAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,SAAS,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACjC,
|
|
1
|
+
{"version":3,"file":"placement.js","sourceRoot":"","sources":["../src/placement.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAEd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,IAAI,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,YAAY,GAA8B;IACrD,GAAG,IAAI;IACP,OAAO,CAAC,GAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,SAAS,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACjC,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;QAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,sBAAsB;gBACjC,IAAI,EAAE,wBAAwB;gBAC9B,MAAM,EAAE,6EAA6E,GAAG,CAAC,KAAK,EAAE;aACjG,CAAC;QACJ,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@integraledger/lcp-placement-ucp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Places an LCP reference into a UCP (Universal Commerce Protocol) checkout, 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-ucp#readme",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@integraledger/lcp-binding-core": "0.
|
|
43
|
+
"@integraledger/lcp-binding-core": "0.12.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@cfworker/json-schema": "4.1.1",
|
package/src/manifest.ts
CHANGED
|
@@ -67,12 +67,18 @@ import type { PlacementManifest } from "@integraledger/lcp-binding-core";
|
|
|
67
67
|
* `links[].type` is an OPEN set ("Businesses MAY define custom types"); the well-known `terms_of_service`
|
|
68
68
|
* tag is used because it is the spelling counterparties already read, not because it is the only one legal.
|
|
69
69
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
70
|
+
* **The terms URL rides the policies entry, declared as the container's `termsUrlField`.** UCP's policy
|
|
71
|
+
* object is `additionalProperties: true` and declares `url` outright — "Optional link to the full policy
|
|
72
|
+
* document", `format: uri` (re-verified at UCP HEAD, `source/schemas/shopping/types/policy.json`) — and
|
|
73
|
+
* §C.3's own illustration carries `url` and `atrHash` side by side in ONE entry. So the per-transaction
|
|
74
|
+
* locator belongs on the per-transaction record, which is the entry this placement writes.
|
|
75
|
+
*
|
|
76
|
+
* It is declared container-relative rather than in `termsUrlFields` because the entry's index is chosen at
|
|
77
|
+
* write time (replace-by-tag, else append), so no dotted path names it — not because the host lacks a slot.
|
|
78
|
+
* `links[]` is NOT that slot and never was: §C.3 separates the two explicitly, `links[]` being "a standing
|
|
79
|
+
* page, not a per-transaction record". A predecessor of this manifest gave the links entry as the reason
|
|
80
|
+
* this placement could advertise no terms URL at all, which read the wrong carrier and left UCP the one
|
|
81
|
+
* shipped protocol that refused an advertisement carrying its locator (integra-protocol#8).
|
|
76
82
|
*
|
|
77
83
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
78
84
|
* reference against this list, so the discovery alias's url-typed hits would refuse
|
|
@@ -107,6 +113,10 @@ export const UCP_PLACEMENT: PlacementManifest = {
|
|
|
107
113
|
"Terms of sale for this order, identified by a Legal Context Protocol reference. The reference identifies the exact terms document; it is not itself the terms.",
|
|
108
114
|
},
|
|
109
115
|
},
|
|
116
|
+
// `url` is declared on this very object by the host — "Optional link to the full policy document",
|
|
117
|
+
// format uri — and §C.3's illustration carries it beside `atrHash` in one entry. Verified at UCP HEAD
|
|
118
|
+
// (source/schemas/shopping/types/policy.json).
|
|
119
|
+
termsUrlField: "url",
|
|
110
120
|
},
|
|
111
121
|
field: "policies[type=com.integraledger.policy.legal_context]",
|
|
112
122
|
readAlso: [
|
package/src/placement.ts
CHANGED
|
@@ -34,12 +34,13 @@ export const ucpPlacement: ReferencePlacementAdapter = {
|
|
|
34
34
|
extract(doc: unknown) {
|
|
35
35
|
const out = base.extract(doc);
|
|
36
36
|
if ("refused" in out) return out;
|
|
37
|
-
|
|
37
|
+
const { ref } = out.value;
|
|
38
|
+
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
38
39
|
return {
|
|
39
40
|
refused: true,
|
|
40
41
|
haltClass: "verification-failure",
|
|
41
42
|
code: "ucp/insecure-terms-url",
|
|
42
|
-
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${
|
|
43
|
+
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
43
44
|
};
|
|
44
45
|
return out;
|
|
45
46
|
},
|