@unrdf/knowledge-engine 5.0.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +64 -0
  4. package/src/browser-shims.mjs +343 -0
  5. package/src/browser.mjs +910 -0
  6. package/src/canonicalize.mjs +414 -0
  7. package/src/condition-cache.mjs +109 -0
  8. package/src/condition-evaluator.mjs +722 -0
  9. package/src/dark-matter-core.mjs +742 -0
  10. package/src/define-hook.mjs +213 -0
  11. package/src/effect-sandbox-browser.mjs +283 -0
  12. package/src/effect-sandbox-worker.mjs +170 -0
  13. package/src/effect-sandbox.mjs +517 -0
  14. package/src/engines/index.mjs +11 -0
  15. package/src/engines/rdf-engine.mjs +299 -0
  16. package/src/file-resolver.mjs +387 -0
  17. package/src/hook-executor-batching.mjs +277 -0
  18. package/src/hook-executor.mjs +870 -0
  19. package/src/hook-management.mjs +150 -0
  20. package/src/index.mjs +93 -0
  21. package/src/ken-parliment.mjs +119 -0
  22. package/src/ken.mjs +149 -0
  23. package/src/knowledge-engine/builtin-rules.mjs +190 -0
  24. package/src/knowledge-engine/inference-engine.mjs +418 -0
  25. package/src/knowledge-engine/knowledge-engine.mjs +317 -0
  26. package/src/knowledge-engine/pattern-dsl.mjs +142 -0
  27. package/src/knowledge-engine/pattern-matcher.mjs +215 -0
  28. package/src/knowledge-engine/rules.mjs +184 -0
  29. package/src/knowledge-engine.mjs +319 -0
  30. package/src/knowledge-hook-engine.mjs +360 -0
  31. package/src/knowledge-hook-manager.mjs +469 -0
  32. package/src/knowledge-substrate-core.mjs +927 -0
  33. package/src/lite.mjs +222 -0
  34. package/src/lockchain-writer-browser.mjs +414 -0
  35. package/src/lockchain-writer.mjs +602 -0
  36. package/src/monitoring/andon-signals.mjs +775 -0
  37. package/src/observability.mjs +531 -0
  38. package/src/parse.mjs +290 -0
  39. package/src/performance-optimizer.mjs +678 -0
  40. package/src/policy-pack.mjs +572 -0
  41. package/src/query-cache.mjs +116 -0
  42. package/src/query-optimizer.mjs +1051 -0
  43. package/src/query.mjs +306 -0
  44. package/src/reason.mjs +350 -0
  45. package/src/resolution-layer.mjs +506 -0
  46. package/src/schemas.mjs +1063 -0
  47. package/src/security/error-sanitizer.mjs +257 -0
  48. package/src/security/path-validator.mjs +194 -0
  49. package/src/security/sandbox-restrictions.mjs +331 -0
  50. package/src/security-validator.mjs +389 -0
  51. package/src/store-cache.mjs +137 -0
  52. package/src/telemetry.mjs +167 -0
  53. package/src/transaction.mjs +810 -0
  54. package/src/utils/adaptive-monitor.mjs +746 -0
  55. package/src/utils/circuit-breaker.mjs +513 -0
  56. package/src/utils/edge-case-handler.mjs +503 -0
  57. package/src/utils/memory-manager.mjs +498 -0
  58. package/src/utils/ring-buffer.mjs +282 -0
  59. package/src/validate.mjs +319 -0
  60. package/src/validators/index.mjs +338 -0
