@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,35 @@
1
+ {
2
+ "name": "KGC Plugin Registry",
3
+ "version": "1.0.0",
4
+ "description": "Official registry for KGC Runtime plugins",
5
+ "updated": "2024-12-27",
6
+ "plugins": [
7
+ {
8
+ "name": "example-receipt",
9
+ "version": "1.0.0",
10
+ "description": "Example custom receipt plugin",
11
+ "author": "UNRDF Team",
12
+ "license": "MIT",
13
+ "entryPoint": "./example-plugin.mjs",
14
+ "capabilities": [
15
+ "receipt:generate",
16
+ "receipt:validate",
17
+ "custom:example"
18
+ ],
19
+ "api_version": "5.0.1",
20
+ "repository": "https://github.com/unrdf/kgc-plugins",
21
+ "tags": ["receipt", "example"],
22
+ "verified": true
23
+ }
24
+ ],
25
+ "categories": {
26
+ "receipt": "Custom receipt types and validation",
27
+ "schema": "Schema validation and transformation",
28
+ "tools": "Tool registry extensions",
29
+ "governance": "Governance policy plugins"
30
+ },
31
+ "metadata": {
32
+ "total_plugins": 1,
33
+ "last_updated": "2024-12-27T00:00:00Z"
34
+ }
35
+ }
@@ -0,0 +1,414 @@
1
+ /**
2
+ * KGC 4D Admission Gate & Receipt Chain System
3
+ * Implements cryptographic receipts with BLAKE3 hashing and Merkle root computation
4
+ *
5
+ * Pattern: Pure functions + Zod validation + BLAKE3 for deterministic hashing
6
+ * Receipts are immutable and form a tamper-evident chain
7
+ */
8
+
9
+ import { blake3 } from 'hash-wasm';
10
+ import { z } from 'zod';
11
+
12
+ // ============================================================================
13
+ // SCHEMAS & TYPES
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Bounds schema - resource limits for operations
18
+ */
19
+ const BoundsSchema = z.object({
20
+ maxTriples: z.number().int().positive().optional(),
21
+ maxMemoryMB: z.number().positive().optional(),
22
+ maxTimeMs: z.number().positive().optional(),
23
+ maxQueries: z.number().int().positive().optional(),
24
+ });
25
+
26
+ /**
27
+ * Delta schema - represents a change to be admitted
28
+ */
29
+ const DeltaSchema = z.object({
30
+ operation: z.enum(['add', 'delete', 'query', 'update']),
31
+ triples: z.array(z.any()).optional(),
32
+ query: z.string().optional(),
33
+ metadata: z.any().optional(),
34
+ });
35
+
36
+ /**
37
+ * Predicate schema - validation rules for admission
38
+ */
39
+ const PredicateSchema = z.object({
40
+ name: z.string(),
41
+ check: z.custom((val) => typeof val === 'function', {
42
+ message: 'check must be a function',
43
+ }),
44
+ description: z.string().optional(),
45
+ });
46
+
47
+ /**
48
+ * Receipt schema - immutable record of admission decision
49
+ */
50
+ const ReceiptSchema = z.object({
51
+ id: z.string(),
52
+ timestamp_ns: z.bigint(),
53
+ operation: z.string(),
54
+ result: z.enum(['admit', 'deny']),
55
+ reason: z.string(),
56
+ bounds_used: z.any(),
57
+ parent_receipt_id: z.string().nullable(),
58
+ hash: z.string(),
59
+ });
60
+
61
+ // ============================================================================
62
+ // ADMISSION GATE CLASS
63
+ // ============================================================================
64
+
65
+ /**
66
+ * AdmissionGate - Controls admission of operations with receipt generation
67
+ *
68
+ * Features:
69
+ * - BLAKE3 cryptographic hashing for receipts
70
+ * - Merkle tree receipt chaining
71
+ * - Bounds enforcement (resource limits)
72
+ * - Forbidden operation detection
73
+ * - Preserve(Q) invariant checking
74
+ * - Deterministic batch anchoring
75
+ *
76
+ * @example
77
+ * import { AdmissionGate } from './admission-gate.mjs';
78
+ *
79
+ * const gate = new AdmissionGate();
80
+ * const delta = { operation: 'add', triples: [{ s: 'a', p: 'b', o: 'c' }] };
81
+ * const bounds = { maxTriples: 1000 };
82
+ * const predicates = [];
83
+ *
84
+ * const receipt = await gate.admit(delta, bounds, predicates);
85
+ * console.log(receipt.result); // 'admit' or 'deny'
86
+ */
87
+ export class AdmissionGate {
88
+ /**
89
+ * @param {Object} options - Configuration options
90
+ * @param {string[]} options.forbiddenOperations - Operations that require receipts
91
+ * @param {Function} options.timeSource - Custom time source (for testing)
92
+ */
93
+ constructor(options = {}) {
94
+ /** @type {Receipt[]} */
95
+ this.receiptChain = [];
96
+
97
+ /** @type {Set<string>} */
98
+ this.forbiddenOperations = new Set(options.forbiddenOperations || ['update', 'delete']);
99
+
100
+ /** @type {Function} */
101
+ this.timeSource = options.timeSource || (() => process.hrtime.bigint());
102
+
103
+ /** @type {Map<string, any>} */
104
+ this.preservedQueries = new Map();
105
+ }
106
+
107
+ /**
108
+ * Admit a delta (operation) with bounds and predicate checks
109
+ *
110
+ * @param {Object} delta - The operation to admit
111
+ * @param {Object} bounds - Resource bounds/limits
112
+ * @param {Array<Object>} predicates - Validation predicates
113
+ * @returns {Promise<Object>} Receipt of admission decision
114
+ *
115
+ * @example
116
+ * const receipt = await gate.admit(
117
+ * { operation: 'add', triples: [{s: 'a', p: 'b', o: 'c'}] },
118
+ * { maxTriples: 1000 },
119
+ * []
120
+ * );
121
+ */
122
+ async admit(delta, bounds = {}, predicates = []) {
123
+ // Validate inputs
124
+ const validatedDelta = DeltaSchema.parse(delta);
125
+ const validatedBounds = BoundsSchema.parse(bounds);
126
+ const validatedPredicates = z.array(PredicateSchema).parse(predicates);
127
+
128
+ // Check forbidden operations
129
+ if (this.forbiddenOperations.has(validatedDelta.operation)) {
130
+ const hasValidReceipt = this._hasValidReceiptForOperation(validatedDelta.operation);
131
+ if (!hasValidReceipt) {
132
+ return this._createReceipt(validatedDelta, 'deny', `Forbidden operation '${validatedDelta.operation}' without valid receipt`, validatedBounds);
133
+ }
134
+ }
135
+
136
+ // Check bounds
137
+ const boundsViolation = this._checkBounds(validatedDelta, validatedBounds);
138
+ if (boundsViolation) {
139
+ return this._createReceipt(validatedDelta, 'deny', boundsViolation, validatedBounds);
140
+ }
141
+
142
+ // Check predicates
143
+ for (const predicate of validatedPredicates) {
144
+ try {
145
+ const passed = predicate.check(validatedDelta);
146
+ if (!passed) {
147
+ return this._createReceipt(
148
+ validatedDelta,
149
+ 'deny',
150
+ `Predicate '${predicate.name}' failed: ${predicate.description || 'validation failed'}`,
151
+ validatedBounds
152
+ );
153
+ }
154
+ } catch (error) {
155
+ return this._createReceipt(validatedDelta, 'deny', `Predicate '${predicate.name}' threw error: ${error.message}`, validatedBounds);
156
+ }
157
+ }
158
+
159
+ // Check Preserve(Q) invariant for queries
160
+ if (validatedDelta.operation === 'query' && validatedDelta.query) {
161
+ const preserved = this._checkPreserveInvariant(validatedDelta.query);
162
+ if (!preserved) {
163
+ return this._createReceipt(validatedDelta, 'deny', `Preserve(Q) invariant violated for query`, validatedBounds);
164
+ }
165
+ }
166
+
167
+ // All checks passed - admit the operation
168
+ return this._createReceipt(validatedDelta, 'admit', 'All checks passed', validatedBounds);
169
+ }
170
+
171
+ /**
172
+ * Check if operation has valid receipt in chain
173
+ * @private
174
+ */
175
+ _hasValidReceiptForOperation(operation) {
176
+ // For now, we allow if there's ANY admitted operation in the chain
177
+ // In production, you'd check for specific authorization receipts
178
+ return this.receiptChain.some((receipt) => receipt.result === 'admit' && receipt.operation === operation);
179
+ }
180
+
181
+ /**
182
+ * Check bounds violations
183
+ * @private
184
+ */
185
+ _checkBounds(delta, bounds) {
186
+ if (bounds.maxTriples && delta.triples && delta.triples.length > bounds.maxTriples) {
187
+ return `Triple count ${delta.triples.length} exceeds bound ${bounds.maxTriples}`;
188
+ }
189
+
190
+ if (bounds.maxQueries && delta.operation === 'query') {
191
+ const queryCount = this.receiptChain.filter((r) => r.operation === 'query').length;
192
+ if (queryCount >= bounds.maxQueries) {
193
+ return `Query count ${queryCount + 1} exceeds bound ${bounds.maxQueries}`;
194
+ }
195
+ }
196
+
197
+ // Memory and time bounds would be checked during execution
198
+ // Here we just validate the structure
199
+ return null;
200
+ }
201
+
202
+ /**
203
+ * Check Preserve(Q) invariant - query results should remain unchanged
204
+ * @private
205
+ */
206
+ _checkPreserveInvariant(query) {
207
+ if (!this.preservedQueries.has(query)) {
208
+ // First time seeing this query - preserve its result
209
+ this.preservedQueries.set(query, { timestamp: this.timeSource(), count: 1 });
210
+ return true;
211
+ }
212
+
213
+ // Query has been seen before - check if it's still valid
214
+ // In a real implementation, you'd re-execute and compare results
215
+ // For now, we just track that it's preserved
216
+ const preserved = this.preservedQueries.get(query);
217
+ preserved.count += 1;
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * Create a receipt with BLAKE3 hash and chain it
223
+ * @private
224
+ */
225
+ async _createReceipt(delta, result, reason, bounds) {
226
+ const timestamp = this.timeSource();
227
+ const parentId = this.receiptChain.length > 0 ? this.receiptChain[this.receiptChain.length - 1].id : null;
228
+
229
+ // Create receipt ID from timestamp + operation
230
+ const id = `receipt_${timestamp}_${delta.operation}`;
231
+
232
+ // Compute BLAKE3 hash of receipt content (excluding hash field itself)
233
+ const receiptContent = {
234
+ id,
235
+ timestamp_ns: timestamp.toString(),
236
+ operation: delta.operation,
237
+ result,
238
+ reason,
239
+ bounds_used: bounds,
240
+ parent_receipt_id: parentId,
241
+ };
242
+
243
+ const hash = await blake3(JSON.stringify(receiptContent));
244
+
245
+ const receipt = {
246
+ id,
247
+ timestamp_ns: timestamp,
248
+ operation: delta.operation,
249
+ result,
250
+ reason,
251
+ bounds_used: bounds,
252
+ parent_receipt_id: parentId,
253
+ hash,
254
+ };
255
+
256
+ // Validate receipt
257
+ ReceiptSchema.parse(receipt);
258
+
259
+ // Add to chain
260
+ this.receiptChain.push(receipt);
261
+
262
+ return receipt;
263
+ }
264
+
265
+ /**
266
+ * Verify the integrity of a receipt chain using Merkle root computation
267
+ *
268
+ * @param {Array<Object>} receipts - Array of receipts to verify
269
+ * @returns {Promise<boolean>} True if chain is valid
270
+ *
271
+ * @example
272
+ * const isValid = await gate.verifyChain(gate.receiptChain);
273
+ * console.assert(isValid === true, 'Chain should be valid');
274
+ */
275
+ async verifyChain(receipts) {
276
+ if (!Array.isArray(receipts) || receipts.length === 0) {
277
+ return false;
278
+ }
279
+
280
+ // Verify each receipt's hash
281
+ for (const receipt of receipts) {
282
+ const { hash: storedHash, ...contentWithoutHash } = receipt;
283
+
284
+ // Convert BigInt to string for hashing
285
+ const hashableContent = {
286
+ ...contentWithoutHash,
287
+ timestamp_ns: contentWithoutHash.timestamp_ns.toString(),
288
+ };
289
+
290
+ const computedHash = await blake3(JSON.stringify(hashableContent));
291
+
292
+ if (computedHash !== storedHash) {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ // Verify chain linkage - each receipt should reference previous
298
+ for (let i = 1; i < receipts.length; i++) {
299
+ const current = receipts[i];
300
+ const previous = receipts[i - 1];
301
+
302
+ if (current.parent_receipt_id !== previous.id) {
303
+ return false;
304
+ }
305
+
306
+ // Verify monotonic timestamps
307
+ if (current.timestamp_ns <= previous.timestamp_ns) {
308
+ return false;
309
+ }
310
+ }
311
+
312
+ return true;
313
+ }
314
+
315
+ /**
316
+ * Compute Merkle root of receipt chain
317
+ *
318
+ * @param {Array<Object>} receipts - Receipts to compute root for
319
+ * @returns {Promise<string>} Merkle root hash
320
+ *
321
+ * @example
322
+ * const root = await gate.computeMerkleRoot(gate.receiptChain);
323
+ * console.log('Merkle root:', root);
324
+ */
325
+ async computeMerkleRoot(receipts) {
326
+ if (!Array.isArray(receipts) || receipts.length === 0) {
327
+ return await blake3('');
328
+ }
329
+
330
+ if (receipts.length === 1) {
331
+ return receipts[0].hash;
332
+ }
333
+
334
+ // Build Merkle tree bottom-up
335
+ let currentLevel = receipts.map((r) => r.hash);
336
+
337
+ while (currentLevel.length > 1) {
338
+ const nextLevel = [];
339
+
340
+ for (let i = 0; i < currentLevel.length; i += 2) {
341
+ if (i + 1 < currentLevel.length) {
342
+ // Hash pair
343
+ const combined = currentLevel[i] + currentLevel[i + 1];
344
+ nextLevel.push(await blake3(combined));
345
+ } else {
346
+ // Odd one out - promote to next level
347
+ nextLevel.push(currentLevel[i]);
348
+ }
349
+ }
350
+
351
+ currentLevel = nextLevel;
352
+ }
353
+
354
+ return currentLevel[0];
355
+ }
356
+
357
+ /**
358
+ * Batch anchor multiple operations deterministically
359
+ *
360
+ * @param {Array<Object>} deltas - Operations to anchor
361
+ * @param {Object} bounds - Shared bounds for all operations
362
+ * @returns {Promise<Object>} Batch receipt with Merkle root
363
+ *
364
+ * @example
365
+ * const batch = await gate.batchAnchor([delta1, delta2], { maxTriples: 1000 });
366
+ * console.log('Batch Merkle root:', batch.merkle_root);
367
+ */
368
+ async batchAnchor(deltas, bounds = {}) {
369
+ if (!Array.isArray(deltas) || deltas.length === 0) {
370
+ throw new Error('batchAnchor requires non-empty array of deltas');
371
+ }
372
+
373
+ // Sort deltas deterministically by operation type for consistency
374
+ const sortedDeltas = [...deltas].sort((a, b) => a.operation.localeCompare(b.operation));
375
+
376
+ // Admit all operations
377
+ const receipts = [];
378
+ for (const delta of sortedDeltas) {
379
+ const receipt = await this.admit(delta, bounds, []);
380
+ receipts.push(receipt);
381
+ }
382
+
383
+ // Compute Merkle root
384
+ const merkleRoot = await this.computeMerkleRoot(receipts);
385
+
386
+ // Create batch receipt
387
+ const batchReceipt = {
388
+ type: 'batch',
389
+ timestamp_ns: this.timeSource(),
390
+ operation_count: receipts.length,
391
+ merkle_root: merkleRoot,
392
+ receipts,
393
+ all_admitted: receipts.every((r) => r.result === 'admit'),
394
+ };
395
+
396
+ return batchReceipt;
397
+ }
398
+
399
+ /**
400
+ * Get current receipt chain
401
+ * @returns {Array<Object>} Current receipt chain
402
+ */
403
+ getReceiptChain() {
404
+ return [...this.receiptChain];
405
+ }
406
+
407
+ /**
408
+ * Clear receipt chain (for testing)
409
+ */
410
+ clearReceiptChain() {
411
+ this.receiptChain = [];
412
+ this.preservedQueries.clear();
413
+ }
414
+ }