@dupecom/botcha-cloudflare 0.20.2 → 0.23.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 (88) hide show
  1. package/README.md +74 -9
  2. package/dist/agent-auth.d.ts +129 -0
  3. package/dist/agent-auth.d.ts.map +1 -0
  4. package/dist/agent-auth.js +210 -0
  5. package/dist/agents.d.ts +10 -0
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js +51 -1
  8. package/dist/app-gate.d.ts +6 -0
  9. package/dist/app-gate.d.ts.map +1 -0
  10. package/dist/app-gate.js +69 -0
  11. package/dist/apps.d.ts +13 -4
  12. package/dist/apps.d.ts.map +1 -1
  13. package/dist/apps.js +30 -4
  14. package/dist/dashboard/account.d.ts +63 -0
  15. package/dist/dashboard/account.d.ts.map +1 -0
  16. package/dist/dashboard/account.js +488 -0
  17. package/dist/dashboard/api.js +15 -68
  18. package/dist/dashboard/auth.d.ts.map +1 -1
  19. package/dist/dashboard/auth.js +14 -14
  20. package/dist/dashboard/docs.d.ts.map +1 -1
  21. package/dist/dashboard/docs.js +146 -3
  22. package/dist/dashboard/layout.d.ts.map +1 -1
  23. package/dist/dashboard/layout.js +2 -2
  24. package/dist/dashboard/mcp-setup.d.ts +15 -0
  25. package/dist/dashboard/mcp-setup.d.ts.map +1 -0
  26. package/dist/dashboard/mcp-setup.js +391 -0
  27. package/dist/dashboard/showcase.d.ts +6 -10
  28. package/dist/dashboard/showcase.d.ts.map +1 -1
  29. package/dist/dashboard/showcase.js +67 -991
  30. package/dist/dashboard/whitepaper.d.ts.map +1 -1
  31. package/dist/dashboard/whitepaper.js +42 -4
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +660 -83
  35. package/dist/mcp.d.ts +20 -0
  36. package/dist/mcp.d.ts.map +1 -0
  37. package/dist/mcp.js +1290 -0
  38. package/dist/oauth-agent.d.ts +130 -0
  39. package/dist/oauth-agent.d.ts.map +1 -0
  40. package/dist/oauth-agent.js +194 -0
  41. package/dist/static.d.ts +781 -5
  42. package/dist/static.d.ts.map +1 -1
  43. package/dist/static.js +790 -111
  44. package/dist/tap-a2a-routes.d.ts +355 -0
  45. package/dist/tap-a2a-routes.d.ts.map +1 -0
  46. package/dist/tap-a2a-routes.js +475 -0
  47. package/dist/tap-a2a.d.ts +199 -0
  48. package/dist/tap-a2a.d.ts.map +1 -0
  49. package/dist/tap-a2a.js +502 -0
  50. package/dist/tap-agents.d.ts +15 -0
  51. package/dist/tap-agents.d.ts.map +1 -1
  52. package/dist/tap-agents.js +31 -1
  53. package/dist/tap-ans-routes.d.ts +302 -0
  54. package/dist/tap-ans-routes.d.ts.map +1 -0
  55. package/dist/tap-ans-routes.js +535 -0
  56. package/dist/tap-ans.d.ts +241 -0
  57. package/dist/tap-ans.d.ts.map +1 -0
  58. package/dist/tap-ans.js +481 -0
  59. package/dist/tap-delegation-routes.d.ts.map +1 -1
  60. package/dist/tap-delegation-routes.js +11 -0
  61. package/dist/tap-did.d.ts +140 -0
  62. package/dist/tap-did.d.ts.map +1 -0
  63. package/dist/tap-did.js +262 -0
  64. package/dist/tap-oidca-routes.d.ts +383 -0
  65. package/dist/tap-oidca-routes.d.ts.map +1 -0
  66. package/dist/tap-oidca-routes.js +597 -0
  67. package/dist/tap-oidca.d.ts +288 -0
  68. package/dist/tap-oidca.d.ts.map +1 -0
  69. package/dist/tap-oidca.js +461 -0
  70. package/dist/tap-routes.d.ts +24 -8
  71. package/dist/tap-routes.d.ts.map +1 -1
  72. package/dist/tap-routes.js +169 -23
  73. package/dist/tap-vc-routes.d.ts +358 -0
  74. package/dist/tap-vc-routes.d.ts.map +1 -0
  75. package/dist/tap-vc-routes.js +367 -0
  76. package/dist/tap-vc.d.ts +125 -0
  77. package/dist/tap-vc.d.ts.map +1 -0
  78. package/dist/tap-vc.js +245 -0
  79. package/dist/tap-x402-routes.d.ts +89 -0
  80. package/dist/tap-x402-routes.d.ts.map +1 -0
  81. package/dist/tap-x402-routes.js +579 -0
  82. package/dist/tap-x402.d.ts +222 -0
  83. package/dist/tap-x402.d.ts.map +1 -0
  84. package/dist/tap-x402.js +546 -0
  85. package/dist/webhooks.d.ts +99 -0
  86. package/dist/webhooks.d.ts.map +1 -0
  87. package/dist/webhooks.js +642 -0
  88. package/package.json +3 -1
