@brainai/satp-client 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SATP V3 SDK - Integration Patterns & App-Agnostic Examples
5
+ *
6
+ * Practical patterns for integrating SATP V3 into applications.
7
+ * Each example is self-contained and can be run against devnet.
8
+ *
9
+ * Usage:
10
+ * node examples/integration-patterns.js [example-name]
11
+ *
12
+ * Examples: lookup, register, review-flow, reputation-flow,
13
+ * attestation-flow, migration, multi-wallet, batch-lookup
14
+ *
15
+ * @author brainChain — brainAI
16
+ * @version 3.0.0
17
+ */
18
+
19
+ const { Connection, PublicKey, Keypair, sendAndConfirmTransaction } = require('@solana/web3.js');
20
+ const {
21
+ SATPV3SDK,
22
+ hashAgentId,
23
+ getGenesisPDA,
24
+ getV3ReviewPDA,
25
+ getV3AttestationPDA,
26
+ getNameRegistryPDA,
27
+ getLinkedWalletPDA,
28
+ } = require('../src');
29
+
30
+ // ═══════════════════════════════════════════════════════
31
+ // PATTERN 1: Agent Identity Lookup (Read-Only)
32
+ // Use case: downstream profile pages and app-owned listings
33
+ // ═══════════════════════════════════════════════════════
34
+
35
+ async function lookupAgent(agentId = 'brainChain') {
36
+ const sdk = new SATPV3SDK({ network: 'devnet' });
37
+
38
+ // Quick existence check (1 RPC call)
39
+ const exists = await sdk.hasIdentity(agentId);
40
+ if (!exists) {
41
+ console.log(`Agent "${agentId}" not found on-chain.`);
42
+ return null;
43
+ }
44
+
45
+ // Full profile read (1 RPC call)
46
+ const record = await sdk.getGenesisRecord(agentId);
47
+
48
+ console.log('=== Agent Profile ===');
49
+ console.log(`Name: ${record.agentName}`);
50
+ console.log(`Category: ${record.category}`);
51
+ console.log(`Description: ${record.description}`);
52
+ console.log(`Capabilities: ${record.capabilities.join(', ')}`);
53
+ console.log(`Active: ${record.isActive}`);
54
+ console.log(`Born: ${record.isBorn}`);
55
+ console.log(`Rep Score: ${record.reputationScore} (${(record.reputationScore / 10000).toFixed(2)}%)`);
56
+ console.log(`Verify Level: L${record.verificationLevel}`);
57
+ console.log(`Authority: ${record.authority}`);
58
+ console.log(`PDA: ${record.pda}`);
59
+ console.log(`Created: ${new Date(record.createdAt * 1000).toISOString()}`);
60
+
61
+ if (record.faceMint) {
62
+ console.log(`Face Mint: ${record.faceMint}`);
63
+ console.log(`Face Image: ${record.faceImage}`);
64
+ }
65
+
66
+ return record;
67
+ }
68
+
69
+ // ═══════════════════════════════════════════════════════
70
+ // PATTERN 2: Agent Registration (Full Flow)
71
+ // Use case: New agent onboarding, self-registration
72
+ // ═══════════════════════════════════════════════════════
73
+
74
+ async function registerAgent(keypairPath) {
75
+ const sdk = new SATPV3SDK({ network: 'devnet' });
76
+
77
+ // In production, load from wallet adapter or server keypair
78
+ // const wallet = Keypair.fromSecretKey(Buffer.from(JSON.parse(fs.readFileSync(keypairPath))));
79
+ // For demo, generate ephemeral
80
+ const wallet = Keypair.generate();
81
+ const agentId = `agent-${Date.now()}`;
82
+
83
+ console.log('=== Step 1: Create Identity ===');
84
+ const { transaction: createTx, genesisPDA } = await sdk.buildCreateIdentity(
85
+ wallet.publicKey,
86
+ agentId,
87
+ {
88
+ name: 'My Agent',
89
+ description: 'An AI agent specialized in data analysis',
90
+ category: 'analytics',
91
+ capabilities: ['data-analysis', 'reporting', 'visualization'],
92
+ metadataUri: 'https://arweave.net/agent-metadata.json',
93
+ }
94
+ );
95
+ console.log(`Genesis PDA: ${genesisPDA.toBase58()}`);
96
+ // await sendAndConfirmTransaction(sdk.connection, createTx, [wallet]);
97
+
98
+ console.log('\n=== Step 2: Register Unique Name ===');
99
+ const { transaction: nameTx, nameRegistryPDA } = await sdk.buildRegisterName(
100
+ wallet.publicKey,
101
+ agentId,
102
+ 'My Agent'
103
+ );
104
+ console.log(`Name Registry PDA: ${nameRegistryPDA.toBase58()}`);
105
+ // await sendAndConfirmTransaction(sdk.connection, nameTx, [wallet]);
106
+
107
+ console.log('\n=== Step 3: Link Wallets ===');
108
+ const hotWallet = Keypair.generate().publicKey;
109
+ const { transaction: linkTx, linkedWalletPDA } = await sdk.buildLinkWallet(
110
+ wallet.publicKey,
111
+ agentId,
112
+ hotWallet,
113
+ 'solana', // chain
114
+ 'hot-wallet' // label
115
+ );
116
+ console.log(`Linked Wallet PDA: ${linkedWalletPDA.toBase58()}`);
117
+ // await sendAndConfirmTransaction(sdk.connection, linkTx, [wallet]);
118
+
119
+ console.log('\n=== Step 4: Init Mint Tracker ===');
120
+ const { transaction: trackerTx, mintTrackerPDA } = await sdk.buildInitMintTracker(
121
+ wallet.publicKey,
122
+ agentId
123
+ );
124
+ console.log(`Mint Tracker PDA: ${mintTrackerPDA.toBase58()}`);
125
+ // await sendAndConfirmTransaction(sdk.connection, trackerTx, [wallet]);
126
+
127
+ console.log('\n✅ Full registration flow built (4 transactions)');
128
+ console.log('Uncomment sendAndConfirmTransaction calls to execute on-chain.');
129
+
130
+ return { agentId, genesisPDA };
131
+ }
132
+
133
+ // ═══════════════════════════════════════════════════════
134
+ // PATTERN 3: Review + Reputation Flow
135
+ // Use case: post-engagement review and consumer-owned feedback
136
+ // ═══════════════════════════════════════════════════════
137
+
138
+ async function reviewAndReputationFlow() {
139
+ const sdk = new SATPV3SDK({ network: 'devnet' });
140
+
141
+ const reviewer = Keypair.generate();
142
+ const agentId = 'brainChain';
143
+ const jobPDA = Keypair.generate().publicKey; // In practice, derive from escrow
144
+
145
+ console.log('=== Review + Reputation Flow ===');
146
+ console.log('This demonstrates the full review → reputation recompute pipeline.\n');
147
+
148
+ // Step 1: Submit a review (requires Reviews program)
149
+ // Note: Review submission uses the Reviews V3 program directly.
150
+ // The SDK provides PDA derivation for reviews:
151
+ const [reviewPDA] = getV3ReviewPDA(jobPDA, reviewer.publicKey, 'devnet');
152
+ console.log(`Review PDA (derived): ${reviewPDA.toBase58()}`);
153
+ console.log('→ Reviews are submitted via the Reviews program (5-star scale).\n');
154
+
155
+ // Step 2: Gather all review accounts for the agent
156
+ // In a real application, you'd query reviews using getProgramAccounts:
157
+ console.log('Querying existing reviews...');
158
+ const connection = new Connection('https://api.devnet.solana.com');
159
+ // Filter reviews by agent — in practice, filter by account data
160
+ // const reviews = await connection.getProgramAccounts(REVIEWS_PROGRAM_ID, { filters: [...] });
161
+
162
+ // Step 3: Trigger reputation recompute (permissionless — anyone can call)
163
+ const caller = Keypair.generate();
164
+ const mockReviewAccounts = [reviewPDA]; // Would be real review PDAs
165
+
166
+ const { transaction: repTx } = await sdk.buildRecomputeReputation(
167
+ caller.publicKey,
168
+ agentId,
169
+ mockReviewAccounts
170
+ );
171
+ console.log('Reputation recompute transaction built.');
172
+ console.log(`Includes ${mockReviewAccounts.length} review account(s) as remaining_accounts.`);
173
+ console.log('→ Score = time-decay weighted average × 200,000 (5★ = 1,000,000)');
174
+ console.log('→ Base score (no reviews) = 500,000\n');
175
+
176
+ // Step 4: Read updated reputation
177
+ // After submitting the recompute tx, read the updated Genesis Record:
178
+ // const updated = await sdk.getGenesisRecord(agentId);
179
+ // console.log(`New reputation score: ${updated.reputationScore}`);
180
+
181
+ console.log('✅ Review → Reputation pipeline demonstrated');
182
+ }
183
+
184
+ // ═══════════════════════════════════════════════════════
185
+ // PATTERN 4: Attestation + Validation Flow
186
+ // Use case: Identity verification, skill certification
187
+ // ═══════════════════════════════════════════════════════
188
+
189
+ async function attestationAndValidationFlow() {
190
+ const sdk = new SATPV3SDK({ network: 'devnet' });
191
+
192
+ const issuer = Keypair.generate(); // Verification authority
193
+ const agentId = 'brainChain';
194
+
195
+ console.log('=== Attestation + Validation Flow ===');
196
+ console.log('Demonstrates: create → verify → recompute level.\n');
197
+
198
+ // Step 1: Create attestation
199
+ const attestationType = 'kyc-identity';
200
+ const proofData = JSON.stringify({
201
+ method: 'document-verification',
202
+ provider: 'brainAI-verify',
203
+ timestamp: Date.now(),
204
+ confidence: 0.98,
205
+ });
206
+
207
+ const { transaction: attTx, attestationPDA } = await sdk.buildCreateAttestation(
208
+ issuer.publicKey,
209
+ agentId,
210
+ attestationType,
211
+ proofData,
212
+ null // No expiry (permanent)
213
+ );
214
+ console.log(`Attestation PDA: ${attestationPDA.toBase58()}`);
215
+ console.log(`Type: ${attestationType}`);
216
+ // await sendAndConfirmTransaction(sdk.connection, attTx, [issuer]);
217
+
218
+ // Step 2: Verify the attestation (issuer confirms)
219
+ const { transaction: verifyTx } = await sdk.buildVerifyAttestation(
220
+ issuer.publicKey,
221
+ attestationPDA
222
+ );
223
+ console.log('Verification transaction built.');
224
+ // await sendAndConfirmTransaction(sdk.connection, verifyTx, [issuer]);
225
+
226
+ // Step 3: Recompute validation level (permissionless)
227
+ const caller = Keypair.generate();
228
+ const { transaction: valTx } = await sdk.buildRecomputeLevel(
229
+ caller.publicKey,
230
+ agentId,
231
+ [attestationPDA]
232
+ );
233
+ console.log('Validation level recompute transaction built.\n');
234
+ // await sendAndConfirmTransaction(sdk.connection, valTx, [caller]);
235
+
236
+ // Validation Level Map:
237
+ console.log('Verification Levels:');
238
+ console.log(' L0 = Unverified (0 unique types)');
239
+ console.log(' L1 = Basic (1 unique type)');
240
+ console.log(' L2 = Verified (2 unique types)');
241
+ console.log(' L3 = Trusted (3 unique types)');
242
+ console.log(' L4 = Certified (4 unique types)');
243
+ console.log(' L5 = Sovereign (5+ unique types)\n');
244
+
245
+ // Attestation types that count toward level:
246
+ console.log('Example attestation types:');
247
+ console.log(' - kyc-identity: Government ID verification');
248
+ console.log(' - code-audit: Code security audit passed');
249
+ console.log(' - performance: Performance benchmark attestation');
250
+ console.log(' - domain-expert: Domain expertise certification');
251
+ console.log(' - community: Community vouching/endorsement');
252
+
253
+ console.log('\n✅ Attestation → Validation pipeline demonstrated');
254
+ }
255
+
256
+ // ═══════════════════════════════════════════════════════
257
+ // PATTERN 5: V2 → V3 Migration
258
+ // Use case: Existing agents upgrading to Genesis Records
259
+ // ═══════════════════════════════════════════════════════
260
+
261
+ async function migrationFlow() {
262
+ const sdk = new SATPV3SDK({ network: 'devnet' });
263
+ const v2Authority = Keypair.generate();
264
+ const agentId = 'legacy-agent';
265
+
266
+ console.log('=== V2 → V3 Migration ===');
267
+ console.log('Migrates existing V2 identity to V3 Genesis Record.\n');
268
+
269
+ // The migration instruction:
270
+ // 1. Verifies the caller is the V2 authority
271
+ // 2. Creates a new V3 Genesis Record with provided metadata
272
+ // 3. Sets the V2 authority as the V3 authority
273
+ // 4. Does NOT modify the V2 account (non-destructive)
274
+
275
+ const { transaction: migrateTx, genesisPDA } = await sdk.buildMigrateV2ToV3(
276
+ v2Authority.publicKey,
277
+ agentId,
278
+ {
279
+ name: 'Legacy Agent (V3)',
280
+ description: 'Migrated from V2 identity',
281
+ category: 'general',
282
+ capabilities: ['chat', 'search'],
283
+ metadataUri: '',
284
+ }
285
+ );
286
+
287
+ console.log(`New V3 Genesis PDA: ${genesisPDA.toBase58()}`);
288
+ console.log('Migration preserves:');
289
+ console.log(' ✓ Authority (same signer)');
290
+ console.log(' ✓ Agent ID hash (deterministic)');
291
+ console.log(' ✓ V2 account (untouched)');
292
+ console.log('Migration adds:');
293
+ console.log(' + Genesis Record with face fields');
294
+ console.log(' + Name Registry support');
295
+ console.log(' + Multi-wallet linking');
296
+ console.log(' + CPI-based reputation/validation');
297
+ console.log(' + Mint tracking (cap: 3)');
298
+
299
+ console.log('\n✅ Migration flow demonstrated');
300
+ }
301
+
302
+ // ═══════════════════════════════════════════════════════
303
+ // PATTERN 6: Batch Agent Lookup (Read-Only)
304
+ // Use case: Marketplace search results, leaderboard
305
+ // ═══════════════════════════════════════════════════════
306
+
307
+ async function batchLookup(agentIds = ['brainChain', 'brainForge', 'brainGrowth']) {
308
+ const sdk = new SATPV3SDK({ network: 'devnet' });
309
+
310
+ console.log('=== Batch Agent Lookup ===');
311
+ console.log(`Looking up ${agentIds.length} agents...\n`);
312
+
313
+ // Method 1: Sequential (simple, rate-limit safe)
314
+ const results = [];
315
+ for (const id of agentIds) {
316
+ const record = await sdk.getGenesisRecord(id);
317
+ results.push({ agentId: id, record });
318
+ }
319
+
320
+ // Method 2: Parallel with getMultipleAccountsInfo (more efficient)
321
+ // Derive all PDAs first (no RPC needed), then batch fetch:
322
+ const pdas = agentIds.map(id => getGenesisPDA(id, 'devnet')[0]);
323
+ const accounts = await sdk.connection.getMultipleAccountsInfo(pdas);
324
+
325
+ console.log('Results:');
326
+ for (let i = 0; i < agentIds.length; i++) {
327
+ const record = results[i].record;
328
+ if (record && !record.error) {
329
+ console.log(` ${record.agentName}: Rep=${record.reputationScore}, L${record.verificationLevel}, Active=${record.isActive}`);
330
+ } else {
331
+ console.log(` ${agentIds[i]}: not found`);
332
+ }
333
+ }
334
+
335
+ // Sorting example (for leaderboard):
336
+ const sorted = results
337
+ .filter(r => r.record && !r.record.error)
338
+ .sort((a, b) => b.record.reputationScore - a.record.reputationScore);
339
+
340
+ console.log('\nLeaderboard (by reputation):');
341
+ sorted.forEach((r, i) => {
342
+ console.log(` ${i + 1}. ${r.record.agentName} — ${r.record.reputationScore}`);
343
+ });
344
+
345
+ console.log('\n✅ Batch lookup demonstrated');
346
+ }
347
+
348
+ // ═══════════════════════════════════════════════════════
349
+ // PATTERN 7: Authority Rotation (2-Step)
350
+ // Use case: Key rotation, team handoff, security recovery
351
+ // ═══════════════════════════════════════════════════════
352
+
353
+ async function authorityRotation() {
354
+ const sdk = new SATPV3SDK({ network: 'devnet' });
355
+
356
+ const currentAuth = Keypair.generate();
357
+ const newAuth = Keypair.generate();
358
+ const agentId = 'my-agent';
359
+
360
+ console.log('=== 2-Step Authority Rotation ===');
361
+ console.log('Secure key rotation with propose → accept pattern.\n');
362
+
363
+ // Step 1: Current authority proposes new authority
364
+ const { transaction: proposeTx } = await sdk.buildProposeAuthority(
365
+ currentAuth.publicKey,
366
+ agentId,
367
+ newAuth.publicKey
368
+ );
369
+ console.log(`Step 1: Propose ${newAuth.publicKey.toBase58().slice(0, 8)}... as new authority`);
370
+ // await sendAndConfirmTransaction(sdk.connection, proposeTx, [currentAuth]);
371
+
372
+ // Step 2: New authority accepts
373
+ const { transaction: acceptTx } = await sdk.buildAcceptAuthority(
374
+ newAuth.publicKey,
375
+ agentId
376
+ );
377
+ console.log(`Step 2: New authority accepts control`);
378
+ // await sendAndConfirmTransaction(sdk.connection, acceptTx, [newAuth]);
379
+
380
+ // Cancel option (current auth can cancel before acceptance):
381
+ const { transaction: cancelTx } = await sdk.buildCancelAuthorityTransfer(
382
+ currentAuth.publicKey,
383
+ agentId
384
+ );
385
+ console.log(`(Optional: Cancel transfer before acceptance)\n`);
386
+
387
+ console.log('Security properties:');
388
+ console.log(' ✓ Current authority must initiate (prevents unauthorized rotation)');
389
+ console.log(' ✓ New authority must confirm (prevents accidental rotation)');
390
+ console.log(' ✓ Current auth can cancel anytime before acceptance');
391
+ console.log(' ✓ After acceptance, old authority has NO access');
392
+
393
+ console.log('\n✅ Authority rotation demonstrated');
394
+ }
395
+
396
+ // ═══════════════════════════════════════════════════════
397
+ // PATTERN 8: PDA Derivation (Offline / No RPC)
398
+ // Use case: Pre-computing addresses, caching, indexers
399
+ // ═══════════════════════════════════════════════════════
400
+
401
+ function offlinePDADerivation() {
402
+ console.log('=== Offline PDA Derivation ===');
403
+ console.log('All PDAs can be derived without any RPC calls.\n');
404
+
405
+ const agentId = 'brainChain';
406
+ const sdk = new SATPV3SDK({ network: 'devnet' });
407
+
408
+ // All at once
409
+ const pdas = sdk.getV3PDAs(agentId);
410
+ console.log('Agent PDAs:');
411
+ console.log(` Genesis: ${pdas.genesis}`);
412
+ console.log(` Mint Tracker: ${pdas.mintTracker}`);
413
+ console.log(` Rep Authority: ${pdas.reputationAuthority}`);
414
+ console.log(` Val Authority: ${pdas.validationAuthority}`);
415
+ console.log(` Agent ID Hash: ${pdas.agentIdHash.slice(0, 16)}...`);
416
+
417
+ // Individual derivations
418
+ const wallet = Keypair.generate().publicKey;
419
+ const [genesisPDA] = getGenesisPDA(agentId, 'devnet');
420
+ const [linkedPDA] = getLinkedWalletPDA(genesisPDA, wallet, 'devnet');
421
+ const [namePDA] = getNameRegistryPDA('brainChain', 'devnet');
422
+ console.log(`\n Name Registry: ${namePDA.toBase58()}`);
423
+ console.log(` Linked Wallet: ${linkedPDA.toBase58()}`);
424
+
425
+ // Hash functions (deterministic, no RPC)
426
+ const hash1 = hashAgentId('brainChain');
427
+ const hash2 = hashAgentId('brainChain');
428
+ console.log(`\n Hash stable: ${hash1.equals(hash2) ? 'YES ✓' : 'NO ✗'}`);
429
+ console.log(` Hash hex: ${hash1.toString('hex').slice(0, 32)}...`);
430
+
431
+ console.log('\n✅ All derivations are deterministic and offline');
432
+ }
433
+
434
+ // ═══════════════════════════════════════════════════════
435
+ // PATTERN 9: Name Availability Check
436
+ // Use case: Registration form, name suggestions
437
+ // ═══════════════════════════════════════════════════════
438
+
439
+ async function nameAvailability() {
440
+ const sdk = new SATPV3SDK({ network: 'devnet' });
441
+
442
+ console.log('=== Name Availability Check ===\n');
443
+
444
+ const names = ['brainChain', 'available-name-12345', 'BrainChain']; // Note: case-insensitive
445
+
446
+ for (const name of names) {
447
+ const available = await sdk.isNameAvailable(name);
448
+ const [pda] = getNameRegistryPDA(name, 'devnet');
449
+ console.log(` "${name}" → ${available ? '✅ Available' : '❌ Taken'} (PDA: ${pda.toBase58().slice(0, 12)}...)`);
450
+ }
451
+
452
+ console.log('\nNote: Names are case-insensitive.');
453
+ console.log('"brainChain" and "BrainChain" hash to the same PDA.');
454
+
455
+ console.log('\n✅ Name check demonstrated');
456
+ }
457
+
458
+ // ═══════════════════════════════════════════════════════
459
+ // CLI Runner
460
+ // ═══════════════════════════════════════════════════════
461
+
462
+ const examples = {
463
+ 'lookup': () => lookupAgent(process.argv[3] || 'brainChain'),
464
+ 'register': () => registerAgent(),
465
+ 'review-flow': () => reviewAndReputationFlow(),
466
+ 'reputation-flow': () => reviewAndReputationFlow(),
467
+ 'attestation-flow': () => attestationAndValidationFlow(),
468
+ 'migration': () => migrationFlow(),
469
+ 'batch-lookup': () => batchLookup(),
470
+ 'authority-rotation': () => authorityRotation(),
471
+ 'pda-derivation': () => offlinePDADerivation(),
472
+ 'name-check': () => nameAvailability(),
473
+ };
474
+
475
+ async function main() {
476
+ const example = process.argv[2];
477
+
478
+ if (!example || !examples[example]) {
479
+ console.log('SATP V3 SDK — Integration Patterns\n');
480
+ console.log('Usage: node integration-patterns.js <example>\n');
481
+ console.log('Available examples:');
482
+ Object.keys(examples).forEach(name => console.log(` ${name}`));
483
+ console.log('\nExample: node integration-patterns.js lookup brainChain');
484
+ return;
485
+ }
486
+
487
+ try {
488
+ await examples[example]();
489
+ } catch (err) {
490
+ console.error(`Error: ${err.message}`);
491
+ process.exit(1);
492
+ }
493
+ }
494
+
495
+ main();
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { evaluateRuntimePolicy } = require('..');
5
+
6
+ const trustedIdentity = {
7
+ agentId: 'brainchain-demo',
8
+ active: true,
9
+ satpVerified: true,
10
+ trustScore: 88,
11
+ capabilities: ['mcp:deploy-readiness', 'satp:trust-read'],
12
+ evidenceUpdatedAt: '2026-05-21T00:00:00Z',
13
+ };
14
+
15
+ const examples = [
16
+ {
17
+ name: 'MCP protected tool',
18
+ identity: trustedIdentity,
19
+ action: {
20
+ type: 'mcp_protected_tool',
21
+ resource: 'mcp://protected/deploy-readiness',
22
+ operation: 'read',
23
+ requiresCapability: 'mcp:deploy-readiness',
24
+ requiresFreshEvidence: true,
25
+ },
26
+ },
27
+ {
28
+ name: 'x402 paid endpoint',
29
+ identity: trustedIdentity,
30
+ action: {
31
+ type: 'x402_endpoint',
32
+ resource: 'https://api.example.test/reputation',
33
+ operation: 'lookup',
34
+ costUsd: 0.05,
35
+ },
36
+ options: {
37
+ actionPaymentPreapproved: true,
38
+ policy: { maxAutoSpendUsd: 0.01 },
39
+ },
40
+ },
41
+ {
42
+ name: 'Host trust-score degrade',
43
+ identity: { ...trustedIdentity, trustScore: 62 },
44
+ action: {
45
+ type: 'host_trust_gate',
46
+ resource: 'satp://trust-score',
47
+ operation: 'gate',
48
+ minimumTrustScore: 80,
49
+ allowDegraded: true,
50
+ },
51
+ },
52
+ {
53
+ name: 'Optional paid x402 evidence lookup',
54
+ identity: { ...trustedIdentity, evidenceUpdatedAt: '2026-04-01T00:00:00Z' },
55
+ action: {
56
+ type: 'mcp_protected_tool',
57
+ resource: 'mcp://protected/high-risk-action',
58
+ operation: 'prepare',
59
+ requiresFreshEvidence: true,
60
+ evidenceLookup: {
61
+ type: 'x402',
62
+ endpoint: 'https://api.example.test/evidence',
63
+ maxCostUsd: 0.05,
64
+ },
65
+ },
66
+ options: {
67
+ now: '2026-05-21T00:00:00Z',
68
+ },
69
+ },
70
+ ];
71
+
72
+ for (const item of examples) {
73
+ const result = evaluateRuntimePolicy(item.identity, item.action, item.options);
74
+ console.log(JSON.stringify({ name: item.name, ...result }, null, 2));
75
+ }
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const assert = require('node:assert/strict');
5
+ const {
6
+ X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
7
+ parseX402DiscoveryMetadata,
8
+ buildX402EvidenceLookup,
9
+ buildRuntimePolicyActionDescriptorFromX402Discovery,
10
+ } = require('../src');
11
+
12
+ const discoveryMetadata = {
13
+ action: 'satp.resolveIdentity',
14
+ endpoint: 'https://example.invalid/.well-known/x402/satp',
15
+ accepts: [
16
+ {
17
+ scheme: 'exact',
18
+ network: 'base',
19
+ asset: 'USDC',
20
+ amountRequired: '1',
21
+ resource: 'satp://identity/brainChain',
22
+ description: 'Read-only SATP evidence lookup',
23
+ mimeType: 'application/json',
24
+ },
25
+ ],
26
+ };
27
+
28
+ const discovery = parseX402DiscoveryMetadata(discoveryMetadata);
29
+ const evidenceLookup = buildX402EvidenceLookup(discoveryMetadata, {
30
+ sourceKind: 'well-known-x402',
31
+ maxCostUsd: 0.01,
32
+ });
33
+ const descriptor = buildRuntimePolicyActionDescriptorFromX402Discovery(discoveryMetadata, {
34
+ sourceKind: 'well-known-x402',
35
+ maxCostUsd: 0.01,
36
+ });
37
+
38
+ assert.equal(discovery.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
39
+ assert.equal(evidenceLookup.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
40
+ assert.equal(evidenceLookup.paymentAuthorization, false);
41
+ assert.equal(evidenceLookup.actionAuthorization, false);
42
+ assert.equal(evidenceLookup.spendAuthorized, false);
43
+ assert.equal(evidenceLookup.livePaymentRequired, false);
44
+ assert.equal(descriptor.guardrail, X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
45
+ assert.equal(descriptor.paymentAuthorization, false);
46
+ assert.equal(descriptor.actionAuthorization, false);
47
+ assert.equal(descriptor.spendAuthorized, false);
48
+ assert.equal(descriptor.livePaymentRequired, false);
49
+ assert.deepEqual(descriptor.evidenceLookup, evidenceLookup);
50
+
51
+ console.log(JSON.stringify(
52
+ {
53
+ resource: discovery.resource,
54
+ endpoint: discovery.endpoint,
55
+ evidenceLookupType: evidenceLookup.type,
56
+ operation: descriptor.operation,
57
+ guardrail: descriptor.guardrail,
58
+ paymentAuthorization: descriptor.paymentAuthorization,
59
+ actionAuthorization: descriptor.actionAuthorization,
60
+ spendAuthorized: descriptor.spendAuthorized,
61
+ livePaymentRequired: descriptor.livePaymentRequired,
62
+ warning: 'x402 payment metadata is discovery/evidence lookup only; it is not action authorization.',
63
+ },
64
+ null,
65
+ 2
66
+ ));