@longarc/mdash 3.1.2 → 3.1.3

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 (172) hide show
  1. package/README.md +86 -23
  2. package/SECURITY.md +254 -0
  3. package/dist/accountability/engine.d.ts +27 -0
  4. package/dist/accountability/engine.d.ts.map +1 -0
  5. package/dist/accountability/engine.js +148 -0
  6. package/dist/accountability/engine.js.map +1 -0
  7. package/dist/accountability/types.d.ts +46 -0
  8. package/dist/accountability/types.d.ts.map +1 -0
  9. package/dist/accountability/types.js +8 -0
  10. package/dist/accountability/types.js.map +1 -0
  11. package/dist/checkpoint/engine.d.ts.map +1 -1
  12. package/dist/checkpoint/engine.js +4 -0
  13. package/dist/checkpoint/engine.js.map +1 -1
  14. package/dist/context/compose.d.ts +62 -0
  15. package/dist/context/compose.d.ts.map +1 -0
  16. package/dist/context/compose.js +286 -0
  17. package/dist/context/compose.js.map +1 -0
  18. package/dist/context/crypto/hash.d.ts +100 -0
  19. package/dist/context/crypto/hash.d.ts.map +1 -0
  20. package/dist/context/crypto/hash.js +248 -0
  21. package/dist/context/crypto/hash.js.map +1 -0
  22. package/dist/context/crypto/hmac.d.ts +80 -0
  23. package/dist/context/crypto/hmac.d.ts.map +1 -0
  24. package/dist/context/crypto/hmac.js +192 -0
  25. package/dist/context/crypto/hmac.js.map +1 -0
  26. package/dist/context/crypto/index.d.ts +7 -0
  27. package/dist/context/crypto/index.d.ts.map +1 -0
  28. package/dist/context/crypto/index.js +7 -0
  29. package/dist/context/crypto/index.js.map +1 -0
  30. package/dist/context/engine-v3.0-backup.d.ts +197 -0
  31. package/dist/context/engine-v3.0-backup.d.ts.map +1 -0
  32. package/dist/context/engine-v3.0-backup.js +392 -0
  33. package/dist/context/engine-v3.0-backup.js.map +1 -0
  34. package/dist/context/fragment.d.ts +99 -0
  35. package/dist/context/fragment.d.ts.map +1 -0
  36. package/dist/context/fragment.js +316 -0
  37. package/dist/context/fragment.js.map +1 -0
  38. package/dist/context/index.d.ts +99 -0
  39. package/dist/context/index.d.ts.map +1 -0
  40. package/dist/context/index.js +180 -0
  41. package/dist/context/index.js.map +1 -0
  42. package/dist/context/provenance.d.ts +80 -0
  43. package/dist/context/provenance.d.ts.map +1 -0
  44. package/dist/context/provenance.js +294 -0
  45. package/dist/context/provenance.js.map +1 -0
  46. package/dist/context/resolve.d.ts +106 -0
  47. package/dist/context/resolve.d.ts.map +1 -0
  48. package/dist/context/resolve.js +440 -0
  49. package/dist/context/resolve.js.map +1 -0
  50. package/dist/context/store.d.ts +156 -0
  51. package/dist/context/store.d.ts.map +1 -0
  52. package/dist/context/store.js +396 -0
  53. package/dist/context/store.js.map +1 -0
  54. package/dist/context/types.d.ts +463 -0
  55. package/dist/context/types.d.ts.map +1 -0
  56. package/dist/context/types.js +94 -0
  57. package/dist/context/types.js.map +1 -0
  58. package/dist/context/utils/atomic.d.ts +76 -0
  59. package/dist/context/utils/atomic.d.ts.map +1 -0
  60. package/dist/context/utils/atomic.js +159 -0
  61. package/dist/context/utils/atomic.js.map +1 -0
  62. package/dist/context/utils/credit.d.ts +65 -0
  63. package/dist/context/utils/credit.d.ts.map +1 -0
  64. package/dist/context/utils/credit.js +164 -0
  65. package/dist/context/utils/credit.js.map +1 -0
  66. package/dist/context/utils/index.d.ts +13 -0
  67. package/dist/context/utils/index.d.ts.map +1 -0
  68. package/dist/context/utils/index.js +13 -0
  69. package/dist/context/utils/index.js.map +1 -0
  70. package/dist/context/utils/utility.d.ts +63 -0
  71. package/dist/context/utils/utility.d.ts.map +1 -0
  72. package/dist/context/utils/utility.js +141 -0
  73. package/dist/context/utils/utility.js.map +1 -0
  74. package/dist/core/commitment.d.ts +25 -2
  75. package/dist/core/commitment.d.ts.map +1 -1
  76. package/dist/core/commitment.js +44 -6
  77. package/dist/core/commitment.js.map +1 -1
  78. package/dist/core/crypto.d.ts +2 -0
  79. package/dist/core/crypto.d.ts.map +1 -1
  80. package/dist/core/crypto.js +12 -0
  81. package/dist/core/crypto.js.map +1 -1
  82. package/dist/index.d.ts +11 -6
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +35 -10
  85. package/dist/index.js.map +1 -1
  86. package/dist/mcca/engine.d.ts.map +1 -1
  87. package/dist/mcca/engine.js +5 -4
  88. package/dist/mcca/engine.js.map +1 -1
  89. package/dist/physics/engine.d.ts +1 -0
  90. package/dist/physics/engine.d.ts.map +1 -1
  91. package/dist/physics/engine.js +36 -2
  92. package/dist/physics/engine.js.map +1 -1
  93. package/dist/provenance/api-handler.d.ts +45 -0
  94. package/dist/provenance/api-handler.d.ts.map +1 -0
  95. package/dist/provenance/api-handler.js +223 -0
  96. package/dist/provenance/api-handler.js.map +1 -0
  97. package/dist/provenance/api-types.d.ts +108 -0
  98. package/dist/provenance/api-types.d.ts.map +1 -0
  99. package/dist/provenance/api-types.js +9 -0
  100. package/dist/provenance/api-types.js.map +1 -0
  101. package/dist/provenance/index.d.ts +6 -0
  102. package/dist/provenance/index.d.ts.map +1 -0
  103. package/dist/provenance/index.js +3 -0
  104. package/dist/provenance/index.js.map +1 -0
  105. package/dist/provenance/provenance-engine.d.ts +63 -0
  106. package/dist/provenance/provenance-engine.d.ts.map +1 -0
  107. package/dist/provenance/provenance-engine.js +311 -0
  108. package/dist/provenance/provenance-engine.js.map +1 -0
  109. package/dist/provenance/types.d.ts +193 -0
  110. package/dist/provenance/types.d.ts.map +1 -0
  111. package/dist/provenance/types.js +9 -0
  112. package/dist/provenance/types.js.map +1 -0
  113. package/dist/tee/engine.d.ts.map +1 -1
  114. package/dist/tee/engine.js +14 -0
  115. package/dist/tee/engine.js.map +1 -1
  116. package/dist/warrant/engine.d.ts +24 -1
  117. package/dist/warrant/engine.d.ts.map +1 -1
  118. package/dist/warrant/engine.js +76 -1
  119. package/dist/warrant/engine.js.map +1 -1
  120. package/dist/zk/engine.d.ts.map +1 -1
  121. package/dist/zk/engine.js +7 -4
  122. package/dist/zk/engine.js.map +1 -1
  123. package/docs/SECURITY-PATCHES.md +170 -0
  124. package/package.json +17 -5
  125. package/src/__tests__/accountability.test.ts +308 -0
  126. package/src/__tests__/l1-verification-modes.test.ts +424 -0
  127. package/src/__tests__/phase1.benchmark.test.ts +94 -0
  128. package/src/__tests__/phase1.test.ts +0 -77
  129. package/src/__tests__/phase2-4.benchmark.test.ts +60 -0
  130. package/src/__tests__/phase2-4.test.ts +1 -52
  131. package/src/__tests__/provenance/api-handler.test.ts +356 -0
  132. package/src/__tests__/provenance/provenance-engine.test.ts +628 -0
  133. package/src/__tests__/sa-2026-008.test.ts +45 -0
  134. package/src/__tests__/sa-2026-009.test.ts +86 -0
  135. package/src/__tests__/sa-2026-010.test.ts +72 -0
  136. package/src/__tests__/sa-2026-012.test.ts +65 -0
  137. package/src/__tests__/sa-2026-nfc.test.ts +40 -0
  138. package/src/__tests__/security.test.ts +786 -0
  139. package/src/accountability/engine.ts +230 -0
  140. package/src/accountability/types.ts +58 -0
  141. package/src/checkpoint/engine.ts +4 -0
  142. package/src/context/__tests__/caret-v0.2.0.test.ts +860 -0
  143. package/src/context/__tests__/integration.test.ts +356 -0
  144. package/src/context/compose.ts +388 -0
  145. package/src/context/crypto/hash.ts +277 -0
  146. package/src/context/crypto/hmac.ts +253 -0
  147. package/src/context/crypto/index.ts +29 -0
  148. package/src/context/engine-v3.0-backup.ts +598 -0
  149. package/src/context/fragment.ts +454 -0
  150. package/src/context/index.ts +427 -0
  151. package/src/context/provenance.ts +380 -0
  152. package/src/context/resolve.ts +581 -0
  153. package/src/context/store.ts +503 -0
  154. package/src/context/types.ts +679 -0
  155. package/src/context/utils/atomic.ts +207 -0
  156. package/src/context/utils/credit.ts +224 -0
  157. package/src/context/utils/index.ts +13 -0
  158. package/src/context/utils/utility.ts +200 -0
  159. package/src/core/commitment.ts +129 -67
  160. package/src/core/crypto.ts +13 -0
  161. package/src/index.ts +62 -10
  162. package/src/mcca/engine.ts +5 -4
  163. package/src/physics/engine.ts +40 -3
  164. package/src/provenance/api-handler.ts +248 -0
  165. package/src/provenance/api-types.ts +112 -0
  166. package/src/provenance/index.ts +19 -0
  167. package/src/provenance/provenance-engine.ts +387 -0
  168. package/src/provenance/types.ts +211 -0
  169. package/src/tee/engine.ts +16 -0
  170. package/src/warrant/engine.ts +89 -1
  171. package/src/zk/engine.ts +8 -4
  172. package/tsconfig.json +1 -1
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Caret — Context Fragment Implementation
3
+ * @module @longarcstudios/caret/fragment
4
+ *
5
+ * The atomic unit of structured context.
6
+ * Immutable, sealed, attributable.
7
+ */
8
+
9
+ import type {
10
+ ContextFragment,
11
+ FragmentId,
12
+ Timestamp,
13
+ SemanticUnit,
14
+ SemanticType,
15
+ TokenCount,
16
+ Constraint,
17
+ ProvenanceChain,
18
+ Attribution,
19
+ SourceURI,
20
+ TrustLevel,
21
+ } from './types.js';
22
+ import { sha256Object } from './crypto/hash.js';
23
+ import { sealFragment, verifyFragmentSeal } from './crypto/hmac.js';
24
+ import {
25
+ createProvenanceRecord,
26
+ createProvenanceChain,
27
+ extendProvenanceChain,
28
+ } from './provenance.js';
29
+
30
+ // ============================================================================
31
+ // UUID GENERATION
32
+ // ============================================================================
33
+
34
+ /**
35
+ * Generate a UUID v4.
36
+ * Uses crypto.randomUUID if available, falls back to crypto.getRandomValues.
37
+ * SECURITY: Never falls back to Math.random — fails instead.
38
+ */
39
+ function generateUUID(): FragmentId {
40
+ // Preferred: native randomUUID
41
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
42
+ return crypto.randomUUID() as FragmentId;
43
+ }
44
+
45
+ // Fallback: manual UUID from getRandomValues (still cryptographically secure)
46
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
47
+ const bytes = new Uint8Array(16);
48
+ crypto.getRandomValues(bytes);
49
+
50
+ // Set version (4) and variant (RFC4122)
51
+ bytes[6] = (bytes[6]! & 0x0f) | 0x40;
52
+ bytes[8] = (bytes[8]! & 0x3f) | 0x80;
53
+
54
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
55
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` as FragmentId;
56
+ }
57
+
58
+ // SECURITY: Do not fall back to Math.random — fail secure
59
+ throw new Error('Cryptographically secure random number generator not available');
60
+ }
61
+
62
+ // ============================================================================
63
+ // TOKEN COUNTING
64
+ // ============================================================================
65
+
66
+ /** Default tokenizer: approximate cl100k_base behavior */
67
+ function defaultTokenCount(text: string): TokenCount {
68
+ // Rough approximation: ~4 chars per token for English
69
+ // More accurate would require actual tokenizer
70
+ const charCount = text.length;
71
+ const estimate = Math.ceil(charCount / 4);
72
+ return estimate as TokenCount;
73
+ }
74
+
75
+ /** Tokenizer function type */
76
+ type Tokenizer = (content: unknown) => TokenCount;
77
+
78
+ /** Current tokenizer (can be replaced) */
79
+ let currentTokenizer: Tokenizer = (content: unknown): TokenCount => {
80
+ const text = typeof content === 'string'
81
+ ? content
82
+ : JSON.stringify(content);
83
+ return defaultTokenCount(text);
84
+ };
85
+
86
+ /**
87
+ * Set a custom tokenizer function.
88
+ */
89
+ export function setTokenizer(tokenizer: Tokenizer): void {
90
+ currentTokenizer = tokenizer;
91
+ }
92
+
93
+ /**
94
+ * Count tokens in content using current tokenizer.
95
+ */
96
+ export function countTokens(content: unknown): TokenCount {
97
+ return currentTokenizer(content);
98
+ }
99
+
100
+ // ============================================================================
101
+ // SEMANTIC UNIT
102
+ // ============================================================================
103
+
104
+ /**
105
+ * Create a SemanticUnit from content.
106
+ */
107
+ export function createSemanticUnit<T>(
108
+ data: T,
109
+ options: {
110
+ type?: SemanticType;
111
+ compression_ratio?: number;
112
+ } = {}
113
+ ): SemanticUnit<T> {
114
+ const type = options.type ?? inferSemanticType(data);
115
+ const token_count = countTokens(data);
116
+
117
+ return {
118
+ type,
119
+ data,
120
+ token_count,
121
+ compression_ratio: options.compression_ratio,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Infer semantic type from data.
127
+ */
128
+ function inferSemanticType(data: unknown): SemanticType {
129
+ if (typeof data === 'string') {
130
+ return 'text';
131
+ }
132
+
133
+ if (typeof data === 'object' && data !== null) {
134
+ // Check for special object patterns
135
+ if ('$ref' in data || 'uri' in data || 'url' in data) {
136
+ return 'reference';
137
+ }
138
+ return 'structured';
139
+ }
140
+
141
+ return 'text';
142
+ }
143
+
144
+ // ============================================================================
145
+ // FRAGMENT CREATION
146
+ // ============================================================================
147
+
148
+ /** Options for creating a fragment */
149
+ export interface CreateFragmentOptions<T = unknown> {
150
+ /** The content to store */
151
+ content: T;
152
+
153
+ /** Source URI for provenance */
154
+ source: SourceURI | string;
155
+
156
+ /** Attribution type */
157
+ attribution: Attribution;
158
+
159
+ /** Trust level (0-100, optional) */
160
+ trust_level?: number;
161
+
162
+ /** Constraints to apply */
163
+ constraints?: Constraint[];
164
+
165
+ /** Parent fragment for provenance chain */
166
+ parent?: ContextFragment;
167
+
168
+ /** Optional signature */
169
+ signature?: string;
170
+
171
+ /** Semantic type override */
172
+ semantic_type?: SemanticType;
173
+
174
+ /** Secret key for sealing */
175
+ seal_key: string;
176
+ }
177
+
178
+ /**
179
+ * Create a new ContextFragment.
180
+ * This is the primary factory function for fragments.
181
+ */
182
+ export async function createFragment<T = unknown>(
183
+ options: CreateFragmentOptions<T>
184
+ ): Promise<ContextFragment<T>> {
185
+ const id = generateUUID();
186
+ const sealed_at = new Date().toISOString() as Timestamp;
187
+
188
+ // Create semantic unit
189
+ const content = createSemanticUnit(options.content, {
190
+ type: options.semantic_type,
191
+ });
192
+
193
+ // Create provenance
194
+ const record = await createProvenanceRecord({
195
+ source: options.source,
196
+ attribution: options.attribution,
197
+ trust_level: options.trust_level,
198
+ parent_hash: options.parent?.hash ?? null,
199
+ signature: options.signature,
200
+ });
201
+
202
+ let provenance: ProvenanceChain;
203
+ if (options.parent) {
204
+ provenance = await extendProvenanceChain(options.parent.provenance, record);
205
+ } else {
206
+ provenance = await createProvenanceChain(record);
207
+ }
208
+
209
+ // Compute content hash
210
+ const hash = await sha256Object({
211
+ content,
212
+ provenance,
213
+ sealed_at,
214
+ });
215
+
216
+ // Create seal
217
+ const fragmentData = {
218
+ id,
219
+ hash,
220
+ content,
221
+ provenance,
222
+ sealed_at,
223
+ constraints: options.constraints ?? [],
224
+ };
225
+
226
+ const seal = await sealFragment(fragmentData, options.seal_key);
227
+
228
+ return {
229
+ id,
230
+ hash,
231
+ content,
232
+ provenance,
233
+ sealed_at,
234
+ seal,
235
+ constraints: options.constraints ?? [],
236
+ version: 'v3.1',
237
+ };
238
+ }
239
+
240
+ // ============================================================================
241
+ // FRAGMENT VERIFICATION
242
+ // ============================================================================
243
+
244
+ /**
245
+ * Verify a fragment's integrity.
246
+ * Checks both the content hash and the HMAC seal.
247
+ */
248
+ export async function verifyFragment(
249
+ fragment: ContextFragment,
250
+ seal_key: string
251
+ ): Promise<{
252
+ valid: boolean;
253
+ errors: string[];
254
+ }> {
255
+ const errors: string[] = [];
256
+
257
+ // Verify content hash
258
+ const expectedHash = await sha256Object({
259
+ content: fragment.content,
260
+ provenance: fragment.provenance,
261
+ sealed_at: fragment.sealed_at,
262
+ });
263
+
264
+ if (expectedHash !== fragment.hash) {
265
+ errors.push('Content hash mismatch');
266
+ }
267
+
268
+ // Verify HMAC seal
269
+ const sealValid = await verifyFragmentSeal(
270
+ {
271
+ id: fragment.id,
272
+ hash: fragment.hash,
273
+ content: fragment.content,
274
+ provenance: fragment.provenance,
275
+ sealed_at: fragment.sealed_at,
276
+ constraints: fragment.constraints,
277
+ seal: fragment.seal,
278
+ },
279
+ seal_key
280
+ );
281
+
282
+ if (!sealValid) {
283
+ errors.push('HMAC seal verification failed');
284
+ }
285
+
286
+ // Verify version (accept both v3.1 and legacy 1.0)
287
+ if (fragment.version !== 'v3.1' && fragment.version !== '1.0') {
288
+ errors.push(`Unknown fragment version: ${fragment.version}`);
289
+ }
290
+
291
+ return {
292
+ valid: errors.length === 0,
293
+ errors,
294
+ };
295
+ }
296
+
297
+ // ============================================================================
298
+ // FRAGMENT OPERATIONS
299
+ // ============================================================================
300
+
301
+ /**
302
+ * Derive a new fragment from an existing one.
303
+ * Creates a new fragment with the parent's provenance extended.
304
+ */
305
+ export async function deriveFragment<T, U = T>(
306
+ parent: ContextFragment<T>,
307
+ options: {
308
+ content: U;
309
+ source: SourceURI | string;
310
+ attribution?: Attribution;
311
+ trust_level?: number;
312
+ constraints?: Constraint[];
313
+ seal_key: string;
314
+ }
315
+ ): Promise<ContextFragment<U>> {
316
+ return createFragment({
317
+ content: options.content,
318
+ source: options.source,
319
+ attribution: options.attribution ?? 'derived',
320
+ trust_level: options.trust_level,
321
+ constraints: [...(options.constraints ?? parent.constraints)],
322
+ parent,
323
+ seal_key: options.seal_key,
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Clone a fragment with new constraints.
329
+ * Creates a new fragment with the same content but different constraints.
330
+ */
331
+ export async function withConstraints<T>(
332
+ fragment: ContextFragment<T>,
333
+ constraints: Constraint[],
334
+ seal_key: string
335
+ ): Promise<ContextFragment<T>> {
336
+ return deriveFragment(fragment, {
337
+ content: fragment.content.data,
338
+ source: fragment.provenance.head.source,
339
+ attribution: 'derived',
340
+ constraints,
341
+ seal_key,
342
+ });
343
+ }
344
+
345
+ /**
346
+ * Get the age of a fragment in milliseconds.
347
+ */
348
+ export function getFragmentAge(fragment: ContextFragment): number {
349
+ const sealed = new Date(fragment.sealed_at).getTime();
350
+ return Date.now() - sealed;
351
+ }
352
+
353
+ /**
354
+ * Check if a fragment is expired based on time constraints.
355
+ */
356
+ export function isFragmentExpired(fragment: ContextFragment): boolean {
357
+ for (const constraint of fragment.constraints) {
358
+ if (constraint.kind === 'time') {
359
+ // Check max_age
360
+ if (constraint.max_age_ms !== undefined) {
361
+ const age = getFragmentAge(fragment);
362
+ if (age > constraint.max_age_ms) {
363
+ return true;
364
+ }
365
+ }
366
+
367
+ // Check not_after
368
+ if (constraint.not_after !== undefined) {
369
+ const deadline = new Date(constraint.not_after).getTime();
370
+ if (Date.now() > deadline) {
371
+ return true;
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ return false;
378
+ }
379
+
380
+ /**
381
+ * Get the trust level of a fragment (minimum in chain).
382
+ */
383
+ export function getFragmentTrust(fragment: ContextFragment): TrustLevel {
384
+ let min = fragment.provenance.head.trust_level;
385
+ let current = fragment.provenance.tail;
386
+
387
+ while (current !== null) {
388
+ if (current.head.trust_level < min) {
389
+ min = current.head.trust_level;
390
+ }
391
+ current = current.tail;
392
+ }
393
+
394
+ return min;
395
+ }
396
+
397
+ // ============================================================================
398
+ // SERIALIZATION
399
+ // ============================================================================
400
+
401
+ /**
402
+ * Serialize a fragment to JSON.
403
+ */
404
+ export function serializeFragment(fragment: ContextFragment): string {
405
+ return JSON.stringify(fragment);
406
+ }
407
+
408
+ /**
409
+ * Sanitize an object to prevent prototype pollution.
410
+ * Removes __proto__, constructor, and prototype properties recursively.
411
+ */
412
+ function sanitizeObject(obj: unknown): unknown {
413
+ if (obj === null || typeof obj !== 'object') {
414
+ return obj;
415
+ }
416
+
417
+ if (Array.isArray(obj)) {
418
+ return obj.map(sanitizeObject);
419
+ }
420
+
421
+ const sanitized: Record<string, unknown> = Object.create(null);
422
+ for (const key of Object.keys(obj)) {
423
+ // Block dangerous properties
424
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
425
+ continue;
426
+ }
427
+ sanitized[key] = sanitizeObject((obj as Record<string, unknown>)[key]);
428
+ }
429
+
430
+ return sanitized;
431
+ }
432
+
433
+ /**
434
+ * Deserialize a fragment from JSON.
435
+ * Note: Does NOT verify integrity — call verifyFragment separately.
436
+ */
437
+ export function deserializeFragment<T = unknown>(json: string): ContextFragment<T> {
438
+ const raw = JSON.parse(json);
439
+
440
+ // Sanitize to prevent prototype pollution
441
+ const parsed = sanitizeObject(raw) as Record<string, unknown>;
442
+
443
+ // Type validation
444
+ if (!parsed.id || !parsed.hash || !parsed.content || !parsed.provenance) {
445
+ throw new Error('Invalid fragment structure');
446
+ }
447
+
448
+ if (parsed.version !== 'v3.1' && parsed.version !== '1.0') {
449
+ // SECURITY: Do not leak version value in error message
450
+ throw new Error('Unsupported fragment version');
451
+ }
452
+
453
+ return parsed as unknown as ContextFragment<T>;
454
+ }