@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,581 @@
1
+ /**
2
+ * Caret — Constraint Resolution Engine
3
+ * @module @longarcstudios/caret/resolve
4
+ *
5
+ * Resolve context fragments through their constraints.
6
+ * Determines if a fragment is valid for use in the current context.
7
+ */
8
+
9
+ import type {
10
+ ContextFragment,
11
+ Constraint,
12
+ TimeConstraint,
13
+ ScopeConstraint,
14
+ TrustConstraint,
15
+ SignatureConstraint,
16
+ InfluenceConstraint,
17
+ InfluenceBudget,
18
+ ResolutionResult,
19
+ ConstraintViolation,
20
+ Timestamp,
21
+ TrustLevel,
22
+ Attribution,
23
+ } from './types.js';
24
+ import { isValidInfluenceBudget } from './types.js';
25
+ import { getFragmentTrust, getFragmentAge } from './fragment.js';
26
+ import { flattenChain } from './provenance.js';
27
+
28
+ // ============================================================================
29
+ // RESOLUTION CONTEXT
30
+ // ============================================================================
31
+
32
+ /** Context for resolution operations */
33
+ export interface ResolutionContext {
34
+ /** Current domain for scope checking */
35
+ domain?: string;
36
+
37
+ /** Current time for time constraint checking (default: now) */
38
+ current_time?: Date;
39
+
40
+ /** Minimum required trust level */
41
+ min_trust?: TrustLevel;
42
+
43
+ /** Required attributions */
44
+ required_attributions?: Attribution[];
45
+
46
+ /** Known trusted signers */
47
+ trusted_signers?: string[];
48
+
49
+ /** Current influence budget for MCCA compliance checking */
50
+ current_influence?: InfluenceBudget;
51
+ }
52
+
53
+ // ============================================================================
54
+ // MAIN RESOLUTION FUNCTION
55
+ // ============================================================================
56
+
57
+ /**
58
+ * Resolve a fragment through its constraints.
59
+ *
60
+ * @param fragment - The fragment to resolve
61
+ * @param context - Resolution context
62
+ * @returns Resolution result indicating success or violations
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const result = await resolve(fragment, {
67
+ * domain: 'hr.company.com',
68
+ * min_trust: 50
69
+ * });
70
+ *
71
+ * if (result.success) {
72
+ * // Use fragment.content safely
73
+ * } else {
74
+ * console.log('Violations:', result.violations);
75
+ * }
76
+ * ```
77
+ */
78
+ export async function resolve<T = unknown>(
79
+ fragment: ContextFragment<T>,
80
+ context: ResolutionContext = {}
81
+ ): Promise<ResolutionResult<T>> {
82
+ const violations: ConstraintViolation[] = [];
83
+ const currentTime = context.current_time ?? new Date();
84
+
85
+ // Check each constraint
86
+ for (const constraint of fragment.constraints) {
87
+ const violation = checkConstraint(constraint, fragment, context, currentTime);
88
+ if (violation) {
89
+ violations.push(violation);
90
+ }
91
+ }
92
+
93
+ // Check context-level requirements (not in fragment constraints)
94
+ if (context.min_trust !== undefined) {
95
+ const trust = getFragmentTrust(fragment);
96
+ if (trust < context.min_trust) {
97
+ violations.push({
98
+ constraint: { kind: 'trust', minimum_trust: context.min_trust },
99
+ reason: `Fragment trust ${trust} is below required ${context.min_trust}`,
100
+ severity: 'error',
101
+ });
102
+ }
103
+ }
104
+
105
+ if (context.required_attributions !== undefined) {
106
+ const records = flattenChain(fragment.provenance);
107
+ const attributions = new Set(records.map(r => r.attribution));
108
+
109
+ for (const required of context.required_attributions) {
110
+ if (!attributions.has(required)) {
111
+ violations.push({
112
+ constraint: { kind: 'trust', minimum_trust: 0 as TrustLevel, required_attributions: context.required_attributions },
113
+ reason: `Missing required attribution: ${required}`,
114
+ severity: 'error',
115
+ });
116
+ }
117
+ }
118
+ }
119
+
120
+ return {
121
+ success: violations.filter(v => v.severity === 'error').length === 0,
122
+ fragment: violations.filter(v => v.severity === 'error').length === 0 ? fragment : null,
123
+ violations,
124
+ resolved_at: new Date().toISOString() as Timestamp,
125
+ };
126
+ }
127
+
128
+ // ============================================================================
129
+ // CONSTRAINT CHECKERS
130
+ // ============================================================================
131
+
132
+ /**
133
+ * Check a single constraint against a fragment.
134
+ */
135
+ function checkConstraint(
136
+ constraint: Constraint,
137
+ fragment: ContextFragment,
138
+ context: ResolutionContext,
139
+ currentTime: Date
140
+ ): ConstraintViolation | null {
141
+ switch (constraint.kind) {
142
+ case 'time':
143
+ return checkTimeConstraint(constraint, fragment, currentTime);
144
+ case 'scope':
145
+ return checkScopeConstraint(constraint, context);
146
+ case 'trust':
147
+ return checkTrustConstraint(constraint, fragment);
148
+ case 'signature':
149
+ return checkSignatureConstraint(constraint, fragment, context);
150
+ case 'influence':
151
+ return checkInfluenceConstraint(constraint, context);
152
+ default:
153
+ return null;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Check time-based constraints.
159
+ */
160
+ function checkTimeConstraint(
161
+ constraint: TimeConstraint,
162
+ fragment: ContextFragment,
163
+ currentTime: Date
164
+ ): ConstraintViolation | null {
165
+ // Check max_age
166
+ if (constraint.max_age_ms !== undefined) {
167
+ const age = getFragmentAge(fragment);
168
+ if (age > constraint.max_age_ms) {
169
+ return {
170
+ constraint,
171
+ reason: `Fragment age (${formatDuration(age)}) exceeds max_age (${formatDuration(constraint.max_age_ms)})`,
172
+ severity: 'error',
173
+ };
174
+ }
175
+ }
176
+
177
+ // Check not_before
178
+ if (constraint.not_before !== undefined) {
179
+ const notBefore = new Date(constraint.not_before);
180
+ if (currentTime < notBefore) {
181
+ return {
182
+ constraint,
183
+ reason: `Current time is before not_before (${constraint.not_before})`,
184
+ severity: 'error',
185
+ };
186
+ }
187
+ }
188
+
189
+ // Check not_after
190
+ if (constraint.not_after !== undefined) {
191
+ const notAfter = new Date(constraint.not_after);
192
+ if (currentTime > notAfter) {
193
+ return {
194
+ constraint,
195
+ reason: `Current time is after not_after (${constraint.not_after})`,
196
+ severity: 'error',
197
+ };
198
+ }
199
+ }
200
+
201
+ return null;
202
+ }
203
+
204
+ /**
205
+ * Check scope-based constraints.
206
+ */
207
+ function checkScopeConstraint(
208
+ constraint: ScopeConstraint,
209
+ context: ResolutionContext
210
+ ): ConstraintViolation | null {
211
+ if (!context.domain) {
212
+ // No domain context — can't verify scope
213
+ return {
214
+ constraint,
215
+ reason: 'No domain context provided for scope verification',
216
+ severity: 'warning',
217
+ };
218
+ }
219
+
220
+ // Check denied domains first
221
+ if (constraint.denied_domains) {
222
+ for (const denied of constraint.denied_domains) {
223
+ if (domainMatches(context.domain, denied)) {
224
+ return {
225
+ constraint,
226
+ reason: `Domain ${context.domain} is in denied list`,
227
+ severity: 'error',
228
+ };
229
+ }
230
+ }
231
+ }
232
+
233
+ // Check allowed domains
234
+ let allowed = false;
235
+ for (const allowedDomain of constraint.allowed_domains) {
236
+ if (domainMatches(context.domain, allowedDomain)) {
237
+ allowed = true;
238
+ break;
239
+ }
240
+ }
241
+
242
+ if (!allowed) {
243
+ return {
244
+ constraint,
245
+ reason: `Domain ${context.domain} is not in allowed list`,
246
+ severity: 'error',
247
+ };
248
+ }
249
+
250
+ return null;
251
+ }
252
+
253
+ /**
254
+ * Check trust-based constraints.
255
+ */
256
+ function checkTrustConstraint(
257
+ constraint: TrustConstraint,
258
+ fragment: ContextFragment
259
+ ): ConstraintViolation | null {
260
+ const trust = getFragmentTrust(fragment);
261
+
262
+ // Check minimum trust
263
+ if (trust < constraint.minimum_trust) {
264
+ return {
265
+ constraint,
266
+ reason: `Fragment trust ${trust} is below minimum ${constraint.minimum_trust}`,
267
+ severity: 'error',
268
+ };
269
+ }
270
+
271
+ // Check required attributions
272
+ if (constraint.required_attributions) {
273
+ const records = flattenChain(fragment.provenance);
274
+ const attributions = new Set(records.map(r => r.attribution));
275
+
276
+ for (const required of constraint.required_attributions) {
277
+ if (!attributions.has(required)) {
278
+ return {
279
+ constraint,
280
+ reason: `Missing required attribution: ${required}`,
281
+ severity: 'error',
282
+ };
283
+ }
284
+ }
285
+ }
286
+
287
+ return null;
288
+ }
289
+
290
+ /**
291
+ * Check signature-based constraints.
292
+ */
293
+ function checkSignatureConstraint(
294
+ constraint: SignatureConstraint,
295
+ fragment: ContextFragment,
296
+ _context: ResolutionContext
297
+ ): ConstraintViolation | null {
298
+ if (!constraint.required) {
299
+ return null;
300
+ }
301
+
302
+ // Check if any record in the chain has a signature
303
+ const records = flattenChain(fragment.provenance);
304
+ const signedRecords = records.filter(r => r.signature !== undefined);
305
+
306
+ if (signedRecords.length === 0) {
307
+ return {
308
+ constraint,
309
+ reason: 'No signatures found in provenance chain',
310
+ severity: 'error',
311
+ };
312
+ }
313
+
314
+ // If allowed_signers specified, verify at least one matches
315
+ if (constraint.allowed_signers && constraint.allowed_signers.length > 0) {
316
+ const allowedSignersSet = new Set(constraint.allowed_signers);
317
+ const hasAllowedSigner = signedRecords.some(r =>
318
+ r.signature !== undefined && allowedSignersSet.has(r.signature)
319
+ );
320
+
321
+ if (!hasAllowedSigner) {
322
+ return {
323
+ constraint,
324
+ reason: 'No signatures from allowed signers',
325
+ severity: 'error',
326
+ };
327
+ }
328
+ }
329
+
330
+ return null;
331
+ }
332
+
333
+ /**
334
+ * Check influence-based constraints (MCCA compliance).
335
+ */
336
+ function checkInfluenceConstraint(
337
+ constraint: InfluenceConstraint,
338
+ context: ResolutionContext
339
+ ): ConstraintViolation | null {
340
+ // First validate the constraint's budget itself
341
+ if (!isValidInfluenceBudget(constraint.budget)) {
342
+ return {
343
+ constraint,
344
+ reason: 'Constraint budget is not MCCA-compliant',
345
+ severity: constraint.enforcement === 'strict' ? 'error' : 'warning',
346
+ };
347
+ }
348
+
349
+ // If no current influence in context, we can't verify compliance
350
+ if (!context.current_influence) {
351
+ return {
352
+ constraint,
353
+ reason: 'No current influence budget in resolution context',
354
+ severity: 'warning',
355
+ };
356
+ }
357
+
358
+ const current = context.current_influence;
359
+ const budget = constraint.budget;
360
+ const violations: string[] = [];
361
+
362
+ // Check each MCCA bound
363
+ if (current.system < budget.system) {
364
+ violations.push(`system influence ${current.system.toFixed(2)} below required ${budget.system.toFixed(2)}`);
365
+ }
366
+ if (current.user > budget.user) {
367
+ violations.push(`user influence ${current.user.toFixed(2)} exceeds limit ${budget.user.toFixed(2)}`);
368
+ }
369
+ if (current.environment > budget.environment) {
370
+ violations.push(`environment influence ${current.environment.toFixed(2)} exceeds limit ${budget.environment.toFixed(2)}`);
371
+ }
372
+ if (current.assistant > budget.assistant) {
373
+ violations.push(`assistant influence ${current.assistant.toFixed(2)} exceeds limit ${budget.assistant.toFixed(2)}`);
374
+ }
375
+
376
+ if (violations.length > 0) {
377
+ return {
378
+ constraint,
379
+ reason: `MCCA violations: ${violations.join('; ')}`,
380
+ severity: constraint.enforcement === 'strict' ? 'error' : 'warning',
381
+ };
382
+ }
383
+
384
+ return null;
385
+ }
386
+
387
+ // ============================================================================
388
+ // BATCH RESOLUTION
389
+ // ============================================================================
390
+
391
+ /**
392
+ * Resolve multiple fragments at once.
393
+ * Returns only fragments that pass all constraints.
394
+ */
395
+ export async function resolveAll<T = unknown>(
396
+ fragments: readonly ContextFragment<T>[],
397
+ context: ResolutionContext = {}
398
+ ): Promise<{
399
+ resolved: ContextFragment<T>[];
400
+ rejected: Array<{ fragment: ContextFragment<T>; violations: ConstraintViolation[] }>;
401
+ }> {
402
+ const resolved: ContextFragment<T>[] = [];
403
+ const rejected: Array<{ fragment: ContextFragment<T>; violations: ConstraintViolation[] }> = [];
404
+
405
+ for (const fragment of fragments) {
406
+ const result = await resolve(fragment, context);
407
+
408
+ if (result.success && result.fragment) {
409
+ resolved.push(result.fragment);
410
+ } else {
411
+ rejected.push({ fragment, violations: [...result.violations] });
412
+ }
413
+ }
414
+
415
+ return { resolved, rejected };
416
+ }
417
+
418
+ /**
419
+ * Filter fragments by trust level.
420
+ */
421
+ export function filterByTrust<T>(
422
+ fragments: readonly ContextFragment<T>[],
423
+ minTrust: TrustLevel
424
+ ): ContextFragment<T>[] {
425
+ return fragments.filter(f => getFragmentTrust(f) >= minTrust);
426
+ }
427
+
428
+ /**
429
+ * Filter fragments by age.
430
+ */
431
+ export function filterByAge<T>(
432
+ fragments: readonly ContextFragment<T>[],
433
+ maxAgeMs: number
434
+ ): ContextFragment<T>[] {
435
+ return fragments.filter(f => getFragmentAge(f) <= maxAgeMs);
436
+ }
437
+
438
+ // ============================================================================
439
+ // UTILITY FUNCTIONS
440
+ // ============================================================================
441
+
442
+ /**
443
+ * Check if a domain matches a pattern.
444
+ * Supports wildcards: *.example.com matches sub.example.com
445
+ */
446
+ function domainMatches(domain: string, pattern: string): boolean {
447
+ if (pattern === '*') {
448
+ return true;
449
+ }
450
+
451
+ if (pattern.startsWith('*.')) {
452
+ const suffix = pattern.slice(1); // Remove the *
453
+ return domain.endsWith(suffix) || domain === pattern.slice(2);
454
+ }
455
+
456
+ return domain === pattern;
457
+ }
458
+
459
+ /**
460
+ * Format a duration in milliseconds to human readable string.
461
+ */
462
+ function formatDuration(ms: number): string {
463
+ if (ms < 1000) return `${ms}ms`;
464
+ if (ms < 60000) return `${Math.round(ms / 1000)}s`;
465
+ if (ms < 3600000) return `${Math.round(ms / 60000)}m`;
466
+ if (ms < 86400000) return `${Math.round(ms / 3600000)}h`;
467
+ return `${Math.round(ms / 86400000)}d`;
468
+ }
469
+
470
+ // ============================================================================
471
+ // CONSTRAINT BUILDERS
472
+ // ============================================================================
473
+
474
+ /**
475
+ * Create a max age constraint.
476
+ */
477
+ export function maxAge(duration: string | number): TimeConstraint {
478
+ const ms = typeof duration === 'number' ? duration : parseDuration(duration);
479
+ return { kind: 'time', max_age_ms: ms };
480
+ }
481
+
482
+ /**
483
+ * Create a not-before constraint.
484
+ */
485
+ export function notBefore(time: Date | string): TimeConstraint {
486
+ const ts = typeof time === 'string' ? time : time.toISOString();
487
+ return { kind: 'time', not_before: ts as Timestamp };
488
+ }
489
+
490
+ /**
491
+ * Create a not-after constraint.
492
+ */
493
+ export function notAfter(time: Date | string): TimeConstraint {
494
+ const ts = typeof time === 'string' ? time : time.toISOString();
495
+ return { kind: 'time', not_after: ts as Timestamp };
496
+ }
497
+
498
+ /**
499
+ * Create a scope constraint.
500
+ */
501
+ export function allowDomains(...domains: string[]): ScopeConstraint {
502
+ return { kind: 'scope', allowed_domains: domains };
503
+ }
504
+
505
+ /**
506
+ * Create a minimum trust constraint.
507
+ */
508
+ export function requireTrust(level: number): TrustConstraint {
509
+ return { kind: 'trust', minimum_trust: level as TrustLevel };
510
+ }
511
+
512
+ /**
513
+ * Create a signature requirement constraint.
514
+ */
515
+ export function requireSignature(allowedSigners?: string[]): SignatureConstraint {
516
+ return { kind: 'signature', required: true, allowed_signers: allowedSigners };
517
+ }
518
+
519
+ /**
520
+ * Create an influence budget constraint (MCCA compliance).
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * // Strict MCCA enforcement
525
+ * requireInfluence({ system: 0.45, user: 0.30, environment: 0.15, assistant: 0.10 })
526
+ *
527
+ * // Advisory mode (warnings only)
528
+ * requireInfluence({ system: 0.40, user: 0.35, environment: 0.20, assistant: 0.05 }, 'advisory')
529
+ * ```
530
+ */
531
+ export function requireInfluence(
532
+ budget: InfluenceBudget,
533
+ enforcement: 'strict' | 'advisory' = 'strict'
534
+ ): InfluenceConstraint {
535
+ return { kind: 'influence', budget, enforcement };
536
+ }
537
+
538
+ /**
539
+ * Create a default MCCA-compliant influence constraint.
540
+ */
541
+ export function requireMCCA(
542
+ overrides?: Partial<InfluenceBudget> | 'strict' | 'advisory',
543
+ enforcement: 'strict' | 'advisory' = 'strict'
544
+ ): InfluenceConstraint {
545
+ const defaults = {
546
+ system: 0.45,
547
+ user: 0.30,
548
+ environment: 0.15,
549
+ assistant: 0.10,
550
+ };
551
+
552
+ if (typeof overrides === 'string') {
553
+ return { kind: 'influence', budget: defaults, enforcement: overrides };
554
+ }
555
+
556
+ const budget = overrides ? { ...defaults, ...overrides } : defaults;
557
+ return { kind: 'influence', budget, enforcement };
558
+ }
559
+
560
+ /**
561
+ * Parse a duration string like "24h", "7d", "30m", "1w"
562
+ */
563
+ function parseDuration(duration: string): number {
564
+ const match = duration.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d|w)$/i);
565
+ if (!match || !match[1] || !match[2]) {
566
+ throw new Error(`Invalid duration format: ${duration}`);
567
+ }
568
+
569
+ const value = parseFloat(match[1]);
570
+ const unit = match[2].toLowerCase();
571
+
572
+ switch (unit) {
573
+ case 'ms': return value;
574
+ case 's': return value * 1000;
575
+ case 'm': return value * 60 * 1000;
576
+ case 'h': return value * 60 * 60 * 1000;
577
+ case 'd': return value * 24 * 60 * 60 * 1000;
578
+ case 'w': return value * 7 * 24 * 60 * 60 * 1000;
579
+ default: throw new Error(`Unknown duration unit: ${unit}`);
580
+ }
581
+ }