web-agent-bridge 3.2.0 → 3.4.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 (256) hide show
  1. package/LICENSE +84 -72
  2. package/README.ar.md +1304 -1152
  3. package/README.md +298 -1635
  4. package/bin/agent-runner.js +474 -474
  5. package/bin/cli.js +237 -138
  6. package/bin/wab-init.js +223 -0
  7. package/bin/wab.js +80 -80
  8. package/examples/azure-dns-wab.js +83 -0
  9. package/examples/bidi-agent.js +119 -119
  10. package/examples/cloudflare-wab-dns.js +121 -0
  11. package/examples/cpanel-wab-dns.js +114 -0
  12. package/examples/cross-site-agent.js +91 -91
  13. package/examples/dns-discovery-agent.js +166 -0
  14. package/examples/gcp-dns-wab.js +76 -0
  15. package/examples/governance-agent.js +169 -0
  16. package/examples/mcp-agent.js +94 -94
  17. package/examples/next-app-router/README.md +44 -44
  18. package/examples/plesk-wab-dns.js +103 -0
  19. package/examples/puppeteer-agent.js +108 -108
  20. package/examples/route53-wab-dns.js +144 -0
  21. package/examples/saas-dashboard/README.md +55 -55
  22. package/examples/safe-mode-agent.js +96 -0
  23. package/examples/shopify-hydrogen/README.md +74 -74
  24. package/examples/vision-agent.js +171 -171
  25. package/examples/wab-sign.js +74 -0
  26. package/examples/wab-verify.js +60 -0
  27. package/examples/wordpress-elementor/README.md +77 -77
  28. package/package.json +19 -6
  29. package/public/.well-known/agent-tools.json +180 -180
  30. package/public/.well-known/ai-assets.json +59 -59
  31. package/public/.well-known/security.txt +8 -0
  32. package/public/.well-known/wab.json +28 -0
  33. package/public/activate.html +368 -0
  34. package/public/adoption-metrics.html +188 -0
  35. package/public/agent-workspace.html +349 -349
  36. package/public/ai.html +198 -198
  37. package/public/api.html +413 -412
  38. package/public/azure-dns-integration.html +289 -0
  39. package/public/browser.html +486 -486
  40. package/public/cloudflare-integration.html +380 -0
  41. package/public/commander-dashboard.html +243 -243
  42. package/public/cookies.html +210 -210
  43. package/public/cpanel-integration.html +398 -0
  44. package/public/css/agent-workspace.css +1713 -1713
  45. package/public/css/premium.css +317 -317
  46. package/public/css/styles.css +1263 -1235
  47. package/public/dashboard.html +707 -706
  48. package/public/dns.html +436 -0
  49. package/public/docs.html +588 -587
  50. package/public/feed.xml +89 -89
  51. package/public/gcp-dns-integration.html +318 -0
  52. package/public/growth.html +465 -463
  53. package/public/index.html +1266 -982
  54. package/public/integrations.html +556 -0
  55. package/public/js/activate.js +145 -0
  56. package/public/js/agent-workspace.js +1740 -1740
  57. package/public/js/auth-nav.js +65 -31
  58. package/public/js/auth-redirect.js +12 -12
  59. package/public/js/cookie-consent.js +56 -56
  60. package/public/js/dns.js +438 -0
  61. package/public/js/wab-demo-page.js +721 -721
  62. package/public/js/ws-client.js +74 -74
  63. package/public/llms-full.txt +360 -360
  64. package/public/llms.txt +125 -125
  65. package/public/login.html +85 -85
  66. package/public/mesh-dashboard.html +328 -328
  67. package/public/openapi.json +669 -580
  68. package/public/phone-shield.html +281 -0
  69. package/public/plesk-integration.html +375 -0
  70. package/public/premium-dashboard.html +2489 -2489
  71. package/public/premium.html +793 -793
  72. package/public/privacy.html +297 -297
  73. package/public/provider-onboarding.html +172 -0
  74. package/public/provider-sandbox.html +134 -0
  75. package/public/providers.html +359 -0
  76. package/public/register.html +105 -105
  77. package/public/registrar-integrations.html +141 -0
  78. package/public/robots.txt +99 -87
  79. package/public/route53-integration.html +531 -0
  80. package/public/script/wab-consent.d.ts +36 -36
  81. package/public/script/wab-consent.js +104 -104
  82. package/public/script/wab-schema.js +131 -131
  83. package/public/script/wab.d.ts +108 -108
  84. package/public/script/wab.min.js +580 -580
  85. package/public/security.txt +8 -0
  86. package/public/shieldqr.html +231 -0
  87. package/public/sitemap.xml +6 -0
  88. package/public/terms.html +256 -256
  89. package/public/wab-trust.html +200 -0
  90. package/public/wab-vs-protocols.html +210 -0
  91. package/public/whitepaper.html +449 -0
  92. package/script/ai-agent-bridge.js +1754 -1754
  93. package/sdk/README.md +99 -99
  94. package/sdk/agent-mesh.js +449 -449
  95. package/sdk/auto-discovery.js +288 -0
  96. package/sdk/commander.js +262 -262
  97. package/sdk/governance.js +262 -0
  98. package/sdk/index.d.ts +464 -464
  99. package/sdk/index.js +25 -1
  100. package/sdk/multi-agent.js +318 -318
  101. package/sdk/package.json +2 -2
  102. package/sdk/safe-mode.js +221 -0
  103. package/sdk/safety-shield.js +219 -0
  104. package/sdk/schema-discovery.js +83 -83
  105. package/server/adapters/index.js +520 -520
  106. package/server/config/plans.js +367 -367
  107. package/server/config/secrets.js +102 -102
  108. package/server/control-plane/index.js +301 -301
  109. package/server/data-plane/index.js +354 -354
  110. package/server/index.js +670 -427
  111. package/server/llm/index.js +404 -404
  112. package/server/middleware/adminAuth.js +35 -35
  113. package/server/middleware/auth.js +50 -50
  114. package/server/middleware/featureGate.js +88 -88
  115. package/server/middleware/rateLimits.js +100 -100
  116. package/server/middleware/sensitiveAction.js +157 -0
  117. package/server/migrations/001_add_analytics_indexes.sql +7 -7
  118. package/server/migrations/002_premium_features.sql +418 -418
  119. package/server/migrations/003_ads_integer_cents.sql +33 -33
  120. package/server/migrations/004_agent_os.sql +158 -158
  121. package/server/migrations/005_marketplace_metering.sql +126 -126
  122. package/server/migrations/007_governance.sql +106 -0
  123. package/server/migrations/008_plans.sql +144 -0
  124. package/server/migrations/009_shieldqr.sql +30 -0
  125. package/server/migrations/010_extended_trust.sql +33 -0
  126. package/server/models/adapters/index.js +33 -33
  127. package/server/models/adapters/mysql.js +183 -183
  128. package/server/models/adapters/postgresql.js +172 -172
  129. package/server/models/adapters/sqlite.js +7 -7
  130. package/server/models/db.js +740 -681
  131. package/server/observability/failure-analysis.js +337 -337
  132. package/server/observability/index.js +394 -394
  133. package/server/protocol/capabilities.js +223 -223
  134. package/server/protocol/index.js +243 -243
  135. package/server/protocol/schema.js +584 -584
  136. package/server/registry/certification.js +271 -271
  137. package/server/registry/index.js +326 -326
  138. package/server/routes/admin-plans.js +76 -0
  139. package/server/routes/admin-premium.js +673 -671
  140. package/server/routes/admin-shieldqr.js +90 -0
  141. package/server/routes/admin-trust-monitor.js +83 -0
  142. package/server/routes/admin.js +549 -261
  143. package/server/routes/ads.js +130 -130
  144. package/server/routes/agent-workspace.js +540 -540
  145. package/server/routes/api.js +150 -150
  146. package/server/routes/auth.js +71 -71
  147. package/server/routes/billing.js +57 -45
  148. package/server/routes/commander.js +316 -316
  149. package/server/routes/demo-showcase.js +332 -332
  150. package/server/routes/demo-store.js +154 -0
  151. package/server/routes/discovery.js +2348 -417
  152. package/server/routes/gateway.js +173 -157
  153. package/server/routes/governance.js +208 -0
  154. package/server/routes/license.js +251 -240
  155. package/server/routes/mesh.js +469 -469
  156. package/server/routes/noscript.js +543 -543
  157. package/server/routes/plans.js +33 -0
  158. package/server/routes/premium-v2.js +686 -686
  159. package/server/routes/premium.js +724 -724
  160. package/server/routes/providers.js +650 -0
  161. package/server/routes/runtime.js +2148 -2147
  162. package/server/routes/shieldqr.js +88 -0
  163. package/server/routes/sovereign.js +465 -385
  164. package/server/routes/universal.js +200 -185
  165. package/server/routes/wab-api.js +850 -501
  166. package/server/runtime/container-worker.js +111 -111
  167. package/server/runtime/container.js +448 -448
  168. package/server/runtime/distributed-worker.js +362 -362
  169. package/server/runtime/event-bus.js +210 -210
  170. package/server/runtime/index.js +253 -253
  171. package/server/runtime/queue.js +599 -599
  172. package/server/runtime/replay.js +666 -666
  173. package/server/runtime/sandbox.js +266 -266
  174. package/server/runtime/scheduler.js +534 -534
  175. package/server/runtime/session-engine.js +293 -293
  176. package/server/runtime/state-manager.js +188 -188
  177. package/server/security/cross-site-redactor.js +196 -0
  178. package/server/security/dry-run.js +180 -0
  179. package/server/security/human-gate-rate-limit.js +147 -0
  180. package/server/security/human-gate-transports.js +178 -0
  181. package/server/security/human-gate.js +281 -0
  182. package/server/security/index.js +368 -368
  183. package/server/security/intent-engine.js +245 -0
  184. package/server/security/reward-guard.js +171 -0
  185. package/server/security/rollback-store.js +239 -0
  186. package/server/security/token-scope.js +404 -0
  187. package/server/security/url-policy.js +139 -0
  188. package/server/services/agent-chat.js +506 -506
  189. package/server/services/agent-learning.js +601 -575
  190. package/server/services/agent-memory.js +625 -625
  191. package/server/services/agent-mesh.js +555 -539
  192. package/server/services/agent-symphony.js +717 -717
  193. package/server/services/agent-tasks.js +1807 -1807
  194. package/server/services/api-key-engine.js +292 -261
  195. package/server/services/cluster.js +894 -894
  196. package/server/services/commander.js +738 -738
  197. package/server/services/edge-compute.js +440 -440
  198. package/server/services/email.js +233 -204
  199. package/server/services/governance.js +466 -0
  200. package/server/services/hosted-runtime.js +205 -205
  201. package/server/services/lfd.js +635 -635
  202. package/server/services/local-ai.js +389 -389
  203. package/server/services/marketplace.js +270 -270
  204. package/server/services/metering.js +182 -182
  205. package/server/services/modules/affiliate-intelligence.js +93 -93
  206. package/server/services/modules/agent-firewall.js +90 -90
  207. package/server/services/modules/bounty.js +89 -89
  208. package/server/services/modules/collective-bargaining.js +92 -92
  209. package/server/services/modules/dark-pattern.js +66 -66
  210. package/server/services/modules/gov-intelligence.js +45 -45
  211. package/server/services/modules/neural.js +55 -55
  212. package/server/services/modules/notary.js +49 -49
  213. package/server/services/modules/price-time-machine.js +86 -86
  214. package/server/services/modules/protocol.js +104 -104
  215. package/server/services/negotiation.js +439 -439
  216. package/server/services/plans.js +214 -0
  217. package/server/services/plugins.js +771 -771
  218. package/server/services/premium.js +1 -1
  219. package/server/services/price-intelligence.js +566 -566
  220. package/server/services/price-shield.js +1137 -1137
  221. package/server/services/provider-clients.js +740 -0
  222. package/server/services/reputation.js +465 -465
  223. package/server/services/search-engine.js +357 -357
  224. package/server/services/security.js +513 -513
  225. package/server/services/self-healing.js +843 -843
  226. package/server/services/shieldqr.js +322 -0
  227. package/server/services/sovereign-shield.js +542 -0
  228. package/server/services/ssl-inspector.js +42 -0
  229. package/server/services/ssl-monitor.js +167 -0
  230. package/server/services/stripe.js +205 -192
  231. package/server/services/swarm.js +788 -788
  232. package/server/services/universal-scraper.js +662 -661
  233. package/server/services/verification.js +481 -481
  234. package/server/services/vision.js +1163 -1163
  235. package/server/services/wab-crypto.js +178 -0
  236. package/server/utils/cache.js +125 -125
  237. package/server/utils/migrate.js +81 -81
  238. package/server/utils/safe-fetch.js +228 -0
  239. package/server/utils/secureFields.js +50 -50
  240. package/server/ws.js +161 -161
  241. package/templates/artisan-marketplace.yaml +104 -104
  242. package/templates/book-price-scout.yaml +98 -98
  243. package/templates/electronics-price-tracker.yaml +108 -108
  244. package/templates/flight-deal-hunter.yaml +113 -113
  245. package/templates/freelancer-direct.yaml +116 -116
  246. package/templates/grocery-price-compare.yaml +93 -93
  247. package/templates/hotel-direct-booking.yaml +113 -113
  248. package/templates/local-services.yaml +98 -98
  249. package/templates/olive-oil-tunisia.yaml +88 -88
  250. package/templates/organic-farm-fresh.yaml +101 -101
  251. package/templates/restaurant-direct.yaml +97 -97
  252. package/public/score.html +0 -263
  253. package/server/migrations/006_growth_suite.sql +0 -138
  254. package/server/routes/growth.js +0 -962
  255. package/server/services/fairness-engine.js +0 -409
  256. package/server/services/fairness.js +0 -420
