@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
package/src/crdt.mjs ADDED
@@ -0,0 +1,361 @@
1
+ /**
2
+ * @fileoverview CRDT (Conflict-free Replicated Data Types) for KGC Runtime
3
+ * Implements LWW-Register and OR-Set for semantic content merging
4
+ *
5
+ * Pattern: Pure functions + vector clocks + deterministic merge
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ // ============================================================================
11
+ // Schemas
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Timestamp schema - Lamport timestamp for causality
16
+ */
17
+ const TimestampSchema = z.object({
18
+ counter: z.number().int().nonnegative(),
19
+ actor: z.string(),
20
+ });
21
+
22
+ /**
23
+ * LWW-Register schema - Last Write Wins register
24
+ */
25
+ const LWWRegisterSchema = z.object({
26
+ value: z.any(),
27
+ timestamp: TimestampSchema,
28
+ });
29
+
30
+ /**
31
+ * OR-Set element schema
32
+ */
33
+ const ORSetElementSchema = z.object({
34
+ value: z.any(),
35
+ addTimestamp: TimestampSchema,
36
+ removeTimestamp: TimestampSchema.optional().nullable(),
37
+ });
38
+
39
+ /**
40
+ * OR-Set schema - Observed-Remove set
41
+ */
42
+ const ORSetSchema = z.object({
43
+ elements: z.array(ORSetElementSchema),
44
+ });
45
+
46
+ /**
47
+ * @typedef {z.infer<typeof LWWRegisterSchema>} LWWRegister
48
+ */
49
+
50
+ /**
51
+ * @typedef {z.infer<typeof ORSetSchema>} ORSet
52
+ */
53
+
54
+ /**
55
+ * @typedef {z.infer<typeof TimestampSchema>} Timestamp
56
+ */
57
+
58
+ // ============================================================================
59
+ // CRDT Operations
60
+ // ============================================================================
61
+
62
+ /**
63
+ * Create a Lamport timestamp
64
+ *
65
+ * @param {number} counter - Logical clock counter
66
+ * @param {string} actor - Actor ID
67
+ * @returns {Timestamp} Lamport timestamp
68
+ */
69
+ export function createTimestamp(counter, actor) {
70
+ return TimestampSchema.parse({ counter, actor });
71
+ }
72
+
73
+ /**
74
+ * Compare two timestamps (returns -1, 0, 1)
75
+ *
76
+ * @param {Timestamp} t1 - First timestamp
77
+ * @param {Timestamp} t2 - Second timestamp
78
+ * @returns {number} Comparison result
79
+ */
80
+ export function compareTimestamps(t1, t2) {
81
+ if (t1.counter !== t2.counter) {
82
+ return t1.counter - t2.counter;
83
+ }
84
+ return t1.actor.localeCompare(t2.actor);
85
+ }
86
+
87
+ // ============================================================================
88
+ // LWW-Register (Last Write Wins)
89
+ // ============================================================================
90
+
91
+ /**
92
+ * Create LWW-Register
93
+ *
94
+ * @param {any} value - Initial value
95
+ * @param {Timestamp} timestamp - Initial timestamp
96
+ * @returns {LWWRegister} LWW-Register
97
+ */
98
+ export function createLWWRegister(value, timestamp) {
99
+ return LWWRegisterSchema.parse({ value, timestamp });
100
+ }
101
+
102
+ /**
103
+ * Update LWW-Register value
104
+ *
105
+ * @param {LWWRegister} register - Register to update
106
+ * @param {any} newValue - New value
107
+ * @param {Timestamp} timestamp - Update timestamp
108
+ * @returns {LWWRegister} Updated register
109
+ */
110
+ export function updateLWWRegister(register, newValue, timestamp) {
111
+ const validated = LWWRegisterSchema.parse(register);
112
+
113
+ // Only update if new timestamp is later
114
+ if (compareTimestamps(timestamp, validated.timestamp) > 0) {
115
+ return createLWWRegister(newValue, timestamp);
116
+ }
117
+
118
+ return validated;
119
+ }
120
+
121
+ /**
122
+ * Merge two LWW-Registers (last write wins)
123
+ *
124
+ * @param {LWWRegister} r1 - First register
125
+ * @param {LWWRegister} r2 - Second register
126
+ * @returns {LWWRegister} Merged register
127
+ */
128
+ export function mergeLWWRegisters(r1, r2) {
129
+ const reg1 = LWWRegisterSchema.parse(r1);
130
+ const reg2 = LWWRegisterSchema.parse(r2);
131
+
132
+ // Compare timestamps - later wins
133
+ const cmp = compareTimestamps(reg1.timestamp, reg2.timestamp);
134
+
135
+ if (cmp > 0) {
136
+ return reg1;
137
+ } else if (cmp < 0) {
138
+ return reg2;
139
+ } else {
140
+ // Same timestamp - use deterministic tiebreaker (lexicographic on value)
141
+ const v1Str = JSON.stringify(reg1.value);
142
+ const v2Str = JSON.stringify(reg2.value);
143
+ return v1Str.localeCompare(v2Str) > 0 ? reg1 : reg2;
144
+ }
145
+ }
146
+
147
+ // ============================================================================
148
+ // OR-Set (Observed-Remove Set)
149
+ // ============================================================================
150
+
151
+ /**
152
+ * Create empty OR-Set
153
+ *
154
+ * @returns {ORSet} Empty OR-Set
155
+ */
156
+ export function createORSet() {
157
+ return ORSetSchema.parse({ elements: [] });
158
+ }
159
+
160
+ /**
161
+ * Add element to OR-Set
162
+ *
163
+ * @param {ORSet} set - OR-Set to modify
164
+ * @param {any} value - Value to add
165
+ * @param {Timestamp} timestamp - Add timestamp
166
+ * @returns {ORSet} Updated OR-Set
167
+ */
168
+ export function addToORSet(set, value, timestamp) {
169
+ const validated = ORSetSchema.parse(set);
170
+
171
+ // Add new element with timestamp
172
+ const newElement = ORSetElementSchema.parse({
173
+ value,
174
+ addTimestamp: timestamp,
175
+ });
176
+
177
+ return ORSetSchema.parse({
178
+ elements: [...validated.elements, newElement],
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Remove element from OR-Set
184
+ *
185
+ * @param {ORSet} set - OR-Set to modify
186
+ * @param {any} value - Value to remove
187
+ * @param {Timestamp} timestamp - Remove timestamp
188
+ * @returns {ORSet} Updated OR-Set
189
+ */
190
+ export function removeFromORSet(set, value, timestamp) {
191
+ const validated = ORSetSchema.parse(set);
192
+
193
+ // Mark elements as removed if they match value
194
+ const updatedElements = validated.elements.map(elem => {
195
+ const valueMatch = JSON.stringify(elem.value) === JSON.stringify(value);
196
+ if (valueMatch && !elem.removeTimestamp) {
197
+ return { ...elem, removeTimestamp: timestamp };
198
+ }
199
+ return elem;
200
+ });
201
+
202
+ return ORSetSchema.parse({ elements: updatedElements });
203
+ }
204
+
205
+ /**
206
+ * Get current values in OR-Set (not removed)
207
+ *
208
+ * @param {ORSet} set - OR-Set
209
+ * @returns {any[]} Active values
210
+ */
211
+ export function getORSetValues(set) {
212
+ const validated = ORSetSchema.parse(set);
213
+
214
+ // Return values that are not removed
215
+ return validated.elements
216
+ .filter(elem => !elem.removeTimestamp)
217
+ .map(elem => elem.value);
218
+ }
219
+
220
+ /**
221
+ * Merge two OR-Sets
222
+ *
223
+ * @param {ORSet} s1 - First OR-Set
224
+ * @param {ORSet} s2 - Second OR-Set
225
+ * @returns {ORSet} Merged OR-Set
226
+ */
227
+ export function mergeORSets(s1, s2) {
228
+ const set1 = ORSetSchema.parse(s1);
229
+ const set2 = ORSetSchema.parse(s2);
230
+
231
+ // Combine all elements
232
+ const allElements = [...set1.elements, ...set2.elements];
233
+
234
+ // Group by value
235
+ const valueMap = new Map();
236
+
237
+ for (const elem of allElements) {
238
+ const key = JSON.stringify(elem.value);
239
+
240
+ if (!valueMap.has(key)) {
241
+ valueMap.set(key, []);
242
+ }
243
+
244
+ valueMap.get(key).push(elem);
245
+ }
246
+
247
+ // Merge each group
248
+ const mergedElements = [];
249
+
250
+ for (const [, elems] of valueMap) {
251
+ // Keep all unique add timestamps
252
+ const addTimestamps = new Map();
253
+ for (const elem of elems) {
254
+ const key = `${elem.addTimestamp.counter}_${elem.addTimestamp.actor}`;
255
+ if (!addTimestamps.has(key)) {
256
+ addTimestamps.set(key, elem);
257
+ }
258
+ }
259
+
260
+ // Find latest remove timestamp
261
+ let latestRemove = null;
262
+ for (const elem of elems) {
263
+ if (elem.removeTimestamp) {
264
+ if (!latestRemove || compareTimestamps(elem.removeTimestamp, latestRemove) > 0) {
265
+ latestRemove = elem.removeTimestamp;
266
+ }
267
+ }
268
+ }
269
+
270
+ // Keep elements where add timestamp > remove timestamp
271
+ for (const [, elem] of addTimestamps) {
272
+ if (!latestRemove || compareTimestamps(elem.addTimestamp, latestRemove) > 0) {
273
+ mergedElements.push({
274
+ value: elem.value,
275
+ addTimestamp: elem.addTimestamp,
276
+ removeTimestamp: latestRemove,
277
+ });
278
+ } else {
279
+ // Element was removed after being added
280
+ mergedElements.push({
281
+ value: elem.value,
282
+ addTimestamp: elem.addTimestamp,
283
+ removeTimestamp: latestRemove,
284
+ });
285
+ }
286
+ }
287
+ }
288
+
289
+ return ORSetSchema.parse({ elements: mergedElements });
290
+ }
291
+
292
+ // ============================================================================
293
+ // Three-way Merge with Common Ancestor
294
+ // ============================================================================
295
+
296
+ /**
297
+ * Three-way merge for LWW-Registers
298
+ *
299
+ * @param {LWWRegister} ancestor - Common ancestor state
300
+ * @param {LWWRegister} left - Left branch
301
+ * @param {LWWRegister} right - Right branch
302
+ * @returns {LWWRegister} Merged state
303
+ */
304
+ export function threeWayMergeLWW(ancestor, left, right) {
305
+ // If both branches modified, use LWW merge
306
+ const ancestorValidated = LWWRegisterSchema.parse(ancestor);
307
+ const leftValidated = LWWRegisterSchema.parse(left);
308
+ const rightValidated = LWWRegisterSchema.parse(right);
309
+
310
+ // Check if either branch is unchanged from ancestor
311
+ const leftUnchanged = compareTimestamps(leftValidated.timestamp, ancestorValidated.timestamp) === 0;
312
+ const rightUnchanged = compareTimestamps(rightValidated.timestamp, ancestorValidated.timestamp) === 0;
313
+
314
+ if (leftUnchanged && rightUnchanged) {
315
+ // No changes - return ancestor
316
+ return ancestorValidated;
317
+ } else if (leftUnchanged) {
318
+ // Only right changed
319
+ return rightValidated;
320
+ } else if (rightUnchanged) {
321
+ // Only left changed
322
+ return leftValidated;
323
+ } else {
324
+ // Both changed - use LWW merge
325
+ return mergeLWWRegisters(leftValidated, rightValidated);
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Three-way merge for OR-Sets
331
+ *
332
+ * @param {ORSet} ancestor - Common ancestor state
333
+ * @param {ORSet} left - Left branch
334
+ * @param {ORSet} right - Right branch
335
+ * @returns {ORSet} Merged state
336
+ */
337
+ export function threeWayMergeORSet(ancestor, left, right) {
338
+ // Merge left and right branches
339
+ const merged = mergeORSets(left, right);
340
+
341
+ return merged;
342
+ }
343
+
344
+ // ============================================================================
345
+ // Exports
346
+ // ============================================================================
347
+
348
+ export default {
349
+ createTimestamp,
350
+ compareTimestamps,
351
+ createLWWRegister,
352
+ updateLWWRegister,
353
+ mergeLWWRegisters,
354
+ threeWayMergeLWW,
355
+ createORSet,
356
+ addToORSet,
357
+ removeFromORSet,
358
+ getORSetValues,
359
+ mergeORSets,
360
+ threeWayMergeORSet,
361
+ };