yakmesh 2.8.2 → 3.0.0

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 (232) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/Caddyfile +77 -0
  4. package/README.md +119 -29
  5. package/adapters/adapter-mlv-bible/README.md +124 -0
  6. package/adapters/adapter-mlv-bible/index.js +400 -0
  7. package/adapters/chat-mod-adapter.js +532 -0
  8. package/adapters/content-adapter.js +273 -0
  9. package/content/api.js +50 -41
  10. package/content/index.js +2 -2
  11. package/content/store.js +355 -173
  12. package/dashboard/index.html +19 -3
  13. package/database/replication.js +117 -37
  14. package/docs/CRYPTO-AGILITY.md +204 -0
  15. package/docs/MTLS-RESEARCH.md +367 -0
  16. package/docs/NAMCHE-SPEC.md +681 -0
  17. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  18. package/docs/PRECISION-DISCLOSURE.md +96 -0
  19. package/docs/README.md +76 -0
  20. package/docs/ROADMAP-2.4.0.md +447 -0
  21. package/docs/ROADMAP-2.5.0.md +244 -0
  22. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  23. package/docs/SST-INTEGRATION.md +712 -0
  24. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  25. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  26. package/docs/TME-FAQ.md +221 -0
  27. package/docs/WHITEPAPER.md +623 -0
  28. package/docs/adapters.html +1001 -0
  29. package/docs/advanced-systems.html +1045 -0
  30. package/docs/annex.html +1046 -0
  31. package/docs/api.html +970 -0
  32. package/docs/business/response-templates.md +160 -0
  33. package/docs/c2c.html +1225 -0
  34. package/docs/cli.html +1332 -0
  35. package/docs/configuration.html +1248 -0
  36. package/docs/darshan.html +1085 -0
  37. package/docs/dharma.html +966 -0
  38. package/docs/docs-bundle.html +1075 -0
  39. package/docs/docs.css +3120 -0
  40. package/docs/docs.js +556 -0
  41. package/docs/doko.html +969 -0
  42. package/docs/geo-proof.html +858 -0
  43. package/docs/getting-started.html +840 -0
  44. package/docs/gumba-tutorial.html +1144 -0
  45. package/docs/gumba.html +1098 -0
  46. package/docs/index.html +914 -0
  47. package/docs/jhilke.html +1312 -0
  48. package/docs/karma.html +1100 -0
  49. package/docs/katha.html +1037 -0
  50. package/docs/lama.html +978 -0
  51. package/docs/mandala.html +1067 -0
  52. package/docs/mani.html +964 -0
  53. package/docs/mantra.html +967 -0
  54. package/docs/mesh.html +1409 -0
  55. package/docs/nakpak.html +869 -0
  56. package/docs/namche.html +928 -0
  57. package/docs/nav-order.json +53 -0
  58. package/docs/prahari.html +1043 -0
  59. package/docs/prism-bash.min.js +1 -0
  60. package/docs/prism-javascript.min.js +1 -0
  61. package/docs/prism-json.min.js +1 -0
  62. package/docs/prism-tomorrow.min.css +1 -0
  63. package/docs/prism.min.js +1 -0
  64. package/docs/privacy.html +699 -0
  65. package/docs/quick-reference.html +1181 -0
  66. package/docs/sakshi.html +1402 -0
  67. package/docs/sandboxing.md +386 -0
  68. package/docs/seva.html +911 -0
  69. package/docs/sherpa.html +871 -0
  70. package/docs/studio.html +860 -0
  71. package/docs/stupa.html +995 -0
  72. package/docs/tailwind.min.css +2 -0
  73. package/docs/tattva.html +1332 -0
  74. package/docs/terms.html +686 -0
  75. package/docs/time-server-deployment.md +166 -0
  76. package/docs/time-sources.html +1392 -0
  77. package/docs/tivra.html +1127 -0
  78. package/docs/trademark-policy.html +686 -0
  79. package/docs/tribhuj.html +1183 -0
  80. package/docs/trust-security.html +1029 -0
  81. package/docs/tutorials/backup-recovery.html +654 -0
  82. package/docs/tutorials/dashboard.html +604 -0
  83. package/docs/tutorials/domain-setup.html +605 -0
  84. package/docs/tutorials/host-website.html +456 -0
  85. package/docs/tutorials/mesh-network.html +505 -0
  86. package/docs/tutorials/mobile-access.html +445 -0
  87. package/docs/tutorials/privacy.html +467 -0
  88. package/docs/tutorials/raspberry-pi.html +600 -0
  89. package/docs/tutorials/security-basics.html +539 -0
  90. package/docs/tutorials/share-files.html +431 -0
  91. package/docs/tutorials/troubleshooting.html +637 -0
  92. package/docs/tutorials/trust-karma.html +419 -0
  93. package/docs/tutorials/yak-protocol.html +456 -0
  94. package/docs/tutorials.html +1034 -0
  95. package/docs/vani.html +1270 -0
  96. package/docs/webserver.html +809 -0
  97. package/docs/yak-protocol.html +940 -0
  98. package/docs/yak-timeserver-design.md +475 -0
  99. package/docs/yakapp.html +1015 -0
  100. package/docs/ypc27.html +1069 -0
  101. package/docs/yurt.html +1344 -0
  102. package/embedded-docs/bundle.js +334 -74
  103. package/gossip/protocol.js +247 -27
  104. package/identity/key-resolver.js +262 -0
  105. package/identity/machine-seed.js +632 -0
  106. package/identity/node-key.js +669 -368
  107. package/identity/tribhuj-ratchet.js +506 -0
  108. package/knowledge-base.js +37 -8
  109. package/launcher/yakmesh.bat +62 -0
  110. package/launcher/yakmesh.sh +70 -0
  111. package/mesh/annex.js +462 -108
  112. package/mesh/beacon-broadcast.js +113 -1
  113. package/mesh/darshan.js +1718 -0
  114. package/mesh/gumba.js +1567 -0
  115. package/mesh/jhilke.js +651 -0
  116. package/mesh/katha.js +1012 -0
  117. package/mesh/nakpak-routing.js +8 -5
  118. package/mesh/network.js +724 -34
  119. package/mesh/pulse-sync.js +4 -1
  120. package/mesh/rate-limiter.js +127 -15
  121. package/mesh/seva.js +526 -0
  122. package/mesh/sherpa-discovery.js +89 -8
  123. package/mesh/sybil-defense.js +19 -5
  124. package/mesh/temporal-encoder.js +4 -3
  125. package/mesh/vani.js +1364 -0
  126. package/mesh/yurt.js +1340 -0
  127. package/models/entropy-sentinel.onnx +0 -0
  128. package/models/karma-trust.onnx +0 -0
  129. package/models/manifest.json +43 -0
  130. package/models/sakshi-anomaly.onnx +0 -0
  131. package/oracle/code-proof-protocol.js +7 -6
  132. package/oracle/codebase-lock.js +257 -28
  133. package/oracle/index.js +74 -15
  134. package/oracle/ma902-snmp.js +678 -0
  135. package/oracle/module-sealer.js +5 -3
  136. package/oracle/network-identity.js +16 -0
  137. package/oracle/packet-checksum.js +201 -0
  138. package/oracle/sst.js +579 -0
  139. package/oracle/ternary-144t.js +714 -0
  140. package/oracle/ternary-ml.js +481 -0
  141. package/oracle/time-api.js +239 -0
  142. package/oracle/time-source.js +137 -47
  143. package/oracle/validation-oracle-hardened.js +1111 -1071
  144. package/oracle/validation-oracle.js +4 -2
  145. package/oracle/ypc27.js +211 -0
  146. package/package.json +20 -3
  147. package/protocol/yak-handler.js +35 -9
  148. package/protocol/yak-protocol.js +28 -13
  149. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  150. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  151. package/sbom.json +87 -0
  152. package/scripts/security-audit.mjs +264 -0
  153. package/scripts/update-docs-nav.js +194 -0
  154. package/scripts/update-docs-sidebar.cjs +164 -0
  155. package/security/crypto-config.js +4 -3
  156. package/security/dharma-moderation.js +517 -0
  157. package/security/doko-identity.js +193 -143
  158. package/security/domain-consensus.js +86 -85
  159. package/security/fs-hardening.js +620 -0
  160. package/security/hardware-attestation.js +5 -3
  161. package/security/hybrid-trust.js +227 -87
  162. package/security/karma-rate-limiter.js +692 -0
  163. package/security/khata-protocol.js +22 -21
  164. package/security/khata-trust-integration.js +277 -150
  165. package/security/memory-safety.js +635 -0
  166. package/security/mesh-auth.js +11 -10
  167. package/security/mesh-revocation.js +373 -5
  168. package/security/namche-gateway.js +298 -69
  169. package/security/sakshi.js +460 -3
  170. package/security/sangha.js +770 -0
  171. package/security/secure-config.js +473 -0
  172. package/security/silicon-parity.js +13 -10
  173. package/security/steadywatch.js +1142 -0
  174. package/security/strike-system.js +32 -3
  175. package/security/temporal-signing.js +488 -0
  176. package/security/trit-commitment.js +464 -0
  177. package/server/crypto/annex.js +247 -0
  178. package/server/darshan-api.js +343 -0
  179. package/server/index.js +3259 -362
  180. package/server/komm-api.js +668 -0
  181. package/utils/accel.js +2273 -0
  182. package/utils/ternary-id.js +79 -0
  183. package/utils/verify-worker.js +57 -0
  184. package/webserver/index.js +95 -5
  185. package/assets/yakmesh-logo.png +0 -0
  186. package/assets/yakmesh-logo.svg +0 -80
  187. package/assets/yakmesh-logo2.png +0 -0
  188. package/assets/yakmesh-logo2sm.png +0 -0
  189. package/assets/ymsm.png +0 -0
  190. package/website/assets/silhouettes/adapters.svg +0 -107
  191. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  192. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  193. package/website/assets/silhouettes/base-camp.svg +0 -81
  194. package/website/assets/silhouettes/bridge.svg +0 -69
  195. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  196. package/website/assets/silhouettes/doko-basket.svg +0 -70
  197. package/website/assets/silhouettes/fortress.svg +0 -93
  198. package/website/assets/silhouettes/gateway.svg +0 -54
  199. package/website/assets/silhouettes/gears.svg +0 -93
  200. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  201. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  202. package/website/assets/silhouettes/lama-council.svg +0 -141
  203. package/website/assets/silhouettes/mandala-network.svg +0 -169
  204. package/website/assets/silhouettes/mani-stones.svg +0 -149
  205. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  206. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  207. package/website/assets/silhouettes/nakpak.svg +0 -56
  208. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  209. package/website/assets/silhouettes/sherpa.svg +0 -69
  210. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  211. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  212. package/website/assets/silhouettes/terminal.svg +0 -74
  213. package/website/assets/silhouettes/webserver.svg +0 -145
  214. package/website/assets/silhouettes/yak.svg +0 -78
  215. package/website/assets/yakmesh-logo.png +0 -0
  216. package/website/assets/yakmesh-logo.webp +0 -0
  217. package/website/assets/yakmesh-logo128x140.webp +0 -0
  218. package/website/assets/yakmesh-logo2.png +0 -0
  219. package/website/assets/yakmesh-logo2.svg +0 -51
  220. package/website/assets/yakmesh-logo40x44.webp +0 -0
  221. package/website/assets/yakmesh.gif +0 -0
  222. package/website/assets/yakmesh.ico +0 -0
  223. package/website/assets/yakmesh.jpg +0 -0
  224. package/website/assets/yakmesh.pdf +0 -0
  225. package/website/assets/yakmesh.png +0 -0
  226. package/website/assets/yakmesh.svg +0 -70
  227. package/website/assets/yakmesh128.webp +0 -0
  228. package/website/assets/yakmesh32.png +0 -0
  229. package/website/assets/yakmesh32.svg +0 -65
  230. package/website/assets/yakmesh32o.ico +0 -2
  231. package/website/assets/yakmesh32o.svg +0 -65
  232. package/website/assets/yakmesh32o.svgz +0 -0