@@ -1,465 +1,465 @@
1
- /**
2
- * Decentralized Reputation System
3
- * ════════════════════════════════════════════════════════════════════════
4
- * Trustless, agent-to-agent reputation protocol. When an agent completes
5
- * a successful interaction with a site (purchase, booking, query), it
6
- * leaves a cryptographically signed "trust attestation" that other agents
7
- * can verify without relying on any central authority.
8
- *
9
- * Key concepts:
10
- * - Trust Attestation: HMAC-signed proof of a successful interaction
11
- * - Reputation Score: Weighted aggregation of attestations over time
12
- * - Decay: Older attestations lose weight (freshness matters)
13
- * - Sybil Resistance: Rate-limited per agent key, cross-verified via vision
14
- */
15
-
16
- const { db } = require('../models/db');
17
- const crypto = require('crypto');
18
-
19
- // ─── Schema ──────────────────────────────────────────────────────────
20
-
21
- db.exec(`
22
- CREATE TABLE IF NOT EXISTS reputation_attestations (
23
- id TEXT PRIMARY KEY,
24
- site_id TEXT NOT NULL,
25
- agent_id TEXT NOT NULL,
26
- agent_key_hash TEXT NOT NULL,
27
- interaction_type TEXT NOT NULL CHECK(interaction_type IN (
28
- 'purchase','booking','query','form_submit','navigation','verification'
29
- )),
30
- outcome TEXT NOT NULL CHECK(outcome IN ('success','partial','failure','fraud')),
31
- price_accuracy REAL DEFAULT 1.0,
32
- response_time_ms INTEGER DEFAULT 0,
33
- data_integrity REAL DEFAULT 1.0,
34
- vision_verified INTEGER DEFAULT 0,
35
- details TEXT DEFAULT '{}',
36
- signature TEXT NOT NULL,
37
- timestamp TEXT DEFAULT (datetime('now')),
38
- expires_at TEXT,
39
- revoked INTEGER DEFAULT 0
40
- );
41
-
42
- CREATE TABLE IF NOT EXISTS reputation_scores (
43
- site_id TEXT PRIMARY KEY,
44
- total_attestations INTEGER DEFAULT 0,
45
- success_count INTEGER DEFAULT 0,
46
- failure_count INTEGER DEFAULT 0,
47
- fraud_count INTEGER DEFAULT 0,
48
- avg_price_accuracy REAL DEFAULT 1.0,
49
- avg_response_time_ms REAL DEFAULT 0,
50
- avg_data_integrity REAL DEFAULT 1.0,
51
- vision_verified_pct REAL DEFAULT 0,
52
- reputation_score REAL DEFAULT 50.0,
53
- trust_level TEXT DEFAULT 'unknown' CHECK(trust_level IN (
54
- 'unknown','emerging','trusted','verified','exemplary','suspicious','blacklisted'
55
- )),
56
- first_seen TEXT DEFAULT (datetime('now')),
57
- last_updated TEXT DEFAULT (datetime('now'))
58
- );
59
-
60
- CREATE TABLE IF NOT EXISTS reputation_agent_keys (
61
- agent_id TEXT PRIMARY KEY,
62
- public_key_hash TEXT NOT NULL,
63
- attestation_count INTEGER DEFAULT 0,
64
- created_at TEXT DEFAULT (datetime('now')),
65
- last_active TEXT DEFAULT (datetime('now')),
66
- rate_window_start TEXT,
67
- rate_window_count INTEGER DEFAULT 0,
68
- banned INTEGER DEFAULT 0
69
- );
70
-
71
- CREATE TABLE IF NOT EXISTS reputation_challenges (
72
- id TEXT PRIMARY KEY,
73
- site_id TEXT NOT NULL,
74
- challenger_agent TEXT NOT NULL,
75
- reason TEXT NOT NULL,
76
- evidence TEXT DEFAULT '{}',
77
- status TEXT DEFAULT 'pending' CHECK(status IN ('pending','investigating','upheld','dismissed')),
78
- created_at TEXT DEFAULT (datetime('now')),
79
- resolved_at TEXT
80
- );
81
-
82
- CREATE INDEX IF NOT EXISTS idx_rep_att_site ON reputation_attestations(site_id);
83
- CREATE INDEX IF NOT EXISTS idx_rep_att_agent ON reputation_attestations(agent_id);
84
- CREATE INDEX IF NOT EXISTS idx_rep_att_time ON reputation_attestations(timestamp);
85
- CREATE INDEX IF NOT EXISTS idx_rep_scores_score ON reputation_scores(reputation_score);
86
- CREATE INDEX IF NOT EXISTS idx_rep_scores_level ON reputation_scores(trust_level);
87
- `);
88
-
89
- // ─── Constants ───────────────────────────────────────────────────────
90
-
91
- const DECAY_HALF_LIFE_DAYS = 30;
92
- const MAX_ATTESTATIONS_PER_HOUR = 10;
93
- const MIN_ATTESTATIONS_FOR_TRUST = 5;
94
- const FRAUD_PENALTY_MULTIPLIER = 3;
95
- const VISION_VERIFICATION_BONUS = 1.25;
96
- const TRUST_LEVELS = {
97
- blacklisted: { min: 0, max: 10 },
98
- suspicious: { min: 10, max: 25 },
99
- unknown: { min: 25, max: 40 },
100
- emerging: { min: 40, max: 60 },
101
- trusted: { min: 60, max: 80 },
102
- verified: { min: 80, max: 92 },
103
- exemplary: { min: 92, max: 100 },
104
- };
105
-
106
- // ─── Crypto Helpers ──────────────────────────────────────────────────
107
-
108
- const SIGNING_SECRET = process.env.WAB_REPUTATION_SECRET || crypto.randomBytes(32).toString('hex');
109
-
110
- function generateAgentId() {
111
- return 'agent_' + crypto.randomBytes(16).toString('hex');
112
- }
113
-
114
- function hashAgentKey(key) {
115
- return crypto.createHash('sha256').update(key).digest('hex');
116
- }
117
-
118
- function signAttestation(data) {
119
- const payload = JSON.stringify({
120
- site_id: data.site_id,
121
- agent_id: data.agent_id,
122
- interaction_type: data.interaction_type,
123
- outcome: data.outcome,
124
- price_accuracy: data.price_accuracy,
125
- timestamp: data.timestamp
126
- });
127
- return crypto.createHmac('sha256', SIGNING_SECRET).update(payload).digest('hex');
128
- }
129
-
130
- function verifySignature(attestation) {
131
- const expected = signAttestation(attestation);
132
- return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(attestation.signature));
133
- }
134
-
135
- // ─── Agent Registration ──────────────────────────────────────────────
136
-
137
- function registerAgent(agentKey) {
138
- const agentId = generateAgentId();
139
- const keyHash = hashAgentKey(agentKey);
140
-
141
- db.prepare(`
142
- INSERT OR IGNORE INTO reputation_agent_keys (agent_id, public_key_hash)
143
- VALUES (?, ?)
144
- `).run(agentId, keyHash);
145
-
146
- return { agentId, keyHash };
147
- }
148
-
149
- function checkRateLimit(agentId) {
150
- const agent = db.prepare('SELECT * FROM reputation_agent_keys WHERE agent_id = ?').get(agentId);
151
- if (!agent) return { allowed: false, reason: 'unknown_agent' };
152
- if (agent.banned) return { allowed: false, reason: 'agent_banned' };
153
-
154
- const now = new Date();
155
- const windowStart = agent.rate_window_start ? new Date(agent.rate_window_start) : null;
156
-
157
- if (!windowStart || (now - windowStart) > 3600000) {
158
- db.prepare(`
159
- UPDATE reputation_agent_keys
160
- SET rate_window_start = datetime('now'), rate_window_count = 0
161
- WHERE agent_id = ?
162
- `).run(agentId);
163
- return { allowed: true, remaining: MAX_ATTESTATIONS_PER_HOUR };
164
- }
165
-
166
- if (agent.rate_window_count >= MAX_ATTESTATIONS_PER_HOUR) {
167
- return { allowed: false, reason: 'rate_limited', retryAfterMs: 3600000 - (now - windowStart) };
168
- }
169
-
170
- return { allowed: true, remaining: MAX_ATTESTATIONS_PER_HOUR - agent.rate_window_count };
171
- }
172
-
173
- // ─── Trust Attestation ───────────────────────────────────────────────
174
-
175
- function createAttestation({
176
- siteId, agentId, interactionType, outcome,
177
- priceAccuracy = 1.0, responseTimeMs = 0,
178
- dataIntegrity = 1.0, visionVerified = false, details = {}
179
- }) {
180
- const rateCheck = checkRateLimit(agentId);
181
- if (!rateCheck.allowed) {
182
- return { error: rateCheck.reason, retryAfterMs: rateCheck.retryAfterMs };
183
- }
184
-
185
- const id = crypto.randomBytes(16).toString('hex');
186
- const timestamp = new Date().toISOString();
187
- const expiresAt = new Date(Date.now() + DECAY_HALF_LIFE_DAYS * 4 * 86400000).toISOString();
188
-
189
- const attestation = {
190
- id, site_id: siteId, agent_id: agentId,
191
- interaction_type: interactionType, outcome,
192
- price_accuracy: Math.max(0, Math.min(1, priceAccuracy)),
193
- response_time_ms: responseTimeMs,
194
- data_integrity: Math.max(0, Math.min(1, dataIntegrity)),
195
- vision_verified: visionVerified ? 1 : 0,
196
- details: JSON.stringify(details),
197
- timestamp, expires_at: expiresAt
198
- };
199
-
200
- attestation.signature = signAttestation(attestation);
201
-
202
- db.prepare(`
203
- INSERT INTO reputation_attestations
204
- (id, site_id, agent_id, agent_key_hash, interaction_type, outcome,
205
- price_accuracy, response_time_ms, data_integrity, vision_verified,
206
- details, signature, timestamp, expires_at)
207
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
208
- `).run(
209
- attestation.id, attestation.site_id, attestation.agent_id,
210
- hashAgentKey(agentId),
211
- attestation.interaction_type, attestation.outcome,
212
- attestation.price_accuracy, attestation.response_time_ms,
213
- attestation.data_integrity, attestation.vision_verified,
214
- attestation.details, attestation.signature,
215
- attestation.timestamp, attestation.expires_at
216
- );
217
-
218
- // Update agent rate limit counter
219
- db.prepare(`
220
- UPDATE reputation_agent_keys
221
- SET rate_window_count = rate_window_count + 1,
222
- attestation_count = attestation_count + 1,
223
- last_active = datetime('now')
224
- WHERE agent_id = ?
225
- `).run(agentId);
226
-
227
- // Recalculate reputation score
228
- recalculateReputation(siteId);
229
-
230
- return { attestation: { id, signature: attestation.signature, timestamp } };
231
- }
232
-
233
- // ─── Reputation Calculation ──────────────────────────────────────────
234
-
235
- function recalculateReputation(siteId) {
236
- const attestations = db.prepare(`
237
- SELECT * FROM reputation_attestations
238
- WHERE site_id = ? AND revoked = 0 AND expires_at > datetime('now')
239
- ORDER BY timestamp DESC
240
- `).all(siteId);
241
-
242
- if (attestations.length === 0) {
243
- db.prepare(`
244
- INSERT OR REPLACE INTO reputation_scores
245
- (site_id, total_attestations, reputation_score, trust_level, last_updated)
246
- VALUES (?, 0, 50.0, 'unknown', datetime('now'))
247
- `).run(siteId);
248
- return;
249
- }
250
-
251
- const now = Date.now();
252
- let weightedSum = 0;
253
- let totalWeight = 0;
254
- let successCount = 0;
255
- let failureCount = 0;
256
- let fraudCount = 0;
257
- let priceAccSum = 0;
258
- let responseSum = 0;
259
- let integritySum = 0;
260
- let visionVerifiedCount = 0;
261
-
262
- for (const att of attestations) {
263
- const ageMs = now - new Date(att.timestamp).getTime();
264
- const ageDays = ageMs / 86400000;
265
- const decayWeight = Math.pow(0.5, ageDays / DECAY_HALF_LIFE_DAYS);
266
-
267
- let outcomeScore;
268
- switch (att.outcome) {
269
- case 'success': outcomeScore = 1.0; successCount++; break;
270
- case 'partial': outcomeScore = 0.5; break;
271
- case 'failure': outcomeScore = 0.0; failureCount++; break;
272
- case 'fraud': outcomeScore = -FRAUD_PENALTY_MULTIPLIER; fraudCount++; break;
273
- default: outcomeScore = 0;
274
- }
275
-
276
- let weight = decayWeight;
277
- if (att.vision_verified) {
278
- weight *= VISION_VERIFICATION_BONUS;
279
- visionVerifiedCount++;
280
- }
281
-
282
- const qualityScore = (
283
- outcomeScore * 0.5 +
284
- att.price_accuracy * 0.25 +
285
- att.data_integrity * 0.25
286
- );
287
-
288
- weightedSum += qualityScore * weight;
289
- totalWeight += weight;
290
- priceAccSum += att.price_accuracy;
291
- responseSum += att.response_time_ms;
292
- integritySum += att.data_integrity;
293
- }
294
-
295
- const rawScore = totalWeight > 0 ? weightedSum / totalWeight : 0.5;
296
- // Normalize to 0-100 with confidence adjustment
297
- const confidence = Math.min(attestations.length / MIN_ATTESTATIONS_FOR_TRUST, 1);
298
- const reputationScore = Math.max(0, Math.min(100,
299
- (rawScore * 50 + 50) * confidence + 50 * (1 - confidence)
300
- ));
301
-
302
- // Determine trust level
303
- let trustLevel = 'unknown';
304
- for (const [level, range] of Object.entries(TRUST_LEVELS)) {
305
- if (reputationScore >= range.min && reputationScore < range.max) {
306
- trustLevel = level;
307
- break;
308
- }
309
- }
310
- if (reputationScore >= 92) trustLevel = 'exemplary';
311
-
312
- db.prepare(`
313
- INSERT INTO reputation_scores
314
- (site_id, total_attestations, success_count, failure_count, fraud_count,
315
- avg_price_accuracy, avg_response_time_ms, avg_data_integrity,
316
- vision_verified_pct, reputation_score, trust_level, last_updated)
317
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
318
- ON CONFLICT(site_id) DO UPDATE SET
319
- total_attestations = excluded.total_attestations,
320
- success_count = excluded.success_count,
321
- failure_count = excluded.failure_count,
322
- fraud_count = excluded.fraud_count,
323
- avg_price_accuracy = excluded.avg_price_accuracy,
324
- avg_response_time_ms = excluded.avg_response_time_ms,
325
- avg_data_integrity = excluded.avg_data_integrity,
326
- vision_verified_pct = excluded.vision_verified_pct,
327
- reputation_score = excluded.reputation_score,
328
- trust_level = excluded.trust_level,
329
- last_updated = datetime('now')
330
- `).run(
331
- siteId, attestations.length, successCount, failureCount, fraudCount,
332
- priceAccSum / attestations.length,
333
- responseSum / attestations.length,
334
- integritySum / attestations.length,
335
- attestations.length > 0 ? (visionVerifiedCount / attestations.length) * 100 : 0,
336
- Math.round(reputationScore * 100) / 100,
337
- trustLevel
338
- );
339
- }
340
-
341
- // ─── Query Reputation ────────────────────────────────────────────────
342
-
343
- function getReputation(siteId) {
344
- const score = db.prepare('SELECT * FROM reputation_scores WHERE site_id = ?').get(siteId);
345
- if (!score) return { siteId, reputationScore: 50, trustLevel: 'unknown', attestations: 0 };
346
-
347
- const recentAttestations = db.prepare(`
348
- SELECT interaction_type, outcome, price_accuracy, vision_verified, timestamp
349
- FROM reputation_attestations
350
- WHERE site_id = ? AND revoked = 0
351
- ORDER BY timestamp DESC LIMIT 10
352
- `).all(siteId);
353
-
354
- return {
355
- siteId,
356
- reputationScore: score.reputation_score,
357
- trustLevel: score.trust_level,
358
- totalAttestations: score.total_attestations,
359
- successRate: score.total_attestations > 0
360
- ? Math.round((score.success_count / score.total_attestations) * 100) : 0,
361
- avgPriceAccuracy: Math.round(score.avg_price_accuracy * 100),
362
- avgResponseTimeMs: Math.round(score.avg_response_time_ms),
363
- dataIntegrity: Math.round(score.avg_data_integrity * 100),
364
- visionVerifiedPct: Math.round(score.vision_verified_pct),
365
- lastUpdated: score.last_updated,
366
- recentAttestations
367
- };
368
- }
369
-
370
- function getReputationLeaderboard(limit = 20) {
371
- return db.prepare(`
372
- SELECT site_id, reputation_score, trust_level, total_attestations,
373
- success_count, fraud_count, avg_price_accuracy, vision_verified_pct
374
- FROM reputation_scores
375
- WHERE total_attestations >= ?
376
- ORDER BY reputation_score DESC
377
- LIMIT ?
378
- `).all(MIN_ATTESTATIONS_FOR_TRUST, limit);
379
- }
380
-
381
- function searchByReputation(category, minScore = 60) {
382
- return db.prepare(`
383
- SELECT rs.*, wd.category, wd.tags, wd.is_independent
384
- FROM reputation_scores rs
385
- LEFT JOIN wab_directory wd ON rs.site_id = wd.site_id
386
- WHERE rs.reputation_score >= ?
387
- AND (wd.category = ? OR ? = 'all')
388
- AND rs.trust_level NOT IN ('suspicious', 'blacklisted')
389
- ORDER BY rs.reputation_score DESC
390
- `).all(minScore, category, category);
391
- }
392
-
393
- // ─── Challenges (dispute mechanism) ──────────────────────────────────
394
-
395
- function challengeReputation(siteId, challengerAgent, reason, evidence = {}) {
396
- const id = crypto.randomBytes(16).toString('hex');
397
-
398
- db.prepare(`
399
- INSERT INTO reputation_challenges (id, site_id, challenger_agent, reason, evidence)
400
- VALUES (?, ?, ?, ?, ?)
401
- `).run(id, siteId, challengerAgent, reason, JSON.stringify(evidence));
402
-
403
- // Auto-investigate if multiple challenges
404
- const challengeCount = db.prepare(`
405
- SELECT COUNT(*) as cnt FROM reputation_challenges
406
- WHERE site_id = ? AND status = 'pending'
407
- `).get(siteId);
408
-
409
- if (challengeCount.cnt >= 3) {
410
- // Flag site for review
411
- db.prepare(`
412
- UPDATE reputation_scores SET trust_level = 'suspicious' WHERE site_id = ?
413
- `).run(siteId);
414
- }
415
-
416
- return { challengeId: id, status: 'pending' };
417
- }
418
-
419
- // ─── Verification (verify an attestation cryptographically) ──────────
420
-
421
- function verifyAttestation(attestationId) {
422
- const att = db.prepare('SELECT * FROM reputation_attestations WHERE id = ?').get(attestationId);
423
- if (!att) return { valid: false, reason: 'not_found' };
424
- if (att.revoked) return { valid: false, reason: 'revoked' };
425
-
426
- const isValid = verifySignature(att);
427
- return {
428
- valid: isValid,
429
- attestation: isValid ? {
430
- siteId: att.site_id,
431
- interactionType: att.interaction_type,
432
- outcome: att.outcome,
433
- priceAccuracy: att.price_accuracy,
434
- visionVerified: att.vision_verified === 1,
435
- timestamp: att.timestamp
436
- } : null
437
- };
438
- }
439
-
440
- // ─── Cleanup ─────────────────────────────────────────────────────────
441
-
442
- function cleanupExpired() {
443
- const result = db.prepare(`
444
- DELETE FROM reputation_attestations WHERE expires_at < datetime('now')
445
- `).run();
446
-
447
- // Recalculate affected sites
448
- const sites = db.prepare('SELECT DISTINCT site_id FROM reputation_scores').all();
449
- for (const { site_id } of sites) {
450
- recalculateReputation(site_id);
451
- }
452
- return { cleaned: result.changes };
453
- }
454
-
455
- module.exports = {
456
- registerAgent,
457
- createAttestation,
458
- getReputation,
459
- getReputationLeaderboard,
460
- searchByReputation,
461
- challengeReputation,
462
- verifyAttestation,
463
- cleanupExpired,
464
- recalculateReputation
465
- };
1
+ /**
2
+ * Decentralized Reputation System
3
+ * ════════════════════════════════════════════════════════════════════════
4
+ * Trustless, agent-to-agent reputation protocol. When an agent completes
5
+ * a successful interaction with a site (purchase, booking, query), it
6
+ * leaves a cryptographically signed "trust attestation" that other agents
7
+ * can verify without relying on any central authority.
8
+ *
9
+ * Key concepts:
10
+ * - Trust Attestation: HMAC-signed proof of a successful interaction
11
+ * - Reputation Score: Weighted aggregation of attestations over time
12
+ * - Decay: Older attestations lose weight (freshness matters)
13
+ * - Sybil Resistance: Rate-limited per agent key, cross-verified via vision
14
+ */
15
+
16
+ const { db } = require('../models/db');
17
+ const crypto = require('crypto');
18
+
19
+ // ─── Schema ──────────────────────────────────────────────────────────
20
+
21
+ db.exec(`
22
+ CREATE TABLE IF NOT EXISTS reputation_attestations (
23
+ id TEXT PRIMARY KEY,
24
+ site_id TEXT NOT NULL,
25
+ agent_id TEXT NOT NULL,
26
+ agent_key_hash TEXT NOT NULL,
27
+ interaction_type TEXT NOT NULL CHECK(interaction_type IN (
28
+ 'purchase','booking','query','form_submit','navigation','verification'
29
+ )),
30
+ outcome TEXT NOT NULL CHECK(outcome IN ('success','partial','failure','fraud')),
31
+ price_accuracy REAL DEFAULT 1.0,
32
+ response_time_ms INTEGER DEFAULT 0,
33
+ data_integrity REAL DEFAULT 1.0,
34
+ vision_verified INTEGER DEFAULT 0,
35
+ details TEXT DEFAULT '{}',
36
+ signature TEXT NOT NULL,
37
+ timestamp TEXT DEFAULT (datetime('now')),
38
+ expires_at TEXT,
39
+ revoked INTEGER DEFAULT 0
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS reputation_scores (
43
+ site_id TEXT PRIMARY KEY,
44
+ total_attestations INTEGER DEFAULT 0,
45
+ success_count INTEGER DEFAULT 0,
46
+ failure_count INTEGER DEFAULT 0,
47
+ fraud_count INTEGER DEFAULT 0,
48
+ avg_price_accuracy REAL DEFAULT 1.0,
49
+ avg_response_time_ms REAL DEFAULT 0,
50
+ avg_data_integrity REAL DEFAULT 1.0,
51
+ vision_verified_pct REAL DEFAULT 0,
52
+ reputation_score REAL DEFAULT 50.0,
53
+ trust_level TEXT DEFAULT 'unknown' CHECK(trust_level IN (
54
+ 'unknown','emerging','trusted','verified','exemplary','suspicious','blacklisted'
55
+ )),
56
+ first_seen TEXT DEFAULT (datetime('now')),
57
+ last_updated TEXT DEFAULT (datetime('now'))
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS reputation_agent_keys (
61
+ agent_id TEXT PRIMARY KEY,
62
+ public_key_hash TEXT NOT NULL,
63
+ attestation_count INTEGER DEFAULT 0,
64
+ created_at TEXT DEFAULT (datetime('now')),
65
+ last_active TEXT DEFAULT (datetime('now')),
66
+ rate_window_start TEXT,
67
+ rate_window_count INTEGER DEFAULT 0,
68
+ banned INTEGER DEFAULT 0
69
+ );
70
+
71
+ CREATE TABLE IF NOT EXISTS reputation_challenges (
72
+ id TEXT PRIMARY KEY,
73
+ site_id TEXT NOT NULL,
74
+ challenger_agent TEXT NOT NULL,
75
+ reason TEXT NOT NULL,
76
+ evidence TEXT DEFAULT '{}',
77
+ status TEXT DEFAULT 'pending' CHECK(status IN ('pending','investigating','upheld','dismissed')),
78
+ created_at TEXT DEFAULT (datetime('now')),
79
+ resolved_at TEXT
80
+ );
81
+
82
+ CREATE INDEX IF NOT EXISTS idx_rep_att_site ON reputation_attestations(site_id);
83
+ CREATE INDEX IF NOT EXISTS idx_rep_att_agent ON reputation_attestations(agent_id);
84
+ CREATE INDEX IF NOT EXISTS idx_rep_att_time ON reputation_attestations(timestamp);
85
+ CREATE INDEX IF NOT EXISTS idx_rep_scores_score ON reputation_scores(reputation_score);
86
+ CREATE INDEX IF NOT EXISTS idx_rep_scores_level ON reputation_scores(trust_level);
87
+ `);
88
+
89
+ // ─── Constants ───────────────────────────────────────────────────────
90
+
91
+ const DECAY_HALF_LIFE_DAYS = 30;
92
+ const MAX_ATTESTATIONS_PER_HOUR = 10;
93
+ const MIN_ATTESTATIONS_FOR_TRUST = 5;
94
+ const FRAUD_PENALTY_MULTIPLIER = 3;
95
+ const VISION_VERIFICATION_BONUS = 1.25;
96
+ const TRUST_LEVELS = {
97
+ blacklisted: { min: 0, max: 10 },
98
+ suspicious: { min: 10, max: 25 },
99
+ unknown: { min: 25, max: 40 },
100
+ emerging: { min: 40, max: 60 },
101
+ trusted: { min: 60, max: 80 },
102
+ verified: { min: 80, max: 92 },
103
+ exemplary: { min: 92, max: 100 },
104
+ };
105
+
106
+ // ─── Crypto Helpers ──────────────────────────────────────────────────
107
+
108
+ const SIGNING_SECRET = process.env.WAB_REPUTATION_SECRET || crypto.randomBytes(32).toString('hex');
109
+
110
+ function generateAgentId() {
111
+ return 'agent_' + crypto.randomBytes(16).toString('hex');
112
+ }
113
+
114
+ function hashAgentKey(key) {
115
+ return crypto.createHash('sha256').update(key).digest('hex');
116
+ }
117
+
118
+ function signAttestation(data) {
119
+ const payload = JSON.stringify({
120
+ site_id: data.site_id,
121
+ agent_id: data.agent_id,
122
+ interaction_type: data.interaction_type,
123
+ outcome: data.outcome,
124
+ price_accuracy: data.price_accuracy,
125
+ timestamp: data.timestamp
126
+ });
127
+ return crypto.createHmac('sha256', SIGNING_SECRET).update(payload).digest('hex');
128
+ }
129
+
130
+ function verifySignature(attestation) {
131
+ const expected = signAttestation(attestation);
132
+ return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(attestation.signature));
133
+ }
134
+
135
+ // ─── Agent Registration ──────────────────────────────────────────────
136
+
137
+ function registerAgent(agentKey) {
138
+ const agentId = generateAgentId();
139
+ const keyHash = hashAgentKey(agentKey);
140
+
141
+ db.prepare(`
142
+ INSERT OR IGNORE INTO reputation_agent_keys (agent_id, public_key_hash)
143
+ VALUES (?, ?)
144
+ `).run(agentId, keyHash);
145
+
146
+ return { agentId, keyHash };
147
+ }
148
+
149
+ function checkRateLimit(agentId) {
150
+ const agent = db.prepare('SELECT * FROM reputation_agent_keys WHERE agent_id = ?').get(agentId);
151
+ if (!agent) return { allowed: false, reason: 'unknown_agent' };
152
+ if (agent.banned) return { allowed: false, reason: 'agent_banned' };
153
+
154
+ const now = new Date();
155
+ const windowStart = agent.rate_window_start ? new Date(agent.rate_window_start) : null;
156
+
157
+ if (!windowStart || (now - windowStart) > 3600000) {
158
+ db.prepare(`
159
+ UPDATE reputation_agent_keys
160
+ SET rate_window_start = datetime('now'), rate_window_count = 0
161
+ WHERE agent_id = ?
162
+ `).run(agentId);
163
+ return { allowed: true, remaining: MAX_ATTESTATIONS_PER_HOUR };
164
+ }
165
+
166
+ if (agent.rate_window_count >= MAX_ATTESTATIONS_PER_HOUR) {
167
+ return { allowed: false, reason: 'rate_limited', retryAfterMs: 3600000 - (now - windowStart) };
168
+ }
169
+
170
+ return { allowed: true, remaining: MAX_ATTESTATIONS_PER_HOUR - agent.rate_window_count };
171
+ }
172
+
173
+ // ─── Trust Attestation ───────────────────────────────────────────────
174
+
175
+ function createAttestation({
176
+ siteId, agentId, interactionType, outcome,
177
+ priceAccuracy = 1.0, responseTimeMs = 0,
178
+ dataIntegrity = 1.0, visionVerified = false, details = {}
179
+ }) {
180
+ const rateCheck = checkRateLimit(agentId);
181
+ if (!rateCheck.allowed) {
182
+ return { error: rateCheck.reason, retryAfterMs: rateCheck.retryAfterMs };
183
+ }
184
+
185
+ const id = crypto.randomBytes(16).toString('hex');
186
+ const timestamp = new Date().toISOString();
187
+ const expiresAt = new Date(Date.now() + DECAY_HALF_LIFE_DAYS * 4 * 86400000).toISOString();
188
+
189
+ const attestation = {
190
+ id, site_id: siteId, agent_id: agentId,
191
+ interaction_type: interactionType, outcome,
192
+ price_accuracy: Math.max(0, Math.min(1, priceAccuracy)),
193
+ response_time_ms: responseTimeMs,
194
+ data_integrity: Math.max(0, Math.min(1, dataIntegrity)),
195
+ vision_verified: visionVerified ? 1 : 0,
196
+ details: JSON.stringify(details),
197
+ timestamp, expires_at: expiresAt
198
+ };
199
+
200
+ attestation.signature = signAttestation(attestation);
201
+
202
+ db.prepare(`
203
+ INSERT INTO reputation_attestations
204
+ (id, site_id, agent_id, agent_key_hash, interaction_type, outcome,
205
+ price_accuracy, response_time_ms, data_integrity, vision_verified,
206
+ details, signature, timestamp, expires_at)
207
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
208
+ `).run(
209
+ attestation.id, attestation.site_id, attestation.agent_id,
210
+ hashAgentKey(agentId),
211
+ attestation.interaction_type, attestation.outcome,
212
+ attestation.price_accuracy, attestation.response_time_ms,
213
+ attestation.data_integrity, attestation.vision_verified,
214
+ attestation.details, attestation.signature,
215
+ attestation.timestamp, attestation.expires_at
216
+ );
217
+
218
+ // Update agent rate limit counter
219
+ db.prepare(`
220
+ UPDATE reputation_agent_keys
221
+ SET rate_window_count = rate_window_count + 1,
222
+ attestation_count = attestation_count + 1,
223
+ last_active = datetime('now')
224
+ WHERE agent_id = ?
225
+ `).run(agentId);
226
+
227
+ // Recalculate reputation score
228
+ recalculateReputation(siteId);
229
+
230
+ return { attestation: { id, signature: attestation.signature, timestamp } };
231
+ }
232
+
233
+ // ─── Reputation Calculation ──────────────────────────────────────────
234
+
235
+ function recalculateReputation(siteId) {
236
+ const attestations = db.prepare(`
237
+ SELECT * FROM reputation_attestations
238
+ WHERE site_id = ? AND revoked = 0 AND expires_at > datetime('now')
239
+ ORDER BY timestamp DESC
240
+ `).all(siteId);
241
+
242
+ if (attestations.length === 0) {
243
+ db.prepare(`
244
+ INSERT OR REPLACE INTO reputation_scores
245
+ (site_id, total_attestations, reputation_score, trust_level, last_updated)
246
+ VALUES (?, 0, 50.0, 'unknown', datetime('now'))
247
+ `).run(siteId);
248
+ return;
249
+ }
250
+
251
+ const now = Date.now();
252
+ let weightedSum = 0;
253
+ let totalWeight = 0;
254
+ let successCount = 0;
255
+ let failureCount = 0;
256
+ let fraudCount = 0;
257
+ let priceAccSum = 0;
258
+ let responseSum = 0;
259
+ let integritySum = 0;
260
+ let visionVerifiedCount = 0;
261
+
262
+ for (const att of attestations) {
263
+ const ageMs = now - new Date(att.timestamp).getTime();
264
+ const ageDays = ageMs / 86400000;
265
+ const decayWeight = Math.pow(0.5, ageDays / DECAY_HALF_LIFE_DAYS);
266
+
267
+ let outcomeScore;
268
+ switch (att.outcome) {
269
+ case 'success': outcomeScore = 1.0; successCount++; break;
270
+ case 'partial': outcomeScore = 0.5; break;
271
+ case 'failure': outcomeScore = 0.0; failureCount++; break;
272
+ case 'fraud': outcomeScore = -FRAUD_PENALTY_MULTIPLIER; fraudCount++; break;
273
+ default: outcomeScore = 0;
274
+ }
275
+
276
+ let weight = decayWeight;
277
+ if (att.vision_verified) {
278
+ weight *= VISION_VERIFICATION_BONUS;
279
+ visionVerifiedCount++;
280
+ }
281
+
282
+ const qualityScore = (
283
+ outcomeScore * 0.5 +
284
+ att.price_accuracy * 0.25 +
285
+ att.data_integrity * 0.25
286
+ );
287
+
288
+ weightedSum += qualityScore * weight;
289
+ totalWeight += weight;
290
+ priceAccSum += att.price_accuracy;
291
+ responseSum += att.response_time_ms;
292
+ integritySum += att.data_integrity;
293
+ }
294
+
295
+ const rawScore = totalWeight > 0 ? weightedSum / totalWeight : 0.5;
296
+ // Normalize to 0-100 with confidence adjustment
297
+ const confidence = Math.min(attestations.length / MIN_ATTESTATIONS_FOR_TRUST, 1);
298
+ const reputationScore = Math.max(0, Math.min(100,
299
+ (rawScore * 50 + 50) * confidence + 50 * (1 - confidence)
300
+ ));
301
+
302
+ // Determine trust level
303
+ let trustLevel = 'unknown';
304
+ for (const [level, range] of Object.entries(TRUST_LEVELS)) {
305
+ if (reputationScore >= range.min && reputationScore < range.max) {
306
+ trustLevel = level;
307
+ break;
308
+ }
309
+ }
310
+ if (reputationScore >= 92) trustLevel = 'exemplary';
311
+
312
+ db.prepare(`
313
+ INSERT INTO reputation_scores
314
+ (site_id, total_attestations, success_count, failure_count, fraud_count,
315
+ avg_price_accuracy, avg_response_time_ms, avg_data_integrity,
316
+ vision_verified_pct, reputation_score, trust_level, last_updated)
317
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
318
+ ON CONFLICT(site_id) DO UPDATE SET
319
+ total_attestations = excluded.total_attestations,
320
+ success_count = excluded.success_count,
321
+ failure_count = excluded.failure_count,
322
+ fraud_count = excluded.fraud_count,
323
+ avg_price_accuracy = excluded.avg_price_accuracy,
324
+ avg_response_time_ms = excluded.avg_response_time_ms,
325
+ avg_data_integrity = excluded.avg_data_integrity,
326
+ vision_verified_pct = excluded.vision_verified_pct,
327
+ reputation_score = excluded.reputation_score,
328
+ trust_level = excluded.trust_level,
329
+ last_updated = datetime('now')
330
+ `).run(
331
+ siteId, attestations.length, successCount, failureCount, fraudCount,
332
+ priceAccSum / attestations.length,
333
+ responseSum / attestations.length,
334
+ integritySum / attestations.length,
335
+ attestations.length > 0 ? (visionVerifiedCount / attestations.length) * 100 : 0,
336
+ Math.round(reputationScore * 100) / 100,
337
+ trustLevel
338
+ );
339
+ }
340
+
341
+ // ─── Query Reputation ────────────────────────────────────────────────
342
+
343
+ function getReputation(siteId) {
344
+ const score = db.prepare('SELECT * FROM reputation_scores WHERE site_id = ?').get(siteId);
345
+ if (!score) return { siteId, reputationScore: 50, trustLevel: 'unknown', attestations: 0 };
346
+
347
+ const recentAttestations = db.prepare(`
348
+ SELECT interaction_type, outcome, price_accuracy, vision_verified, timestamp
349
+ FROM reputation_attestations
350
+ WHERE site_id = ? AND revoked = 0
351
+ ORDER BY timestamp DESC LIMIT 10
352
+ `).all(siteId);
353
+
354
+ return {
355
+ siteId,
356
+ reputationScore: score.reputation_score,
357
+ trustLevel: score.trust_level,
358
+ totalAttestations: score.total_attestations,
359
+ successRate: score.total_attestations > 0
360
+ ? Math.round((score.success_count / score.total_attestations) * 100) : 0,
361
+ avgPriceAccuracy: Math.round(score.avg_price_accuracy * 100),
362
+ avgResponseTimeMs: Math.round(score.avg_response_time_ms),
363
+ dataIntegrity: Math.round(score.avg_data_integrity * 100),
364
+ visionVerifiedPct: Math.round(score.vision_verified_pct),
365
+ lastUpdated: score.last_updated,
366
+ recentAttestations
367
+ };
368
+ }
369
+
370
+ function getReputationLeaderboard(limit = 20) {
371
+ return db.prepare(`
372
+ SELECT site_id, reputation_score, trust_level, total_attestations,
373
+ success_count, fraud_count, avg_price_accuracy, vision_verified_pct
374
+ FROM reputation_scores
375
+ WHERE total_attestations >= ?
376
+ ORDER BY reputation_score DESC
377
+ LIMIT ?
378
+ `).all(MIN_ATTESTATIONS_FOR_TRUST, limit);
379
+ }
380
+
381
+ function searchByReputation(category, minScore = 60) {
382
+ return db.prepare(`
383
+ SELECT rs.*, wd.category, wd.tags, wd.is_independent
384
+ FROM reputation_scores rs
385
+ LEFT JOIN wab_directory wd ON rs.site_id = wd.site_id
386
+ WHERE rs.reputation_score >= ?
387
+ AND (wd.category = ? OR ? = 'all')
388
+ AND rs.trust_level NOT IN ('suspicious', 'blacklisted')
389
+ ORDER BY rs.reputation_score DESC
390
+ `).all(minScore, category, category);
391
+ }
392
+
393
+ // ─── Challenges (dispute mechanism) ──────────────────────────────────
394
+
395
+ function challengeReputation(siteId, challengerAgent, reason, evidence = {}) {
396
+ const id = crypto.randomBytes(16).toString('hex');
397
+
398
+ db.prepare(`
399
+ INSERT INTO reputation_challenges (id, site_id, challenger_agent, reason, evidence)
400
+ VALUES (?, ?, ?, ?, ?)
401
+ `).run(id, siteId, challengerAgent, reason, JSON.stringify(evidence));
402
+
403
+ // Auto-investigate if multiple challenges
404
+ const challengeCount = db.prepare(`
405
+ SELECT COUNT(*) as cnt FROM reputation_challenges
406
+ WHERE site_id = ? AND status = 'pending'
407
+ `).get(siteId);
408
+
409
+ if (challengeCount.cnt >= 3) {
410
+ // Flag site for review
411
+ db.prepare(`
412
+ UPDATE reputation_scores SET trust_level = 'suspicious' WHERE site_id = ?
413
+ `).run(siteId);
414
+ }
415
+
416
+ return { challengeId: id, status: 'pending' };
417
+ }
418
+
419
+ // ─── Verification (verify an attestation cryptographically) ──────────
420
+
421
+ function verifyAttestation(attestationId) {
422
+ const att = db.prepare('SELECT * FROM reputation_attestations WHERE id = ?').get(attestationId);
423
+ if (!att) return { valid: false, reason: 'not_found' };
424
+ if (att.revoked) return { valid: false, reason: 'revoked' };
425
+
426
+ const isValid = verifySignature(att);
427
+ return {
428
+ valid: isValid,
429
+ attestation: isValid ? {
430
+ siteId: att.site_id,
431
+ interactionType: att.interaction_type,
432
+ outcome: att.outcome,
433
+ priceAccuracy: att.price_accuracy,
434
+ visionVerified: att.vision_verified === 1,
435
+ timestamp: att.timestamp
436
+ } : null
437
+ };
438
+ }
439
+
440
+ // ─── Cleanup ─────────────────────────────────────────────────────────
441
+
442
+ function cleanupExpired() {
443
+ const result = db.prepare(`
444
+ DELETE FROM reputation_attestations WHERE expires_at < datetime('now')
445
+ `).run();
446
+
447
+ // Recalculate affected sites
448
+ const sites = db.prepare('SELECT DISTINCT site_id FROM reputation_scores').all();
449
+ for (const { site_id } of sites) {
450
+ recalculateReputation(site_id);
451
+ }
452
+ return { cleaned: result.changes };
453
+ }
454
+
455
+ module.exports = {
456
+ registerAgent,
457
+ createAttestation,
458
+ getReputation,
459
+ getReputationLeaderboard,
460
+ searchByReputation,
461
+ challengeReputation,
462
+ verifyAttestation,
463
+ cleanupExpired,
464
+ recalculateReputation
465
+ };