triage-ows 1.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 (91) hide show
  1. package/README.md +786 -0
  2. package/bin/triage-ows.js +218 -0
  3. package/bin/triage-policy.js +66 -0
  4. package/bin/triage-server.js +4 -0
  5. package/dashboard-dist/assets/index-Dnhi_dJQ.css +2 -0
  6. package/dashboard-dist/assets/index-g_2MwC-o.js +9 -0
  7. package/dashboard-dist/favicon.svg +7 -0
  8. package/dashboard-dist/index.html +16 -0
  9. package/dist/config.d.ts +32 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +61 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/emitter.d.ts +7 -0
  14. package/dist/emitter.d.ts.map +1 -0
  15. package/dist/emitter.js +41 -0
  16. package/dist/emitter.js.map +1 -0
  17. package/dist/index.d.ts +17 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +55 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/scoring/behavior.d.ts +3 -0
  22. package/dist/scoring/behavior.d.ts.map +1 -0
  23. package/dist/scoring/behavior.js +13 -0
  24. package/dist/scoring/behavior.js.map +1 -0
  25. package/dist/scoring/compliance.d.ts +3 -0
  26. package/dist/scoring/compliance.d.ts.map +1 -0
  27. package/dist/scoring/compliance.js +11 -0
  28. package/dist/scoring/compliance.js.map +1 -0
  29. package/dist/scoring/identity.d.ts +3 -0
  30. package/dist/scoring/identity.d.ts.map +1 -0
  31. package/dist/scoring/identity.js +16 -0
  32. package/dist/scoring/identity.js.map +1 -0
  33. package/dist/scoring/index.d.ts +10 -0
  34. package/dist/scoring/index.d.ts.map +1 -0
  35. package/dist/scoring/index.js +35 -0
  36. package/dist/scoring/index.js.map +1 -0
  37. package/dist/scoring/limits.d.ts +7 -0
  38. package/dist/scoring/limits.d.ts.map +1 -0
  39. package/dist/scoring/limits.js +9 -0
  40. package/dist/scoring/limits.js.map +1 -0
  41. package/dist/scoring/network.d.ts +3 -0
  42. package/dist/scoring/network.d.ts.map +1 -0
  43. package/dist/scoring/network.js +16 -0
  44. package/dist/scoring/network.js.map +1 -0
  45. package/dist/scoring/onchain.d.ts +4 -0
  46. package/dist/scoring/onchain.d.ts.map +1 -0
  47. package/dist/scoring/onchain.js +35 -0
  48. package/dist/scoring/onchain.js.map +1 -0
  49. package/dist/scoring/risk.d.ts +3 -0
  50. package/dist/scoring/risk.d.ts.map +1 -0
  51. package/dist/scoring/risk.js +22 -0
  52. package/dist/scoring/risk.js.map +1 -0
  53. package/dist/server.d.ts +6 -0
  54. package/dist/server.d.ts.map +1 -0
  55. package/dist/server.js +405 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/store.d.ts +13 -0
  58. package/dist/store.d.ts.map +1 -0
  59. package/dist/store.js +177 -0
  60. package/dist/store.js.map +1 -0
  61. package/dist/types.d.ts +107 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +26 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/webbotauth.d.ts +38 -0
  66. package/dist/webbotauth.d.ts.map +1 -0
  67. package/dist/webbotauth.js +120 -0
  68. package/dist/webbotauth.js.map +1 -0
  69. package/dist/xmtp.d.ts +6 -0
  70. package/dist/xmtp.d.ts.map +1 -0
  71. package/dist/xmtp.js +161 -0
  72. package/dist/xmtp.js.map +1 -0
  73. package/package.json +58 -0
  74. package/policy-template.json +14 -0
  75. package/src/config.ts +86 -0
  76. package/src/emitter.ts +40 -0
  77. package/src/index.ts +18 -0
  78. package/src/scoring/behavior.ts +15 -0
  79. package/src/scoring/compliance.ts +12 -0
  80. package/src/scoring/identity.ts +12 -0
  81. package/src/scoring/index.ts +31 -0
  82. package/src/scoring/limits.ts +10 -0
  83. package/src/scoring/network.ts +18 -0
  84. package/src/scoring/onchain.ts +44 -0
  85. package/src/scoring/risk.ts +25 -0
  86. package/src/server.ts +410 -0
  87. package/src/store.ts +197 -0
  88. package/src/types.ts +137 -0
  89. package/src/webbotauth.ts +128 -0
  90. package/src/xmtp.ts +188 -0
  91. package/triage.config.example.json +22 -0