@@ -11,12 +11,18 @@
11
11
  * @version 1.0.0
12
12
  */
13
13
 
14
- import { sha3_256 } from '@noble/hashes/sha3.js';
14
+ import { sha3_256 as _nobleSha3 } from '@noble/hashes/sha3.js';
15
15
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
16
16
  import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
17
+ // ACCEL: Hardware-accelerated crypto
18
+ import { sha3_256, mlDsa65Sign, mlDsa65Verify } from '../utils/accel.js';
17
19
  import { EventEmitter } from 'events';
18
20
  import { createLogger } from '../utils/logger.js';
19
21
 
22
+ // ═══ TRIBHUJ — Balanced ternary for revocation status ═══
23
+ // POSITIVE: retained (not revoked), NEUTRAL: forming (insufficient network), NEGATIVE: revoked
24
+ import { POSITIVE, NEUTRAL, NEGATIVE } from '../oracle/tribhuj.js';
25
+
20
26
  const log = createLogger('security:mesh-revocation');
21
27
 
22
28
  /**
@@ -85,7 +91,7 @@ export class Attestation {
85
91
  */
86
92
  sign(privateKey) {
87
93
  const bytes = this.getSignableBytes();
88
- const sig = ml_dsa65.sign(bytes, privateKey);
94
+ const sig = mlDsa65Sign(bytes, privateKey);
89
95
  this.signature = bytesToHex(sig);
90
96
  return this;
91
97
  }