@@ -0,0 +1,810 @@
1
+ /**
2
+ * @file Transaction manager with hooks and receipts.
3
+ * @module transaction
4
+ */
5
+
6
+ import { createStore } from '@unrdf/oxigraph';
7
+ import { sha3_256 } from '@noble/hashes/sha3.js';
8
+ import { blake3 } from '@noble/hashes/blake3.js';
9
+ import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils.js';
10
+ import { canonicalize } from './canonicalize.mjs';
11
+ import { createLockchainWriter } from './lockchain-writer.mjs';
12
+ import { createResolutionLayer } from './resolution-layer.mjs';
13
+ import { createObservabilityManager } from './observability.mjs';
14
+ import { randomUUID } from 'crypto';
15
+ import { z } from 'zod';
16
+ import { trace, SpanStatusCode } from '@opentelemetry/api';
17
+
18
+ const tracer = trace.getTracer('unrdf');
19
+
20
+ // Import consolidated schemas
21
+ import {
22
+ _QuadSchema,
23
+ DeltaSchema,
24
+ TransactionHookSchema,
25
+ _TransactionHookResultSchema,
26
+ _HashSchema,
27
+ _TransactionReceiptSchemaNew,
28
+ TransactionOptionsSchema,
29
+ ManagerOptionsSchema,
30
+ } from './schemas.mjs';
31
+
32
+ // Zod schemas for validation
33
+ // QuadSchema now imported from schemas.mjs
34
+
35
+ // All schemas now imported from schemas.mjs
36
+
37
+ /**
38
+ * @typedef {z.infer<typeof DeltaSchema>} Delta
39
+ * @typedef {z.infer<typeof TransactionHookSchema>} Hook
40
+ * @typedef {z.infer<typeof TransactionHookResultSchema>} HookResult
41
+ * @typedef {z.infer<typeof TransactionReceiptSchemaNew>} Receipt
42
+ * @typedef {z.infer<typeof TransactionOptionsSchema>} TransactionOptions
43
+ * @typedef {z.infer<typeof ManagerOptionsSchema>} ManagerOptions
44
+ */
45
+
46
+ /**
47
+ * Hash a store canonically with SHA-3 and BLAKE3.
48
+ * @param {Store} store - The store to hash
49
+ * @param {Object} [options] - Hashing options
50
+ * @param {boolean} [options.afterHashOnly=false] - Skip canonicalization for performance
51
+ * @returns {Promise<{ sha3: string, blake3: string }>} Promise resolving to hash object
52
+ *
53
+ * @throws {Error} If hashing fails
54
+ */
55
+ async function hashStore(store, options = {}) {
56
+ try {
57
+ if (options.afterHashOnly) {
58
+ // Fast hash without canonicalization for performance
59
+ const quads = store.getQuads();
60
+ const content = quads
61
+ .map(q => `${q.subject.value} ${q.predicate.value} ${q.object.value} ${q.graph.value}`)
62
+ .join('\n');
63
+ const bytes = utf8ToBytes(content);
64
+ return {
65
+ sha3: bytesToHex(sha3_256(bytes)),
66
+ blake3: bytesToHex(blake3(bytes)),
67
+ };
68
+ }
69
+
70
+ const c14n = await canonicalize(store);
71
+ const bytes = utf8ToBytes(c14n);
72
+ return {
73
+ sha3: bytesToHex(sha3_256(bytes)),
74
+ blake3: bytesToHex(blake3(bytes)),
75
+ };
76
+ } catch (error) {
77
+ throw new Error(`Store hashing failed: ${error.message}`);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Transaction manager with hooks and receipts.
83
+ * Provides atomic transactions with pre/post hooks and comprehensive receipts.
84
+ */
85
+ export class TransactionManager {
86
+ /**
87
+ * Create a new transaction manager.
88
+ * @param {ManagerOptions} [options] - Manager options
89
+ */
90
+ constructor(options = {}) {
91
+ /** @type {Hook[]} */
92
+ this.hooks = [];
93
+ this.options = ManagerOptionsSchema.parse(options);
94
+
95
+ // Simple mutex for concurrency control - no circular ref accumulation
96
+ this._applyMutex = null;
97
+ this._resetMutex();
98
+
99
+ // Initialize observability manager
100
+ this.observability = createObservabilityManager(this.options.observability || {});
101
+
102
+ // Performance tracking with bounded arrays
103
+ this.performanceMetrics = {
104
+ transactionLatency: [],
105
+ hookExecutionRate: 0,
106
+ errorCount: 0,
107
+ totalTransactions: 0,
108
+ _maxLatencyEntries: 1000,
109
+ };
110
+
111
+ // Initialize lockchain writer if enabled
112
+ if (this.options.enableLockchain) {
113
+ const lockchainConfig = {
114
+ gitRepo: this.options.lockchainConfig?.gitRepo || process.cwd(),
115
+ refName: this.options.lockchainConfig?.refName || 'refs/notes/lockchain',
116
+ signingKey: this.options.lockchainConfig?.signingKey,
117
+ batchSize: this.options.lockchainConfig?.batchSize || 10,
118
+ };
119
+ this.lockchainWriter = createLockchainWriter(lockchainConfig);
120
+ } else {
121
+ this.lockchainWriter = null;
122
+ }
123
+
124
+ // Initialize resolution layer if enabled
125
+ if (this.options.enableResolution) {
126
+ const resolutionConfig = {
127
+ defaultStrategy: this.options.resolutionConfig?.defaultStrategy || 'voting',
128
+ maxProposals: this.options.resolutionConfig?.maxProposals || 100,
129
+ enableConflictDetection: this.options.resolutionConfig?.enableConflictDetection !== false,
130
+ enableConsensus: this.options.resolutionConfig?.enableConsensus !== false,
131
+ timeout: this.options.resolutionConfig?.timeout || 30000,
132
+ };
133
+ this.resolutionLayer = createResolutionLayer(resolutionConfig);
134
+ } else {
135
+ this.resolutionLayer = null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Register a hook.
141
+ * @param {Hook} hook - Hook to register
142
+ * @throws {Error} If hook is invalid or limit exceeded
143
+ *
144
+ * @example
145
+ * tx.addHook({
146
+ * id: "no-eve",
147
+ * mode: "pre",
148
+ * condition: async (store, delta) => !delta.additions.some(q => q.object.value.endsWith("eve")),
149
+ * effect: "veto"
150
+ * });
151
+ */
152
+ addHook(hook) {
153
+ // Validate hook with Zod
154
+ const validatedHook = TransactionHookSchema.parse(hook);
155
+
156
+ // Check for duplicate IDs
157
+ if (this.hooks.some(h => h.id === validatedHook.id)) {
158
+ throw new Error(`Hook with id "${validatedHook.id}" already exists`);
159
+ }
160
+
161
+ // Check hook limit
162
+ if (this.hooks.length >= this.options.maxHooks) {
163
+ throw new Error(`Maximum number of hooks (${this.options.maxHooks}) exceeded`);
164
+ }
165
+
166
+ this.hooks.push(validatedHook);
167
+ }
168
+
169
+ /**
170
+ * Remove a hook by ID.
171
+ * @param {string} hookId - Hook identifier to remove
172
+ * @returns {boolean} True if hook was removed, false if not found
173
+ *
174
+ * @example
175
+ * const removed = tx.removeHook("no-eve");
176
+ * console.log('Hook removed:', removed);
177
+ */
178
+ removeHook(hookId) {
179
+ // Validate hookId with Zod
180
+ const validatedHookId = z.string().parse(hookId);
181
+
182
+ const index = this.hooks.findIndex(h => h.id === validatedHookId);
183
+ if (index === -1) {
184
+ return false;
185
+ }
186
+
187
+ this.hooks.splice(index, 1);
188
+ return true;
189
+ }
190
+
191
+ /**
192
+ * Get all registered hooks.
193
+ * @returns {Hook[]} Array of registered hooks
194
+ */
195
+ getHooks() {
196
+ return [...this.hooks];
197
+ }
198
+
199
+ /**
200
+ * Clear all hooks.
201
+ */
202
+ clearHooks() {
203
+ this.hooks = [];
204
+ }
205
+
206
+ /**
207
+ * Apply a transaction.
208
+ * @param {Store} store - The store to apply the transaction to
209
+ * @param {Delta} delta - The delta to apply
210
+ * @param {TransactionOptions} [options] - Transaction options
211
+ * @returns {Promise<{store: Store, receipt: Receipt}>} Promise resolving to transaction result
212
+ *
213
+ * @throws {Error} If transaction fails
214
+ *
215
+ * @example
216
+ * const delta = {
217
+ * additions: [quad(namedNode("ex:alice"), namedNode("ex:knows"), namedNode("ex:bob"))],
218
+ * removals: []
219
+ * };
220
+ *
221
+ * const result = await tx.apply(store, delta);
222
+ * console.log('Committed:', result.receipt.committed);
223
+ * console.log('New store size:', result.store.size);
224
+ */
225
+ async apply(store, delta, options = {}) {
226
+ // Validate inputs with Zod
227
+ if (!store || typeof store.getQuads !== 'function') {
228
+ throw new TypeError('apply: store must be a valid Store instance');
229
+ }
230
+
231
+ const validatedDelta = DeltaSchema.parse(delta);
232
+ const validatedOptions = TransactionOptionsSchema.parse(options);
233
+ const startTime = Date.now();
234
+ const transactionId = randomUUID();
235
+
236
+ /** @type {HookResult[]} */
237
+ const hookResults = [];
238
+ /** @type {string[]} */
239
+ const hookErrors = [];
240
+
241
+ // Start observability span
242
+ const _spanContext = this.observability.startTransactionSpan(transactionId, {
243
+ 'kgc.delta.additions': validatedDelta.additions.length,
244
+ 'kgc.delta.removals': validatedDelta.removals.length,
245
+ 'kgc.actor': validatedOptions.actor || 'system',
246
+ 'kgc.skipHooks': validatedOptions.skipHooks || false,
247
+ });
248
+
249
+ // Use mutex for concurrency control - reset to prevent chain buildup
250
+ const currentMutex = this._applyMutex;
251
+
252
+ return new Promise((resolve, reject) => {
253
+ this._applyMutex = currentMutex
254
+ .then(async () => {
255
+ try {
256
+ // Set up timeout with proper cleanup
257
+ let timeoutHandle;
258
+ const timeoutPromise = new Promise((_, timeoutReject) => {
259
+ timeoutHandle = setTimeout(
260
+ () => timeoutReject(new Error('Transaction timeout')),
261
+ validatedOptions.timeoutMs
262
+ );
263
+ });
264
+
265
+ const transactionPromise = this._executeTransaction(
266
+ store,
267
+ validatedDelta,
268
+ validatedOptions.skipHooks,
269
+ hookResults,
270
+ hookErrors,
271
+ transactionId,
272
+ validatedOptions.actor
273
+ );
274
+
275
+ const result = await Promise.race([transactionPromise, timeoutPromise]);
276
+ clearTimeout(timeoutHandle);
277
+
278
+ // Reset mutex chain to prevent circular reference buildup
279
+ this._resetMutex();
280
+
281
+ const finalReceipt = {
282
+ ...result.receipt,
283
+ id: transactionId,
284
+ timestamp: startTime,
285
+ durationMs: Date.now() - startTime,
286
+ actor: validatedOptions.actor,
287
+ hookErrors,
288
+ };
289
+
290
+ // Write to lockchain if enabled
291
+ if (this.lockchainWriter && result.receipt.committed) {
292
+ try {
293
+ await this.lockchainWriter.writeReceipt(finalReceipt);
294
+ } catch (lockchainError) {
295
+ console.warn('Failed to write receipt to lockchain:', lockchainError.message);
296
+ }
297
+ }
298
+
299
+ // Update performance metrics
300
+ const duration = Date.now() - startTime;
301
+ this._updatePerformanceMetrics(duration, true);
302
+
303
+ // End observability span
304
+ this.observability.endTransactionSpan(transactionId, {
305
+ 'kgc.transaction.committed': finalReceipt.committed,
306
+ 'kgc.hook.results': hookResults.length,
307
+ 'kgc.hook.errors': hookErrors.length,
308
+ });
309
+
310
+ resolve({
311
+ store: result.store,
312
+ receipt: finalReceipt,
313
+ });
314
+ } catch (error) {
315
+ const beforeHash = await hashStore(store, this.options).catch(() => ({
316
+ sha3: '',
317
+ blake3: '',
318
+ }));
319
+
320
+ // Update performance metrics
321
+ const duration = Date.now() - startTime;
322
+ this._updatePerformanceMetrics(duration, false);
323
+
324
+ // Record error
325
+ this.observability.recordError(error, {
326
+ 'kgc.transaction.id': transactionId,
327
+ 'kgc.actor': validatedOptions.actor || 'system',
328
+ });
329
+
330
+ // End observability span with error
331
+ this.observability.endTransactionSpan(transactionId, {}, error);
332
+
333
+ resolve({
334
+ store,
335
+ receipt: {
336
+ id: transactionId,
337
+ delta: validatedDelta,
338
+ committed: false,
339
+ hookResults,
340
+ beforeHash,
341
+ afterHash: beforeHash,
342
+ timestamp: startTime,
343
+ durationMs: Date.now() - startTime,
344
+ actor: validatedOptions.actor,
345
+ hookErrors,
346
+ error: error.message,
347
+ },
348
+ });
349
+ }
350
+ })
351
+ .catch(reject);
352
+ });
353
+ }
354
+
355
+ /**
356
+ * Execute the transaction with hooks.
357
+ * @private
358
+ */
359
+ async _executeTransaction(
360
+ store,
361
+ delta,
362
+ skipHooks,
363
+ hookResults,
364
+ hookErrors,
365
+ transactionId,
366
+ actor
367
+ ) {
368
+ return tracer.startActiveSpan('transaction.commit', async span => {
369
+ try {
370
+ span.setAttributes({
371
+ 'transaction.id': transactionId,
372
+ 'transaction.actor': actor || 'system',
373
+ 'transaction.skip_hooks': skipHooks,
374
+ 'transaction.additions_count': delta.additions.length,
375
+ 'transaction.removals_count': delta.removals.length,
376
+ 'transaction.store_size_before': store.size,
377
+ });
378
+
379
+ const beforeHash = await hashStore(store, this.options);
380
+
381
+ const result = await this._executeTransactionWithHooks(
382
+ store,
383
+ delta,
384
+ skipHooks,
385
+ hookResults,
386
+ hookErrors,
387
+ beforeHash
388
+ );
389
+
390
+ span.setAttributes({
391
+ 'transaction.committed': result.receipt.committed,
392
+ 'transaction.hook_results': hookResults.length,
393
+ 'transaction.hook_errors': hookErrors.length,
394
+ 'transaction.store_size_after': store.size,
395
+ });
396
+
397
+ span.setStatus({ code: SpanStatusCode.OK });
398
+ return result;
399
+ } catch (error) {
400
+ span.recordException(error);
401
+ span.setStatus({
402
+ code: SpanStatusCode.ERROR,
403
+ message: error.message,
404
+ });
405
+ throw error;
406
+ } finally {
407
+ span.end();
408
+ }
409
+ });
410
+ }
411
+
412
+ /**
413
+ * Execute transaction with hooks
414
+ * @private
415
+ */
416
+ async _executeTransactionWithHooks(store, delta, skipHooks, hookResults, hookErrors, beforeHash) {
417
+ // Pre-hooks
418
+ if (!skipHooks) {
419
+ for (const hook of this.hooks.filter(h => h.mode === 'pre')) {
420
+ try {
421
+ const ok = await hook.condition(store, delta);
422
+ hookResults.push({ hookId: hook.id, mode: hook.mode, result: ok });
423
+
424
+ if (!ok && hook.effect === 'veto') {
425
+ return {
426
+ store,
427
+ receipt: {
428
+ delta,
429
+ committed: false,
430
+ hookResults,
431
+ beforeHash,
432
+ afterHash: beforeHash,
433
+ },
434
+ };
435
+ }
436
+ } catch (error) {
437
+ const errorMsg = `Pre-hook "${hook.id}" failed: ${error.message}`;
438
+ hookErrors.push(errorMsg);
439
+ hookResults.push({
440
+ hookId: hook.id,
441
+ mode: hook.mode,
442
+ result: false,
443
+ error: error.message,
444
+ });
445
+
446
+ if (this.options.strictMode) {
447
+ throw new Error(errorMsg);
448
+ }
449
+ }
450
+ }
451
+ }
452
+
453
+ // Commit transaction - MUTATE IN PLACE for state accumulation
454
+ // Remove quads first
455
+ for (const quad of delta.removals) {
456
+ store.removeQuad(quad);
457
+ }
458
+
459
+ // Add new quads
460
+ for (const quad of delta.additions) {
461
+ store.addQuad(quad);
462
+ }
463
+
464
+ // Post-hooks
465
+ if (!skipHooks) {
466
+ for (const hook of this.hooks.filter(h => h.mode === 'post')) {
467
+ try {
468
+ const ok = await hook.condition(store, delta);
469
+ hookResults.push({ hookId: hook.id, mode: hook.mode, result: ok });
470
+
471
+ // Post-hooks ignore veto effects - only execute function effects
472
+ if (ok && typeof hook.effect === 'function') {
473
+ await hook.effect(store, delta);
474
+ }
475
+ } catch (error) {
476
+ const errorMsg = `Post-hook "${hook.id}" failed: ${error.message}`;
477
+ hookErrors.push(errorMsg);
478
+ hookResults.push({
479
+ hookId: hook.id,
480
+ mode: hook.mode,
481
+ result: false,
482
+ error: error.message,
483
+ });
484
+
485
+ if (this.options.strictMode) {
486
+ throw new Error(errorMsg);
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ const afterHash = await hashStore(store, this.options);
493
+
494
+ return {
495
+ store,
496
+ receipt: {
497
+ delta,
498
+ committed: true,
499
+ hookResults,
500
+ beforeHash,
501
+ afterHash,
502
+ },
503
+ };
504
+ }
505
+
506
+ /**
507
+ * Create a transaction session for batch operations.
508
+ * @param {Store} initialStore - Initial store state
509
+ * @param {Object} [sessionOptions] - Session options
510
+ * @returns {Object} Transaction session
511
+ *
512
+ * @example
513
+ * const session = tx.createSession(store);
514
+ *
515
+ * // Add multiple deltas
516
+ * session.addDelta(delta1);
517
+ * session.addDelta(delta2);
518
+ *
519
+ * // Apply all deltas
520
+ * const results = await session.applyAll();
521
+ *
522
+ * // Get final state
523
+ * const finalStore = session.getCurrentStore();
524
+ */
525
+ createSession(initialStore, _sessionOptions = {}) {
526
+ if (!initialStore || typeof initialStore.getQuads !== 'function') {
527
+ throw new TypeError('createSession: initialStore must be a valid Store instance');
528
+ }
529
+
530
+ let currentStore = createStore(initialStore.getQuads());
531
+ const deltas = [];
532
+ const receipts = [];
533
+
534
+ return {
535
+ /**
536
+ * Add a delta to the session.
537
+ * @param {Delta} delta - Delta to add
538
+ */
539
+ addDelta(delta) {
540
+ const validatedDelta = DeltaSchema.parse(delta);
541
+ deltas.push(validatedDelta);
542
+ },
543
+
544
+ /**
545
+ * Apply all deltas in the session.
546
+ * @param {TransactionOptions} [options] - Apply options
547
+ * @returns {Promise<Array<Receipt>>} Promise resolving to array of receipts
548
+ */
549
+ applyAll: async (options = {}) => {
550
+ const validatedOptions = TransactionOptionsSchema.parse(options);
551
+ const results = [];
552
+
553
+ for (const delta of deltas) {
554
+ // Use arrow function to preserve this context
555
+ const result = await this.apply(currentStore, delta, validatedOptions);
556
+ currentStore = result.store;
557
+ receipts.push(result.receipt);
558
+ results.push(result.receipt);
559
+ }
560
+
561
+ return results;
562
+ },
563
+
564
+ /**
565
+ * Get current store state.
566
+ * @returns {Store} Current store
567
+ */
568
+ getCurrentStore() {
569
+ return createStore(currentStore.getQuads());
570
+ },
571
+
572
+ /**
573
+ * Get all receipts.
574
+ * @returns {Receipt[]} Array of receipts
575
+ */
576
+ getReceipts() {
577
+ return [...receipts];
578
+ },
579
+
580
+ /**
581
+ * Reset session to initial state.
582
+ */
583
+ reset() {
584
+ currentStore = createStore(initialStore.getQuads());
585
+ deltas.length = 0;
586
+ receipts.length = 0;
587
+ },
588
+
589
+ /**
590
+ * Get session statistics.
591
+ * @returns {Object} Session statistics
592
+ */
593
+ getStats() {
594
+ const committedCount = receipts.filter(r => r.committed).length;
595
+ const failedCount = receipts.length - committedCount;
596
+
597
+ return {
598
+ deltaCount: deltas.length,
599
+ receiptCount: receipts.length,
600
+ committedCount,
601
+ failedCount,
602
+ successRate: receipts.length > 0 ? committedCount / receipts.length : 0,
603
+ };
604
+ },
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Get transaction manager statistics.
610
+ * @returns {Object} Manager statistics
611
+ */
612
+ getStats() {
613
+ const preHooks = this.hooks.filter(h => h.mode === 'pre').length;
614
+ const postHooks = this.hooks.filter(h => h.mode === 'post').length;
615
+
616
+ const stats = {
617
+ totalHooks: this.hooks.length,
618
+ preHooks,
619
+ postHooks,
620
+ maxHooks: this.options.maxHooks,
621
+ strictMode: this.options.strictMode,
622
+ afterHashOnly: this.options.afterHashOnly,
623
+ lockchainEnabled: this.options.enableLockchain,
624
+ };
625
+
626
+ // Add lockchain stats if enabled
627
+ if (this.lockchainWriter) {
628
+ stats.lockchain = this.lockchainWriter.getStats();
629
+ }
630
+
631
+ return stats;
632
+ }
633
+
634
+ /**
635
+ * Commit pending lockchain entries
636
+ * @returns {Promise<Object>} Commit result
637
+ */
638
+ async commitLockchain() {
639
+ if (!this.lockchainWriter) {
640
+ throw new Error('Lockchain is not enabled');
641
+ }
642
+
643
+ return this.lockchainWriter.commitBatch();
644
+ }
645
+
646
+ /**
647
+ * Submit a proposal to the resolution layer
648
+ * @param {string} agentId - Agent identifier
649
+ * @param {Object} delta - Proposed delta
650
+ * @param {Object} [options] - Proposal options
651
+ * @returns {Promise<string>} Proposal ID
652
+ */
653
+ async submitProposal(agentId, delta, options = {}) {
654
+ if (!this.resolutionLayer) {
655
+ throw new Error('Resolution layer is not enabled');
656
+ }
657
+
658
+ return this.resolutionLayer.submitProposal(agentId, delta, options);
659
+ }
660
+
661
+ /**
662
+ * Resolve proposals using the resolution layer
663
+ * @param {Array<string>} proposalIds - Proposal IDs to resolve
664
+ * @param {Object} [strategy] - Resolution strategy
665
+ * @returns {Promise<Object>} Resolution result
666
+ */
667
+ async resolveProposals(proposalIds, strategy = {}) {
668
+ if (!this.resolutionLayer) {
669
+ throw new Error('Resolution layer is not enabled');
670
+ }
671
+
672
+ return this.resolutionLayer.resolveProposals(proposalIds, strategy);
673
+ }
674
+
675
+ /**
676
+ * Get resolution layer statistics
677
+ * @returns {Object} Resolution statistics
678
+ */
679
+ getResolutionStats() {
680
+ if (!this.resolutionLayer) {
681
+ return { enabled: false };
682
+ }
683
+
684
+ return {
685
+ enabled: true,
686
+ ...this.resolutionLayer.getStats(),
687
+ };
688
+ }
689
+
690
+ /**
691
+ * Get manager statistics.
692
+ * @returns {Object} Statistics
693
+ */
694
+ getStats() {
695
+ return {
696
+ hooks: this.hooks.length,
697
+ lockchain: this.lockchainWriter ? this.lockchainWriter.getStats() : null,
698
+ resolution: this.resolutionLayer ? this.resolutionLayer.getStats() : null,
699
+ performance: this.performanceMetrics,
700
+ observability: this.observability.getPerformanceMetrics(),
701
+ };
702
+ }
703
+
704
+ /**
705
+ * Update performance metrics
706
+ * @param {number} duration - Transaction duration
707
+ * @param {boolean} success - Whether transaction succeeded
708
+ * @private
709
+ */
710
+ _updatePerformanceMetrics(duration, success) {
711
+ const maxEntries = this.performanceMetrics._maxLatencyEntries;
712
+
713
+ // Prevent unbounded array growth - remove oldest before adding
714
+ if (this.performanceMetrics.transactionLatency.length >= maxEntries) {
715
+ this.performanceMetrics.transactionLatency.shift();
716
+ }
717
+
718
+ this.performanceMetrics.transactionLatency.push({
719
+ timestamp: Date.now(),
720
+ duration,
721
+ success,
722
+ });
723
+
724
+ this.performanceMetrics.totalTransactions++;
725
+
726
+ if (!success) {
727
+ this.performanceMetrics.errorCount++;
728
+ }
729
+ }
730
+
731
+ /**
732
+ * Reset mutex chain to prevent circular references
733
+ * @private
734
+ */
735
+ _resetMutex() {
736
+ this._applyMutex = Promise.resolve();
737
+ }
738
+
739
+ /**
740
+ * Cleanup transaction manager resources
741
+ */
742
+ async cleanup() {
743
+ // Clear hooks
744
+ this.hooks.length = 0;
745
+
746
+ // Clear performance metrics
747
+ this.performanceMetrics.transactionLatency.length = 0;
748
+ this.performanceMetrics.errorCount = 0;
749
+ this.performanceMetrics.totalTransactions = 0;
750
+
751
+ // Cleanup lockchain writer
752
+ if (this.lockchainWriter && typeof this.lockchainWriter.cleanup === 'function') {
753
+ await this.lockchainWriter.cleanup();
754
+ }
755
+
756
+ // Cleanup resolution layer
757
+ if (this.resolutionLayer && typeof this.resolutionLayer.cleanup === 'function') {
758
+ await this.resolutionLayer.cleanup();
759
+ }
760
+
761
+ // Reset mutex
762
+ this._resetMutex();
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Print a receipt in a consistent format.
768
+ * @param {Receipt} receipt - The receipt to print
769
+ * @param {Object} [options] - Print options
770
+ * @param {boolean} [options.verbose=false] - Include detailed information
771
+ */
772
+ export function printReceipt(receipt, options = {}) {
773
+ const { verbose = false } = options;
774
+
775
+ console.log(`📋 Transaction Receipt ${receipt.id}`);
776
+ console.log(` Status: ${receipt.committed ? '✅ Committed' : '❌ Failed'}`);
777
+ console.log(` Duration: ${receipt.durationMs}ms`);
778
+
779
+ if (receipt.actor) {
780
+ console.log(` Actor: ${receipt.actor}`);
781
+ }
782
+
783
+ if (receipt.error) {
784
+ console.log(` Error: ${receipt.error}`);
785
+ }
786
+
787
+ console.log(` Hooks: ${receipt.hookResults.length} executed`);
788
+ receipt.hookResults.forEach(result => {
789
+ const status = result.result ? '✅' : '❌';
790
+ console.log(` ${status} ${result.hookId} (${result.mode})`);
791
+ if (result.error) {
792
+ console.log(` Error: ${result.error}`);
793
+ }
794
+ });
795
+
796
+ if (receipt.hookErrors.length > 0) {
797
+ console.log(` Hook Errors: ${receipt.hookErrors.length}`);
798
+ receipt.hookErrors.forEach(error => {
799
+ console.log(` • ${error}`);
800
+ });
801
+ }
802
+
803
+ if (verbose) {
804
+ console.log(
805
+ ` Delta: ${receipt.delta.additions.length} additions, ${receipt.delta.removals.length} removals`
806
+ );
807
+ console.log(` Before Hash: ${receipt.beforeHash.sha3.substring(0, 16)}...`);
808
+ console.log(` After Hash: ${receipt.afterHash.sha3.substring(0, 16)}...`);
809
+ }
810
+ }