@unrdf/kgc-runtime 26.4.2

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 (70) hide show
  1. package/IMPLEMENTATION_SUMMARY.json +150 -0
  2. package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
  3. package/README.md +98 -0
  4. package/TRANSACTION_IMPLEMENTATION.json +119 -0
  5. package/capability-map.md +93 -0
  6. package/docs/api-stability.md +269 -0
  7. package/docs/extensions/plugin-development.md +382 -0
  8. package/package.json +40 -0
  9. package/plugins/registry.json +35 -0
  10. package/src/admission-gate.mjs +414 -0
  11. package/src/api-version.mjs +373 -0
  12. package/src/atomic-admission.mjs +310 -0
  13. package/src/bounds.mjs +289 -0
  14. package/src/bulkhead-manager.mjs +280 -0
  15. package/src/capsule.mjs +524 -0
  16. package/src/crdt.mjs +361 -0
  17. package/src/enhanced-bounds.mjs +614 -0
  18. package/src/executor.mjs +73 -0
  19. package/src/freeze-restore.mjs +521 -0
  20. package/src/index.mjs +62 -0
  21. package/src/materialized-views.mjs +371 -0
  22. package/src/merge.mjs +472 -0
  23. package/src/plugin-isolation.mjs +392 -0
  24. package/src/plugin-manager.mjs +441 -0
  25. package/src/projections-api.mjs +336 -0
  26. package/src/projections-cli.mjs +238 -0
  27. package/src/projections-docs.mjs +300 -0
  28. package/src/projections-ide.mjs +278 -0
  29. package/src/receipt.mjs +340 -0
  30. package/src/rollback.mjs +258 -0
  31. package/src/saga-orchestrator.mjs +355 -0
  32. package/src/schemas.mjs +1330 -0
  33. package/src/storage-optimization.mjs +359 -0
  34. package/src/tool-registry.mjs +272 -0
  35. package/src/transaction.mjs +466 -0
  36. package/src/validators.mjs +485 -0
  37. package/src/work-item.mjs +449 -0
  38. package/templates/plugin-template/README.md +58 -0
  39. package/templates/plugin-template/index.mjs +162 -0
  40. package/templates/plugin-template/plugin.json +19 -0
  41. package/test/admission-gate.test.mjs +583 -0
  42. package/test/api-version.test.mjs +74 -0
  43. package/test/atomic-admission.test.mjs +155 -0
  44. package/test/bounds.test.mjs +341 -0
  45. package/test/bulkhead-manager.test.mjs +236 -0
  46. package/test/capsule.test.mjs +625 -0
  47. package/test/crdt.test.mjs +215 -0
  48. package/test/enhanced-bounds.test.mjs +487 -0
  49. package/test/freeze-restore.test.mjs +472 -0
  50. package/test/materialized-views.test.mjs +243 -0
  51. package/test/merge.test.mjs +665 -0
  52. package/test/plugin-isolation.test.mjs +109 -0
  53. package/test/plugin-manager.test.mjs +208 -0
  54. package/test/projections-api.test.mjs +293 -0
  55. package/test/projections-cli.test.mjs +204 -0
  56. package/test/projections-docs.test.mjs +173 -0
  57. package/test/projections-ide.test.mjs +230 -0
  58. package/test/receipt.test.mjs +295 -0
  59. package/test/rollback.test.mjs +132 -0
  60. package/test/saga-orchestrator.test.mjs +279 -0
  61. package/test/schemas.test.mjs +716 -0
  62. package/test/storage-optimization.test.mjs +503 -0
  63. package/test/tool-registry.test.mjs +341 -0
  64. package/test/transaction.test.mjs +189 -0
  65. package/test/validators.test.mjs +463 -0
  66. package/test/work-item.test.mjs +548 -0
  67. package/test/work-item.test.mjs.bak +548 -0
  68. package/var/kgc/test-atomic-log.json +519 -0
  69. package/var/kgc/test-cascading-log.json +145 -0
  70. package/vitest.config.mjs +18 -0