@@ -100,7 +106,7 @@ export class Attestation {
100
106
  const bytes = this.getSignableBytes();
101
107
  const sig = hexToBytes(this.signature);
102
108
  const pubKey = typeof publicKey === 'string' ? hexToBytes(publicKey) : publicKey;
103
- return ml_dsa65.verify(sig, bytes, pubKey);
109
+ return mlDsa65Verify(sig, bytes, pubKey);
104
110
  } catch (e) {
105
111
  return false;
106
112
  }
@@ -220,11 +226,16 @@ export class RevocationState {
220
226
 
221
227
  /**
222
228
  * Check if threshold is met
223
- * This is THE mathematical determination
229
+ * This is THE mathematical determination.
230
+ *
231
+ * Returns `revoked` boolean (backward compat) plus `status` trit:
232
+ * POSITIVE (+1): retained — below threshold, DOKO is valid
233
+ * NEUTRAL ( 0): forming — insufficient network to determine
234
+ * NEGATIVE (-1): revoked — threshold met, DOKO is revoked
224
235
  */
225
236
  isRevoked(activeNodeCount, config = DEFAULT_CONFIG) {
226
237
  if (activeNodeCount < config.minNodes) {
227
- return { revoked: false, reason: 'INSUFFICIENT_NETWORK' };
238
+ return { revoked: false, status: NEUTRAL, reason: 'INSUFFICIENT_NETWORK' };
228
239
  }
229
240
 
230
241
  const threshold = Math.ceil(activeNodeCount * config.threshold);
@@ -233,6 +244,7 @@ export class RevocationState {
233
244
  if (count >= threshold) {
234
245
  return {
235
246
  revoked: true,
247
+ status: NEGATIVE,
236
248
  reason: this.primaryReason,
237
249
  attestationCount: count,
238
250
  threshold,
@@ -243,6 +255,7 @@ export class RevocationState {
243
255
 
244
256
  return {
245
257
  revoked: false,
258
+ status: POSITIVE,
246
259
  reason: 'BELOW_THRESHOLD',
247
260
  attestationCount: count,
248
261
  threshold,
@@ -597,6 +610,361 @@ export const MESH_REVOCATION_MESSAGES = {
597
610
  ATTESTATION: 'mesh:attestation', // Single attestation
598
611
  ATTESTATIONS_SYNC: 'mesh:attest-sync', // Bulk attestation sync
599
612
  REVOCATION_CERT: 'mesh:revoke-cert', // Completed revocation certificate
613
+ ATTESTATION_DISPUTE: 'mesh:attest-dispute', // Challenge an attestation
600
614
  };
601
615
 
616
+ // =============================================================================
617
+ // ATTESTATION ACCOUNTABILITY
618
+ // =============================================================================
619
+
620
+ /**
621
+ * ZIMMEDARI - Attestation Accountability Tracker
622
+ * ज़िम्मेदारी (Hindi: "responsibility, accountability")
623
+ *
624
+ * Tracks the accuracy of attestations filed by nodes.
625
+ * If a node files provably false attestations, they lose karma.
626
+ *
627
+ * This prevents:
628
+ * - Coordinated attacks against innocent nodes
629
+ * - Frivolous attestations without evidence
630
+ * - "Crying wolf" revocation attempts
631
+ *
632
+ * Accountability is determined by:
633
+ * 1. If attested node later proves legitimacy (attestation was wrong)
634
+ * 2. If attestation is counter-attested by majority
635
+ * 3. If attestation is withdrawn by filer
636
+ */
637
+
638
+ /**
639
+ * Attestation accuracy states
640
+ */
641
+ export const ATTESTATION_ACCURACY = Object.freeze({
642
+ PENDING: 'pending', // Not yet determined
643
+ VALIDATED: 'validated', // Attestation was correct
644
+ DISPUTED: 'disputed', // Under dispute
645
+ FALSE_POSITIVE: 'false_positive', // Attested node was innocent
646
+ WITHDRAWN: 'withdrawn', // Filer withdrew attestation
647
+ });
648
+
649
+ /**
650
+ * AttestationAccountability - Tracks who files what and their accuracy
651
+ */
652
+ export class AttestationAccountability extends EventEmitter {
653
+ constructor(options = {}) {
654
+ super();
655
+
656
+ // nodeId -> AttesterRecord
657
+ this.attesters = new Map();
658
+
659
+ // dokoId -> dispute info
660
+ this.disputes = new Map();
661
+
662
+ this.config = {
663
+ // Accuracy tracking window (ms)
664
+ trackingWindow: options.trackingWindow || 30 * 24 * 60 * 60 * 1000, // 30 days
665
+
666
+ // Minimum attestations before accuracy matters
667
+ minAttestationsForAccuracy: options.minAttestationsForAccuracy || 5,
668
+
669
+ // Accuracy thresholds
670
+ accuracyThreshold: {
671
+ unreliable: options.unreliableThreshold || 0.5, // Below 50% = unreliable
672
+ suspicious: options.suspiciousThreshold || 0.7, // Below 70% = suspicious
673
+ reliable: options.reliableThreshold || 0.9, // Above 90% = reliable
674
+ },
675
+
676
+ // Karma penalties for false attestations
677
+ falseAttestationPenalty: options.falseAttestationPenalty || 0.2,
678
+
679
+ // Karma reward for validated attestations
680
+ validatedReward: options.validatedReward || 0.02,
681
+ };
682
+
683
+ log.info('zimmedari', 'Attestation accountability initialized');
684
+ }
685
+
686
+ /**
687
+ * Record an attestation being filed
688
+ */
689
+ recordAttestation(attesterId, targetDokoId, reason) {
690
+ let record = this.attesters.get(attesterId);
691
+ if (!record) {
692
+ record = this._createAttesterRecord(attesterId);
693
+ this.attesters.set(attesterId, record);
694
+ }
695
+
696
+ record.totalFiled++;
697
+ record.lastFiled = Date.now();
698
+ record.attestations.push({
699
+ targetDokoId,
700
+ reason,
701
+ filedAt: Date.now(),
702
+ accuracy: ATTESTATION_ACCURACY.PENDING,
703
+ });
704
+
705
+ // Limit history size
706
+ if (record.attestations.length > 1000) {
707
+ record.attestations = record.attestations.slice(-500);
708
+ }
709
+ }
710
+
711
+ /**
712
+ * Mark an attestation as validated (was correct)
713
+ */
714
+ markValidated(attesterId, targetDokoId) {
715
+ const record = this.attesters.get(attesterId);
716
+ if (!record) return false;
717
+
718
+ const attestation = record.attestations.find(
719
+ a => a.targetDokoId === targetDokoId && a.accuracy === ATTESTATION_ACCURACY.PENDING
720
+ );
721
+
722
+ if (!attestation) return false;
723
+
724
+ attestation.accuracy = ATTESTATION_ACCURACY.VALIDATED;
725
+ attestation.validatedAt = Date.now();
726
+ record.validated++;
727
+
728
+ this.emit('attestation-validated', {
729
+ attesterId,
730
+ targetDokoId,
731
+ accuracy: this.getAccuracy(attesterId),
732
+ });
733
+
734
+ return true;
735
+ }
736
+
737
+ /**
738
+ * Mark an attestation as false positive (was wrong)
739
+ * This triggers karma penalty
740
+ */
741
+ markFalsePositive(attesterId, targetDokoId, evidence = null) {
742
+ const record = this.attesters.get(attesterId);
743
+ if (!record) return false;
744
+
745
+ const attestation = record.attestations.find(
746
+ a => a.targetDokoId === targetDokoId && a.accuracy === ATTESTATION_ACCURACY.PENDING
747
+ );
748
+
749
+ if (!attestation) return false;
750
+
751
+ attestation.accuracy = ATTESTATION_ACCURACY.FALSE_POSITIVE;
752
+ attestation.falsifiedAt = Date.now();
753
+ attestation.evidence = evidence;
754
+ record.falsePositives++;
755
+
756
+ this.emit('attestation-false-positive', {
757
+ attesterId,
758
+ targetDokoId,
759
+ evidence,
760
+ falsePositiveCount: record.falsePositives,
761
+ accuracy: this.getAccuracy(attesterId),
762
+ penalty: this.config.falseAttestationPenalty,
763
+ });
764
+
765
+ log.warn('zimmedari', 'False positive attestation recorded', {
766
+ attesterId,
767
+ targetDokoId,
768
+ accuracy: this.getAccuracy(attesterId),
769
+ });
770
+
771
+ return true;
772
+ }
773
+
774
+ /**
775
+ * Mark an attestation as disputed (under investigation)
776
+ */
777
+ markDisputed(attesterId, targetDokoId, disputeReason) {
778
+ const record = this.attesters.get(attesterId);
779
+ if (!record) return false;
780
+
781
+ const attestation = record.attestations.find(
782
+ a => a.targetDokoId === targetDokoId && a.accuracy === ATTESTATION_ACCURACY.PENDING
783
+ );
784
+
785
+ if (!attestation) return false;
786
+
787
+ attestation.accuracy = ATTESTATION_ACCURACY.DISPUTED;
788
+ attestation.disputedAt = Date.now();
789
+ attestation.disputeReason = disputeReason;
790
+
791
+ this.disputes.set(targetDokoId, {
792
+ attesterId,
793
+ disputeReason,
794
+ disputedAt: Date.now(),
795
+ });
796
+
797
+ this.emit('attestation-disputed', {
798
+ attesterId,
799
+ targetDokoId,
800
+ disputeReason,
801
+ });
802
+
803
+ return true;
804
+ }
805
+
806
+ /**
807
+ * File a dispute against an attestation
808
+ * Returns dispute info for gossip propagation
809
+ */
810
+ fileDispute(disputerId, targetDokoId, originalAttesterId, evidence) {
811
+ const dispute = {
812
+ type: 'attestation_dispute',
813
+ disputerId,
814
+ targetDokoId,
815
+ originalAttesterId,
816
+ evidence,
817
+ filedAt: Date.now(),
818
+ };
819
+
820
+ // Mark the original attestation as disputed
821
+ this.markDisputed(originalAttesterId, targetDokoId, `Disputed by ${disputerId}`);
822
+
823
+ log.info('zimmedari', 'Dispute filed against attestation', {
824
+ disputerId,
825
+ originalAttesterId,
826
+ targetDokoId,
827
+ });
828
+
829
+ return dispute;
830
+ }
831
+
832
+ /**
833
+ * Get accuracy ratio for an attester
834
+ */
835
+ getAccuracy(attesterId) {
836
+ const record = this.attesters.get(attesterId);
837
+ if (!record) return null;
838
+
839
+ const resolved = record.validated + record.falsePositives;
840
+ if (resolved < this.config.minAttestationsForAccuracy) {
841
+ return {
842
+ accuracy: null,
843
+ reason: 'INSUFFICIENT_DATA',
844
+ resolved,
845
+ required: this.config.minAttestationsForAccuracy,
846
+ };
847
+ }
848
+
849
+ const accuracyRatio = record.validated / resolved;
850
+
851
+ let status = 'normal';
852
+ if (accuracyRatio >= this.config.accuracyThreshold.reliable) {
853
+ status = 'reliable';
854
+ } else if (accuracyRatio < this.config.accuracyThreshold.unreliable) {
855
+ status = 'unreliable';
856
+ } else if (accuracyRatio < this.config.accuracyThreshold.suspicious) {
857
+ status = 'suspicious';
858
+ }
859
+
860
+ return {
861
+ accuracy: accuracyRatio,
862
+ status,
863
+ validated: record.validated,
864
+ falsePositives: record.falsePositives,
865
+ pending: record.attestations.filter(a => a.accuracy === ATTESTATION_ACCURACY.PENDING).length,
866
+ total: record.totalFiled,
867
+ };
868
+ }
869
+
870
+ /**
871
+ * Get attester record
872
+ */
873
+ getAttesterRecord(attesterId) {
874
+ return this.attesters.get(attesterId) || null;
875
+ }
876
+
877
+ /**
878
+ * Get statistics
879
+ */
880
+ getStats() {
881
+ let totalAttesters = 0;
882
+ let totalAttestations = 0;
883
+ let totalValidated = 0;
884
+ let totalFalsePositives = 0;
885
+ let unreliableAttesters = 0;
886
+
887
+ for (const record of this.attesters.values()) {
888
+ totalAttesters++;
889
+ totalAttestations += record.totalFiled;
890
+ totalValidated += record.validated;
891
+ totalFalsePositives += record.falsePositives;
892
+
893
+ const accuracy = this.getAccuracy(record.attesterId);
894
+ if (accuracy?.status === 'unreliable') {
895
+ unreliableAttesters++;
896
+ }
897
+ }
898
+
899
+ return {
900
+ totalAttesters,
901
+ totalAttestations,
902
+ totalValidated,
903
+ totalFalsePositives,
904
+ unreliableAttesters,
905
+ activeDisputes: this.disputes.size,
906
+ overallAccuracy: totalValidated > 0 ? totalValidated / (totalValidated + totalFalsePositives) : null,
907
+ };
908
+ }
909
+
910
+ /**
911
+ * Cleanup old records
912
+ */
913
+ cleanup() {
914
+ const now = Date.now();
915
+ const cutoff = now - this.config.trackingWindow;
916
+ let removed = 0;
917
+
918
+ for (const [attesterId, record] of this.attesters) {
919
+ // Remove attestations older than tracking window
920
+ const originalLength = record.attestations.length;
921
+ record.attestations = record.attestations.filter(a => a.filedAt > cutoff);
922
+
923
+ // If no recent attestations, remove the record
924
+ if (record.attestations.length === 0 && record.lastFiled < cutoff) {
925
+ this.attesters.delete(attesterId);
926
+ removed++;
927
+ }
928
+ }
929
+
930
+ // Cleanup old disputes
931
+ for (const [dokoId, dispute] of this.disputes) {
932
+ if (dispute.disputedAt < cutoff) {
933
+ this.disputes.delete(dokoId);
934
+ }
935
+ }
936
+
937
+ if (removed > 0) {
938
+ log.info('zimmedari', `Cleaned up ${removed} stale attester records`);
939
+ }
940
+
941
+ return removed;
942
+ }
943
+
944
+ _createAttesterRecord(attesterId) {
945
+ return {
946
+ attesterId,
947
+ totalFiled: 0,
948
+ validated: 0,
949
+ falsePositives: 0,
950
+ firstSeen: Date.now(),
951
+ lastFiled: null,
952
+ attestations: [],
953
+ };
954
+ }
955
+ }
956
+
957
+ // Singleton instance
958
+ let _accountabilityInstance = null;
959
+
960
+ /**
961
+ * Get the singleton accountability tracker instance
962
+ */
963
+ export function getAttestationAccountability(options) {
964
+ if (!_accountabilityInstance) {
965
+ _accountabilityInstance = new AttestationAccountability(options);
966
+ }
967
+ return _accountabilityInstance;
968
+ }
969
+
602
970
  export default MeshRevocation;