@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.
- package/IMPLEMENTATION_SUMMARY.json +150 -0
- package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
- package/README.md +98 -0
- package/TRANSACTION_IMPLEMENTATION.json +119 -0
- package/capability-map.md +93 -0
- package/docs/api-stability.md +269 -0
- package/docs/extensions/plugin-development.md +382 -0
- package/package.json +40 -0
- package/plugins/registry.json +35 -0
- package/src/admission-gate.mjs +414 -0
- package/src/api-version.mjs +373 -0
- package/src/atomic-admission.mjs +310 -0
- package/src/bounds.mjs +289 -0
- package/src/bulkhead-manager.mjs +280 -0
- package/src/capsule.mjs +524 -0
- package/src/crdt.mjs +361 -0
- package/src/enhanced-bounds.mjs +614 -0
- package/src/executor.mjs +73 -0
- package/src/freeze-restore.mjs +521 -0
- package/src/index.mjs +62 -0
- package/src/materialized-views.mjs +371 -0
- package/src/merge.mjs +472 -0
- package/src/plugin-isolation.mjs +392 -0
- package/src/plugin-manager.mjs +441 -0
- package/src/projections-api.mjs +336 -0
- package/src/projections-cli.mjs +238 -0
- package/src/projections-docs.mjs +300 -0
- package/src/projections-ide.mjs +278 -0
- package/src/receipt.mjs +340 -0
- package/src/rollback.mjs +258 -0
- package/src/saga-orchestrator.mjs +355 -0
- package/src/schemas.mjs +1330 -0
- package/src/storage-optimization.mjs +359 -0
- package/src/tool-registry.mjs +272 -0
- package/src/transaction.mjs +466 -0
- package/src/validators.mjs +485 -0
- package/src/work-item.mjs +449 -0
- package/templates/plugin-template/README.md +58 -0
- package/templates/plugin-template/index.mjs +162 -0
- package/templates/plugin-template/plugin.json +19 -0
- package/test/admission-gate.test.mjs +583 -0
- package/test/api-version.test.mjs +74 -0
- package/test/atomic-admission.test.mjs +155 -0
- package/test/bounds.test.mjs +341 -0
- package/test/bulkhead-manager.test.mjs +236 -0
- package/test/capsule.test.mjs +625 -0
- package/test/crdt.test.mjs +215 -0
- package/test/enhanced-bounds.test.mjs +487 -0
- package/test/freeze-restore.test.mjs +472 -0
- package/test/materialized-views.test.mjs +243 -0
- package/test/merge.test.mjs +665 -0
- package/test/plugin-isolation.test.mjs +109 -0
- package/test/plugin-manager.test.mjs +208 -0
- package/test/projections-api.test.mjs +293 -0
- package/test/projections-cli.test.mjs +204 -0
- package/test/projections-docs.test.mjs +173 -0
- package/test/projections-ide.test.mjs +230 -0
- package/test/receipt.test.mjs +295 -0
- package/test/rollback.test.mjs +132 -0
- package/test/saga-orchestrator.test.mjs +279 -0
- package/test/schemas.test.mjs +716 -0
- package/test/storage-optimization.test.mjs +503 -0
- package/test/tool-registry.test.mjs +341 -0
- package/test/transaction.test.mjs +189 -0
- package/test/validators.test.mjs +463 -0
- package/test/work-item.test.mjs +548 -0
- package/test/work-item.test.mjs.bak +548 -0
- package/var/kgc/test-atomic-log.json +519 -0
- package/var/kgc/test-cascading-log.json +145 -0
- 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
|
+
};
|