package/README.md ADDED
@@ -0,0 +1,786 @@
1
+ # Triage x OWS
2
+
3
+ **Reputation-gated spending policies for the agent economy.**
4
+
5
+ Triage is a scoring server and policy engine for the [Open Wallet Standard](https://github.com/Open-Wallet-Standard). It evaluates every transaction an AI agent attempts, assigns a trust score based on 5 factors, and enforces dynamic spending limits. Agents start restricted and earn financial autonomy over time.
6
+
7
+ Triage does not create agents. It governs how much financial autonomy they earn.
8
+
9
+ ```
10
+ OWS Wallet --> triage-policy (stdin/stdout) --> Scoring Server --> APPROVE / DENY
11
+ |
12
+ Dashboard + XMTP
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # 1. Install
21
+ npm install triage-ows hono
22
+
23
+ # 2. Initialize config
24
+ npx triage-ows init
25
+
26
+ # 3. Start the server + dashboard
27
+ npx triage-ows dev
28
+ ```
29
+
30
+ Dashboard at `http://localhost:4021`. WebSocket at `ws://localhost:4021/ws`.
31
+
32
+ ---
33
+
34
+ ## How It Works
35
+
36
+ ```
37
+ stdin (PolicyContext JSON)
38
+ |
39
+ +------v------+
40
+ | triage- | OWS spawns this as a subprocess
41
+ | policy | for every transaction
42
+ +------+------+
43
+ |
44
+ HTTP POST /api/policy/evaluate
45
+ |
46
+ +------v------+
47
+ | Scoring | 5-factor trust formula
48
+ | Server | + risk penalty
49
+ +------+------+
50
+ |
51
+ +------------+------------+
52
+ | | |
53
+ Record in Emit WS XMTP notify
54
+ store event (on deny)
55
+ | | |
56
+ v v v
57
+ Agent DB Dashboard Human owner
58
+ (in-mem) (React) (can override)
59
+ ```
60
+
61
+ 1. **OWS spawns `triage-policy`** as a subprocess for each transaction
62
+ 2. The executable reads `PolicyContext` from stdin and POSTs to the scoring server
63
+ 3. The server computes the agent's trust score across 5 factors
64
+ 4. Three checks run in order: frozen tier, per-transaction limit, daily limit
65
+ 5. Result (`allow: true/false`) is written to stdout for OWS
66
+ 6. Every decision is broadcast via WebSocket to the dashboard
67
+ 7. Denials trigger XMTP notifications to the human owner, who can reply "override"
68
+
69
+ ---
70
+
71
+ ## Trust Formula
72
+
73
+ ```
74
+ Trust Score = Identity + OnChain + Behavior + Compliance + Network - Risk
75
+ (0-35) (0-20) (0-20) (0-15) (0-5) (0-30)
76
+
77
+ Clamped to [0, 100]
78
+ ```
79
+
80
+ ### Identity (0-35)
81
+
82
+ Additive scoring with a hard cap at 35:
83
+
84
+ | Condition | Score | Cumulative |
85
+ |-----------|-------|------------|
86
+ | Fresh wallet (base) | 4 | 4 |
87
+ | Has transaction history | 12 | 12 |
88
+ | OWS wallet with approved transactions | 20 | 20 |
89
+ | + Web Bot Auth verified (RFC 9421) | +4 | 24 |
90
+ | + World ID verified (human proof) | +11 | 31-35 |
91
+
92
+ An OWS wallet with both Web Bot Auth and World ID: `min(35, 20 + 4 + 11) = 35`.
93
+
94
+ ### On-Chain (0-20)
95
+
96
+ | Sub-factor | Max | Calculation |
97
+ |-----------|-----|-------------|
98
+ | Account age | 5 | `min(5, monthsOld * 0.5)` |
99
+ | Transaction count | 5 | `min(5, log10(totalRequests) * 2.5)` |
100
+ | Counterparty diversity | 5 | `min(5, uniqueCounterparties / 10 * 5)` |
101
+ | Balance (funded) | 5 | 5 if on-chain balance > 0, else 0 |
102
+
103
+ Balance is checked via Base Sepolia RPC (`viem`), cached for 10 minutes.
104
+
105
+ ### Behavior (0-20)
106
+
107
+ | Sub-factor | Max | Calculation |
108
+ |-----------|-----|-------------|
109
+ | Success rate | 5 | `min(5, successfulRequests / totalRequests * 5)` |
110
+ | Pacing | 5 | 5 if <5 req/min, 2 if 5-15, 0 if >15 |
111
+ | Clean days | 5 | `min(5, consecutiveCleanDays * 0.5)` |
112
+ | Counterparty concentration | 5 | 5 if >=5 targets, 2 if >=2, 0 if 1 |
113
+
114
+ ### Compliance (0-15)
115
+
116
+ | Sub-factor | Max | Calculation |
117
+ |-----------|-----|-------------|
118
+ | Approval rate | 5 | `min(5, totalApproved / totalDecisions * 5)` |
119
+ | Approval streak | 5 | `min(5, consecutiveApprovals * 0.25)` |
120
+ | Override frequency | 5 | `5 - min(5, humanOverrides * 1.67)` |
121
+
122
+ Fewer human overrides = higher compliance score. Agents that consistently stay within limits score highest.
123
+
124
+ ### Network (0-5)
125
+
126
+ Average trust score of known counterparties, divided by 20. Agents that transact with other trusted agents earn network reputation.
127
+
128
+ Can be disabled via config: `networkScore.enabled: false`.
129
+
130
+ ### Risk Penalty (0-30, subtracted)
131
+
132
+ | Factor | Max | Trigger |
133
+ |--------|-----|---------|
134
+ | Frequency spike | 10 | >15 requests in 60 seconds |
135
+ | Failed transactions | 5 | `min(5, failedRequests * 2)` |
136
+ | Inactivity decay | 5 | `min(5, hoursInactive * 0.5)` |
137
+ | Spend pressure | 5 | >85% daily limit + active approvals |
138
+ | Denial streak | 5 | `min(5, consecutiveDenials * 2.5)` |
139
+
140
+ Frequency penalty and inactivity decay rate are configurable.
141
+
142
+ ---
143
+
144
+ ## Spending Tiers
145
+
146
+ | Tier | Min Score | Daily Limit | Per-Tx Limit | Color |
147
+ |------|-----------|-------------|--------------|-------|
148
+ | Sovereign | 80 | $1,000 | $500 | `#fff8e1` |
149
+ | Trusted | 60 | $200 | $100 | `#ffd700` |
150
+ | Building | 40 | $50 | $25 | `#4caf50` |
151
+ | Cautious | 20 | $10 | $5 | `#00bcd4` |
152
+ | Restricted | 1 | $2 | $1 | `#2196f3` |
153
+ | Frozen | 0 | $0 | $0 | `#1a1a4e` |
154
+
155
+ All limits are configurable via `triage.config.json` `scoreBands`.
156
+
157
+ ---
158
+
159
+ ## API Reference
160
+
161
+ ### POST /api/policy/evaluate
162
+
163
+ Core policy evaluation endpoint. Called by `triage-policy` for every transaction.
164
+
165
+ **Headers:**
166
+ - `Content-Type: application/json`
167
+ - `x-policy-secret: <secret>` (optional, if `POLICY_SECRET` env is set)
168
+ - `Signature-Input` + `Signature` (optional, RFC 9421 Web Bot Auth)
169
+
170
+ **Request:**
171
+ ```json
172
+ {
173
+ "chain_id": "eip155:84532",
174
+ "wallet_id": "5dccd73e-...",
175
+ "api_key_id": "0d3531c9-...",
176
+ "transaction": {
177
+ "to": "0xRecipient...",
178
+ "value": "1000000000000000",
179
+ "raw_hex": "0x",
180
+ "data": "0x"
181
+ },
182
+ "spending": {
183
+ "daily_total": "0",
184
+ "date": "2026-04-04"
185
+ },
186
+ "timestamp": "2026-04-04T12:00:00Z"
187
+ }
188
+ ```
189
+
190
+ **Response (200):**
191
+ ```json
192
+ {
193
+ "allow": true,
194
+ "trustScore": 82,
195
+ "tier": "Sovereign",
196
+ "dailyLimit": 1000,
197
+ "dailySpent": 150.50,
198
+ "perTxLimit": 500
199
+ }
200
+ ```
201
+
202
+ **Denial reasons:**
203
+ - `Agent is frozen` (tier check)
204
+ - `Exceeds per-transaction limit ($500)` (per-tx check)
205
+ - `Exceeds daily spending limit ($1000)` (daily check)
206
+
207
+ ```bash
208
+ curl -X POST http://localhost:4021/api/policy/evaluate \
209
+ -H "Content-Type: application/json" \
210
+ -d '{"chain_id":"eip155:84532","wallet_id":"w1","api_key_id":"k1","transaction":{"to":"0xABC","value":"1000000000000000","raw_hex":"0x","data":"0x"},"spending":{"daily_total":"0","date":"2026-04-04"},"timestamp":"2026-04-04T12:00:00Z"}'
211
+ ```
212
+
213
+ ### POST /api/override/:address
214
+
215
+ Human override for a denied transaction. Boosts trust score by `overrideBoost` (default +3). Override expires after 5 minutes.
216
+
217
+ ```bash
218
+ curl -X POST http://localhost:4021/api/override/0xAgentAddress
219
+ ```
220
+
221
+ **Response (200):** Full `AgentProfile` JSON.
222
+ **Response (404):** `{ "error": "Agent not found" }` or `{ "error": "No pending override for this agent" }` (including expired overrides).
223
+
224
+ ### GET /api/agents
225
+
226
+ Top 20 agents sorted by trust score.
227
+
228
+ ```bash
229
+ curl http://localhost:4021/api/agents
230
+ ```
231
+
232
+ ### GET /api/agents/:address
233
+
234
+ Single agent profile.
235
+
236
+ ```bash
237
+ curl http://localhost:4021/api/agents/0xAgentAddress
238
+ ```
239
+
240
+ ### POST /api/agents/:address/pubkey
241
+
242
+ Register an agent's Ed25519 public key for Web Bot Auth (RFC 9421).
243
+
244
+ ```bash
245
+ curl -X POST http://localhost:4021/api/agents/0xAgent/pubkey \
246
+ -H "Content-Type: application/json" \
247
+ -d '{"publicKey":"base64EncodedEd25519PublicKey"}'
248
+ ```
249
+
250
+ ### GET /api/stats
251
+
252
+ Aggregate statistics.
253
+
254
+ ```bash
255
+ curl http://localhost:4021/api/stats
256
+ ```
257
+
258
+ **Response:**
259
+ ```json
260
+ {
261
+ "totalAgents": 5,
262
+ "totalDecisions": 142,
263
+ "totalApproved": 128,
264
+ "totalDenied": 14
265
+ }
266
+ ```
267
+
268
+ ### POST /api/verify-context
269
+
270
+ World ID sign request. Returns signing parameters for client-side World ID verification. Returns 501 if `WORLD_ID_RP_ID` and `WORLD_ID_SIGNING_KEY` are not configured.
271
+
272
+ ### POST /api/verify-human
273
+
274
+ World ID proof verification. Verifies proof server-side via World API, stores nullifier hash.
275
+
276
+ ```bash
277
+ curl -X POST http://localhost:4021/api/verify-human \
278
+ -H "Content-Type: application/json" \
279
+ -d '{"address":"0xAgentAddress"}'
280
+ ```
281
+
282
+ ### POST /api/x402/verify
283
+
284
+ Verify an x402 payment signature via the facilitator.
285
+
286
+ ```bash
287
+ curl -X POST http://localhost:4021/api/x402/verify \
288
+ -H "Content-Type: application/json" \
289
+ -d '{"paymentHeader":"...","payTo":"0xRecipient","maxAmountRequired":"1000000"}'
290
+ ```
291
+
292
+ ### GET /api/x402/payment-requirements
293
+
294
+ Returns payment info for HTTP 402 responses. Requires `PAY_TO_ADDRESS` env var.
295
+
296
+ ```bash
297
+ curl http://localhost:4021/api/x402/payment-requirements
298
+ ```
299
+
300
+ ### WebSocket: /ws
301
+
302
+ Real-time event stream. Connect to `ws://localhost:4021/ws`.
303
+
304
+ **Event types:**
305
+ ```typescript
306
+ // Transaction decision
307
+ { type: 'POLICY_DECISION', agent, amount, trustScore, tier, decision: 'APPROVE'|'DENY'|'OVERRIDE', reason, dailyLimit, dailySpent, timestamp }
308
+
309
+ // Trust score change (on override)
310
+ { type: 'TRUST_CHANGE', agent, oldScore, newScore, oldTier, newTier, reason, timestamp }
311
+
312
+ // Budget warning (>80% daily limit)
313
+ { type: 'BUDGET_WARNING', agent, spent, limit, percentage, timestamp }
314
+ ```
315
+
316
+ ---
317
+
318
+ ## CLI Reference
319
+
320
+ ```
321
+ triage-ows -- Reputation-gated spending policies for OWS agent wallets
322
+
323
+ Usage:
324
+ triage-ows init Scaffold triage.config.json + .env.example
325
+ triage-ows dev Start scoring server + dashboard (dev mode)
326
+ triage-ows register Register Triage policy with OWS
327
+ triage-ows attach Attach an OWS agent to Triage governance
328
+ triage-ows seed Populate demo agents for testing
329
+ triage-ows status Show server status + agent summary
330
+ triage-ows help Show this help
331
+
332
+ Options:
333
+ --port <number> Server port (default: 4021)
334
+ --config <path> Config file path (default: ./triage.config.json)
335
+ ```
336
+
337
+ ### triage-ows init
338
+
339
+ Creates `triage.config.json` and `.env.example` in the current directory. Skips if files already exist.
340
+
341
+ ### triage-ows dev
342
+
343
+ Starts the scoring server and dashboard. Reads config from `triage.config.json`.
344
+
345
+ ```bash
346
+ triage-ows dev --port 8080 --config ./custom.config.json
347
+ ```
348
+
349
+ ### triage-ows register
350
+
351
+ Registers the Triage policy with OWS. Copies `policy-template.json` to `~/.ows/policies/triage-trust.json` and runs `ows policy create`.
352
+
353
+ ### triage-ows attach
354
+
355
+ Creates an OWS API key attached to a wallet with the Triage policy.
356
+
357
+ ```bash
358
+ triage-ows attach --wallet sovereign-agent --key sovereign-key
359
+ ```
360
+
361
+ Runs `ows key create --name <key> --wallet <wallet> --policy triage-trust`.
362
+
363
+ ### triage-ows status
364
+
365
+ Shows server status and agent summary table.
366
+
367
+ ```bash
368
+ triage-ows status --port 4021
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Library Usage
374
+
375
+ ```typescript
376
+ import {
377
+ // Server
378
+ app, startServer, BASE_SEPOLIA, USDC_BASE_SEPOLIA,
379
+
380
+ // Scoring
381
+ computeTrustScore, getSpendingLimits,
382
+ identityScore, onChainScore, behaviorScore,
383
+ complianceScore, networkScore, riskPenalty,
384
+
385
+ // Store
386
+ getOrCreateAgent, getAgent, getAllAgents, getTopAgents,
387
+ recordApproval, recordDenial, recordOverride,
388
+ addVerifiedHuman, isVerifiedHuman, setOWSWallet, setWebBotAuthVerified,
389
+
390
+ // Config
391
+ loadConfig, getConfig,
392
+
393
+ // Events
394
+ emitEvent, startWebSocketServer, attachWebSocketToServer,
395
+
396
+ // Web Bot Auth (RFC 9421)
397
+ verifyWebBotAuth, registerAgentPublicKey, parseSignatureInput,
398
+
399
+ // Types & Constants
400
+ getTierForScore, SPENDING_TIERS, updateSpendingTiers,
401
+ } from 'triage-ows'
402
+
403
+ import type {
404
+ PolicyContext, PolicyResult, TrustBreakdown, SpendingTier,
405
+ AgentProfile, TriageConfig,
406
+ PolicyDecisionEvent, TrustChangeEvent, BudgetWarningEvent, TriageEvent,
407
+ } from 'triage-ows'
408
+ ```
409
+
410
+ ### Scoring an Agent
411
+
412
+ ```typescript
413
+ const agent = getOrCreateAgent('0xAgentAddress')
414
+ const breakdown = computeTrustScore(agent, getAgent)
415
+ const { tier, dailyLimit, perTxLimit } = getSpendingLimits(breakdown.total)
416
+
417
+ console.log(`Score: ${breakdown.total}, Tier: ${tier.name}, Limit: $${dailyLimit}/day`)
418
+ ```
419
+
420
+ ### Recording Transactions
421
+
422
+ ```typescript
423
+ // Approved transaction
424
+ const updated = recordApproval('0xAgent', '0xRecipient', 25.50)
425
+
426
+ // Denied transaction (stores pending override)
427
+ const denied = recordDenial('0xAgent', tx, 500.00)
428
+
429
+ // Human override (expires after 5 minutes)
430
+ const overridden = recordOverride('0xAgent')
431
+ ```
432
+
433
+ ### Custom Scoring Server
434
+
435
+ ```typescript
436
+ import { app, startServer } from 'triage-ows'
437
+ import { Hono } from 'hono'
438
+
439
+ // Add custom routes to the Hono app
440
+ app.get('/api/custom', (c) => c.json({ hello: 'world' }))
441
+
442
+ // Start with custom port
443
+ startServer(8080)
444
+ ```
445
+
446
+ ### Custom Config
447
+
448
+ ```typescript
449
+ import { loadConfig, getConfig, updateSpendingTiers } from 'triage-ows'
450
+
451
+ // Load from custom path
452
+ loadConfig('/path/to/config.json')
453
+
454
+ // Read config values
455
+ const threshold = getConfig().warningThreshold
456
+ ```
457
+
458
+ ### XMTP Notifications
459
+
460
+ XMTP uses native bindings (`@xmtp/node-sdk`). It's loaded lazily to avoid crashes on systems where bindings aren't available. The server handles this automatically — manual import is only needed for advanced library usage.
461
+
462
+ ```typescript
463
+ // XMTP requires native bindings — import lazily
464
+ const xmtp = await import('triage-ows/dist/xmtp')
465
+ await xmtp.initXMTP()
466
+
467
+ // Send notifications
468
+ await xmtp.notifyDenial(agent, amount, score, tier, limit, spent, reason, txTo)
469
+ await xmtp.notifyTrustChange(agent, oldScore, newScore, oldTier, newTier, reason)
470
+ await xmtp.notifyBudgetWarning(agent, spent, limit)
471
+ await xmtp.notifyAnomaly(agent, pattern, action, riskPenalty, oldScore, newScore)
472
+ ```
473
+
474
+ ---
475
+
476
+ ## Types Reference
477
+
478
+ ### PolicyContext
479
+
480
+ The input to every policy evaluation. Sent by OWS to `triage-policy` via stdin.
481
+
482
+ | Field | Type | Description |
483
+ |-------|------|-------------|
484
+ | `chain_id` | `string` | Chain identifier (e.g. `eip155:84532`) |
485
+ | `wallet_id` | `string` | OWS wallet UUID |
486
+ | `api_key_id` | `string` | OWS API key UUID (used as agent identifier) |
487
+ | `transaction.to` | `string` | Recipient address |
488
+ | `transaction.value` | `string` | Transaction value in wei |
489
+ | `transaction.raw_hex` | `string` | Raw transaction hex |
490
+ | `transaction.data` | `string` | Transaction data |
491
+ | `spending.daily_total` | `string` | OWS-reported daily total |
492
+ | `spending.date` | `string` | Date string (ISO) |
493
+ | `timestamp` | `string` | Request timestamp (ISO) |
494
+ | `policy_config` | `Record<string, unknown>` | Optional policy-level config |
495
+
496
+ ### AgentProfile
497
+
498
+ Complete agent state tracked by the store.
499
+
500
+ | Field | Type | Description |
501
+ |-------|------|-------------|
502
+ | `address` | `string` | Agent identifier (api_key_id) |
503
+ | `walletId` | `string?` | OWS wallet UUID |
504
+ | `apiKeyId` | `string?` | OWS API key UUID |
505
+ | `isOWSWallet` | `boolean` | Whether registered as OWS wallet |
506
+ | `worldIdVerified` | `boolean` | World ID human proof verified |
507
+ | `webBotAuthVerified` | `boolean` | RFC 9421 signature verified |
508
+ | `nullifierHash` | `string?` | World ID nullifier hash |
509
+ | `trustScore` | `number` | Current trust score (0-100) |
510
+ | `breakdown` | `TrustBreakdown` | Per-factor score breakdown |
511
+ | `tier` | `string` | Current spending tier name |
512
+ | `totalRequests` | `number` | Lifetime request count |
513
+ | `successfulRequests` | `number` | Lifetime approved count |
514
+ | `failedRequests` | `number` | Lifetime denied count |
515
+ | `dailySpent` | `number` | Today's spending in USD |
516
+ | `dailyDate` | `string` | Date of current daily tracking |
517
+ | `consecutiveApprovals` | `number` | Current approval streak |
518
+ | `consecutiveDenials` | `number` | Current denial streak |
519
+ | `totalApproved` | `number` | Lifetime approved transactions |
520
+ | `totalDenied` | `number` | Lifetime denied transactions |
521
+ | `humanOverrides` | `number` | Total human override count |
522
+ | `counterparties` | `string[]` | Unique transaction targets |
523
+ | `pendingOverride` | `object?` | Pending override data with `createdAt` timestamp |
524
+ | `requestTimestamps` | `number[]` | Last 100 request timestamps |
525
+ | `lastActive` | `number` | Last activity timestamp |
526
+ | `consecutiveCleanDays` | `number` | Days with only approved transactions |
527
+ | `cleanDayDate` | `string` | Date of last clean day check |
528
+ | `createdAt` | `number` | Agent creation timestamp |
529
+
530
+ ### TrustBreakdown
531
+
532
+ | Field | Type | Range | Description |
533
+ |-------|------|-------|-------------|
534
+ | `identity` | `number` | 0-35 | Identity verification score |
535
+ | `onChain` | `number` | 0-20 | On-chain history score |
536
+ | `behavior` | `number` | 0-20 | Transaction behavior score |
537
+ | `compliance` | `number` | 0-15 | Policy compliance score |
538
+ | `network` | `number` | 0-5 | Network trust score |
539
+ | `risk` | `number` | 0-30 | Risk penalty (subtracted) |
540
+ | `total` | `number` | 0-100 | Final composite score |
541
+
542
+ ### TriageConfig
543
+
544
+ | Option | Type | Default | Description |
545
+ |--------|------|---------|-------------|
546
+ | `scoreBands` | `Array` | 6 tiers | Custom tier definitions (name, min, dailyLimit, perTxLimit, color) |
547
+ | `warningThreshold` | `number` | `0.8` | Budget warning threshold (0-1) |
548
+ | `xmtp.enabled` | `boolean` | `true` | Enable XMTP notifications |
549
+ | `worldId.enabled` | `boolean` | `true` | Enable World ID verification |
550
+ | `webBotAuth.enabled` | `boolean` | `true` | Enable RFC 9421 signature verification |
551
+ | `networkScore.enabled` | `boolean` | `true` | Enable network trust scoring |
552
+ | `scoring.overrideBoost` | `number` | `3` | Trust score boost per human override |
553
+ | `scoring.maxFrequencyPenalty` | `number` | `10` | Max penalty for request frequency spikes |
554
+ | `scoring.inactivityDecayRate` | `number` | `0.5` | Trust decay per hour of inactivity |
555
+ | `port` | `number` | `4021` | Server port |
556
+ | `dashboardEnabled` | `boolean` | `true` | Serve dashboard static files |
557
+
558
+ ---
559
+
560
+ ## Configuration
561
+
562
+ ### triage.config.json
563
+
564
+ Create with `triage-ows init` or manually:
565
+
566
+ ```json
567
+ {
568
+ "warningThreshold": 0.8,
569
+ "scoreBands": [
570
+ { "name": "Sovereign", "min": 80, "dailyLimit": 1000, "perTxLimit": 500 },
571
+ { "name": "Trusted", "min": 60, "dailyLimit": 200, "perTxLimit": 100 },
572
+ { "name": "Building", "min": 40, "dailyLimit": 50, "perTxLimit": 25 },
573
+ { "name": "Cautious", "min": 20, "dailyLimit": 10, "perTxLimit": 5 },
574
+ { "name": "Restricted", "min": 1, "dailyLimit": 2, "perTxLimit": 1 },
575
+ { "name": "Frozen", "min": 0, "dailyLimit": 0, "perTxLimit": 0 }
576
+ ],
577
+ "scoring": {
578
+ "overrideBoost": 3,
579
+ "maxFrequencyPenalty": 10,
580
+ "inactivityDecayRate": 0.5
581
+ },
582
+ "xmtp": { "enabled": true },
583
+ "worldId": { "enabled": true },
584
+ "webBotAuth": { "enabled": true },
585
+ "networkScore": { "enabled": true },
586
+ "port": 4021,
587
+ "dashboardEnabled": true
588
+ }
589
+ ```
590
+
591
+ Config is loaded from (in order):
592
+ 1. Path passed to `loadConfig(path)`
593
+ 2. `TRIAGE_CONFIG_PATH` environment variable
594
+ 3. `./triage.config.json` in the current working directory
595
+ 4. Built-in defaults (if no file found)
596
+
597
+ ---
598
+
599
+ ## Environment Variables
600
+
601
+ | Variable | Required | Default | Description |
602
+ |----------|----------|---------|-------------|
603
+ | `PORT` | No | `4021` | Server port |
604
+ | `POLICY_SECRET` | No | - | Shared secret for policy endpoint authentication |
605
+ | `WORLD_ID_RP_ID` | No | - | World ID relying party ID |
606
+ | `WORLD_ID_SIGNING_KEY` | No | - | World ID signing key (hex) |
607
+ | `XMTP_PRIVATE_KEY` | No | - | Private key for XMTP notifications |
608
+ | `HUMAN_XMTP_ADDRESS` | No | - | Wallet address for XMTP DM notifications |
609
+ | `PAY_TO_ADDRESS` | No | - | Address for x402 payment requirements |
610
+ | `BASE_SEPOLIA_RPC_URL` | No | `https://sepolia.base.org` | Base Sepolia RPC endpoint |
611
+ | `SCORING_SERVER_URL` | No | `http://localhost:4021` | Scoring server URL (used by triage-policy) |
612
+ | `TRIAGE_CONFIG_PATH` | No | `./triage.config.json` | Path to config file |
613
+
614
+ Generate a `.env.example` with `triage-ows init`.
615
+
616
+ ---
617
+
618
+ ## Override Flow
619
+
620
+ ```
621
+ 1. Agent sends transaction
622
+ 2. Policy evaluation: DENY (exceeds limit)
623
+ 3. Server records pendingOverride with 5-minute TTL
624
+ 4. XMTP notification sent to human owner:
625
+
626
+ "Transaction denied
627
+ Agent: 0xAgent...
628
+ Amount: $50.00 USDC -> 0xRecipient...
629
+ Trust score: 42 (Building tier)
630
+ Reason: Exceeds per-transaction limit ($25)
631
+
632
+ Reply 'override' to approve this transaction."
633
+
634
+ 5. Human replies "override" in XMTP DM
635
+ 6. Message listener detects reply, POSTs to /api/override/:address
636
+ 7. Transaction approved, trust score boosted by +3
637
+ 8. TRUST_CHANGE event emitted to dashboard
638
+ 9. Override expires after 5 minutes if not acted on
639
+ ```
640
+
641
+ ---
642
+
643
+ ## Dashboard
644
+
645
+ The dashboard is a React SPA served from `/` on the scoring server.
646
+
647
+ **Panels:**
648
+
649
+ - **Stat Cards** -- Total agents, policy decisions (with approval rate), active wallets, daily volume
650
+ - **Signing Decisions** -- Canvas particle visualization with 3 lanes (APPROVE green, DENY red, OVERRIDE gold). Particle size scales with transaction amount. Shows running $ volume per lane.
651
+ - **Agent Budgets** -- Top 5 agents with progress bars showing daily spend vs limit. 80% tick marker. Tier labels.
652
+ - **Policy Decisions** -- Real-time feed of every transaction decision with agent, score, amount, budget usage, and denial reasons
653
+ - **Trust Leaderboard** -- Top 8 agents ranked by score with tier badges, score bars, daily limits, trend arrows, and World ID / Web Bot Auth verification icons
654
+
655
+ ---
656
+
657
+ ## Security
658
+
659
+ - **Policy Secret** -- Optional shared secret (`POLICY_SECRET` env) authenticates the policy evaluation endpoint. Both the executable and server must share the same secret.
660
+ - **World ID** -- Nullifier hashes are verified server-side via the World API. The client never self-reports verification status.
661
+ - **Override Timeout** -- Pending overrides expire after 5 minutes. Stale overrides cannot be replayed.
662
+ - **Input Validation** -- Policy evaluation validates JSON structure, handles malformed BigInt values gracefully (falls back to 0).
663
+ - **XMTP Graceful Fallback** -- XMTP is lazy-loaded. If native bindings are unavailable, the server starts normally with notifications disabled.
664
+ - **Web Bot Auth** -- Ed25519 signature verification follows RFC 9421. Public keys must be pre-registered via the API.
665
+
666
+ ---
667
+
668
+ ## Features
669
+
670
+ - **5-factor trust scoring** -- Identity, On-Chain, Behavior, Compliance, Network minus Risk
671
+ - **6 spending tiers** -- Sovereign through Frozen with configurable limits
672
+ - **Real-time dashboard** -- Canvas particle flow, live feed, leaderboard, budget tracking
673
+ - **XMTP notifications** -- Denial alerts, budget warnings, trust changes via DM
674
+ - **Human override** -- Reply "override" in XMTP to approve denied transactions (+3 trust boost)
675
+ - **Override timeout** -- 5-minute expiry on pending overrides
676
+ - **Web Bot Auth** -- RFC 9421 HTTP Message Signatures for cryptographic agent identity
677
+ - **World ID** -- Human verification via World ID proof-of-personhood
678
+ - **x402 payments** -- Payment verification via x402 facilitator
679
+ - **ETH price oracle** -- Live CoinGecko price feed with 5-minute cache and $2,500 fallback
680
+ - **On-chain balance** -- Base Sepolia balance lookup via viem with 10-minute cache
681
+ - **Configurable** -- `triage.config.json` for tiers, thresholds, feature flags, scoring tuning
682
+ - **CLI** -- init, dev, register, attach, seed, status commands
683
+ - **Policy secret** -- Optional shared secret for endpoint authentication
684
+ - **Budget warnings** -- Configurable threshold (default 80%) with XMTP alerts
685
+ - **WebSocket events** -- Real-time POLICY_DECISION, TRUST_CHANGE, BUDGET_WARNING streams
686
+
687
+ ---
688
+
689
+ ## Technologies
690
+
691
+ | Technology | Role |
692
+ |-----------|------|
693
+ | [Hono](https://hono.dev) | HTTP framework |
694
+ | [viem](https://viem.sh) | On-chain queries (Base Sepolia) |
695
+ | [XMTP](https://xmtp.org) | Agent-to-human messaging (`@xmtp/node-sdk`) |
696
+ | [World ID](https://worldcoin.org/world-id) | Human proof-of-personhood |
697
+ | [x402](https://x402.org) | Payment verification protocol |
698
+ | [ws](https://github.com/websockets/ws) | WebSocket server |
699
+ | [React](https://react.dev) | Dashboard UI |
700
+ | [Tailwind CSS](https://tailwindcss.com) | Dashboard styling |
701
+ | [CoinGecko](https://www.coingecko.com) | ETH/USD price feed |
702
+ | Node.js `crypto` | Ed25519 signature verification (RFC 9421) |
703
+
704
+ ---
705
+
706
+ ## OWS Integration
707
+
708
+ ### Register the Policy
709
+
710
+ ```bash
711
+ # Registers triage-trust policy with OWS
712
+ triage-ows register
713
+ ```
714
+
715
+ This copies `policy-template.json` to `~/.ows/policies/triage-trust.json` and runs `ows policy create`.
716
+
717
+ ### Attach an Agent
718
+
719
+ ```bash
720
+ # Create an OWS API key with Triage governance
721
+ triage-ows attach --wallet my-agent --key my-agent-key
722
+ ```
723
+
724
+ This runs `ows key create --name <key> --wallet <wallet> --policy triage-trust`.
725
+
726
+ ### Policy Template
727
+
728
+ ```json
729
+ {
730
+ "id": "triage-trust",
731
+ "name": "Triage Reputation-Gated Policy",
732
+ "version": 1,
733
+ "rules": [
734
+ { "type": "allowed_chains", "chain_ids": ["eip155:84532"] }
735
+ ],
736
+ "executable": "./node_modules/.bin/triage-policy",
737
+ "config": {
738
+ "scoring_server": "http://localhost:4021"
739
+ },
740
+ "action": "deny"
741
+ }
742
+ ```
743
+
744
+ The `action: "deny"` means transactions are blocked by default if the policy executable fails or times out.
745
+
746
+ ---
747
+
748
+ ## Demo & Simulation
749
+
750
+ ### Seed Demo Agents
751
+
752
+ ```bash
753
+ npm run seed
754
+ ```
755
+
756
+ Creates 5 agents at different trust levels: Sovereign (~81), Trusted (~72), Building (~53), Cautious (~23), Restricted (~17).
757
+
758
+ ### Run Live Simulation
759
+
760
+ ```bash
761
+ # Start server in one terminal
762
+ triage-ows dev
763
+
764
+ # Run simulation in another
765
+ npm run simulate
766
+ ```
767
+
768
+ The simulator fires policy evaluations every 2-3 seconds with randomized amounts, occasionally triggering denials and new agent registrations.
769
+
770
+ ---
771
+
772
+ ## What's Next
773
+
774
+ - **Persistence** -- SQLite or Postgres backend for agent state
775
+ - **Multi-chain** -- Extend beyond Base Sepolia to mainnet and other chains
776
+ - **Full network scoring** -- Graph-based trust propagation across agent networks
777
+ - **Trust portability** -- Export/import agent trust scores across Triage instances
778
+ - **Policy marketplace** -- Share and compose policy configurations
779
+ - **Anomaly detection** -- ML-based pattern detection triggering `notifyAnomaly`
780
+ - **Rate limiting** -- Per-agent request rate limits on the API
781
+
782
+ ---
783
+
784
+ ## License
785
+
786
+ MIT