@@ -0,0 +1,502 @@
1
+ /**
2
+ * A2A Agent Card Attestation — BOTCHA as A2A Trust Oracle
3
+ *
4
+ * Implements Google's A2A (Agent-to-Agent) protocol trust layer:
5
+ * 1. BOTCHA's own Agent Card (/.well-known/agent.json)
6
+ * 2. Agent Card attestation issuance (POST /v1/a2a/attest)
7
+ * 3. Agent Card attestation verification (POST /v1/a2a/verify-card)
8
+ * 4. Verified card registry (GET /v1/a2a/cards)
9
+ *
10
+ * Attestation model:
11
+ * - Input: any A2A Agent Card JSON
12
+ * - Output: a signed JWT embedding SHA-256(canonicalized card)
13
+ * - The JWT is embedded into card.extensions.botcha_attestation
14
+ * - Verification checks: signature, hash match, expiration, revocation
15
+ *
16
+ * References:
17
+ * https://google.github.io/A2A/
18
+ * https://github.com/google-a2a/A2A
19
+ */
20
+ import { SignJWT, jwtVerify } from 'jose';
21
+ // ============ BOTCHA'S OWN AGENT CARD ============
22
+ export const BOTCHA_VERSION = '0.21.2';
23
+ export const BOTCHA_URL = 'https://botcha.ai';
24
+ /**
25
+ * BOTCHA's A2A Agent Card.
26
+ * Served at /.well-known/agent.json
27
+ */
28
+ export function getBotchaAgentCard(version) {
29
+ const v = version || BOTCHA_VERSION;
30
+ return {
31
+ name: 'BOTCHA',
32
+ description: 'Reverse CAPTCHA for AI agents. Prove you\'re a bot. Humans need not apply.',
33
+ url: BOTCHA_URL,
34
+ version: v,
35
+ documentationUrl: 'https://botcha.ai/docs',
36
+ capabilities: {
37
+ streaming: false,
38
+ pushNotifications: false,
39
+ stateTransitionHistory: false,
40
+ },
41
+ authentication: [
42
+ {
43
+ schemes: ['Bearer'],
44
+ description: 'BOTCHA access token — obtain via POST /v1/token/verify after solving a challenge',
45
+ },
46
+ ],
47
+ defaultInputModes: ['application/json'],
48
+ defaultOutputModes: ['application/json'],
49
+ skills: [
50
+ {
51
+ id: 'verify-agent',
52
+ name: 'Verify Agent',
53
+ description: 'Issue a BOTCHA challenge to verify an AI agent. Returns a signed access token on success.',
54
+ tags: ['verification', 'identity', 'challenge'],
55
+ examples: [
56
+ 'GET /v1/token?app_id=<app_id> → solve SHA256 challenge → POST /v1/token/verify',
57
+ ],
58
+ inputModes: ['application/json'],
59
+ outputModes: ['application/json'],
60
+ },
61
+ {
62
+ id: 'attest-card',
63
+ name: 'Attest Agent Card',
64
+ description: 'Issue a BOTCHA attestation for an A2A Agent Card. The attestation JWT is embedded into the card\'s extensions.botcha_attestation field, making BOTCHA the trust oracle for the A2A ecosystem.',
65
+ tags: ['attestation', 'a2a', 'trust', 'verification'],
66
+ examples: [
67
+ 'POST /v1/a2a/attest with an A2A Agent Card JSON → attested card with extensions.botcha_attestation',
68
+ ],
69
+ inputModes: ['application/json'],
70
+ outputModes: ['application/json'],
71
+ },
72
+ {
73
+ id: 'verify-card',
74
+ name: 'Verify Attested Agent Card',
75
+ description: 'Verify a BOTCHA-attested A2A Agent Card. Checks signature, card hash integrity, and expiration.',
76
+ tags: ['verification', 'a2a', 'attestation', 'trust'],
77
+ examples: [
78
+ 'POST /v1/a2a/verify-card with an attested A2A Agent Card JSON',
79
+ ],
80
+ inputModes: ['application/json'],
81
+ outputModes: ['application/json'],
82
+ },
83
+ {
84
+ id: 'check-reputation',
85
+ name: 'Check Reputation',
86
+ description: 'Get an agent\'s BOTCHA reputation score based on challenge history and verification track record.',
87
+ tags: ['reputation', 'trust', 'score'],
88
+ examples: [
89
+ 'GET /v1/reputation/:agent_id',
90
+ ],
91
+ inputModes: ['application/json'],
92
+ outputModes: ['application/json'],
93
+ },
94
+ ],
95
+ extensions: {
96
+ botcha: {
97
+ challenge_endpoint: `${BOTCHA_URL}/v1/token`,
98
+ verify_endpoint: `${BOTCHA_URL}/v1/token/verify`,
99
+ attest_endpoint: `${BOTCHA_URL}/v1/a2a/attest`,
100
+ verify_card_endpoint: `${BOTCHA_URL}/v1/a2a/verify-card`,
101
+ registry_endpoint: `${BOTCHA_URL}/v1/a2a/cards`,
102
+ openapi: `${BOTCHA_URL}/openapi.json`,
103
+ ai_txt: `${BOTCHA_URL}/ai.txt`,
104
+ },
105
+ },
106
+ };
107
+ }
108
+ // ============ CARD CANONICALIZATION & HASHING ============
109
+ /**
110
+ * Canonicalize an A2A Agent Card for hashing.
111
+ *
112
+ * We remove the extensions.botcha_attestation field before hashing
113
+ * so that the attestation can be embedded in the card without
114
+ * invalidating its own hash. All other fields are included.
115
+ *
116
+ * Canonicalization: JSON.stringify with sorted keys (deterministic).
117
+ */
118
+ export function canonicalizeCard(card) {
119
+ // Deep clone and strip the attestation extension
120
+ const stripped = deepCloneWithoutAttestation(card);
121
+ // Sort keys recursively for deterministic output
122
+ return stableStringify(stripped);
123
+ }
124
+ /**
125
+ * Compute SHA-256 of a canonicalized card string.
126
+ * Returns lowercase hex string.
127
+ */
128
+ export async function hashCard(card) {
129
+ const canonical = canonicalizeCard(card);
130
+ const encoder = new TextEncoder();
131
+ const data = encoder.encode(canonical);
132
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
133
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
134
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
135
+ }
136
+ function deepCloneWithoutAttestation(card) {
137
+ const clone = {};
138
+ for (const [key, value] of Object.entries(card)) {
139
+ if (key === 'extensions') {
140
+ if (value && typeof value === 'object') {
141
+ const exts = { ...value };
142
+ delete exts['botcha_attestation'];
143
+ if (Object.keys(exts).length > 0) {
144
+ clone['extensions'] = deepCloneValue(exts);
145
+ }
146
+ // If extensions only had botcha_attestation, omit extensions entirely
147
+ }
148
+ // else: no extensions, skip
149
+ }
150
+ else {
151
+ clone[key] = deepCloneValue(value);
152
+ }
153
+ }
154
+ return clone;
155
+ }
156
+ function deepCloneValue(value) {
157
+ if (value === null || typeof value !== 'object')
158
+ return value;
159
+ if (Array.isArray(value))
160
+ return value.map(deepCloneValue);
161
+ const obj = value;
162
+ const result = {};
163
+ for (const [k, v] of Object.entries(obj)) {
164
+ result[k] = deepCloneValue(v);
165
+ }
166
+ return result;
167
+ }
168
+ /**
169
+ * Stable (deterministic) JSON stringify with sorted keys.
170
+ * Handles nested objects, arrays, primitives.
171
+ */
172
+ function stableStringify(value) {
173
+ if (value === null || value === undefined)
174
+ return String(value);
175
+ if (typeof value !== 'object')
176
+ return JSON.stringify(value);
177
+ if (Array.isArray(value)) {
178
+ return '[' + value.map(stableStringify).join(',') + ']';
179
+ }
180
+ const obj = value;
181
+ const keys = Object.keys(obj).sort();
182
+ const pairs = keys
183
+ .filter(k => obj[k] !== undefined)
184
+ .map(k => JSON.stringify(k) + ':' + stableStringify(obj[k]));
185
+ return '{' + pairs.join(',') + '}';
186
+ }
187
+ // ============ ATTESTATION ISSUANCE ============
188
+ const DEFAULT_DURATION = 86400; // 24 hours
189
+ const MAX_DURATION = 2592000; // 30 days
190
+ /**
191
+ * Issue a BOTCHA attestation for an A2A Agent Card.
192
+ *
193
+ * Steps:
194
+ * 1. Validate card (name + url required)
195
+ * 2. Canonicalize and hash the card (without extensions)
196
+ * 3. Sign a JWT with card_hash + agent identity
197
+ * 4. Embed attestation in card.extensions.botcha_attestation
198
+ * 5. Store attestation in KV for lookup and revocation
199
+ */
200
+ export async function attestCard(sessions, secret, options) {
201
+ try {
202
+ const { card, app_id } = options;
203
+ // Validate required card fields
204
+ if (!card.name || typeof card.name !== 'string') {
205
+ return { success: false, error: 'Agent Card must have a "name" field' };
206
+ }
207
+ if (!card.url || typeof card.url !== 'string') {
208
+ return { success: false, error: 'Agent Card must have a "url" field' };
209
+ }
210
+ // Validate URL format
211
+ try {
212
+ new URL(card.url);
213
+ }
214
+ catch {
215
+ return { success: false, error: `Invalid "url" field: ${card.url}` };
216
+ }
217
+ const trustLevel = options.trust_level || 'verified';
218
+ const durationSeconds = Math.min(options.duration_seconds ?? DEFAULT_DURATION, MAX_DURATION);
219
+ const now = Date.now();
220
+ const expiresAt = now + durationSeconds * 1000;
221
+ const attestationId = crypto.randomUUID();
222
+ // Hash the card (without any existing attestation)
223
+ const cardHash = await hashCard(card);
224
+ // Sign attestation JWT
225
+ const encoder = new TextEncoder();
226
+ const secretKey = encoder.encode(secret);
227
+ const token = await new SignJWT({
228
+ type: 'botcha-a2a-attestation',
229
+ card_hash: cardHash,
230
+ agent_name: card.name,
231
+ agent_url: card.url,
232
+ app_id,
233
+ trust_level: trustLevel,
234
+ jti: attestationId,
235
+ })
236
+ .setProtectedHeader({ alg: 'HS256' })
237
+ .setSubject(card.url)
238
+ .setIssuer(BOTCHA_URL)
239
+ .setIssuedAt()
240
+ .setExpirationTime(Math.floor(expiresAt / 1000))
241
+ .sign(secretKey);
242
+ // Build attestation record
243
+ const attestation = {
244
+ attestation_id: attestationId,
245
+ agent_url: card.url,
246
+ agent_name: card.name,
247
+ app_id,
248
+ card_hash: cardHash,
249
+ token,
250
+ trust_level: trustLevel,
251
+ created_at: now,
252
+ expires_at: expiresAt,
253
+ revoked: false,
254
+ card_snapshot: deepCloneWithoutAttestation(card),
255
+ };
256
+ // Store in KV with TTL
257
+ const ttlSeconds = Math.max(60, Math.floor(durationSeconds));
258
+ await sessions.put(`a2a_attestation:${attestationId}`, JSON.stringify(attestation), { expirationTtl: ttlSeconds });
259
+ // Update registry index (agent_url → list of attestation IDs)
260
+ await updateCardRegistryIndex(sessions, card.url, attestationId, 'add');
261
+ // Build the attested card (card + extensions.botcha_attestation)
262
+ const extension = {
263
+ token,
264
+ verified_at: new Date(now).toISOString(),
265
+ trust_level: trustLevel,
266
+ issuer: BOTCHA_URL,
267
+ card_hash: cardHash,
268
+ expires_at: new Date(expiresAt).toISOString(),
269
+ };
270
+ const attestedCard = {
271
+ ...card,
272
+ extensions: {
273
+ ...(card.extensions || {}),
274
+ botcha_attestation: extension,
275
+ },
276
+ };
277
+ return { success: true, attestation, attested_card: attestedCard };
278
+ }
279
+ catch (error) {
280
+ console.error('A2A card attestation error:', error);
281
+ return { success: false, error: 'Internal server error' };
282
+ }
283
+ }
284
+ // ============ ATTESTATION VERIFICATION ============
285
+ /**
286
+ * Verify a BOTCHA-attested A2A Agent Card.
287
+ *
288
+ * Checks:
289
+ * 1. Card has extensions.botcha_attestation.token
290
+ * 2. JWT signature and expiration (cryptographic)
291
+ * 3. JWT type is 'botcha-a2a-attestation'
292
+ * 4. card_hash matches the current card content (tamper detection)
293
+ * 5. Revocation status (KV lookup, fail-open)
294
+ *
295
+ * Returns verified/invalid + decoded claims.
296
+ */
297
+ export async function verifyCard(sessions, secret, card) {
298
+ try {
299
+ // Extract attestation token from card
300
+ const attestationExt = card.extensions?.botcha_attestation;
301
+ if (!attestationExt || typeof attestationExt !== 'object') {
302
+ return {
303
+ success: true,
304
+ valid: false,
305
+ error: 'NO_ATTESTATION',
306
+ reason: 'Card does not have extensions.botcha_attestation',
307
+ };
308
+ }
309
+ const token = attestationExt.token;
310
+ if (!token || typeof token !== 'string') {
311
+ return {
312
+ success: true,
313
+ valid: false,
314
+ error: 'MISSING_TOKEN',
315
+ reason: 'extensions.botcha_attestation.token is missing',
316
+ };
317
+ }
318
+ // Verify JWT signature and expiration
319
+ const encoder = new TextEncoder();
320
+ const secretKey = encoder.encode(secret);
321
+ let payload;
322
+ try {
323
+ const result = await jwtVerify(token, secretKey, { algorithms: ['HS256'] });
324
+ payload = result.payload;
325
+ }
326
+ catch (err) {
327
+ return {
328
+ success: true,
329
+ valid: false,
330
+ error: 'INVALID_TOKEN',
331
+ reason: err instanceof Error ? err.message : 'JWT verification failed',
332
+ };
333
+ }
334
+ // Check token type
335
+ if (payload['type'] !== 'botcha-a2a-attestation') {
336
+ return {
337
+ success: true,
338
+ valid: false,
339
+ error: 'WRONG_TOKEN_TYPE',
340
+ reason: `Expected 'botcha-a2a-attestation', got '${payload['type']}'`,
341
+ };
342
+ }
343
+ const jti = payload['jti'];
344
+ // Check revocation
345
+ if (jti) {
346
+ try {
347
+ const revoked = await sessions.get(`a2a_attestation_revoked:${jti}`);
348
+ if (revoked) {
349
+ return {
350
+ success: true,
351
+ valid: false,
352
+ error: 'REVOKED',
353
+ reason: 'Attestation has been revoked',
354
+ attestation_id: jti,
355
+ };
356
+ }
357
+ }
358
+ catch {
359
+ // fail-open on KV errors
360
+ }
361
+ }
362
+ // Verify card hash matches (tamper detection)
363
+ const tokenCardHash = payload['card_hash'];
364
+ const currentCardHash = await hashCard(card);
365
+ if (tokenCardHash !== currentCardHash) {
366
+ return {
367
+ success: true,
368
+ valid: false,
369
+ error: 'HASH_MISMATCH',
370
+ reason: 'Card content has been modified since attestation. The card hash does not match.',
371
+ card_hash: currentCardHash,
372
+ attestation_id: jti,
373
+ };
374
+ }
375
+ return {
376
+ success: true,
377
+ valid: true,
378
+ attestation_id: jti,
379
+ agent_url: payload['agent_url'],
380
+ agent_name: payload['agent_name'],
381
+ card_hash: tokenCardHash,
382
+ trust_level: payload['trust_level'],
383
+ app_id: payload['app_id'],
384
+ issued_at: new Date(payload['iat'] * 1000).toISOString(),
385
+ expires_at: new Date(payload['exp'] * 1000).toISOString(),
386
+ };
387
+ }
388
+ catch (error) {
389
+ console.error('A2A card verification error:', error);
390
+ return { success: false, error: 'Internal server error' };
391
+ }
392
+ }
393
+ // ============ CARD REGISTRY ============
394
+ /**
395
+ * Get a specific A2A attestation record by ID.
396
+ */
397
+ export async function getCardAttestation(sessions, attestationId) {
398
+ try {
399
+ const data = await sessions.get(`a2a_attestation:${attestationId}`, 'text');
400
+ if (!data) {
401
+ return { success: false, error: 'Attestation not found or expired' };
402
+ }
403
+ return { success: true, attestation: JSON.parse(data) };
404
+ }
405
+ catch (error) {
406
+ console.error('Failed to get A2A attestation:', error);
407
+ return { success: false, error: 'Internal server error' };
408
+ }
409
+ }
410
+ /**
411
+ * List BOTCHA-verified A2A cards from the registry.
412
+ *
413
+ * Query options:
414
+ * verified_only: boolean — only return non-revoked attestations (default: true)
415
+ * agent_url: string — filter by agent URL
416
+ * limit: number — max results (default 50, max 200)
417
+ */
418
+ export async function listVerifiedCards(sessions, opts = {}) {
419
+ try {
420
+ const verifiedOnly = opts.verified_only !== false;
421
+ const limit = Math.min(opts.limit || 50, 200);
422
+ // If filtering by agent_url, use the per-agent index
423
+ if (opts.agent_url) {
424
+ const indexData = await sessions.get(`a2a_registry:${encodeURIComponent(opts.agent_url)}`, 'text');
425
+ const ids = indexData ? JSON.parse(indexData) : [];
426
+ const results = [];
427
+ for (const id of ids.slice(-limit)) {
428
+ const result = await getCardAttestation(sessions, id);
429
+ if (result.success && result.attestation) {
430
+ if (!verifiedOnly || !result.attestation.revoked) {
431
+ results.push(result.attestation);
432
+ }
433
+ }
434
+ }
435
+ return { success: true, attestations: results };
436
+ }
437
+ // Global registry index
438
+ const globalIndexData = await sessions.get('a2a_registry:global', 'text');
439
+ const globalIds = globalIndexData ? JSON.parse(globalIndexData) : [];
440
+ const results = [];
441
+ // Take the most recent `limit` entries
442
+ for (const id of globalIds.slice(-limit).reverse()) {
443
+ if (results.length >= limit)
444
+ break;
445
+ const result = await getCardAttestation(sessions, id);
446
+ if (result.success && result.attestation) {
447
+ if (!verifiedOnly || !result.attestation.revoked) {
448
+ results.push(result.attestation);
449
+ }
450
+ }
451
+ }
452
+ return { success: true, attestations: results };
453
+ }
454
+ catch (error) {
455
+ console.error('Failed to list A2A attestations:', error);
456
+ return { success: false, error: 'Internal server error' };
457
+ }
458
+ }
459
+ // ============ UTILITY ============
460
+ async function updateCardRegistryIndex(sessions, agentUrl, attestationId, operation) {
461
+ try {
462
+ // Per-agent index
463
+ const agentKey = `a2a_registry:${encodeURIComponent(agentUrl)}`;
464
+ const agentData = await sessions.get(agentKey, 'text');
465
+ let agentIds = agentData ? JSON.parse(agentData) : [];
466
+ if (operation === 'add' && !agentIds.includes(attestationId)) {
467
+ agentIds.push(attestationId);
468
+ }
469
+ else if (operation === 'remove') {
470
+ agentIds = agentIds.filter(id => id !== attestationId);
471
+ }
472
+ await sessions.put(agentKey, JSON.stringify(agentIds));
473
+ // Global index
474
+ const globalKey = 'a2a_registry:global';
475
+ const globalData = await sessions.get(globalKey, 'text');
476
+ let globalIds = globalData ? JSON.parse(globalData) : [];
477
+ if (operation === 'add' && !globalIds.includes(attestationId)) {
478
+ globalIds.push(attestationId);
479
+ // Keep global index bounded (last 1000)
480
+ if (globalIds.length > 1000) {
481
+ globalIds = globalIds.slice(-1000);
482
+ }
483
+ }
484
+ else if (operation === 'remove') {
485
+ globalIds = globalIds.filter(id => id !== attestationId);
486
+ }
487
+ await sessions.put(globalKey, JSON.stringify(globalIds));
488
+ }
489
+ catch (error) {
490
+ console.error('Failed to update A2A card registry index:', error);
491
+ // Fail silently — index updates are best-effort
492
+ }
493
+ }
494
+ export default {
495
+ getBotchaAgentCard,
496
+ canonicalizeCard,
497
+ hashCard,
498
+ attestCard,
499
+ verifyCard,
500
+ getCardAttestation,
501
+ listVerifiedCards,
502
+ };
@@ -22,6 +22,13 @@ export interface TAPAgent extends Agent {
22
22
  issuer?: string;
23
23
  tap_enabled?: boolean;
24
24
  last_verified_at?: number;
25
+ ans_name?: string;
26
+ ans_badge_id?: string;
27
+ ans_trust_level?: 'domain-validated' | 'key-validated' | 'behavior-validated';
28
+ ans_verified_at?: number;
29
+ did?: string;
30
+ provider?: 'anthropic' | 'openai' | 'google' | 'mistral' | 'cohere' | 'other';
31
+ provider_key_hash?: string;
25
32
  }
