@integraledger/lcp-placement-ucp 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 +15 -9
- package/dist/manifest.d.ts +12 -5
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +16 -5
- package/dist/manifest.js.map +1 -1
- package/dist/placement.d.ts +7 -4
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +10 -6
- package/dist/placement.js.map +1 -1
- package/package.json +2 -2
- package/src/manifest.ts +16 -5
- package/src/placement.ts +10 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
1
|
# @integraledger/lcp-placement-ucp
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b2ffecc: Report the class a record actually supports, place a terms URL on UCP, and stop refusing a conformant UCP
|
|
8
|
+
profile — the remediation of the 2026-08-19 conformance re-audit.
|
|
9
|
+
|
|
10
|
+
**`verify` now computes `supportedClass` instead of echoing the claim.** It was `anyFailed ? "TC-0" :
|
|
11
|
+
claimedClass`, so a record proving nothing — no settlement, no acceptance, no authority chain — reported
|
|
12
|
+
whatever class the caller named, while the field's own published docblock promised "what the record
|
|
13
|
+
honestly supports, not what the caller asked for". It is now the highest class every one of whose required
|
|
14
|
+
steps is `proved`, `TC-0` on any failure, computed from the steps alone: neither capped by the claim (rungs
|
|
15
|
+
that reach TC-3 read TC-3 where the caller claimed TC-2) nor lifted by it. White paper #4 §5 defines the
|
|
16
|
+
class of a transaction as "the highest class whose criteria it fully meets", and this is that.
|
|
17
|
+
|
|
18
|
+
The claim is not discarded — the report gains **`claimedClass`**, a required member, because `verified`
|
|
19
|
+
answers "did the record reach the class it claimed?" and cannot be read without it. The two fields are the
|
|
20
|
+
report's two halves: an input echoed, and a finding computed. Where they differ, the record did not reach
|
|
21
|
+
its own shape. An out-of-taxonomy claim now lands only in the echo and can no longer masquerade as a
|
|
22
|
+
finding.
|
|
23
|
+
|
|
24
|
+
**UCP can advertise a terms URL.** Its policy object declares `url` — "Optional link to the full policy
|
|
25
|
+
document", `format: uri` — on the very entry this placement writes, and §C.3's illustration carries `url`
|
|
26
|
+
and `atrHash` side by side there. The manifest previously said the protocol had no slot, citing `links[]`,
|
|
27
|
+
which §C.3 separates as "a standing page, not a per-transaction record". The obstacle was mechanical:
|
|
28
|
+
`termsUrlFields` addresses document paths, and a tagged-array entry's index is chosen at write time. The
|
|
29
|
+
`tagged-array` container therefore gains `termsUrlField`, written onto the same entry in the same write, and
|
|
30
|
+
read back through the same first-match rule. UCP was the last shipped protocol that refused an
|
|
31
|
+
advertisement carrying its own locator (integra-protocol#8).
|
|
32
|
+
|
|
33
|
+
**`readUcpProfile` no longer refuses a conformant business profile.** `requireHttps` mapped an ABSENT `spec`
|
|
34
|
+
to the same branch as a malformed one, and the live host requires `spec` only of a platform declaration —
|
|
35
|
+
as this repository's own README already said. It is now `requireHttpsIfDeclared`: absence is absence, and a
|
|
36
|
+
declared value is still held to the host's https MUST.
|
|
37
|
+
|
|
38
|
+
Also: `requireWritten` replaces an unchecked cast in the x402 override, so a broken postcondition throws
|
|
39
|
+
instead of returning a success carrying no document; six x402 citations move to the revision that actually
|
|
40
|
+
touches the file they name, and a new gate refuses any `owner/repo@sha` in source that `spec-pins.json` does
|
|
41
|
+
not record; four spec citations move from line anchors to section anchors; the escrow binding states why it
|
|
42
|
+
declares no §8.3.1 off-canonical variant, and asserts it; and §C.3's `policies[]` illustration is recorded as
|
|
43
|
+
invalid against the live UCP schema, which shows `description` as a bare string where the host requires an
|
|
44
|
+
object — owed upstream, not a defect here.
|
|
45
|
+
|
|
46
|
+
Corpus 844 → 847, root `ec4ad1b02a81538b…`.
|
|
47
|
+
|
|
48
|
+
- 822190a: Give the terms URL the write path the published set never had, and certify the composition that broke
|
|
49
|
+
without it (integra-protocol#8).
|
|
50
|
+
|
|
51
|
+
A third party assembling a seller from published parts emitted a 402 the published buyer refuses: every
|
|
52
|
+
published reader demanded `legalContextUrl` and no published writer placed it, and the schema
|
|
53
|
+
`placement-x402` inlined onto the wire (`required: ["type","value"]`, closed) contradicted the authority
|
|
54
|
+
document integraledger.com serves (`required: ["type","value","legalContextUrl"]`, closed) — two
|
|
55
|
+
definitions of one `info`, no document valid against both, each package self-consistent. Three structural
|
|
56
|
+
gaps let it ship: the manifest's `termsUrlField` was singular and read-only (declared, hygiene-checked,
|
|
57
|
+
never written — and x402's wire carries the URL in two slots, so one path could not even name the shape),
|
|
58
|
+
nothing compared the inlined schema to the authority document, and the corpus certified `place` and
|
|
59
|
+
`extract` separately but never fed one to the other.
|
|
60
|
+
|
|
61
|
+
`binding-core` — the placement seam now moves an ADVERTISEMENT, not a bare reference. `place` takes
|
|
62
|
+
`{ ref, termsUrl? }` and writes the URL at every slot the manifest's new `termsUrlFields` (plural,
|
|
63
|
+
replacing `termsUrlField`) declares; it REFUSES an integrity-bearing advertisement with no URL where slots
|
|
64
|
+
are declared (a hash no counterparty can resolve is unverifiable by construction), a URL where no slot
|
|
65
|
+
exists (silent dropping is fail-open), and a non-https URL on either side of the seam. `extract` returns
|
|
66
|
+
`{ ref, termsUrl }` with absence as a typed value — `no-field-declared` is a fact about the protocol,
|
|
67
|
+
`declared-fields-empty` a fact about the document, and the gate decides what an absence means — while two
|
|
68
|
+
slots that disagree, or a malformed value in either, refuse. The object-path writer learned to descend
|
|
69
|
+
into an EXISTING array element (never minting one, never extending a list, refusing an index segment it
|
|
70
|
+
would have to create), which is what lets x402's `accepts[0].extra` mirrors land.
|
|
71
|
+
|
|
72
|
+
`placement-x402` — the inlined wire schema now IS the authority document minus `$id` and `$defs`
|
|
73
|
+
(Bazaar forbids both on the wire), drift-gated in `lcp-conformance` where the two packages meet.
|
|
74
|
+
`termsUrlFields` declares both slots the wire carries; the bare-hash alias is written (`extra` stopped
|
|
75
|
+
being wholly scheme-private when x402 §6.1 reserved names inside it, and LCP v1.38 §C.4's own Tier A
|
|
76
|
+
illustration carries the pair there); the `url` carrier admission is withdrawn (`carrierTypes` is
|
|
77
|
+
`sha256` alone — the schema on the wire is `const: "sha256"`, and no shipped reader ever accepted a url
|
|
78
|
+
in this slot). The `place` override shrinks to composition: the kit performs the whole placement and the
|
|
79
|
+
override adds only the `{info, schema}` wrapper.
|
|
80
|
+
|
|
81
|
+
`placement-mpp` / `placement-acp` — the singular member becomes the one-entry `termsUrlFields`; the kit
|
|
82
|
+
now writes the slot their buyer parsers always demanded and refuses first at the seller.
|
|
83
|
+
|
|
84
|
+
`lcp-conformance` — the corpus grows 812 → 844: a `roundtrip` op (place then extract in one case, the
|
|
85
|
+
composition certification whose absence let two separately-conformant halves ship jointly broken),
|
|
86
|
+
advertisement-rule refusals for every manifest, and the authority↔wire drift gate. Extract expectations
|
|
87
|
+
across every placement area become the extracted advertisement.
|
|
88
|
+
|
|
89
|
+
`lcp-verify` — `referencePlacementStep` reads the advertisement (`extracted.ref.value`) and deliberately
|
|
90
|
+
ignores `termsUrl`: where the terms live is the gate's fetch concern, not a fact the record can
|
|
91
|
+
contradict.
|
|
92
|
+
|
|
93
|
+
`lcp-discovery` — the x402 authority document restates the atrHash pattern inline in both definitions
|
|
94
|
+
(no `$defs` indirection the wire copy would have to rewrite) and moves the two-definitions rationale into
|
|
95
|
+
`$defs.receipt`, so the challenge-time root is byte-derivable for the wire.
|
|
96
|
+
|
|
97
|
+
### Patch Changes
|
|
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
|
@@ -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
|
|
|
@@ -47,15 +47,19 @@ Both members are total: a refusal is a returned value, never a thrown exception.
|
|
|
47
47
|
|
|
48
48
|
Read against the live spec at ucp.dev, version `2026-04-08` (`overview#capabilities`,
|
|
49
49
|
`overview#namespace-governance`, `overview#intersection-algorithm`, and the checkout capability page), the
|
|
50
|
-
gate falsified the
|
|
50
|
+
gate falsified the design this package was specced from, twice:
|
|
51
51
|
|
|
52
|
-
1. **Vendor capabilities are Tier A.**
|
|
52
|
+
1. **Vendor capabilities are Tier A.** It specced a links-only placement on the premise that UCP's
|
|
53
53
|
strict schema rejects unregistered keys, making the integrity path Tier B. The live spec says the
|
|
54
54
|
opposite: "Vendors MUST use their own reverse-domain namespace for custom capabilities" — no central
|
|
55
|
-
registry, no maintainer approval. What replaces registration is **authority binding
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
registry, no maintainer approval. What replaces registration is **authority binding**, and it binds the
|
|
56
|
+
`schema` URL alone: "a declared `schema` URL's origin MUST match the namespace authority in its name",
|
|
57
|
+
with a platform obliged to "validate each business-declared `schema` URL before fetching it". The `spec`
|
|
58
|
+
URL is expressly outside the trust path. Built as planned, this package would have shipped a URL and no
|
|
59
|
+
hash while a hash-bearing carrier was available the whole time. (Verified verbatim at UCP HEAD
|
|
60
|
+
2026-08-11. An earlier release quoted the host as also saying platforms "SHOULD reject capabilities where
|
|
61
|
+
the spec origin does not match" — that sentence is nowhere in UCP, and it inverts the rule above;
|
|
62
|
+
`discovery/src/capability-identity.ts` records the search that established it.)
|
|
59
63
|
2. **`links[].type` is an OPEN set** ("Businesses MAY define custom types"), not the closed enum the plan
|
|
60
64
|
claimed — that enum is ACP's; an earlier reading conflated the two protocols. `links[]` is REQUIRED on checkout
|
|
61
65
|
responses; entries are `{type (req), url (req), title (opt)}`; `terms_of_service` is a recommended
|
|
@@ -104,7 +108,7 @@ alone. Both arms are pinned as vectors.
|
|
|
104
108
|
|
|
105
109
|
## Why the links alias has no `write` flag
|
|
106
110
|
|
|
107
|
-
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)`
|
|
108
112
|
holds one reference, not two data: writing `ref.value` into `links[].url` would put a bare hash where every
|
|
109
113
|
UCP client expects a URL. Publishing the links entry is the deployment's act (UCP already makes `links[]`
|
|
110
114
|
required on checkout responses), and this placement **reads** it: `extract` falls to the alias when no policy entry is
|
|
@@ -112,7 +116,9 @@ present, the `url` carrier type itself signalling discovery-not-integrity.
|
|
|
112
116
|
|
|
113
117
|
## Provenance
|
|
114
118
|
|
|
115
|
-
Cut against UCP `2026-04-08` (ucp.dev) and reconciled against LCP v1.37 §C.3
|
|
119
|
+
Cut against UCP `2026-04-08` (ucp.dev) and reconciled against LCP v1.37 §C.3; re-read against **v1.38 §C.3**
|
|
120
|
+
on 2026-08-12, which now records `policies[]` as a Tier A carrier in the negotiated baseline — the carrier
|
|
121
|
+
this package moved to. A deployment advertises under
|
|
116
122
|
its own reverse-domain namespace; `org.legalcontextprotocol.*` is reserved for a TSC-ratified capability.
|
|
117
123
|
|
|
118
124
|
---
|
package/dist/manifest.d.ts
CHANGED
|
@@ -66,11 +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
|
-
*
|
|
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).
|
|
74
81
|
*
|
|
75
82
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
76
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,11 +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
|
-
*
|
|
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).
|
|
73
80
|
*
|
|
74
81
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
75
82
|
* reference against this list, so the discovery alias's url-typed hits would refuse
|
|
@@ -103,6 +110,10 @@ export const UCP_PLACEMENT = {
|
|
|
103
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.",
|
|
104
111
|
},
|
|
105
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",
|
|
106
117
|
},
|
|
107
118
|
field: "policies[type=com.integraledger.policy.legal_context]",
|
|
108
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
CHANGED
|
@@ -2,14 +2,17 @@ import { type ReferencePlacementAdapter } from "@integraledger/lcp-binding-core"
|
|
|
2
2
|
/**
|
|
3
3
|
* The UCP reference placement — the kit plus ONE protocol rule the kit cannot know.
|
|
4
4
|
*
|
|
5
|
-
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the
|
|
6
|
-
*
|
|
7
|
-
* the
|
|
5
|
+
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the tagged-array write into `policies[]`,
|
|
6
|
+
* the tagged-array read of the `links` discovery alias, strength-ordered extraction, purity, and every
|
|
7
|
+
* refusal the vectors pin. (An earlier revision described a dotted-key capability write through the
|
|
8
|
+
* container's `segments`; that carrier was retired when UCP turned out to define no `extensions` map, and
|
|
9
|
+
* `segments` now has no shipped declarer.) This
|
|
8
10
|
* file adds only the rule that is UCP's semantics rather than any container's mechanics: **a terms link must
|
|
9
11
|
* be HTTPS.** An `http:` URL is rewritable in transit, so accepting one would put an unauthenticated document
|
|
10
12
|
* behind a reference the record cites.
|
|
11
13
|
*
|
|
12
|
-
* This is the composition
|
|
14
|
+
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
15
|
+
* top —
|
|
13
16
|
* and it wraps `extract`, not `place`: `place` never writes the links alias (the terms URL is the
|
|
14
17
|
* DEPLOYMENT's datum, not the reference — see the manifest), so the write side has no URL to police.
|
|
15
18
|
*
|
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
|
|
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
|
@@ -4,14 +4,17 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
4
4
|
/**
|
|
5
5
|
* The UCP reference placement — the kit plus ONE protocol rule the kit cannot know.
|
|
6
6
|
*
|
|
7
|
-
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
7
|
+
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the tagged-array write into `policies[]`,
|
|
8
|
+
* the tagged-array read of the `links` discovery alias, strength-ordered extraction, purity, and every
|
|
9
|
+
* refusal the vectors pin. (An earlier revision described a dotted-key capability write through the
|
|
10
|
+
* container's `segments`; that carrier was retired when UCP turned out to define no `extensions` map, and
|
|
11
|
+
* `segments` now has no shipped declarer.) This
|
|
10
12
|
* file adds only the rule that is UCP's semantics rather than any container's mechanics: **a terms link must
|
|
11
13
|
* be HTTPS.** An `http:` URL is rewritable in transit, so accepting one would put an unauthenticated document
|
|
12
14
|
* behind a reference the record cites.
|
|
13
15
|
*
|
|
14
|
-
* This is the composition
|
|
16
|
+
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
17
|
+
* top —
|
|
15
18
|
* and it wraps `extract`, not `place`: `place` never writes the links alias (the terms URL is the
|
|
16
19
|
* DEPLOYMENT's datum, not the reference — see the manifest), so the write side has no URL to police.
|
|
17
20
|
*
|
|
@@ -27,12 +30,13 @@ export const ucpPlacement = {
|
|
|
27
30
|
const out = base.extract(doc);
|
|
28
31
|
if ("refused" in out)
|
|
29
32
|
return out;
|
|
30
|
-
|
|
33
|
+
const { ref } = out.value;
|
|
34
|
+
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
31
35
|
return {
|
|
32
36
|
refused: true,
|
|
33
37
|
haltClass: "verification-failure",
|
|
34
38
|
code: "ucp/insecure-terms-url",
|
|
35
|
-
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}`,
|
|
36
40
|
};
|
|
37
41
|
return out;
|
|
38
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
|
|
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.11.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.11.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@cfworker/json-schema": "4.1.1",
|
package/src/manifest.ts
CHANGED
|
@@ -67,11 +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
|
-
*
|
|
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).
|
|
75
82
|
*
|
|
76
83
|
* **`carrierTypes` permits `sha256` and `url`, and `url` is load-bearing.** extract checks every decoded
|
|
77
84
|
* reference against this list, so the discovery alias's url-typed hits would refuse
|
|
@@ -106,6 +113,10 @@ export const UCP_PLACEMENT: PlacementManifest = {
|
|
|
106
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.",
|
|
107
114
|
},
|
|
108
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",
|
|
109
120
|
},
|
|
110
121
|
field: "policies[type=com.integraledger.policy.legal_context]",
|
|
111
122
|
readAlso: [
|
package/src/placement.ts
CHANGED
|
@@ -9,14 +9,17 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
9
9
|
/**
|
|
10
10
|
* The UCP reference placement — the kit plus ONE protocol rule the kit cannot know.
|
|
11
11
|
*
|
|
12
|
-
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the
|
|
13
|
-
*
|
|
14
|
-
* the
|
|
12
|
+
* `makePlacement(UCP_PLACEMENT)` supplies everything structural: the tagged-array write into `policies[]`,
|
|
13
|
+
* the tagged-array read of the `links` discovery alias, strength-ordered extraction, purity, and every
|
|
14
|
+
* refusal the vectors pin. (An earlier revision described a dotted-key capability write through the
|
|
15
|
+
* container's `segments`; that carrier was retired when UCP turned out to define no `extensions` map, and
|
|
16
|
+
* `segments` now has no shipped declarer.) This
|
|
15
17
|
* file adds only the rule that is UCP's semantics rather than any container's mechanics: **a terms link must
|
|
16
18
|
* be HTTPS.** An `http:` URL is rewritable in transit, so accepting one would put an unauthenticated document
|
|
17
19
|
* behind a reference the record cites.
|
|
18
20
|
*
|
|
19
|
-
* This is the composition
|
|
21
|
+
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
22
|
+
* top —
|
|
20
23
|
* and it wraps `extract`, not `place`: `place` never writes the links alias (the terms URL is the
|
|
21
24
|
* DEPLOYMENT's datum, not the reference — see the manifest), so the write side has no URL to police.
|
|
22
25
|
*
|
|
@@ -31,12 +34,13 @@ export const ucpPlacement: ReferencePlacementAdapter = {
|
|
|
31
34
|
extract(doc: unknown) {
|
|
32
35
|
const out = base.extract(doc);
|
|
33
36
|
if ("refused" in out) return out;
|
|
34
|
-
|
|
37
|
+
const { ref } = out.value;
|
|
38
|
+
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
35
39
|
return {
|
|
36
40
|
refused: true,
|
|
37
41
|
haltClass: "verification-failure",
|
|
38
42
|
code: "ucp/insecure-terms-url",
|
|
39
|
-
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}`,
|
|
40
44
|
};
|
|
41
45
|
return out;
|
|
42
46
|
},
|