@fedify/vocab-runtime 2.4.0-dev.2056 → 2.4.0-dev.2065

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.
Files changed (34) hide show
  1. package/deno.json +1 -1
  2. package/dist/internal/portable-dereference.cjs +355 -8
  3. package/dist/internal/portable-dereference.d.cts +238 -6
  4. package/dist/internal/portable-dereference.d.ts +238 -6
  5. package/dist/internal/portable-dereference.js +343 -10
  6. package/dist/mod.cjs +1 -1
  7. package/dist/mod.d.cts +2 -2
  8. package/dist/mod.d.ts +2 -2
  9. package/dist/mod.js +1 -1
  10. package/dist/portable-DH3947GK.d.ts +160 -0
  11. package/dist/portable-comBI05Y.d.cts +160 -0
  12. package/dist/tests/{body-wkL-XGhe.mjs → body-CNO0jMV8.mjs} +1 -1
  13. package/dist/tests/{body-h9zUR79f.cjs → body-DXHOazEl.cjs} +1 -1
  14. package/dist/tests/body.test.cjs +1 -1
  15. package/dist/tests/body.test.mjs +1 -1
  16. package/dist/tests/decimal.test.cjs +1 -1
  17. package/dist/tests/decimal.test.mjs +1 -1
  18. package/dist/tests/{docloader-CBLgaVPd.cjs → docloader-4Txefi7y.cjs} +2 -2
  19. package/dist/tests/{docloader-DMGssPEX.mjs → docloader-C-nWtUWo.mjs} +2 -2
  20. package/dist/tests/docloader.test.cjs +2 -2
  21. package/dist/tests/docloader.test.mjs +2 -2
  22. package/dist/tests/internal/portable-dereference.test.cjs +87 -0
  23. package/dist/tests/internal/portable-dereference.test.mjs +87 -0
  24. package/dist/tests/{request-ClFs0ePp.cjs → request-BOLanqAd.cjs} +1 -1
  25. package/dist/tests/{request-Ytm0xyZH.mjs → request-dpFdQKXr.mjs} +1 -1
  26. package/dist/tests/request.test.cjs +1 -1
  27. package/dist/tests/request.test.mjs +1 -1
  28. package/package.json +1 -1
  29. package/src/internal/portable-dereference.test.ts +32 -0
  30. package/src/internal/portable-dereference.ts +521 -12
  31. package/src/mod.ts +2 -0
  32. package/src/portable.ts +136 -8
  33. package/dist/portable-CduElToy.d.ts +0 -52
  34. package/dist/portable-DxF2Ut-Z.d.cts +0 -52
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2056+e6394f3b",
3
+ "version": "2.4.0-dev.2065+b09fdd33",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
@@ -25,6 +25,301 @@ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
25
25
  "https://w3id.org/security/data-integrity/v1"
26
26
  ]);