26
33
  /** Valid TAP capability actions — single source of truth */
27
34
  export declare const TAP_VALID_ACTIONS: readonly ["browse", "compare", "purchase", "audit", "search"];
@@ -63,6 +70,9 @@ export declare function registerTAPAgent(agents: KVNamespace, appId: string, reg
63
70
  capabilities?: TAPCapability[];
64
71
  trust_level?: 'basic' | 'verified' | 'enterprise';
65
72
  issuer?: string;
73
+ ans_name?: string;
74
+ /** Optional W3C DID for this agent — embedded as credentialSubject.id in issued VCs */
75
+ did?: string;
66
76
  }): Promise<{
67
77
  success: boolean;
68
78
  agent?: TAPAgent;
@@ -104,6 +114,11 @@ export declare function getTAPSession(sessions: KVNamespace, sessionId: string):
104
114
  session?: TAPSession;
105
115
  error?: string;
106
116
  }>;
117
+ /**
118
+ * Returns true if the string is a valid JWK JSON (EC, RSA, or OKP public key).
119
+ * Accepts a raw JWK JSON string.
120
+ */
121
+ export declare function isValidJWK(key: string): boolean;
107
122
  export declare function validateCapability(agentCapabilities: TAPCapability[], requiredAction: string, requiredScope?: string): {
108
123
  valid: boolean;
109
124
  error?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tap-agents.d.ts","sourceRoot":"","sources":["../src/tap-agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAC;AAIlE;;GAEG;AACH,MAAM,WAAW,QAAS,SAAQ,KAAK;IAErC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,YAAY,CAAC;IAGlD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,+DAAgE,CAAC;AAC/F,MAAM,MAAM,SAAS,GAAG,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE;QACb,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,mBAAmB,GAAG,gBAAgB,GAAG,SAAS,CAAC;IACzE,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,YAAY,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgDjE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAcjE;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,mBAAmB,EAAE,OAAO,GAC3B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAe,GACvB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsBpE;AAID;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,aAAa,EAAE,EAC7B,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BrE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,WAAW,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBrE;AAqDD,wBAAgB,kBAAkB,CAChC,iBAAiB,EAAE,aAAa,EAAE,EAClC,cAAc,EAAE,MAAM,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAsBpC;;;;;;;;;;AAED,wBAQE"}
1
+ {"version":3,"file":"tap-agents.d.ts","sourceRoot":"","sources":["../src/tap-agents.ts"],"names":[],"mappings":"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"}
@@ -34,7 +34,11 @@ export async function registerTAPAgent(agents, appId, registration) {
34
34
  trust_level: registration.trust_level || 'basic',
35
35
  issuer: registration.issuer,
36
36
  tap_enabled: Boolean(registration.public_key),
37
- last_verified_at: undefined
37
+ last_verified_at: undefined,
38
+ // ANS fields
39
+ ans_name: registration.ans_name,
40
+ // W3C DID (optional)
41
+ did: registration.did,
38
42
  };
39
43
  // Validate TAP configuration
40
44
  if (agent.public_key) {
@@ -175,11 +179,37 @@ function generateSessionId() {
175
179
  .map(b => b.toString(16).padStart(2, '0'))
176
180
  .join('');
177
181
  }
182
+ /**
183
+ * Returns true if the string is a valid JWK JSON (EC, RSA, or OKP public key).
184
+ * Accepts a raw JWK JSON string.
185
+ */
186
+ export function isValidJWK(key) {
187
+ try {
188
+ const jwk = JSON.parse(key);
189
+ if (!jwk || typeof jwk !== 'object')
190
+ return false;
191
+ if (!jwk.kty)
192
+ return false;
193
+ if (jwk.kty === 'EC')
194
+ return Boolean(jwk.crv && jwk.x && jwk.y);
195
+ if (jwk.kty === 'RSA')
196
+ return Boolean(jwk.n && jwk.e);
197
+ if (jwk.kty === 'OKP')
198
+ return Boolean(jwk.crv && jwk.x);
199
+ return false;
200
+ }
201
+ catch {
202
+ return false;
203
+ }
204
+ }
178
205
  function isValidPublicKey(key, algorithm) {
179
206
  // PEM format
180
207
  if (key.includes('BEGIN PUBLIC KEY') && key.includes('END PUBLIC KEY') && key.length > 100) {
181
208
  return true;
182
209
  }
210
+ // JWK JSON string (EC P-256, RSA, or OKP/Ed25519)
211
+ if (isValidJWK(key))
212
+ return true;
183
213
  // Raw Ed25519 key (32 bytes = ~44 base64 chars)
184
214
  if (algorithm === 'ed25519') {
185
215
  const stripped = key.replace(/[\s]/g, '');