@fedify/vocab-runtime 2.4.0-pr.936.41 → 2.4.1

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 (152) hide show
  1. package/deno.json +4 -2
  2. package/dist/{tests/docloader-Ck8bcBir.mjs → contexts-B09jb9Pi.cjs} +173 -274
  3. package/dist/{tests/docloader-S_gQZ2tt.cjs → contexts-DWrj4AGR.js} +169 -293
  4. package/dist/{docloader-C_dir7Xb.d.ts → docloader-BMHljyYb.d.cts} +68 -2
  5. package/dist/{docloader-D2DTRiyA.d.cts → docloader-BMHljyYb.d.ts} +68 -2
  6. package/dist/internal/jsonld-cache.cjs +70 -1
  7. package/dist/internal/jsonld-cache.d.cts +35 -2
  8. package/dist/internal/jsonld-cache.d.ts +35 -2
  9. package/dist/internal/jsonld-cache.js +67 -2
  10. package/dist/internal/portable-dereference.cjs +721 -0
  11. package/dist/internal/portable-dereference.d.cts +349 -0
  12. package/dist/internal/portable-dereference.d.ts +349 -0
  13. package/dist/internal/portable-dereference.js +702 -0
  14. package/dist/internal/signed-representation.cjs +378 -0
  15. package/dist/internal/signed-representation.d.cts +144 -0
  16. package/dist/internal/signed-representation.d.ts +144 -0
  17. package/dist/internal/signed-representation.js +371 -0
  18. package/dist/jsonld.cjs +2 -2
  19. package/dist/mod.cjs +762 -4529
  20. package/dist/mod.d.cts +427 -9
  21. package/dist/mod.d.ts +427 -10
  22. package/dist/mod.js +742 -4524
  23. package/dist/portable-6Aj-7dZJ.d.cts +160 -0
  24. package/dist/portable-nkTiqH0w.d.ts +160 -0
  25. package/dist/tests/body-DpC9p-iG.mjs +130 -0
  26. package/dist/tests/body-cKLBprYm.cjs +153 -0
  27. package/dist/tests/body.test.cjs +89 -0
  28. package/dist/tests/body.test.d.cts +1 -0
  29. package/dist/tests/body.test.d.mts +1 -0
  30. package/dist/tests/body.test.mjs +90 -0
  31. package/dist/tests/contexts-C9Lj11SC.cjs +4533 -0
  32. package/dist/tests/contexts-duRA_dDC.mjs +4522 -0
  33. package/dist/tests/decimal.test.cjs +9 -7
  34. package/dist/tests/decimal.test.mjs +9 -6
  35. package/dist/tests/digest-3FeH2Y-Q.cjs +176 -0
  36. package/dist/tests/digest-COC7xDiQ.mjs +141 -0
  37. package/dist/tests/digest.test.cjs +102 -0
  38. package/dist/tests/digest.test.d.cts +1 -0
  39. package/dist/tests/digest.test.d.mts +1 -0
  40. package/dist/tests/digest.test.mjs +103 -0
  41. package/dist/tests/docloader-DyZ1JGIl.cjs +398 -0
  42. package/dist/tests/docloader-tUmMeXjU.mjs +375 -0
  43. package/dist/tests/docloader.test.cjs +816 -39
  44. package/dist/tests/docloader.test.mjs +810 -34
  45. package/dist/tests/internal/multicodec.test.cjs +2 -3
  46. package/dist/tests/internal/multicodec.test.mjs +2 -2
  47. package/dist/tests/internal/portable-dereference.test.cjs +172 -0
  48. package/dist/tests/internal/portable-dereference.test.d.cts +1 -0
  49. package/dist/tests/internal/portable-dereference.test.d.mts +1 -0
  50. package/dist/tests/internal/portable-dereference.test.mjs +173 -0
  51. package/dist/tests/jsonld-CmDhHNTx.cjs +12 -0
  52. package/dist/tests/jsonld-DweIJ07b.mjs +5 -0
  53. package/dist/tests/jsonld-cache-BEXiiM-V.cjs +386 -0
  54. package/dist/tests/jsonld-cache-DFnWsLuZ.mjs +339 -0
  55. package/dist/tests/jsonld-cache.test.cjs +102 -298
  56. package/dist/tests/jsonld-cache.test.mjs +95 -289
  57. package/dist/tests/{key-_wXwomh_.cjs → key-C-AYkdJJ.cjs} +11 -4
  58. package/dist/tests/{key-CDGDH_vC.mjs → key-C2Db_TAJ.mjs} +11 -3
  59. package/dist/tests/key.test.cjs +6 -5
  60. package/dist/tests/key.test.mjs +6 -4
  61. package/dist/tests/{langstr-Di5AvKpB.mjs → langstr-DYRmJqFn.mjs} +5 -2
  62. package/dist/tests/{langstr-CbAxaeEZ.cjs → langstr-_PqsL1ae.cjs} +5 -2
  63. package/dist/tests/langstr.test.cjs +11 -4
  64. package/dist/tests/langstr.test.mjs +9 -2
  65. package/dist/tests/langtag-CUppHs_s.mjs +45 -0
  66. package/dist/tests/langtag-Czjvxfzx.cjs +50 -0
  67. package/dist/tests/langtag.test.cjs +45 -0
  68. package/dist/tests/langtag.test.d.cts +1 -0
  69. package/dist/tests/langtag.test.d.mts +1 -0
  70. package/dist/tests/langtag.test.mjs +46 -0
  71. package/dist/tests/link.test.cjs +2 -3
  72. package/dist/tests/link.test.mjs +2 -2
  73. package/dist/tests/multibase/multibase.test.cjs +8 -9
  74. package/dist/tests/multibase/multibase.test.mjs +6 -6
  75. package/dist/tests/{multibase-Bz_UUDtL.cjs → multibase-B5Mea7Ip.cjs} +19 -2
  76. package/dist/tests/{multibase-B4bvakyA.mjs → multibase-BPnF_L4e.mjs} +12 -1
  77. package/dist/tests/portable-dereference-DmTYDy2p.mjs +250 -0
  78. package/dist/tests/portable-dereference-qTLMqe2_.cjs +279 -0
  79. package/dist/tests/portable-media-5p8azal0.cjs +153 -0
  80. package/dist/tests/portable-media-B_lt9MMA.mjs +148 -0
  81. package/dist/tests/portable-media.test.cjs +222 -0
  82. package/dist/tests/portable-media.test.d.cts +1 -0
  83. package/dist/tests/portable-media.test.d.mts +1 -0
  84. package/dist/tests/portable-media.test.mjs +223 -0
  85. package/dist/tests/portable-workers.test.cjs +36 -0
  86. package/dist/tests/portable-workers.test.d.cts +2 -0
  87. package/dist/tests/portable-workers.test.d.mts +2 -0
  88. package/dist/tests/portable-workers.test.mjs +35 -0
  89. package/dist/tests/{request-uk51rkhO.cjs → request-BgjA9wyl.cjs} +10 -4
  90. package/dist/tests/{request-C8CaGwtt.mjs → request-DodZNukP.mjs} +8 -2
  91. package/dist/tests/request.test.cjs +7 -4
  92. package/dist/tests/request.test.mjs +5 -2
  93. package/dist/tests/signed-representation.test.cjs +588 -0
  94. package/dist/tests/signed-representation.test.d.cts +1 -0
  95. package/dist/tests/signed-representation.test.d.mts +1 -0
  96. package/dist/tests/signed-representation.test.mjs +589 -0
  97. package/dist/tests/temporal.test.cjs +1 -2
  98. package/dist/tests/temporal.test.mjs +1 -1
  99. package/dist/tests/url-BNakuZ8k.cjs +998 -0
  100. package/dist/tests/url-DMxmp7ZG.mjs +859 -0
  101. package/dist/tests/url.test.cjs +489 -6
  102. package/dist/tests/url.test.mjs +489 -5
  103. package/dist/url-DrGTR8yv.cjs +993 -0
  104. package/dist/url-Dzyp-NsC.js +860 -0
  105. package/package.json +27 -4
  106. package/scripts/test-bun.mjs +17 -0
  107. package/src/body.test.ts +125 -0
  108. package/src/body.ts +152 -0
  109. package/src/contexts/cid-v1.json +114 -0
  110. package/src/contexts/fep-22cd.json +21 -0
  111. package/src/contexts/fep-6757.json +12 -0
  112. package/src/contexts/joinmastodon.json +1 -1
  113. package/src/contexts/miscellany.json +17 -0
  114. package/src/contexts.ts +47 -1
  115. package/src/digest.test.ts +220 -0
  116. package/src/digest.ts +229 -0
  117. package/src/docloader.test.ts +1066 -27
  118. package/src/docloader.ts +322 -105
  119. package/src/internal/jsonld-cache.ts +97 -1
  120. package/src/internal/portable-dereference.test.ts +254 -0
  121. package/src/internal/portable-dereference.ts +1000 -0
  122. package/src/internal/signed-representation.ts +565 -0
  123. package/src/jsonld-cache.test.ts +89 -0
  124. package/src/key.test.ts +9 -0
  125. package/src/key.ts +11 -1
  126. package/src/langstr.test.ts +18 -1
  127. package/src/langstr.ts +6 -2
  128. package/src/langtag.test.ts +53 -0
  129. package/src/langtag.ts +76 -0
  130. package/src/mod.ts +29 -0
  131. package/src/multibase/multibase.test.ts +5 -5
  132. package/src/portable-media.test.ts +293 -0
  133. package/src/portable-media.ts +264 -0
  134. package/src/portable-workers.test.ts +81 -0
  135. package/src/portable.ts +180 -0
  136. package/src/preprocessor.ts +7 -0
  137. package/src/request.test.ts +10 -1
  138. package/src/request.ts +9 -1
  139. package/src/signed-representation.test.ts +338 -0
  140. package/src/url.test.ts +823 -3
  141. package/src/url.ts +700 -23
  142. package/tsdown.config.ts +2 -0
  143. package/dist/tests/url-CsOV_B_P.cjs +0 -482
  144. package/dist/tests/url-Du7RQQgP.mjs +0 -392
  145. package/dist/url-DD4F0ULf.cjs +0 -483
  146. package/dist/url-DGVbSVVi.js +0 -393
  147. /package/dist/{chunk-M78iaK0I.cjs → rolldown-runtime-B7lfambq.cjs} +0 -0
  148. /package/dist/tests/{link-NUUWCdnK.mjs → link-Cevmc87v.mjs} +0 -0
  149. /package/dist/tests/{link-FguCydMA.cjs → link-DlKm8bEr.cjs} +0 -0
  150. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-CLRPeW4N.cjs} +0 -0
  151. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CRIj_05H.mjs} +0 -0
  152. /package/dist/tests/{chunk-C2EiDwsr.cjs → rolldown-runtime-emK7D4bc.cjs} +0 -0