@@ -0,0 +1,485 @@
1
+ /**
2
+ * @file Custom Validators - Advanced Zod validators for KGC runtime
3
+ * @module @unrdf/kgc-runtime/validators
4
+ *
5
+ * @description
6
+ * Custom validators using Zod .refine() and .superRefine():
7
+ * - Receipt chain integrity (parentHash linkage)
8
+ * - Temporal consistency (timestamps ordered)
9
+ * - Artifact hash validation (content integrity)
10
+ * - Dependency DAG validation (no cycles in WorkItem)
11
+ * - Async validators for external checks
12
+ *
13
+ * All validators return detailed error messages with remediation.
14
+ */
15
+
16
+ import { z } from 'zod';
17
+ import { blake3 } from 'hash-wasm';
18
+
19
+ // =============================================================================
20
+ // Receipt Chain Integrity Validator
21
+ // =============================================================================
22
+
23
+ /**
24
+ * Validates receipt chain integrity - each receipt's parentHash must match previous receipt's hash
25
+ *
26
+ * @param {Array<Object>} receipts - Receipt chain to validate
27
+ * @returns {boolean} True if chain is valid
28
+ *
29
+ * @example
30
+ * const receipts = [
31
+ * { id: '1', hash: 'abc123', parentHash: null },
32
+ * { id: '2', hash: 'def456', parentHash: 'abc123' }
33
+ * ];
34
+ * const isValid = validateReceiptChainIntegrity(receipts); // true
35
+ */
36
+ export function validateReceiptChainIntegrity(receipts) {
37
+ if (!Array.isArray(receipts) || receipts.length === 0) {
38
+ return true; // Empty chain is valid
39
+ }
40
+
41
+ // First receipt should have no parent
42
+ if (receipts[0].parentHash !== null && receipts[0].parentHash !== undefined) {
43
+ return false;
44
+ }
45
+
46
+ // Each subsequent receipt must reference previous
47
+ for (let i = 1; i < receipts.length; i++) {
48
+ const current = receipts[i];
49
+ const previous = receipts[i - 1];
50
+
51
+ if (current.parentHash !== previous.hash) {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ /**
60
+ * Receipt chain schema with integrity validation
61
+ */
62
+ export const ReceiptChainSchema = z
63
+ .array(
64
+ z.object({
65
+ id: z.string(),
66
+ hash: z.string(),
67
+ parentHash: z.string().nullable(),
68
+ timestamp: z.number().int().positive(),
69
+ operation: z.string(),
70
+ result: z.any().optional(),
71
+ })
72
+ )
73
+ .refine(validateReceiptChainIntegrity, {
74
+ message:
75
+ 'Receipt chain integrity violated: parentHash linkage broken. Ensure each receipt references the previous receipt\'s hash.',
76
+ });
77
+
78
+ // =============================================================================
79
+ // Temporal Consistency Validator
80
+ // =============================================================================
81
+
82
+ /**
83
+ * Validates temporal consistency - timestamps must be monotonically increasing
84
+ *
85
+ * @param {Array<Object>} items - Items with timestamps to validate
86
+ * @returns {boolean} True if timestamps are ordered
87
+ *
88
+ * @example
89
+ * const items = [
90
+ * { timestamp: 1000 },
91
+ * { timestamp: 2000 },
92
+ * { timestamp: 3000 }
93
+ * ];
94
+ * const isValid = validateTemporalConsistency(items); // true
95
+ */
96
+ export function validateTemporalConsistency(items) {
97
+ if (!Array.isArray(items) || items.length <= 1) {
98
+ return true; // Single item or empty is always consistent
99
+ }
100
+
101
+ for (let i = 1; i < items.length; i++) {
102
+ const current = items[i];
103
+ const previous = items[i - 1];
104
+
105
+ if (current.timestamp <= previous.timestamp) {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Temporally ordered events schema
115
+ */
116
+ export const TemporallyOrderedSchema = z
117
+ .array(
118
+ z.object({
119
+ timestamp: z.number().int().positive(),
120
+ }).passthrough()
121
+ )
122
+ .refine(validateTemporalConsistency, {
123
+ message:
124
+ 'Temporal consistency violated: timestamps must be monotonically increasing. Check for clock skew or ordering issues.',
125
+ });
126
+
127
+ // =============================================================================
128
+ // Artifact Hash Validator
129
+ // =============================================================================
130
+
131
+ /**
132
+ * Validates artifact content integrity by recomputing hash
133
+ *
134
+ * @param {Object} artifact - Artifact with content and hash
135
+ * @returns {Promise<boolean>} True if hash matches content
136
+ *
137
+ * @example
138
+ * const artifact = {
139
+ * content: 'Hello World',
140
+ * hash: await blake3('Hello World')
141
+ * };
142
+ * const isValid = await validateArtifactHash(artifact); // true
143
+ */
144
+ export async function validateArtifactHash(artifact) {
145
+ if (!artifact.content || !artifact.hash) {
146
+ return true; // No content or hash means nothing to validate
147
+ }
148
+
149
+ const computedHash = await blake3(artifact.content);
150
+ return computedHash === artifact.hash;
151
+ }
152
+
153
+ /**
154
+ * Artifact schema with hash validation
155
+ */
156
+ export const ArtifactSchema = z.object({
157
+ type: z.enum(['file', 'directory', 'url', 'inline', 'proof', 'receipt']),
158
+ path: z.string().optional(),
159
+ content: z.string().optional(),
160
+ hash: z.string().optional(),
161
+ size: z.number().nonnegative().optional(),
162
+ metadata: z.record(z.string(), z.any()).optional(),
163
+ }).refine(
164
+ async (artifact) => await validateArtifactHash(artifact),
165
+ {
166
+ message:
167
+ 'Artifact hash validation failed: computed hash does not match stored hash. Content may be corrupted or tampered.',
168
+ }
169
+ );
170
+
171
+ // =============================================================================
172
+ // Dependency DAG Validator (No Cycles)
173
+ // =============================================================================
174
+
175
+ /**
176
+ * Detects cycles in dependency graph using DFS
177
+ *
178
+ * @param {Map<string, string[]>} graph - Adjacency list representation
179
+ * @returns {Object} { hasCycle: boolean, cycle: string[] }
180
+ *
181
+ * @example
182
+ * const graph = new Map([
183
+ * ['A', ['B', 'C']],
184
+ * ['B', ['D']],
185
+ * ['C', ['D']],
186
+ * ['D', []]
187
+ * ]);
188
+ * const result = detectCycle(graph); // { hasCycle: false, cycle: [] }
189
+ */
190
+ export function detectCycle(graph) {
191
+ const visited = new Set();
192
+ const recStack = new Set();
193
+ const path = [];
194
+
195
+ function dfs(node) {
196
+ visited.add(node);
197
+ recStack.add(node);
198
+ path.push(node);
199
+
200
+ const neighbors = graph.get(node) || [];
201
+ for (const neighbor of neighbors) {
202
+ if (!visited.has(neighbor)) {
203
+ const result = dfs(neighbor);
204
+ if (result.hasCycle) {
205
+ return result;
206
+ }
207
+ } else if (recStack.has(neighbor)) {
208
+ // Cycle detected
209
+ const cycleStart = path.indexOf(neighbor);
210
+ const cycle = path.slice(cycleStart);
211
+ cycle.push(neighbor); // Complete the cycle
212
+ return { hasCycle: true, cycle };
213
+ }
214
+ }
215
+
216
+ recStack.delete(node);
217
+ path.pop();
218
+ return { hasCycle: false, cycle: [] };
219
+ }
220
+
221
+ for (const node of graph.keys()) {
222
+ if (!visited.has(node)) {
223
+ const result = dfs(node);
224
+ if (result.hasCycle) {
225
+ return result;
226
+ }
227
+ }
228
+ }
229
+
230
+ return { hasCycle: false, cycle: [] };
231
+ }
232
+
233
+ /**
234
+ * Validates WorkItem dependency graph has no cycles
235
+ *
236
+ * @param {Array<Object>} workItems - Work items with dependencies
237
+ * @returns {Object} { valid: boolean, cycle: string[] }
238
+ *
239
+ * @example
240
+ * const workItems = [
241
+ * { id: 'A', dependencies: ['B'] },
242
+ * { id: 'B', dependencies: ['C'] },
243
+ * { id: 'C', dependencies: [] }
244
+ * ];
245
+ * const result = validateDependencyDAG(workItems); // { valid: true, cycle: [] }
246
+ */
247
+ export function validateDependencyDAG(workItems) {
248
+ if (!Array.isArray(workItems) || workItems.length === 0) {
249
+ return { valid: true, cycle: [] };
250
+ }
251
+
252
+ // Build adjacency list
253
+ const graph = new Map();
254
+ const itemIds = new Set();
255
+
256
+ for (const item of workItems) {
257
+ itemIds.add(item.id);
258
+ graph.set(item.id, item.dependencies || []);
259
+ }
260
+
261
+ // Validate all dependencies reference valid items
262
+ for (const [id, deps] of graph.entries()) {
263
+ for (const dep of deps) {
264
+ if (!itemIds.has(dep)) {
265
+ return {
266
+ valid: false,
267
+ cycle: [],
268
+ error: `WorkItem ${id} references non-existent dependency ${dep}`,
269
+ };
270
+ }
271
+ }
272
+ }
273
+
274
+ // Detect cycles
275
+ const result = detectCycle(graph);
276
+
277
+ return {
278
+ valid: !result.hasCycle,
279
+ cycle: result.cycle,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * WorkItem dependency schema with cycle detection
285
+ */
286
+ export const WorkItemDependencySchema = z
287
+ .array(
288
+ z.object({
289
+ id: z.string(),
290
+ dependencies: z.array(z.string()).optional(),
291
+ }).passthrough()
292
+ )
293
+ .superRefine((workItems, ctx) => {
294
+ const result = validateDependencyDAG(workItems);
295
+
296
+ if (!result.valid) {
297
+ if (result.error) {
298
+ ctx.addIssue({
299
+ code: z.ZodIssueCode.custom,
300
+ message: result.error,
301
+ });
302
+ } else if (result.cycle.length > 0) {
303
+ ctx.addIssue({
304
+ code: z.ZodIssueCode.custom,
305
+ message: `Dependency cycle detected: ${result.cycle.join(' -> ')}. Remove circular dependencies to form a valid DAG.`,
306
+ });
307
+ }
308
+ }
309
+ });
310
+
311
+ // =============================================================================
312
+ // Async Validators
313
+ // =============================================================================
314
+
315
+ /**
316
+ * Async policy validator - checks against external policy service
317
+ *
318
+ * @param {Object} operation - Operation to validate
319
+ * @param {Function} policyCheck - Async policy check function
320
+ * @returns {Promise<boolean>} True if policy allows operation
321
+ *
322
+ * @example
323
+ * const policyCheck = async (op) => {
324
+ * // Simulate external API call
325
+ * await new Promise(resolve => setTimeout(resolve, 10));
326
+ * return op.type === 'safe_operation';
327
+ * };
328
+ *
329
+ * const operation = { type: 'safe_operation', data: 'test' };
330
+ * const isAllowed = await validateAsyncPolicy(operation, policyCheck); // true
331
+ */
332
+ export async function validateAsyncPolicy(operation, policyCheck) {
333
+ if (typeof policyCheck !== 'function') {
334
+ throw new Error('policyCheck must be a function');
335
+ }
336
+
337
+ try {
338
+ const result = await policyCheck(operation);
339
+ return Boolean(result);
340
+ } catch (error) {
341
+ // Policy check failure = deny
342
+ return false;
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Creates an async policy-validated schema
348
+ *
349
+ * @param {Function} policyCheck - Async policy check function
350
+ * @returns {z.ZodObject} Schema with async validation
351
+ *
352
+ * @example
353
+ * const policyCheck = async (op) => op.type !== 'forbidden';
354
+ * const schema = createAsyncPolicySchema(policyCheck);
355
+ * const result = await schema.parseAsync({ type: 'allowed', data: 'test' });
356
+ */
357
+ export function createAsyncPolicySchema(policyCheck) {
358
+ return z
359
+ .object({
360
+ type: z.string(),
361
+ })
362
+ .passthrough()
363
+ .refine(
364
+ async (operation) => await validateAsyncPolicy(operation, policyCheck),
365
+ {
366
+ message:
367
+ 'Operation denied by policy: external policy service rejected this operation. Review policy constraints and retry.',
368
+ }
369
+ );
370
+ }
371
+
372
+ // =============================================================================
373
+ // Cross-Field Validators
374
+ // =============================================================================
375
+
376
+ /**
377
+ * Validates that endTime > startTime for run capsules
378
+ *
379
+ * @param {Object} capsule - Run capsule with time fields
380
+ * @returns {boolean} True if time ordering is valid
381
+ */
382
+ export function validateTimeRange(capsule) {
383
+ if (!capsule.startTime || !capsule.endTime) {
384
+ return true; // If either is missing, let other validators handle it
385
+ }
386
+
387
+ return capsule.endTime > capsule.startTime;
388
+ }
389
+
390
+ /**
391
+ * Run capsule schema with time range validation
392
+ */
393
+ export const RunCapsuleTimeRangeSchema = z
394
+ .object({
395
+ startTime: z.number().int().positive(),
396
+ endTime: z.number().int().positive().nullable(),
397
+ })
398
+ .passthrough()
399
+ .refine(validateTimeRange, {
400
+ message:
401
+ 'Invalid time range: endTime must be greater than startTime. Check for clock issues or logic errors.',
402
+ });
403
+
404
+ // =============================================================================
405
+ // Validator Utilities
406
+ // =============================================================================
407
+
408
+ /**
409
+ * Combines multiple validators with detailed error reporting
410
+ *
411
+ * @param {Array<Function>} validators - Array of validator functions
412
+ * @returns {Function} Combined validator function
413
+ *
414
+ * @example
415
+ * const validator = combineValidators([
416
+ * (data) => data.value > 0 || 'Value must be positive',
417
+ * (data) => data.value < 100 || 'Value must be less than 100'
418
+ * ]);
419
+ *
420
+ * const result = validator({ value: 50 });
421
+ * console.log(result); // { valid: true, errors: [] }
422
+ */
423
+ export function combineValidators(validators) {
424
+ return (data) => {
425
+ const errors = [];
426
+
427
+ for (const validator of validators) {
428
+ const result = validator(data);
429
+ if (typeof result === 'string') {
430
+ errors.push(result);
431
+ } else if (result === false) {
432
+ errors.push('Validation failed');
433
+ }
434
+ }
435
+
436
+ return {
437
+ valid: errors.length === 0,
438
+ errors,
439
+ };
440
+ };
441
+ }
442
+
443
+ /**
444
+ * Creates a detailed validation result object
445
+ *
446
+ * @param {boolean} valid - Whether validation passed
447
+ * @param {string} message - Error message if validation failed
448
+ * @param {Object} context - Additional context
449
+ * @returns {Object} Validation result
450
+ */
451
+ export function createValidationResult(valid, message = '', context = {}) {
452
+ return {
453
+ valid,
454
+ message,
455
+ context,
456
+ timestamp: Date.now(),
457
+ };
458
+ }
459
+
460
+ // =============================================================================
461
+ // Module Exports
462
+ // =============================================================================
463
+
464
+ export default {
465
+ // Validators
466
+ validateReceiptChainIntegrity,
467
+ validateTemporalConsistency,
468
+ validateArtifactHash,
469
+ validateDependencyDAG,
470
+ validateAsyncPolicy,
471
+ validateTimeRange,
472
+
473
+ // Schemas
474
+ ReceiptChainSchema,
475
+ TemporallyOrderedSchema,
476
+ ArtifactSchema,
477
+ WorkItemDependencySchema,
478
+ RunCapsuleTimeRangeSchema,
479
+
480
+ // Utilities
481
+ detectCycle,
482
+ createAsyncPolicySchema,
483
+ combineValidators,
484
+ createValidationResult,
485
+ };