@unrdf/kgc-probe 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.
@@ -0,0 +1,813 @@
1
+ /**
2
+ * @fileoverview KGC Probe - Receipt System Integration
3
+ *
4
+ * Provides cryptographic receipts for probe observations and merge operations:
5
+ * - ObservationReceipt: Per-agent observation hashing with chain
6
+ * - MergeReceipt: Merkle tree of shard hashes
7
+ * - VerificationReceipt: Confidence scoring and verification status
8
+ *
9
+ * Integrates with @unrdf/v6-core BaseReceipt system.
10
+ *
11
+ * @module @unrdf/kgc-probe/receipts
12
+ */
13
+
14
+ import { randomUUID, createHash } from 'crypto';
15
+
16
+ // ============================================================================
17
+ // CONSTANTS
18
+ // ============================================================================
19
+
20
+ /** Hash algorithm used for receipts */
21
+ export const HASH_ALGORITHM = 'sha256';
22
+
23
+ /** Receipt version */
24
+ export const RECEIPT_VERSION = '1.0.0';
25
+
26
+ /** Receipt types */
27
+ export const RECEIPT_TYPES = Object.freeze({
28
+ OBSERVATION: 'probe:observation',
29
+ MERGE: 'probe:merge',
30
+ VERIFICATION: 'probe:verification'
31
+ });
32
+
33
+ // ============================================================================
34
+ // HASH UTILITIES
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Deterministic serialization of object
39
+ * @param {any} obj - Object to serialize
40
+ * @returns {string} Deterministic JSON string
41
+ */
42
+ export function deterministicSerialize(obj) {
43
+ if (obj === null || obj === undefined) {
44
+ return 'null';
45
+ }
46
+ if (typeof obj === 'bigint') {
47
+ return obj.toString();
48
+ }
49
+ if (typeof obj !== 'object') {
50
+ return JSON.stringify(obj);
51
+ }
52
+ if (Array.isArray(obj)) {
53
+ return '[' + obj.map(deterministicSerialize).join(',') + ']';
54
+ }
55
+ const keys = Object.keys(obj).sort();
56
+ const pairs = keys.map(k => `${JSON.stringify(k)}:${deterministicSerialize(obj[k])}`);
57
+ return '{' + pairs.join(',') + '}';
58
+ }
59
+
60
+ /**
61
+ * Compute deterministic hash of data
62
+ * @param {any} data - Data to hash
63
+ * @returns {string} Hex-encoded hash
64
+ */
65
+ export function computeHash(data) {
66
+ const serialized = typeof data === 'string'
67
+ ? data
68
+ : deterministicSerialize(data);
69
+ return createHash(HASH_ALGORITHM).update(serialized).digest('hex');
70
+ }
71
+
72
+ /**
73
+ * Compute chained hash (previousHash + currentHash)
74
+ * @param {string|null} previousHash - Previous hash in chain (null for genesis)
75
+ * @param {string} currentHash - Current payload hash
76
+ * @returns {string} Chained hash
77
+ */
78
+ export function computeChainHash(previousHash, currentHash) {
79
+ const input = `${previousHash || 'GENESIS'}:${currentHash}`;
80
+ return createHash(HASH_ALGORITHM).update(input).digest('hex');
81
+ }
82
+
83
+ // ============================================================================
84
+ // OBSERVATION RECEIPT
85
+ // ============================================================================
86
+
87
+ /**
88
+ * @typedef {Object} ObservationReceipt
89
+ * @property {string} id - Receipt UUID
90
+ * @property {string} receiptType - Receipt type discriminator
91
+ * @property {string} agentId - Agent that produced observation
92
+ * @property {number} observationIndex - Sequence number in agent chain
93
+ * @property {string} domain - Observation domain
94
+ * @property {string} obsHash - Hash of observation payload
95
+ * @property {string|null} prevHash - Previous observation hash (chain)
96
+ * @property {bigint} timestamp_ns - Nanosecond timestamp
97
+ * @property {string} timestamp_iso - ISO timestamp
98
+ * @property {string} payloadHash - Hash of receipt payload
99
+ * @property {string|null} previousHash - Previous receipt hash
100
+ * @property {string} receiptHash - Chained receipt hash
101
+ * @property {Object} observation - Original observation
102
+ * @property {Object[]} checks - Determinism checks performed
103
+ */
104
+
105
+ /**
106
+ * Create an observation receipt
107
+ * @param {Object} params - Receipt parameters
108
+ * @param {string} params.agentId - Agent identifier
109
+ * @param {number} params.observationIndex - Sequence number
110
+ * @param {Object} params.payload - Observation payload
111
+ * @param {string} params.domain - Observation domain
112
+ * @param {ObservationReceipt|null} [params.previousReceipt] - Previous receipt for chaining
113
+ * @returns {ObservationReceipt} Created receipt
114
+ */
115
+ export function createObservationReceipt({
116
+ agentId,
117
+ observationIndex,
118
+ payload,
119
+ domain,
120
+ previousReceipt = null
121
+ }) {
122
+ if (!agentId) throw new Error('agentId is required');
123
+ if (observationIndex < 1) throw new Error('observationIndex must be >= 1');
124
+ if (!payload) throw new Error('payload is required');
125
+ if (!domain) throw new Error('domain is required');
126
+
127
+ const id = randomUUID();
128
+ const now = process.hrtime.bigint();
129
+ const timestamp_iso = new Date().toISOString();
130
+
131
+ // Compute observation hash
132
+ const obsHash = computeHash(payload);
133
+
134
+ // Previous hash in agent's chain
135
+ const prevHash = previousReceipt ? previousReceipt.obsHash : null;
136
+
137
+ // Determinism checks
138
+ const checks = [
139
+ {
140
+ checkType: 'hash_recompute',
141
+ passed: computeHash(payload) === obsHash,
142
+ details: 'Hash recomputation matches'
143
+ },
144
+ {
145
+ checkType: 'serialization_stable',
146
+ passed: deterministicSerialize(payload) === deterministicSerialize(payload),
147
+ details: 'Serialization is stable'
148
+ },
149
+ {
150
+ checkType: 'payload_integrity',
151
+ passed: typeof payload === 'object' && payload !== null,
152
+ details: 'Payload is valid object'
153
+ }
154
+ ];
155
+
156
+ // Ensure all checks pass
157
+ const allPassed = checks.every(c => c.passed);
158
+ if (!allPassed) {
159
+ const failed = checks.filter(c => !c.passed).map(c => c.checkType);
160
+ throw new Error(`Determinism checks failed: ${failed.join(', ')}`);
161
+ }
162
+
163
+ // Build receipt payload
164
+ const receiptPayload = {
165
+ id,
166
+ receiptType: RECEIPT_TYPES.OBSERVATION,
167
+ agentId,
168
+ observationIndex,
169
+ domain,
170
+ obsHash,
171
+ prevHash,
172
+ timestamp_ns: now,
173
+ timestamp_iso,
174
+ observation: {
175
+ payload,
176
+ timestamp: now,
177
+ hash: obsHash,
178
+ metadata: {
179
+ serializationVersion: RECEIPT_VERSION,
180
+ encoding: 'utf-8',
181
+ deterministic: true
182
+ }
183
+ },
184
+ checks
185
+ };
186
+
187
+ // Compute hashes
188
+ const payloadHash = computeHash(receiptPayload);
189
+ const previousReceiptHash = previousReceipt ? previousReceipt.receiptHash : null;
190
+ const receiptHash = computeChainHash(previousReceiptHash, payloadHash);
191
+
192
+ return {
193
+ ...receiptPayload,
194
+ payloadHash,
195
+ previousHash: previousReceiptHash,
196
+ receiptHash
197
+ };
198
+ }
199
+
200
+ // ============================================================================
201
+ // MERGE RECEIPT
202
+ // ============================================================================
203
+
204
+ /**
205
+ * @typedef {Object} ShardInfo
206
+ * @property {string} agentId - Agent ID
207
+ * @property {string} chainFinalHash - Final hash in agent's chain
208
+ * @property {number} obsCount - Number of observations
209
+ * @property {string} domain - Agent domain
210
+ */
211
+
212
+ /**
213
+ * @typedef {Object} MergeReceipt
214
+ * @property {string} id - Receipt UUID
215
+ * @property {string} receiptType - Receipt type
216
+ * @property {string} mergeId - Merge operation ID
217
+ * @property {ShardInfo[]} shards - Merged shard information
218
+ * @property {string} merkleRoot - Merkle tree root hash
219
+ * @property {string[]} proofPath - Merkle proof path
220
+ * @property {Object} mergeAlgorithm - Algorithm metadata
221
+ * @property {Object[]|null} conflicts - Detected conflicts
222
+ * @property {bigint} timestamp_ns - Nanosecond timestamp
223
+ * @property {string} timestamp_iso - ISO timestamp
224
+ * @property {string} payloadHash - Payload hash
225
+ * @property {string|null} previousHash - Always null for merge
226
+ * @property {string} receiptHash - Receipt hash
227
+ */
228
+
229
+ /**
230
+ * Build merkle tree from shard hashes
231
+ * @param {ShardInfo[]} shards - Shard information
232
+ * @returns {{root: string, proofPath: string[]}} Merkle root and proof
233
+ */
234
+ export function buildMerkleTree(shards) {
235
+ if (shards.length === 0) {
236
+ return { root: computeHash('EMPTY'), proofPath: [] };
237
+ }
238
+
239
+ // Sort shards by agentId for determinism
240
+ const sorted = [...shards].sort((a, b) => a.agentId.localeCompare(b.agentId));
241
+
242
+ // Build leaf hashes
243
+ const leaves = sorted.map(s => computeHash(`${s.agentId}:${s.chainFinalHash}`));
244
+ const proofPath = [...leaves];
245
+
246
+ // Build tree bottom-up
247
+ let level = leaves;
248
+ while (level.length > 1) {
249
+ const nextLevel = [];
250
+ for (let i = 0; i < level.length; i += 2) {
251
+ const left = level[i];
252
+ const right = level[i + 1] || left; // Duplicate if odd
253
+ nextLevel.push(computeHash(left + right));
254
+ }
255
+ level = nextLevel;
256
+ }
257
+
258
+ return { root: level[0], proofPath };
259
+ }
260
+
261
+ /**
262
+ * Verify merkle root
263
+ * @param {ShardInfo[]} shards - Shard information
264
+ * @param {string} expectedRoot - Expected merkle root
265
+ * @returns {boolean} True if root matches
266
+ */
267
+ export function verifyMerkleRoot(shards, expectedRoot) {
268
+ const { root } = buildMerkleTree(shards);
269
+ return root === expectedRoot;
270
+ }
271
+
272
+ /**
273
+ * Create a merge receipt
274
+ * @param {Object} params - Receipt parameters
275
+ * @param {string} params.mergeId - Unique merge operation ID
276
+ * @param {ShardInfo[]} params.shards - Shard information
277
+ * @param {Object[]|null} [params.conflicts] - Detected conflicts
278
+ * @param {string} [params.orchestratorId] - Orchestrator ID
279
+ * @returns {MergeReceipt} Created receipt
280
+ */
281
+ export function createMergeReceipt({
282
+ mergeId,
283
+ shards,
284
+ conflicts = null,
285
+ orchestratorId = 'default'
286
+ }) {
287
+ if (!mergeId) throw new Error('mergeId is required');
288
+ if (!shards || shards.length === 0) throw new Error('shards is required (non-empty)');
289
+
290
+ const id = randomUUID();
291
+ const now = process.hrtime.bigint();
292
+ const timestamp_iso = new Date().toISOString();
293
+
294
+ // Build merkle tree
295
+ const { root: merkleRoot, proofPath } = buildMerkleTree(shards);
296
+
297
+ // Build receipt payload
298
+ const receiptPayload = {
299
+ id,
300
+ receiptType: RECEIPT_TYPES.MERGE,
301
+ mergeId,
302
+ shards,
303
+ merkleRoot,
304
+ proofPath,
305
+ mergeAlgorithm: {
306
+ algorithm: 'lww-deterministic',
307
+ version: '1.0.0',
308
+ parameters: {
309
+ leafOrder: 'agentId-ascending',
310
+ hashFunction: HASH_ALGORITHM
311
+ }
312
+ },
313
+ conflicts,
314
+ timestamp_ns: now,
315
+ timestamp_iso,
316
+ orchestratorId,
317
+ mergedAt: timestamp_iso
318
+ };
319
+
320
+ // Compute hashes
321
+ const payloadHash = computeHash(receiptPayload);
322
+ const receiptHash = computeChainHash(null, payloadHash);
323
+
324
+ return {
325
+ ...receiptPayload,
326
+ payloadHash,
327
+ previousHash: null,
328
+ receiptHash
329
+ };
330
+ }
331
+
332
+ // ============================================================================
333
+ // VERIFICATION RECEIPT
334
+ // ============================================================================
335
+
336
+ /**
337
+ * @typedef {Object} VerificationCheck
338
+ * @property {string} checkType - Type of verification check
339
+ * @property {boolean} passed - Whether check passed
340
+ * @property {string} details - Check details
341
+ * @property {number} weight - Weight in confidence calculation
342
+ */
343
+
344
+ /**
345
+ * @typedef {Object} CertificateStep
346
+ * @property {string} step - Step name
347
+ * @property {string} status - Step status
348
+ * @property {string} hash - Step hash
349
+ */
350
+
351
+ /**
352
+ * @typedef {Object} VerificationReceipt
353
+ * @property {string} id - Receipt UUID
354
+ * @property {string} receiptType - Receipt type
355
+ * @property {string} verificationId - Verification operation ID
356
+ * @property {string} mergeReceiptHash - Hash of merge receipt verified
357
+ * @property {VerificationCheck[]} verifications - Checks performed
358
+ * @property {boolean} deterministic - Overall determinism result
359
+ * @property {boolean} conflictFree - Whether conflicts were found
360
+ * @property {CertificateStep[]} certificateChain - Certificate chain
361
+ * @property {number} obsCount - Observations verified
362
+ * @property {number} agentCount - Agents verified
363
+ * @property {number} confidenceScore - Confidence score [0, 1]
364
+ * @property {bigint} timestamp_ns - Nanosecond timestamp
365
+ * @property {string} timestamp_iso - ISO timestamp
366
+ * @property {string} payloadHash - Payload hash
367
+ * @property {string|null} previousHash - Always null for verification
368
+ * @property {string} receiptHash - Receipt hash
369
+ */
370
+
371
+ /**
372
+ * Calculate confidence score from verification checks
373
+ * @param {VerificationCheck[]} checks - Verification checks
374
+ * @returns {number} Confidence score [0, 1]
375
+ */
376
+ export function calculateConfidenceScore(checks) {
377
+ if (checks.length === 0) return 0;
378
+
379
+ let totalWeight = 0;
380
+ let passedWeight = 0;
381
+
382
+ for (const check of checks) {
383
+ const weight = check.weight ?? 1;
384
+ totalWeight += weight;
385
+ if (check.passed) {
386
+ passedWeight += weight;
387
+ }
388
+ }
389
+
390
+ return totalWeight > 0 ? passedWeight / totalWeight : 0;
391
+ }
392
+
393
+ /**
394
+ * Get failed checks from verification
395
+ * @param {VerificationCheck[]} checks - Verification checks
396
+ * @returns {VerificationCheck[]} Failed checks
397
+ */
398
+ export function getFailedChecks(checks) {
399
+ return checks.filter(c => !c.passed);
400
+ }
401
+
402
+ /**
403
+ * Summarize verification result
404
+ * @param {VerificationReceipt} receipt - Verification receipt
405
+ * @returns {Object} Summary
406
+ */
407
+ export function summarizeVerification(receipt) {
408
+ const failed = getFailedChecks(receipt.verifications);
409
+ return {
410
+ verificationId: receipt.verificationId,
411
+ passed: failed.length === 0,
412
+ deterministic: receipt.deterministic,
413
+ conflictFree: receipt.conflictFree,
414
+ confidenceScore: receipt.confidenceScore,
415
+ totalChecks: receipt.verifications.length,
416
+ failedChecks: failed.length,
417
+ obsCount: receipt.obsCount,
418
+ agentCount: receipt.agentCount
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Create a verification receipt
424
+ * @param {Object} params - Receipt parameters
425
+ * @param {string} params.verificationId - Verification ID
426
+ * @param {string} params.mergeReceiptHash - Hash of merge receipt
427
+ * @param {VerificationCheck[]} params.verifications - Checks performed
428
+ * @param {boolean} params.deterministic - Determinism result
429
+ * @param {boolean} params.conflictFree - Conflict-free result
430
+ * @param {CertificateStep[]} params.certificateChain - Certificate chain
431
+ * @param {number} params.obsCount - Observations verified
432
+ * @param {number} params.agentCount - Agents verified
433
+ * @param {string} [params.verifierId] - Verifier ID
434
+ * @returns {VerificationReceipt} Created receipt
435
+ */
436
+ export function createVerificationReceipt({
437
+ verificationId,
438
+ mergeReceiptHash,
439
+ verifications,
440
+ deterministic,
441
+ conflictFree,
442
+ certificateChain,
443
+ obsCount,
444
+ agentCount,
445
+ verifierId = 'default'
446
+ }) {
447
+ if (!verificationId) throw new Error('verificationId is required');
448
+ if (!mergeReceiptHash) throw new Error('mergeReceiptHash is required');
449
+ if (!Array.isArray(verifications)) throw new Error('verifications array is required');
450
+ if (!Array.isArray(certificateChain)) throw new Error('certificateChain array is required');
451
+
452
+ const id = randomUUID();
453
+ const now = process.hrtime.bigint();
454
+ const timestamp_iso = new Date().toISOString();
455
+
456
+ // Calculate confidence score
457
+ const confidenceScore = calculateConfidenceScore(verifications);
458
+
459
+ // Build receipt payload
460
+ const receiptPayload = {
461
+ id,
462
+ receiptType: RECEIPT_TYPES.VERIFICATION,
463
+ verificationId,
464
+ mergeReceiptHash,
465
+ verifications,
466
+ deterministic,
467
+ conflictFree,
468
+ certificateChain,
469
+ obsCount,
470
+ agentCount,
471
+ confidenceScore,
472
+ timestamp_ns: now,
473
+ timestamp_iso,
474
+ verifierId,
475
+ verifiedAt: timestamp_iso
476
+ };
477
+
478
+ // Compute hashes
479
+ const payloadHash = computeHash(receiptPayload);
480
+ const receiptHash = computeChainHash(null, payloadHash);
481
+
482
+ return {
483
+ ...receiptPayload,
484
+ payloadHash,
485
+ previousHash: null,
486
+ receiptHash
487
+ };
488
+ }
489
+
490
+ // ============================================================================
491
+ // VERIFICATION FUNCTIONS
492
+ // ============================================================================
493
+
494
+ /**
495
+ * Verify an observation receipt
496
+ * @param {ObservationReceipt} receipt - Receipt to verify
497
+ * @param {ObservationReceipt|null} [previousReceipt] - Previous receipt for chain verification
498
+ * @returns {{valid: boolean, errors: string[], checks: Object}}
499
+ */
500
+ export function verifyObservationReceipt(receipt, previousReceipt = null) {
501
+ const errors = [];
502
+ const checks = {
503
+ hasRequiredFields: true,
504
+ obsHashValid: true,
505
+ chainValid: true,
506
+ receiptHashValid: true
507
+ };
508
+
509
+ // Check required fields
510
+ const requiredFields = ['id', 'agentId', 'obsHash', 'payloadHash', 'receiptHash'];
511
+ for (const field of requiredFields) {
512
+ if (!receipt[field]) {
513
+ checks.hasRequiredFields = false;
514
+ errors.push(`Missing required field: ${field}`);
515
+ }
516
+ }
517
+
518
+ // Verify observation hash
519
+ if (receipt.observation?.payload) {
520
+ const expectedObsHash = computeHash(receipt.observation.payload);
521
+ if (expectedObsHash !== receipt.obsHash) {
522
+ checks.obsHashValid = false;
523
+ errors.push('Observation hash mismatch');
524
+ }
525
+ }
526
+
527
+ // Verify chain
528
+ if (previousReceipt) {
529
+ if (receipt.prevHash !== previousReceipt.obsHash) {
530
+ checks.chainValid = false;
531
+ errors.push('Previous observation hash mismatch');
532
+ }
533
+ if (receipt.previousHash !== previousReceipt.receiptHash) {
534
+ checks.chainValid = false;
535
+ errors.push('Previous receipt hash mismatch');
536
+ }
537
+ }
538
+
539
+ // Verify receipt hash
540
+ const expectedChainHash = computeChainHash(receipt.previousHash, receipt.payloadHash);
541
+ if (expectedChainHash !== receipt.receiptHash) {
542
+ checks.receiptHashValid = false;
543
+ errors.push('Receipt hash mismatch');
544
+ }
545
+
546
+ return {
547
+ valid: errors.length === 0,
548
+ errors,
549
+ checks
550
+ };
551
+ }
552
+
553
+ /**
554
+ * Verify a merge receipt
555
+ * @param {MergeReceipt} receipt - Receipt to verify
556
+ * @returns {{valid: boolean, errors: string[], checks: Object}}
557
+ */
558
+ export function verifyMergeReceipt(receipt) {
559
+ const errors = [];
560
+ const checks = {
561
+ hasRequiredFields: true,
562
+ merkleRootValid: true,
563
+ receiptHashValid: true
564
+ };
565
+
566
+ // Check required fields
567
+ const requiredFields = ['id', 'mergeId', 'shards', 'merkleRoot', 'payloadHash', 'receiptHash'];
568
+ for (const field of requiredFields) {
569
+ if (!receipt[field]) {
570
+ checks.hasRequiredFields = false;
571
+ errors.push(`Missing required field: ${field}`);
572
+ }
573
+ }
574
+
575
+ // Verify merkle root
576
+ if (receipt.shards) {
577
+ const expectedRoot = buildMerkleTree(receipt.shards).root;
578
+ if (expectedRoot !== receipt.merkleRoot) {
579
+ checks.merkleRootValid = false;
580
+ errors.push('Merkle root mismatch');
581
+ }
582
+ }
583
+
584
+ // Verify receipt hash
585
+ const expectedChainHash = computeChainHash(null, receipt.payloadHash);
586
+ if (expectedChainHash !== receipt.receiptHash) {
587
+ checks.receiptHashValid = false;
588
+ errors.push('Receipt hash mismatch');
589
+ }
590
+
591
+ return {
592
+ valid: errors.length === 0,
593
+ errors,
594
+ checks
595
+ };
596
+ }
597
+
598
+ /**
599
+ * Verify a verification receipt
600
+ * @param {VerificationReceipt} receipt - Receipt to verify
601
+ * @returns {{valid: boolean, errors: string[], checks: Object}}
602
+ */
603
+ export function verifyVerificationReceipt(receipt) {
604
+ const errors = [];
605
+ const checks = {
606
+ hasRequiredFields: true,
607
+ confidenceValid: true,
608
+ receiptHashValid: true
609
+ };
610
+
611
+ // Check required fields
612
+ const requiredFields = ['id', 'verificationId', 'mergeReceiptHash', 'payloadHash', 'receiptHash'];
613
+ for (const field of requiredFields) {
614
+ if (!receipt[field]) {
615
+ checks.hasRequiredFields = false;
616
+ errors.push(`Missing required field: ${field}`);
617
+ }
618
+ }
619
+
620
+ // Verify confidence calculation
621
+ if (receipt.verifications) {
622
+ const expectedConfidence = calculateConfidenceScore(receipt.verifications);
623
+ if (Math.abs(expectedConfidence - receipt.confidenceScore) > 0.001) {
624
+ checks.confidenceValid = false;
625
+ errors.push('Confidence score mismatch');
626
+ }
627
+ }
628
+
629
+ // Verify receipt hash
630
+ const expectedChainHash = computeChainHash(null, receipt.payloadHash);
631
+ if (expectedChainHash !== receipt.receiptHash) {
632
+ checks.receiptHashValid = false;
633
+ errors.push('Receipt hash mismatch');
634
+ }
635
+
636
+ return {
637
+ valid: errors.length === 0,
638
+ errors,
639
+ checks
640
+ };
641
+ }
642
+
643
+ // ============================================================================
644
+ // RECEIPT CHAIN BUILDER
645
+ // ============================================================================
646
+
647
+ /**
648
+ * ReceiptChainBuilder - Build chains of observation receipts
649
+ */
650
+ export class ReceiptChainBuilder {
651
+ /**
652
+ *
653
+ */
654
+ constructor() {
655
+ /** @type {Map<string, ObservationReceipt[]>} */
656
+ this.chains = new Map();
657
+ }
658
+
659
+ /**
660
+ * Add observation to agent's chain
661
+ * @param {string} agentId - Agent ID
662
+ * @param {Object} payload - Observation payload
663
+ * @param {string} domain - Observation domain
664
+ * @returns {ObservationReceipt} Created receipt
665
+ */
666
+ addObservation(agentId, payload, domain) {
667
+ if (!this.chains.has(agentId)) {
668
+ this.chains.set(agentId, []);
669
+ }
670
+
671
+ const chain = this.chains.get(agentId);
672
+ const previousReceipt = chain.length > 0 ? chain[chain.length - 1] : null;
673
+ const observationIndex = chain.length + 1;
674
+
675
+ const receipt = createObservationReceipt({
676
+ agentId,
677
+ observationIndex,
678
+ payload,
679
+ domain,
680
+ previousReceipt
681
+ });
682
+
683
+ chain.push(receipt);
684
+ return receipt;
685
+ }
686
+
687
+ /**
688
+ * Get chain for agent
689
+ * @param {string} agentId - Agent ID
690
+ * @returns {ObservationReceipt[]} Agent's receipt chain
691
+ */
692
+ getChain(agentId) {
693
+ return this.chains.get(agentId) || [];
694
+ }
695
+
696
+ /**
697
+ * Get final hash for agent's chain
698
+ * @param {string} agentId - Agent ID
699
+ * @returns {string|null} Final chain hash or null
700
+ */
701
+ getFinalHash(agentId) {
702
+ const chain = this.chains.get(agentId);
703
+ if (!chain || chain.length === 0) return null;
704
+ return chain[chain.length - 1].obsHash;
705
+ }
706
+
707
+ /**
708
+ * Get all agent IDs
709
+ * @returns {string[]}
710
+ */
711
+ getAgentIds() {
712
+ return Array.from(this.chains.keys());
713
+ }
714
+
715
+ /**
716
+ * Get shard info for all agents
717
+ * @returns {ShardInfo[]}
718
+ */
719
+ getShardInfo() {
720
+ const shards = [];
721
+ for (const [agentId, chain] of this.chains) {
722
+ if (chain.length > 0) {
723
+ shards.push({
724
+ agentId,
725
+ chainFinalHash: chain[chain.length - 1].obsHash,
726
+ obsCount: chain.length,
727
+ domain: chain[0].domain
728
+ });
729
+ }
730
+ }
731
+ return shards;
732
+ }
733
+
734
+ /**
735
+ * Verify entire chain for agent
736
+ * @param {string} agentId - Agent ID
737
+ * @returns {{valid: boolean, errors: string[]}}
738
+ */
739
+ verifyChain(agentId) {
740
+ const chain = this.chains.get(agentId);
741
+ if (!chain) {
742
+ return { valid: false, errors: ['No chain found for agent'] };
743
+ }
744
+
745
+ const allErrors = [];
746
+ for (let i = 0; i < chain.length; i++) {
747
+ const previous = i > 0 ? chain[i - 1] : null;
748
+ const result = verifyObservationReceipt(chain[i], previous);
749
+ if (!result.valid) {
750
+ allErrors.push(...result.errors.map(e => `[${i}] ${e}`));
751
+ }
752
+ }
753
+
754
+ return {
755
+ valid: allErrors.length === 0,
756
+ errors: allErrors
757
+ };
758
+ }
759
+
760
+ /**
761
+ * Clear all chains
762
+ */
763
+ clear() {
764
+ this.chains.clear();
765
+ }
766
+ }
767
+
768
+ /**
769
+ * Create receipt chain builder
770
+ * @returns {ReceiptChainBuilder}
771
+ */
772
+ export function createReceiptChainBuilder() {
773
+ return new ReceiptChainBuilder();
774
+ }
775
+
776
+ // ============================================================================
777
+ // EXPORTS
778
+ // ============================================================================
779
+
780
+ export default {
781
+ // Constants
782
+ HASH_ALGORITHM,
783
+ RECEIPT_VERSION,
784
+ RECEIPT_TYPES,
785
+
786
+ // Hash utilities
787
+ computeHash,
788
+ computeChainHash,
789
+ deterministicSerialize,
790
+
791
+ // Merkle
792
+ buildMerkleTree,
793
+ verifyMerkleRoot,
794
+
795
+ // Receipt creation
796
+ createObservationReceipt,
797
+ createMergeReceipt,
798
+ createVerificationReceipt,
799
+
800
+ // Verification
801
+ verifyObservationReceipt,
802
+ verifyMergeReceipt,
803
+ verifyVerificationReceipt,
804
+
805
+ // Confidence
806
+ calculateConfidenceScore,
807
+ getFailedChecks,
808
+ summarizeVerification,
809
+
810
+ // Chain builder
811
+ ReceiptChainBuilder,
812
+ createReceiptChainBuilder
813
+ };