@@ -0,0 +1,338 @@
1
+ import { deepStrictEqual, ok, strictEqual, throws } from "node:assert";
2
+ import { test } from "node:test";
3
+ import {
4
+ enterSignedValueScope,
5
+ getRetainedSignedRepresentation,
6
+ isMarkerSafeContext,
7
+ isPlainJsonTree,
8
+ resolveSignedValues,
9
+ retainedSignedValueRef,
10
+ retainSignedRepresentation,
11
+ type SignedValueScope,
12
+ } from "./internal/signed-representation.ts";
13
+
14
+ const securedDocument = {
15
+ "@context": [
16
+ "https://www.w3.org/ns/activitystreams",
17
+ "https://w3id.org/security/data-integrity/v1",
18
+ ],
19
+ id: "ap://did:key:z6Mkabc/objects/1",
20
+ type: "Note",
21
+ content: "Hello",
22
+ proof: {
23
+ "@context": [
24
+ "https://www.w3.org/ns/activitystreams",
25
+ "https://w3id.org/security/data-integrity/v1",
26
+ ],
27
+ type: "DataIntegrityProof",
28
+ cryptosuite: "eddsa-jcs-2022",
29
+ created: "2023-02-24T23:36:38Z",
30
+ verificationMethod: "did:key:z6Mkabc#z6Mkabc",
31
+ proofPurpose: "assertionMethod",
32
+ proofValue: "z3FXQ",
33
+ },
34
+ };
35
+
36
+ function newScope(options: Record<string, unknown> = {}): SignedValueScope {
37
+ return enterSignedValueScope(options).scope;
38
+ }
39
+
40
+ function retainedSubject(): object {
41
+ const subject = {};
42
+ retainSignedRepresentation(subject, structuredClone(securedDocument));
43
+ return subject;
44
+ }
45
+
46
+ test("isPlainJsonTree() accepts plain JSON and rejects everything else", () => {
47
+ ok(isPlainJsonTree(securedDocument));
48
+ ok(isPlainJsonTree([1, "two", null, { three: true }]));
49
+ ok(isPlainJsonTree(Object.create(null)));
50
+
51
+ ok(!isPlainJsonTree(undefined));
52
+ ok(!isPlainJsonTree(Number.NaN));
53
+ ok(!isPlainJsonTree(Number.POSITIVE_INFINITY));
54
+ ok(!isPlainJsonTree(new Date()));
55
+ ok(!isPlainJsonTree(new URL("https://example.com/")));
56
+ ok(!isPlainJsonTree({ nested: { deeper: undefined } }));
57
+
58
+ const withSymbolKey: Record<string | symbol, unknown> = { a: 1 };
59
+ withSymbolKey[Symbol("b")] = 2;
60
+ ok(!isPlainJsonTree(withSymbolKey));
61
+
62
+ const withAccessor = {};
63
+ Object.defineProperty(withAccessor, "a", { get: () => 1, enumerable: true });
64
+ ok(!isPlainJsonTree(withAccessor));
65
+
66
+ const withHiddenProperty = {};
67
+ Object.defineProperty(withHiddenProperty, "a", {
68
+ value: 1,
69
+ enumerable: false,
70
+ });
71
+ ok(!isPlainJsonTree(withHiddenProperty));
72
+ });
73
+
74
+ test("retainSignedRepresentation() attaches a frozen, hidden snapshot", () => {
75
+ const subject: Record<string, unknown> = {};
76
+ retainSignedRepresentation(subject, structuredClone(securedDocument));
77
+ const retained = getRetainedSignedRepresentation(subject);
78
+
79
+ deepStrictEqual(retained, securedDocument);
80
+ ok(Object.isFrozen(retained));
81
+ ok(Object.isFrozen((retained as Record<string, unknown>).proof));
82
+ // The carrier must not show up in ordinary enumeration or serialization.
83
+ deepStrictEqual(Object.keys(subject), []);
84
+ strictEqual(JSON.stringify(subject), "{}");
85
+ // A fresh object, such as the one `clone()` builds, has no snapshot.
86
+ strictEqual(getRetainedSignedRepresentation({}), undefined);
87
+ strictEqual(getRetainedSignedRepresentation(null), undefined);
88
+ strictEqual(getRetainedSignedRepresentation("string"), undefined);
89
+ });
90
+
91
+ test("retainSignedRepresentation() rejects a value that is not plain JSON", () => {
92
+ throws(
93
+ () =>
94
+ retainSignedRepresentation({}, {
95
+ created: new Date(),
96
+ } as unknown as Record<string, unknown>),
97
+ TypeError,
98
+ );
99
+ throws(
100
+ () =>
101
+ retainSignedRepresentation(
102
+ {},
103
+ [1, 2] as unknown as Record<string, unknown>,
104
+ ),
105
+ TypeError,
106
+ );
107
+ });
108
+
109
+ test("isMarkerSafeContext() accepts contexts Fedify can vet offline", () => {
110
+ ok(isMarkerSafeContext(null));
111
+ ok(isMarkerSafeContext("https://www.w3.org/ns/activitystreams"));
112
+ ok(isMarkerSafeContext([
113
+ "https://www.w3.org/ns/activitystreams",
114
+ "https://w3id.org/security/data-integrity/v1",
115
+ "https://w3id.org/fep/ef61",
116
+ ]));
117
+ ok(isMarkerSafeContext([
118
+ "https://www.w3.org/ns/activitystreams",
119
+ { ex: "https://example.com/ns#" },
120
+ ]));
121
+ });
122
+
123
+ test("isMarkerSafeContext() rejects contexts that could hide a placeholder", () => {
124
+ // A context Fedify cannot resolve offline cannot be vetted at all.
125
+ ok(!isMarkerSafeContext("https://example.com/unknown-context"));
126
+ // An `@id` alias other than ActivityStreams' own `id`.
127
+ ok(!isMarkerSafeContext({ identifier: "@id" }));
128
+ ok(!isMarkerSafeContext({ identifier: { "@id": "@id" } }));
129
+ // An indirect alias: `i` resolves through `id`, which ActivityStreams maps
130
+ // to `@id`. A target with no scheme separator is never an IRI.
131
+ ok(
132
+ !isMarkerSafeContext([
133
+ "https://www.w3.org/ns/activitystreams",
134
+ { i: "id" },
135
+ ]),
136
+ );
137
+ ok(!isMarkerSafeContext({ relative: { "@id": "ns#term" } }));
138
+ // `@nest` moves a property value into a wrapper map.
139
+ ok(!isMarkerSafeContext({ payload: "@nest" }));
140
+ ok(
141
+ !isMarkerSafeContext({
142
+ object: {
143
+ "@id": "https://www.w3.org/ns/activitystreams#object",
144
+ "@nest": "payload",
145
+ },
146
+ }),
147
+ );
148
+ // An id map or type map turns the identifier into a map key.
149
+ ok(
150
+ !isMarkerSafeContext({
151
+ object: {
152
+ "@id": "https://www.w3.org/ns/activitystreams#object",
153
+ "@container": "@id",
154
+ },
155
+ }),
156
+ );
157
+ ok(
158
+ !isMarkerSafeContext({
159
+ object: {
160
+ "@id": "https://www.w3.org/ns/activitystreams#object",
161
+ "@container": ["@set", "@type"],
162
+ },
163
+ }),
164
+ );
165
+ // A prefix, `@vocab` or `@base` covering the placeholder namespace lets
166
+ // compaction emit a shortened or relative marker.
167
+ ok(!isMarkerSafeContext({ urn: "urn:" }));
168
+ // A term named after the placeholder's scheme makes a JSON-LD processor
169
+ // read the marker as a compact IRI, whatever the term points at.
170
+ ok(!isMarkerSafeContext({ urn: "https://example.com/ns#" }));
171
+ ok(!isMarkerSafeContext({ urn: { "@id": "https://example.com/ns#urn" } }));
172
+ ok(!isMarkerSafeContext({ "@vocab": "urn:x-fedify-signed-value:" }));
173
+ ok(!isMarkerSafeContext({ "@base": "urn:" }));
174
+ ok(!isMarkerSafeContext({ everything: "" }));
175
+ // A scoped context is only as safe as its own definitions.
176
+ ok(isMarkerSafeContext({
177
+ Note: {
178
+ "@id": "https://www.w3.org/ns/activitystreams#Note",
179
+ "@context": { ex: "https://example.com/ns#" },
180
+ },
181
+ }));
182
+ ok(
183
+ !isMarkerSafeContext({
184
+ Note: {
185
+ "@id": "https://www.w3.org/ns/activitystreams#Note",
186
+ "@context": { identifier: "@id" },
187
+ },
188
+ }),
189
+ );
190
+ });
191
+
192
+ test("enterSignedValueScope() gives each call its own scope", () => {
193
+ const callerOptions = { format: "compact" as const };
194
+ const first = { ...callerOptions };
195
+ const second = { ...callerOptions };
196
+ const firstEntry = enterSignedValueScope(first);
197
+ const secondEntry = enterSignedValueScope(second);
198
+
199
+ ok(firstEntry.owner);
200
+ ok(secondEntry.owner);
201
+ ok(firstEntry.scope !== secondEntry.scope);
202
+ // The caller's own object is never touched.
203
+ deepStrictEqual(Object.getOwnPropertySymbols(callerOptions), []);
204
+
205
+ // A nested frame inherits the scope through the generated spread.
206
+ const nested = enterSignedValueScope({ ...first, format: "expand" });
207
+ ok(!nested.owner);
208
+ strictEqual(nested.scope, firstEntry.scope);
209
+ });
210
+
211
+ test("enterSignedValueScope() disables retention where it cannot apply", () => {
212
+ ok(enterSignedValueScope({}).scope.enabled);
213
+ ok(enterSignedValueScope({ format: "compact" }).scope.enabled);
214
+ // An expanded document has no compact representation to preserve.
215
+ ok(!enterSignedValueScope({ format: "expand" }).scope.enabled);
216
+ // A context that could hide a placeholder turns the whole mechanism off
217
+ // rather than risking a corrupted document.
218
+ ok(
219
+ !enterSignedValueScope({
220
+ format: "compact",
221
+ context: { identifier: "@id" } as unknown as Record<string, string>,
222
+ }).scope.enabled,
223
+ );
224
+ });
225
+
226
+ test("retainedSignedValueRef() mints one marker per instance", () => {
227
+ const scope = newScope();
228
+ const subject = retainedSubject();
229
+ const first = retainedSignedValueRef(subject, scope);
230
+ const second = retainedSignedValueRef(subject, scope);
231
+
232
+ ok(first != null);
233
+ deepStrictEqual(first, second);
234
+ ok(first["@id"].startsWith("urn:x-fedify-signed-value:"));
235
+ strictEqual(
236
+ scope.documents.get(first["@id"]),
237
+ scope.documents.get(second!["@id"]),
238
+ );
239
+ strictEqual(retainedSignedValueRef({}, scope), undefined);
240
+ strictEqual(retainedSignedValueRef(null, scope), undefined);
241
+ strictEqual(
242
+ retainedSignedValueRef(subject, newScope({ format: "expand" })),
243
+ undefined,
244
+ );
245
+ });
246
+
247
+ test("resolveSignedValues() restores every supported reference shape", () => {
248
+ const scope = newScope();
249
+ const subject = retainedSubject();
250
+ const marker = retainedSignedValueRef(subject, scope)!["@id"];
251
+ retainedSignedValueRef(subject, scope);
252
+ retainedSignedValueRef(subject, scope);
253
+
254
+ const resolved = resolveSignedValues({
255
+ "@context": ["https://www.w3.org/ns/activitystreams"],
256
+ object: marker,
257
+ attachment: [{ "@id": marker }],
258
+ tag: { id: marker },
259
+ }, scope) as Record<string, unknown>;
260
+
261
+ deepStrictEqual(resolved.object, securedDocument);
262
+ deepStrictEqual((resolved.attachment as unknown[])[0], securedDocument);
263
+ deepStrictEqual(resolved.tag, securedDocument);
264
+ // Each site gets its own copy.
265
+ ok(resolved.object !== resolved.tag);
266
+ });
267
+
268
+ test("resolveSignedValues() leaves a document without placeholders alone", () => {
269
+ const scope = newScope();
270
+ const document = { id: "https://example.com/notes/1" };
271
+ strictEqual(resolveSignedValues(document, scope), document);
272
+ });
273
+
274
+ test("resolveSignedValues() does not rewrite a context definition", () => {
275
+ const scope = newScope();
276
+ const subject = retainedSubject();
277
+ const marker = retainedSignedValueRef(subject, scope)!["@id"];
278
+ // A placeholder that only survives inside `@context` was never restored,
279
+ // so the accounting check has to reject the document.
280
+ throws(
281
+ () => resolveSignedValues({ "@context": { marker } }, scope),
282
+ TypeError,
283
+ );
284
+ });
285
+
286
+ test("resolveSignedValues() rejects a document that lost a placeholder", () => {
287
+ const scope = newScope();
288
+ const subject = retainedSubject();
289
+ retainedSignedValueRef(subject, scope);
290
+
291
+ throws(
292
+ () => resolveSignedValues({ id: "https://example.com/notes/1" }, scope),
293
+ TypeError,
294
+ );
295
+ });
296
+
297
+ test("resolveSignedValues() rejects a document that kept a placeholder trace", () => {
298
+ const scope = newScope();
299
+ const subject = retainedSubject();
300
+ const marker = retainedSignedValueRef(subject, scope)!["@id"];
301
+ const uuid = marker.slice("urn:x-fedify-signed-value:".length);
302
+
303
+ throws(
304
+ () => resolveSignedValues({ object: marker, note: `sv:${uuid}` }, scope),
305
+ TypeError,
306
+ );
307
+ });
308
+
309
+ test("resolveSignedValues() restores a placeholder only where it is a reference", () => {
310
+ const scope = newScope();
311
+ const subject = retainedSubject();
312
+ const marker = retainedSignedValueRef(subject, scope)!["@id"];
313
+
314
+ // A map with more than the identifier is a node of its own, not a
315
+ // reference, so only the string inside it is replaced.
316
+ const resolved = resolveSignedValues(
317
+ { object: { "@id": marker, type: "Note" } },
318
+ scope,
319
+ ) as Record<string, unknown>;
320
+ const object = resolved.object as Record<string, unknown>;
321
+ deepStrictEqual(object["@id"], securedDocument);
322
+ strictEqual(object.type, "Note");
323
+ });
324
+
325
+ test("resolveSignedValues() refuses a document too deep to walk", () => {
326
+ const scope = newScope();
327
+ const subject = retainedSubject();
328
+ const marker = retainedSignedValueRef(subject, scope)!["@id"];
329
+ // The shallow occurrence alone must not satisfy the check: a placeholder in
330
+ // an unvisited subtree would otherwise reach the wire.
331
+ let deep: unknown = { "@id": marker };
332
+ for (let i = 0; i < 300; i++) deep = { attachment: deep };
333
+
334
+ throws(
335
+ () => resolveSignedValues({ object: marker, tag: deep }, scope),
336
+ TypeError,
337
+ );
338
+ });