27
27
  /**
28
+ * The maximum number of links in a referrer chain passed to a
29
+ * {@link PortableObjectVerifier}. Longer chains are cut off and marked as
30
+ * truncated.
31
+ */
32
+ const MAX_REFERRER_CHAIN_LENGTH = 32;
33
+ const provenances = /* @__PURE__ */ new WeakMap();
34
+ function getObjectId(object) {
35
+ if (object == null || typeof object !== "object" || !("id" in object)) return null;
36
+ const id = object.id;
37
+ return id instanceof URL ? id : null;
38
+ }
39
+ /**
40
+ * Checks whether a vocabulary object is part of a chain of portable objects,
41
+ * i.e., whether it has a portable ID or was obtained from a portable object.
42
+ *
43
+ * @internal Technically exported for generated vocabulary classes, but not
44
+ * part of the public API contract. This is not considered public API for
45
+ * Semantic Versioning decisions.
46
+ */
47
+ function isInPortableChain(object) {
48
+ if (provenances.has(object)) return true;
49
+ const id = getObjectId(object);
50
+ return id != null && isPortableIri(id);
51
+ }
52
+ /**
53
+ * Checks whether a vocabulary object was accepted without an integrity
54
+ * proof, e.g., as an unsecured portable collection served by a trusted
55
+ * gateway. Objects embedded in such an object must not be trusted because
56
+ * of their origin.
57
+ *
58
+ * @internal Technically exported for generated vocabulary classes, but not
59
+ * part of the public API contract. This is not considered public API for
60
+ * Semantic Versioning decisions.
61
+ */
62
+ function isUnsecuredPortableObject(object) {
63
+ return provenances.get(object)?.acceptance === "unsecured";
64
+ }
65
+ /**
66
+ * Checks whether a URL is an FEP-ef61 compatible identifier, i.e., an HTTP(S)
67
+ * URL under a gateway's `/.well-known/apgateway/` path that stands for
68
+ * a portable object. Malformed compatible identifiers count as compatible
69
+ * identifiers too.
70
+ *
71
+ * @internal Technically exported for generated vocabulary classes, but not
72
+ * part of the public API contract. This is not considered public API for
73
+ * Semantic Versioning decisions.
74
+ */
75
+ function isCompatibleEf61Iri(url) {
76
+ try {
77
+ return require_url.fromCompatibleEf61Id(url) != null;
78
+ } catch (error) {
79
+ if (error instanceof TypeError) return true;
80
+ throw error;
81
+ }
82
+ }
83
+ /**
84
+ * Records that a vocabulary object returned by a property accessor was
85
+ * obtained through the given property of a portable object, so that later
86
+ * dereferences from it can tell where it came from. Nothing is recorded if
87
+ * the parent is not in a portable chain, or if the child already has its
88
+ * provenance.
89
+ *
90
+ * @internal Technically exported for generated vocabulary classes, but not
91
+ * part of the public API contract. This is not considered public API for
92
+ * Semantic Versioning decisions.
93
+ */
94
+ function recordPortableReferrer(parent, child, property) {
95
+ if (child == null || typeof child !== "object") return;
96
+ if (provenances.has(child) || !isInPortableChain(parent)) return;
97
+ provenances.set(child, { referrer: {
98
+ object: parent,
99
+ property
100
+ } });
101
+ }
102
+ /**
103
+ * Copies the provenance of a vocabulary object to its clone. The clone
104
+ * keeps where the original came from and the restriction on unsecured
105
+ * objects, but not a positive verification status nor a collection context,
106
+ * since the clone may have different property values.
107
+ *
108
+ * @internal Technically exported for generated vocabulary classes, but not
109
+ * part of the public API contract. This is not considered public API for
110
+ * Semantic Versioning decisions.
111
+ */
112
+ function copyPortableProvenance(from, to) {
113
+ const provenance = provenances.get(from);
114
+ if (provenance == null) return;
115
+ provenances.set(to, {
116
+ referrer: provenance.referrer,
117
+ ...provenance.acceptance === "unsecured" ? { acceptance: "unsecured" } : {}
118
+ });
119
+ }
120
+ /**
121
+ * Recognizes an FEP-ef61 compatible identifier to dereference as a portable
122
+ * object.
123
+ *
124
+ * @returns `undefined` if the URL is not a compatible identifier; `null` if
125
+ * it is a malformed compatible identifier; otherwise, the portable
126
+ * ID it stands for and the gateway it names.
127
+ * @internal Technically exported for generated vocabulary classes, but not
128
+ * part of the public API contract. This is not considered public API for
129
+ * Semantic Versioning decisions.
130
+ */
131
+ function parseCompatibleEf61Reference(url) {
132
+ if (typeof url === "string") {
133
+ if (!URL.canParse(url)) return void 0;
134
+ url = new URL(url);
135
+ }
136
+ try {
137
+ const id = require_url.fromCompatibleEf61Id(url);
138
+ if (id == null) return void 0;
139
+ return {
140
+ id,
141
+ gateway: new URL(url.origin)
142
+ };
143
+ } catch (error) {
144
+ if (error instanceof TypeError) {
145
+ logger.debug("Invalid FEP-ef61 compatible identifier {url}: {error}", {
146
+ url: url.href,
147
+ error
148
+ });
149
+ return null;
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ /**
155
+ * Checks whether a property accessor call on a vocabulary object applies the
156
+ * FEP-ef61 portable object policy, i.e., whether the `verifyPortableObject`
157
+ * option is given or the object is part of a chain of portable objects.
158
+ * In this mode, compatible identifiers are dereferenced as portable objects,
159
+ * and fetched documents that turn out to stand for portable objects are
160
+ * verified as such.
161
+ *
162
+ * @internal Technically exported for generated vocabulary classes, but not
163
+ * part of the public API contract. This is not considered public API for
164
+ * Semantic Versioning decisions.
165
+ */
166
+ function isPortableMode(object, options) {
167
+ return options.verifyPortableObject != null || isInPortableChain(object);
168
+ }
169
+ /**
170
+ * Checks whether an object embedded in a property of a vocabulary object has
171
+ * to be dereferenced by its `@id` and verified as a portable object rather
172
+ * than trusted as embedded, even with `crossOrigin: "trust"`. This is the
173
+ * case in {@link isPortableMode | portable mode} if its `@id` is an FEP-ef61
174
+ * compatible identifier, or a portable IRI whose DID differs from the
175
+ * owner's portable ID.
176
+ *
177
+ * @param parent The object whose property embeds the object.
178
+ * @param ownerId The ID of the object that vouches for embedded objects, or
179
+ * `null` if embedded objects are not trusted by their owner.
180
+ * @param id The `@id` of the embedded object.
181
+ * @param options The options of the accessor.
182
+ * @internal Technically exported for generated vocabulary classes, but not
183
+ * part of the public API contract. This is not considered public API for
184
+ * Semantic Versioning decisions.
185
+ */
186
+ function mustDereferencePortableObject(parent, ownerId, id, options) {
187
+ if (!isPortableMode(parent, options)) return false;
188
+ if (isCompatibleEf61Iri(id)) return true;
189
+ if (!isPortableIri(id)) return false;
190
+ return ownerId == null || !isPortableIri(ownerId) || !require_url.haveSameFe34Origin(ownerId, id);
191
+ }
192
+ const PORTABLE_IRI_PATTERN = /^ap(?:\+ef61)?:/i;
193
+ /**
194
+ * Finds out whether a document fetched as an ordinary HTTP(S) document
195
+ * claims to be a portable object: its final URL is a portable IRI or an
196
+ * FEP-ef61 compatible identifier, or else its `@id` is. The final URL takes
197
+ * precedence, so a document whose `@id` claims another object fails the
198
+ * identity check against the final URL.
199
+ *
200
+ * @param documentUrl The final URL of the document.
201
+ * @param objectId The `@id` of the parsed document.
202
+ * @returns `undefined` if the document does not claim to be a portable
203
+ * object; `null` if the claim is malformed; otherwise, the claim.
204
+ * @internal Technically exported for generated vocabulary classes, but not
205
+ * part of the public API contract. This is not considered public API for
206
+ * Semantic Versioning decisions.
207
+ */
208
+ function getPortableResponseClaim(documentUrl, objectId) {
209
+ if (PORTABLE_IRI_PATTERN.test(documentUrl)) {
210
+ let id;
211
+ try {
212
+ id = require_url.parseIri(documentUrl);
213
+ if (require_url.canonicalizePortableUri(documentUrl) !== require_url.canonicalizePortableUri(require_url.formatIri(id))) return null;
214
+ } catch (error) {
215
+ if (error instanceof TypeError) return null;
216
+ throw error;
217
+ }
218
+ return {
219
+ id,
220
+ inferredGateways: getPortableGatewayCandidates(id)
221
+ };
222
+ }
223
+ for (const url of [documentUrl, objectId]) {
224
+ if (url == null) continue;
225
+ const compatible = parseCompatibleEf61Reference(url);
226
+ if (compatible === null) return null;
227
+ if (compatible != null) return {
228
+ id: compatible.id,
229
+ inferredGateways: [compatible.gateway]
230
+ };
231
+ if (url instanceof URL && isPortableIri(url)) return {
232
+ id: url,
233
+ inferredGateways: getPortableGatewayCandidates(url)
234
+ };
235
+ }
236
+ }
237
+ const unverifiedPortableClaims = /* @__PURE__ */ new WeakSet();
238
+ /**
239
+ * Records that an object fetched as an ordinary HTTP(S) object stands for
240
+ * a portable object if its reference, its final URL, or its `@id` is
241
+ * a portable IRI or an FEP-ef61 compatible identifier. Accessors do not
242
+ * cache such objects in their parents, so that a later call with the
243
+ * `verifyPortableObject` option dereferences and verifies them.
244
+ *
245
+ * @internal Technically exported for generated vocabulary classes, but not
246
+ * part of the public API contract. This is not considered public API for
247
+ * Semantic Versioning decisions.
248
+ */
249
+ function markUnverifiedPortableClaim(object, reference, documentUrl) {
250
+ const objectId = getObjectId(object);
251
+ if (isPortableIri(reference) || isCompatibleEf61Iri(reference) || PORTABLE_IRI_PATTERN.test(documentUrl) || isCompatibleEf61Iri(documentUrl) || objectId != null && (isPortableIri(objectId) || isCompatibleEf61Iri(objectId))) unverifiedPortableClaims.add(object);
252
+ }
253
+ /**
254
+ * Checks whether an object was fetched as an ordinary HTTP(S) object although
255
+ * it stands for a portable object. See {@link markUnverifiedPortableClaim}.
256
+ *
257
+ * @internal Technically exported for generated vocabulary classes, but not
258
+ * part of the public API contract. This is not considered public API for
259
+ * Semantic Versioning decisions.
260
+ */
261
+ function isUnverifiedPortableClaim(object) {
262
+ return unverifiedPortableClaims.has(object);
263
+ }
264
+ /**
265
+ * Rejects a malformed FEP-ef61 compatible identifier found in a reference,
266
+ * the final URL of a fetched document, or its `@id`, when it would be
267
+ * dereferenced as a portable object. It is rejected without a request (or
268
+ * without returning the fetched document), since it cannot be verified as
269
+ * a portable object, and fetching it as an ordinary HTTP(S) object would
270
+ * skip the portable object policy.
271
+ *
272
+ * @param url The rejected URL, for the log message.
273
+ * @param options The options of the accessor.
274
+ * @returns Always `null`.
275
+ * @throws {Error} If `crossOrigin` is `"throw"` and `suppressError` is not
276
+ * set.
277
+ * @internal Technically exported for generated vocabulary classes, but not
278
+ * part of the public API contract. This is not considered public API for
279
+ * Semantic Versioning decisions.
280
+ */
281
+ function rejectMalformedCompatibleReference(url, options) {
282
+ const message = "Refusing to dereference {url}, because it is a malformed FEP-ef61 compatible identifier, which cannot be verified as a portable object.";
283
+ if (options.crossOrigin === "throw" && !options.suppressError) throw new PortableObjectRejectedError(message.replace("{url}", url));
284
+ logger.warn(message, { url });
285
+ return null;
286
+ }
287
+ /**
288
+ * Logs that an embedded object without `@id` in an object accepted without
289
+ * an integrity proof is dropped, because it cannot be dereferenced and
290
+ * verified on its own.
291
+ *
292
+ * @internal Technically exported for generated vocabulary classes, but not
293
+ * part of the public API contract. This is not considered public API for
294
+ * Semantic Versioning decisions.
295
+ */
296
+ function warnUnverifiableEmbeddedObject(parent, property) {
297
+ logger.warn("Dropping an embedded object without @id in the {property} property of {parentId}, because the parent was accepted without an integrity proof and the embedded object cannot be verified on its own.", {
298
+ property,
299
+ parentId: getObjectId(parent)?.href ?? null
300
+ });
301
+ }
302
+ function buildReferrerChain(link, depth = 0) {
303
+ if (link == null) return void 0;
304
+ const provenance = provenances.get(link.object);
305
+ const base = {
306
+ object: link.object,
307
+ id: getObjectId(link.object),
308
+ property: link.property,
309
+ ...provenance?.acceptance == null ? {} : { acceptance: provenance.acceptance },
310
+ ...provenance?.collectionContext === void 0 ? {} : { collectionContext: provenance.collectionContext }
311
+ };
312
+ if (provenance?.referrer == null) return base;
313
+ if (depth + 1 >= MAX_REFERRER_CHAIN_LENGTH) return {
314
+ ...base,
315
+ truncated: true
316
+ };
317
+ return {
318
+ ...base,
319
+ referrer: buildReferrerChain(provenance.referrer, depth + 1)
320
+ };
321
+ }
322
+ /**
28
323
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
29
324
  *
30
325
  * @internal Technically exported for generated vocabulary classes, but not
@@ -93,12 +388,18 @@ function parseGatewayOrigin(gateway) {
93
388
  * The parsed object keeps the loader for its own later dereferences, so
94
389
  * `release()` turns it into a plain pass-through to the underlying loader
95
390
  * once the operation is over.
391
+ *
392
+ * @internal Technically exported for generated vocabulary classes, but not
393
+ * part of the public API contract. This is not considered public API for
394
+ * Semantic Versioning decisions.
96
395
  */
97
396
  function createSnapshotContextLoader(contextLoader) {
397
+ for (let wrapped = snapshotLoaders.get(contextLoader); wrapped?.released; wrapped = snapshotLoaders.get(contextLoader)) contextLoader = wrapped.base;
98
398
  const cache = /* @__PURE__ */ new Map();
99
399
  let released = false;
100
400
  const release = () => {
101
401
  released = true;
402
+ state.released = true;
102
403
  cache.clear();
103
404
  };
104
405
  const loader = async (url, options) => {
@@ -120,11 +421,17 @@ function createSnapshotContextLoader(contextLoader) {
120
421
  }
121
422
  return structuredClone(await promise);
122
423
  };
424
+ const state = {
425
+ base: contextLoader,
426
+ released: false
427
+ };
428
+ snapshotLoaders.set(loader, state);
123
429
  return {
124
430
  loader,
125
431
  release
126
432
  };
127
433
  }
434
+ const snapshotLoaders = /* @__PURE__ */ new WeakMap();
128
435
  /**
129
436
  * The error thrown by {@link dereferencePortableIri} under
130
437
  * `crossOrigin: "throw"` when gateways returned objects but none of them
@@ -151,9 +458,10 @@ var PortableObjectRejection = class {
151
458
  *
152
459
  * Gateways are tried one by one until one of them returns a single-node
153
460
  * document whose `@id`, if any, canonically matches the requested IRI and
154
- * which passes `verifyPortableObject`. When there is no gateway to try, the document
155
- * loader is asked for the portable IRI itself, and its result is validated
156
- * the same way.
461
+ * which passes `verifyPortableObject`. When there is no gateway to try, the
462
+ * document loader is asked for the portable IRI itself, and its result is
463
+ * validated the same way. If the `response` option is given, only that
464
+ * document is validated, and nothing is fetched.
157
465
  *
158
466
  * @returns The parsed object, or `null` if no valid object was retrieved and
159
467
  * `suppressError` is set or the failure was a rejected object
@@ -169,7 +477,10 @@ async function dereferencePortableIri(url, options) {
169
477
  url = require_url.parseIri(url);
170
478
  const lookupUrl = require_url.formatIri(url);
171
479
  const { span } = options;
172
- const gateways = getPortableGatewayCandidates(url, options.gateways);
480
+ const explicitGateways = options.gateways == null ? void 0 : getPortableGatewayCandidates(url, options.gateways);
481
+ const inferredGateways = options.inferredGateways ?? (explicitGateways == null ? getPortableGatewayCandidates(url) : []);
482
+ const gateways = [...inferredGateways];
483
+ for (const gateway of explicitGateways ?? []) if (!gateways.some((g) => g.href === gateway.href)) gateways.push(gateway);
173
484
  const fail = (error) => {
174
485
  span?.setStatus({
175
486
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -190,7 +501,12 @@ async function dereferencePortableIri(url, options) {
190
501
  let expectedId;
191
502
  try {
192
503
  expectedId = require_url.canonicalizePortableUri(lookupUrl);
193
- for (const gateway of gateways) requestUrls.push({
504
+ if (options.response != null) requestUrls.push({
505
+ url: options.response.documentUrl,
506
+ gateway: null,
507
+ response: options.response
508
+ });
509
+ else for (const gateway of gateways) requestUrls.push({
194
510
  url: require_url.toCompatibleEf61Id(url, gateway).href,
195
511
  gateway
196
512
  });
@@ -205,12 +521,14 @@ async function dereferencePortableIri(url, options) {
205
521
  const contextLoader = snapshot.loader;
206
522
  const attempts = [];
207
523
  const { signal } = options;
524
+ const gatewayHints = explicitGateways == null && inferredGateways.length > 0 ? inferredGateways : void 0;
525
+ const referrer = buildReferrerChain(options.referrer);
208
526
  try {
209
- for (const { url: requestUrl, gateway } of requestUrls) {
527
+ for (const { url: requestUrl, gateway, response } of requestUrls) {
210
528
  signal?.throwIfAborted();
211
529
  let remoteDocument;
212
530
  try {
213
- remoteDocument = await options.documentLoader(requestUrl, signal == null ? void 0 : { signal });
531
+ remoteDocument = response ?? await options.documentLoader(requestUrl, signal == null ? void 0 : { signal });
214
532
  } catch (error) {
215
533
  signal?.throwIfAborted();
216
534
  logger.debug("Failed to fetch {url} from {requestUrl}: {error}", {
@@ -227,12 +545,22 @@ async function dereferencePortableIri(url, options) {
227
545
  const { document } = remoteDocument;
228
546
  try {
229
547
  await checkPortableObjectId(document, expectedId, contextLoader);
548
+ let documentUrl;
549
+ try {
550
+ documentUrl = require_url.parseIri(remoteDocument.documentUrl);
551
+ } catch {
552
+ documentUrl = void 0;
553
+ }
230
554
  let result;
231
555
  try {
232
556
  result = await verify(document, {
233
557
  documentLoader: options.documentLoader,
234
558
  contextLoader,
235
- tracerProvider: options.tracerProvider
559
+ tracerProvider: options.tracerProvider,
560
+ ...documentUrl == null ? {} : { documentUrl },
561
+ ...explicitGateways == null ? {} : { gateways: explicitGateways },
562
+ ...gatewayHints == null ? {} : { gatewayHints },
563
+ ...referrer == null ? {} : { referrer }
236
564
  });
237
565
  } catch (error) {
238
566
  throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
@@ -243,6 +571,11 @@ async function dereferencePortableIri(url, options) {
243
571
  baseUrl: url
244
572
  });
245
573
  signal?.throwIfAborted();
574
+ if (object != null && typeof object === "object") provenances.set(object, {
575
+ acceptance: result.unsecured === true ? "unsecured" : "verified",
576
+ ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
577
+ ...options.referrer == null ? {} : { referrer: options.referrer }
578
+ });
246
579
  if (gateway != null) span?.setAttribute("activitypub.gateway", gateway.href);
247
580
  return object;
248
581
  } catch (error) {
@@ -310,6 +643,20 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
310
643
  }
311
644
  //#endregion
312
645
  exports.PortableObjectRejectedError = PortableObjectRejectedError;
646
+ exports.copyPortableProvenance = copyPortableProvenance;
647
+ exports.createSnapshotContextLoader = createSnapshotContextLoader;
313
648
  exports.dereferencePortableIri = dereferencePortableIri;
314
649
  exports.getPortableGatewayCandidates = getPortableGatewayCandidates;
650
+ exports.getPortableResponseClaim = getPortableResponseClaim;
651
+ exports.isCompatibleEf61Iri = isCompatibleEf61Iri;
652
+ exports.isInPortableChain = isInPortableChain;
315
653
  exports.isPortableIri = isPortableIri;
654
+ exports.isPortableMode = isPortableMode;
655
+ exports.isUnsecuredPortableObject = isUnsecuredPortableObject;
656
+ exports.isUnverifiedPortableClaim = isUnverifiedPortableClaim;
657
+ exports.markUnverifiedPortableClaim = markUnverifiedPortableClaim;
658
+ exports.mustDereferencePortableObject = mustDereferencePortableObject;
659
+ exports.parseCompatibleEf61Reference = parseCompatibleEf61Reference;
660
+ exports.recordPortableReferrer = recordPortableReferrer;
661
+ exports.rejectMalformedCompatibleReference = rejectMalformedCompatibleReference;
662
+ exports.warnUnverifiableEmbeddedObject = warnUnverifiableEmbeddedObject;