@integraledger/lcp-placement-ucp 0.15.0 → 0.16.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 +60 -2
- package/README.md +16 -4
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/placement.d.ts +12 -3
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +29 -12
- package/dist/placement.js.map +1 -1
- package/package.json +2 -2
- package/src/manifest.ts +1 -1
- package/src/placement.ts +32 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @integraledger/lcp-placement-ucp
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3ca1d2b: UCP's HTTPS rule now applies to `place`, not only to `extract`.
|
|
8
|
+
|
|
9
|
+
The placement wrapped `extract` alone, under a docblock reasoning that "`place` never writes the links
|
|
10
|
+
alias, so the write side has no URL to police". The write side does have one: the `policies[]` entry
|
|
11
|
+
`place` writes carries the reference itself, `url` is a permitted carrier type here, and `place` accepted an
|
|
12
|
+
`http:` url reference and emitted a document this very module's `extract` refuses. A placement that will
|
|
13
|
+
not read back the bytes it wrote fails a round trip on its own output.
|
|
14
|
+
|
|
15
|
+
The terms URL was never the gap — the kit already refuses a non-https `termsUrl` at
|
|
16
|
+
`ucp/terms-url-malformed`. The reference was, and a url-typed reference inside a policies entry is exactly
|
|
17
|
+
as rewritable in transit as one in `links`. The rule is now stated once and applied in both directions.
|
|
18
|
+
|
|
19
|
+
**This changes published behaviour**: `ucpPlacement.place` refuses `ucp/insecure-terms-url` where it
|
|
20
|
+
previously returned a written document.
|
|
21
|
+
|
|
22
|
+
The corpus gains the write half of the rule and an https control beside it. 859 → 861; the corpus root
|
|
23
|
+
moved to `c2875add14f5f2bf…`.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- feab885: Five README examples that failed on their first line, and a gate that runs the examples.
|
|
28
|
+
|
|
29
|
+
Each was the first thing a stranger runs, on the npmjs landing page:
|
|
30
|
+
|
|
31
|
+
- `binding-evm-x402` opened with `getX402Deployment("base-sepolia-usdc")`; the keys are `base`,
|
|
32
|
+
`base-sepolia`, `avalanche`, `monad`. It threw.
|
|
33
|
+
- `discovery` told the reader to read `hashesAgree`; the field is `hashesMatch`, so the documented read is
|
|
34
|
+
`undefined` — falsy — and records a MISMATCH for a hash that matched, the exact defect the two-field
|
|
35
|
+
design exists to prevent.
|
|
36
|
+
- `placement-ucp`'s only usage example refused `ucp/terms-url-missing`: this protocol declares a terms-URL
|
|
37
|
+
slot, so an integrity-bearing reference needs one, and every sibling README passes it.
|
|
38
|
+
- `placement-ack` claimed a `url` carrier in the canonical slot "passes `requireIntegrity`". It did when
|
|
39
|
+
written; `requireIntegrity` now checks the value's type as well as the slot's declared class and returns
|
|
40
|
+
`undefined`. The narrower point that survives is stated instead.
|
|
41
|
+
- `binding-canton` documented a DAR filename, `lcp-anchor-0.9.0.dar`, that no build produces —
|
|
42
|
+
`daml.yaml` carries the package version, gated by a test. Both the README and `daml.yaml`'s own comment
|
|
43
|
+
now write the version as a glob, because a pinned number inside a copyable command goes stale at the
|
|
44
|
+
next bump and had already done so twice.
|
|
45
|
+
|
|
46
|
+
The mechanism behind all five is that `check:docs` COMPILES fences and never runs them. `check:doc-calls`
|
|
47
|
+
runs the subset that can be run — a call of a workspace export whose every argument is a literal — and
|
|
48
|
+
refuses a throw or a returned `Refusal`. It also reads the inversion: a line the document annotates
|
|
49
|
+
`// throws` or `// refuses` must fail, so a demonstration that quietly starts succeeding is caught too.
|
|
50
|
+
Elided arguments (`"0x…"`) and calls inside a `try` are skipped as structurally not assertions. It refuses
|
|
51
|
+
an empty subject set, and it runs 30 calls across 74 fences today.
|
|
52
|
+
- @integraledger/lcp-binding-core@0.16.0
|
|
53
|
+
|
|
54
|
+
## 0.15.1
|
|
55
|
+
|
|
56
|
+
### Patch Changes
|
|
57
|
+
|
|
58
|
+
- Updated dependencies [431b8ec]
|
|
59
|
+
- @integraledger/lcp-binding-core@0.15.1
|
|
60
|
+
|
|
3
61
|
## 0.15.0
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
|
@@ -114,7 +172,7 @@ claimedClass`, so a record proving nothing — no settlement, no acceptance, no
|
|
|
114
172
|
`placement-x402` — the inlined wire schema now IS the authority document minus `$id` and `$defs`
|
|
115
173
|
(Bazaar forbids both on the wire), drift-gated in `lcp-conformance` where the two packages meet.
|
|
116
174
|
`termsUrlFields` declares both slots the wire carries; the bare-hash alias is written (`extra` stopped
|
|
117
|
-
being wholly scheme-private when x402 §6.1 reserved names inside it, and LCP
|
|
175
|
+
being wholly scheme-private when x402 §6.1 reserved names inside it, and LCP §C.4's own Tier A
|
|
118
176
|
illustration carries the pair there); the `url` carrier admission is withdrawn (`carrierTypes` is
|
|
119
177
|
`sha256` alone — the schema on the wire is `const: "sha256"`, and no shipped reader ever accepted a url
|
|
120
178
|
in this slot). The `place` override shrinks to composition: the kit performs the whole placement and the
|
|
@@ -166,7 +224,7 @@ and a large number of documentation claims were corrected against the host speci
|
|
|
166
224
|
First public release.
|
|
167
225
|
|
|
168
226
|
`0.9.0` is deliberate: this is a release candidate for 1.0, not a preview. The implementation is complete
|
|
169
|
-
against LCP
|
|
227
|
+
against the published LCP specification and certified by the conformance corpus, and the remaining distance to 1.0 is the
|
|
170
228
|
specification's own — the standard is still moving through its steering committee, and this package will not
|
|
171
229
|
claim a stability its protocol has not yet promised.
|
|
172
230
|
|
package/README.md
CHANGED
|
@@ -36,12 +36,24 @@ npm install @integraledger/lcp-placement-ucp
|
|
|
36
36
|
import { UCP_PLACEMENT, ucpPlacement } from "@integraledger/lcp-placement-ucp";
|
|
37
37
|
|
|
38
38
|
declare const checkout: unknown; // the UCP checkout response, as received
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
declare const atrHash: `0x${string}`;
|
|
40
|
+
|
|
41
|
+
// `termsUrl` is REQUIRED beside an integrity-bearing reference on this protocol: the manifest declares a
|
|
42
|
+
// terms-URL slot, and a counterparty holding only a hash cannot resolve it. Omitting it refuses
|
|
43
|
+
// `ucp/terms-url-missing` rather than writing a reference nobody can follow.
|
|
44
|
+
const placed = ucpPlacement.place(
|
|
45
|
+
{
|
|
46
|
+
ref: { type: "sha256", value: atrHash },
|
|
47
|
+
termsUrl: "https://seller.example/.well-known/legal-context.json",
|
|
48
|
+
},
|
|
49
|
+
checkout,
|
|
50
|
+
);
|
|
41
51
|
const ref = ucpPlacement.extract(checkout);
|
|
42
52
|
```
|
|
43
53
|
|
|
44
|
-
Both members are total: a refusal is a returned value, never a thrown exception.
|
|
54
|
+
Both members are total: a refusal is a returned value, never a thrown exception. Both also enforce one rule
|
|
55
|
+
of UCP's own — a `url`-typed reference must be HTTPS — so `place` will not write what `extract` would
|
|
56
|
+
refuse.
|
|
45
57
|
|
|
46
58
|
## Specification provenance — verified against the live host, 2026-07-29 — and it re-cut the package
|
|
47
59
|
|
|
@@ -116,7 +128,7 @@ present, the `url` carrier type itself signalling discovery-not-integrity.
|
|
|
116
128
|
|
|
117
129
|
## Provenance
|
|
118
130
|
|
|
119
|
-
Cut against UCP `2026-04-08` (ucp.dev) and reconciled against LCP
|
|
131
|
+
Cut against UCP `2026-04-08` (ucp.dev) and reconciled against LCP §C.3; re-read against **LCP §C.3**
|
|
120
132
|
on 2026-08-12, which now records `policies[]` as a Tier A carrier in the negotiated baseline — the carrier
|
|
121
133
|
this package moved to. A deployment advertises under
|
|
122
134
|
its own reverse-domain namespace; `org.legalcontextprotocol.*` is reserved for a TSC-ratified capability.
|
package/dist/manifest.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { PlacementManifest } from "@integraledger/lcp-binding-core";
|
|
|
2
2
|
/**
|
|
3
3
|
* UCP (Universal Commerce Protocol) reference placement, cut against the live spec (2026-04-08, ucp.dev —
|
|
4
4
|
* overview §capabilities, §namespace-governance, §intersection-algorithm, and the checkout capability's
|
|
5
|
-
* response schema), gate discharged 2026-07-29 against LCP
|
|
5
|
+
* response schema), gate discharged 2026-07-29 against LCP §C.3, and re-cut against the live UCP schemas and LCP §C.3
|
|
6
6
|
* on 2026-08-08 when the canonical carrier moved.
|
|
7
7
|
*
|
|
8
8
|
* **THE CANONICAL CARRIER IS A `policies[]` ENTRY, AND UCP HAS NO `extensions` MAP TO WRITE INTO.** A
|
package/dist/manifest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* UCP (Universal Commerce Protocol) reference placement, cut against the live spec (2026-04-08, ucp.dev —
|
|
3
3
|
* overview §capabilities, §namespace-governance, §intersection-algorithm, and the checkout capability's
|
|
4
|
-
* response schema), gate discharged 2026-07-29 against LCP
|
|
4
|
+
* response schema), gate discharged 2026-07-29 against LCP §C.3, and re-cut against the live UCP schemas and LCP §C.3
|
|
5
5
|
* on 2026-08-08 when the canonical carrier moved.
|
|
6
6
|
*
|
|
7
7
|
* **THE CANONICAL CARRIER IS A `policies[]` ENTRY, AND UCP HAS NO `extensions` MAP TO WRITE INTO.** A
|
package/dist/placement.d.ts
CHANGED
|
@@ -12,9 +12,18 @@ import { type ReferencePlacementAdapter } from "@integraledger/lcp-binding-core"
|
|
|
12
12
|
* behind a reference the record cites.
|
|
13
13
|
*
|
|
14
14
|
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
15
|
-
* top —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* top — and it wraps BOTH directions, because the rule is about the carrier and both directions carry one.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ It wrapped `extract` alone until 2026-09-03, under the reasoning that "`place` never writes the links
|
|
18
|
+
* alias, so the write side has no URL to police". That was already false when it was written and became
|
|
19
|
+
* more so: the policies entry `place` writes carries the reference itself, `url` is a permitted carrier
|
|
20
|
+
* type here, and the kit's own terms-URL slot landed on the same entry. So `place` accepted an `http:`
|
|
21
|
+
* reference and emitted a document this very module's `extract` refuses — a placement that will not read
|
|
22
|
+
* back the bytes it wrote, and a round trip that fails on our own output rather than on a counterparty's.
|
|
23
|
+
* A url-typed reference inside a policies entry is exactly as rewritable in transit as one in `links`.
|
|
24
|
+
*
|
|
25
|
+
* The terms URL was never the gap: the kit already refuses a non-https `termsUrl` at
|
|
26
|
+
* `ucp/terms-url-malformed`. The REFERENCE was.
|
|
18
27
|
*
|
|
19
28
|
* **Scoped to url-typed results, deliberately.** A sha256 answer must never be blocked by a bad link sitting
|
|
20
29
|
* beside it — the capability WINS over links, and refusing the winner for the loser's defect would invert the
|
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,EAE/B,MAAM,iCAAiC,CAAC;AAgBzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,YAAY,EAAE,yBAY1B,CAAC"}
|
package/dist/placement.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { makePlacement, } from "@integraledger/lcp-binding-core";
|
|
2
2
|
import { UCP_PLACEMENT } from "./manifest.js";
|
|
3
3
|
const base = makePlacement(UCP_PLACEMENT);
|
|
4
|
+
/** The one rule, stated once: a url-typed reference on this protocol must be HTTPS. */
|
|
5
|
+
function insecureTermsUrl(ref) {
|
|
6
|
+
if (ref.type !== "url" || ref.value.startsWith("https://"))
|
|
7
|
+
return null;
|
|
8
|
+
return {
|
|
9
|
+
refused: true,
|
|
10
|
+
haltClass: "verification-failure",
|
|
11
|
+
code: "ucp/insecure-terms-url",
|
|
12
|
+
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* The UCP reference placement — the kit plus ONE protocol rule the kit cannot know.
|
|
6
17
|
*
|
|
@@ -14,9 +25,18 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
14
25
|
* behind a reference the record cites.
|
|
15
26
|
*
|
|
16
27
|
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
17
|
-
* top —
|
|
18
|
-
*
|
|
19
|
-
*
|
|
28
|
+
* top — and it wraps BOTH directions, because the rule is about the carrier and both directions carry one.
|
|
29
|
+
*
|
|
30
|
+
* ⛔ It wrapped `extract` alone until 2026-09-03, under the reasoning that "`place` never writes the links
|
|
31
|
+
* alias, so the write side has no URL to police". That was already false when it was written and became
|
|
32
|
+
* more so: the policies entry `place` writes carries the reference itself, `url` is a permitted carrier
|
|
33
|
+
* type here, and the kit's own terms-URL slot landed on the same entry. So `place` accepted an `http:`
|
|
34
|
+
* reference and emitted a document this very module's `extract` refuses — a placement that will not read
|
|
35
|
+
* back the bytes it wrote, and a round trip that fails on our own output rather than on a counterparty's.
|
|
36
|
+
* A url-typed reference inside a policies entry is exactly as rewritable in transit as one in `links`.
|
|
37
|
+
*
|
|
38
|
+
* The terms URL was never the gap: the kit already refuses a non-https `termsUrl` at
|
|
39
|
+
* `ucp/terms-url-malformed`. The REFERENCE was.
|
|
20
40
|
*
|
|
21
41
|
* **Scoped to url-typed results, deliberately.** A sha256 answer must never be blocked by a bad link sitting
|
|
22
42
|
* beside it — the capability WINS over links, and refusing the winner for the loser's defect would invert the
|
|
@@ -26,19 +46,16 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
26
46
|
*/
|
|
27
47
|
export const ucpPlacement = {
|
|
28
48
|
...base,
|
|
49
|
+
place(ad, doc) {
|
|
50
|
+
// BEFORE the kit writes: a document that has already been written into is a document a caller may
|
|
51
|
+
// reasonably send, and refusing after building it would make the refusal advisory.
|
|
52
|
+
return insecureTermsUrl(ad.ref) ?? base.place(ad, doc);
|
|
53
|
+
},
|
|
29
54
|
extract(doc) {
|
|
30
55
|
const out = base.extract(doc);
|
|
31
56
|
if ("refused" in out)
|
|
32
57
|
return out;
|
|
33
|
-
|
|
34
|
-
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
35
|
-
return {
|
|
36
|
-
refused: true,
|
|
37
|
-
haltClass: "verification-failure",
|
|
38
|
-
code: "ucp/insecure-terms-url",
|
|
39
|
-
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
40
|
-
};
|
|
41
|
-
return out;
|
|
58
|
+
return insecureTermsUrl(out.value.ref) ?? out;
|
|
42
59
|
},
|
|
43
60
|
};
|
|
44
61
|
//# 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,EAGL,aAAa,GAGd,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,IAAI,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;AAE1C,uFAAuF;AACvF,SAAS,gBAAgB,CAAC,GAAoB;IAC5C,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACxE,OAAO;QACL,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,sBAAsB;QACjC,IAAI,EAAE,wBAAwB;QAC9B,MAAM,EAAE,6EAA6E,GAAG,CAAC,KAAK,EAAE;KACjG,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,YAAY,GAA8B;IACrD,GAAG,IAAI;IACP,KAAK,CAAC,EAA6B,EAAE,GAAY;QAC/C,kGAAkG;QAClG,mFAAmF;QACnF,OAAO,gBAAgB,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,CAAC,GAAY;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,SAAS,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACjC,OAAO,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;IAChD,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.16.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.16.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@cfworker/json-schema": "4.1.1",
|
package/src/manifest.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { PlacementManifest } from "@integraledger/lcp-binding-core";
|
|
|
3
3
|
/**
|
|
4
4
|
* UCP (Universal Commerce Protocol) reference placement, cut against the live spec (2026-04-08, ucp.dev —
|
|
5
5
|
* overview §capabilities, §namespace-governance, §intersection-algorithm, and the checkout capability's
|
|
6
|
-
* response schema), gate discharged 2026-07-29 against LCP
|
|
6
|
+
* response schema), gate discharged 2026-07-29 against LCP §C.3, and re-cut against the live UCP schemas and LCP §C.3
|
|
7
7
|
* on 2026-08-08 when the canonical carrier moved.
|
|
8
8
|
*
|
|
9
9
|
* **THE CANONICAL CARRIER IS A `policies[]` ENTRY, AND UCP HAS NO `extensions` MAP TO WRITE INTO.** A
|
package/src/placement.ts
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type LegalContextAdvertisement,
|
|
3
|
+
type LegalContextRef,
|
|
2
4
|
makePlacement,
|
|
3
5
|
type ReferencePlacementAdapter,
|
|
6
|
+
type Refusal,
|
|
4
7
|
} from "@integraledger/lcp-binding-core";
|
|
5
8
|
import { UCP_PLACEMENT } from "./manifest.js";
|
|
6
9
|
|
|
7
10
|
const base = makePlacement(UCP_PLACEMENT);
|
|
8
11
|
|
|
12
|
+
/** The one rule, stated once: a url-typed reference on this protocol must be HTTPS. */
|
|
13
|
+
function insecureTermsUrl(ref: LegalContextRef): Refusal | null {
|
|
14
|
+
if (ref.type !== "url" || ref.value.startsWith("https://")) return null;
|
|
15
|
+
return {
|
|
16
|
+
refused: true,
|
|
17
|
+
haltClass: "verification-failure",
|
|
18
|
+
code: "ucp/insecure-terms-url",
|
|
19
|
+
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
/**
|
|
10
24
|
* The UCP reference placement — the kit plus ONE protocol rule the kit cannot know.
|
|
11
25
|
*
|
|
@@ -19,9 +33,18 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
19
33
|
* behind a reference the record cites.
|
|
20
34
|
*
|
|
21
35
|
* This is the composition the kit is shaped for — generic mechanics, protocol-specific semantics layered on
|
|
22
|
-
* top —
|
|
23
|
-
*
|
|
24
|
-
*
|
|
36
|
+
* top — and it wraps BOTH directions, because the rule is about the carrier and both directions carry one.
|
|
37
|
+
*
|
|
38
|
+
* ⛔ It wrapped `extract` alone until 2026-09-03, under the reasoning that "`place` never writes the links
|
|
39
|
+
* alias, so the write side has no URL to police". That was already false when it was written and became
|
|
40
|
+
* more so: the policies entry `place` writes carries the reference itself, `url` is a permitted carrier
|
|
41
|
+
* type here, and the kit's own terms-URL slot landed on the same entry. So `place` accepted an `http:`
|
|
42
|
+
* reference and emitted a document this very module's `extract` refuses — a placement that will not read
|
|
43
|
+
* back the bytes it wrote, and a round trip that fails on our own output rather than on a counterparty's.
|
|
44
|
+
* A url-typed reference inside a policies entry is exactly as rewritable in transit as one in `links`.
|
|
45
|
+
*
|
|
46
|
+
* The terms URL was never the gap: the kit already refuses a non-https `termsUrl` at
|
|
47
|
+
* `ucp/terms-url-malformed`. The REFERENCE was.
|
|
25
48
|
*
|
|
26
49
|
* **Scoped to url-typed results, deliberately.** A sha256 answer must never be blocked by a bad link sitting
|
|
27
50
|
* beside it — the capability WINS over links, and refusing the winner for the loser's defect would invert the
|
|
@@ -31,17 +54,14 @@ const base = makePlacement(UCP_PLACEMENT);
|
|
|
31
54
|
*/
|
|
32
55
|
export const ucpPlacement: ReferencePlacementAdapter = {
|
|
33
56
|
...base,
|
|
57
|
+
place(ad: LegalContextAdvertisement, doc: unknown) {
|
|
58
|
+
// BEFORE the kit writes: a document that has already been written into is a document a caller may
|
|
59
|
+
// reasonably send, and refusing after building it would make the refusal advisory.
|
|
60
|
+
return insecureTermsUrl(ad.ref) ?? base.place(ad, doc);
|
|
61
|
+
},
|
|
34
62
|
extract(doc: unknown) {
|
|
35
63
|
const out = base.extract(doc);
|
|
36
64
|
if ("refused" in out) return out;
|
|
37
|
-
|
|
38
|
-
if (ref.type === "url" && !ref.value.startsWith("https://"))
|
|
39
|
-
return {
|
|
40
|
-
refused: true,
|
|
41
|
-
haltClass: "verification-failure",
|
|
42
|
-
code: "ucp/insecure-terms-url",
|
|
43
|
-
detail: `a terms link must be HTTPS — an http: reference is rewritable in transit: ${ref.value}`,
|
|
44
|
-
};
|
|
45
|
-
return out;
|
|
65
|
+
return insecureTermsUrl(out.value.ref) ?? out;
|
|
46
66
|
},
|
|
47
67
|
};
|