@brainai/satp-client 2.0.1 → 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.
- package/README.md +113 -19
- package/examples/integration-patterns.js +495 -0
- package/examples/runtime-policy-adapter.js +75 -0
- package/examples/x402-discovery-evidence-lookup.js +66 -0
- package/package.json +20 -3
- package/src/index.d.ts +241 -0
- package/src/index.js +30 -0
- package/src/runtime-policy-adapter.js +206 -0
- package/src/wallet-control-challenge.js +351 -0
- package/src/x402-discovery.js +180 -0
|
@@ -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
|
+
));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainai/satp-client",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "SATP client SDK surface prepared for release-candidate review; publish only after the release packet passes.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
@@ -9,15 +9,29 @@
|
|
|
9
9
|
"types": "./src/index.d.ts",
|
|
10
10
|
"default": "./src/index.js"
|
|
11
11
|
},
|
|
12
|
+
"./wallet-control-challenge": {
|
|
13
|
+
"types": "./src/index.d.ts",
|
|
14
|
+
"default": "./src/wallet-control-challenge.js"
|
|
15
|
+
},
|
|
16
|
+
"./x402-discovery": {
|
|
17
|
+
"types": "./src/index.d.ts",
|
|
18
|
+
"default": "./src/x402-discovery.js"
|
|
19
|
+
},
|
|
12
20
|
"./src/*": "./src/*",
|
|
13
21
|
"./package.json": "./package.json"
|
|
14
22
|
},
|
|
15
23
|
"files": [
|
|
16
24
|
"src/",
|
|
25
|
+
"examples/",
|
|
17
26
|
"README.md"
|
|
18
27
|
],
|
|
19
28
|
"scripts": {
|
|
20
|
-
"test": "node test.js"
|
|
29
|
+
"test": "node test.js",
|
|
30
|
+
"test:runtime-policy": "node --test test-runtime-policy-adapter.js",
|
|
31
|
+
"runtime-policy:example": "node examples/runtime-policy-adapter.js",
|
|
32
|
+
"test:wallet-control": "node test-wallet-control-challenge.js",
|
|
33
|
+
"test:x402-discovery": "node test-x402-discovery.js",
|
|
34
|
+
"check:exports": "node test-release-safety.js"
|
|
21
35
|
},
|
|
22
36
|
"keywords": [
|
|
23
37
|
"solana",
|
|
@@ -45,7 +59,10 @@
|
|
|
45
59
|
},
|
|
46
60
|
"private": false,
|
|
47
61
|
"overrides": {
|
|
48
|
-
"ws": "^8.
|
|
62
|
+
"ws": "^8.21.0",
|
|
63
|
+
"jayson": {
|
|
64
|
+
"uuid": "^11.1.1"
|
|
65
|
+
}
|
|
49
66
|
},
|
|
50
67
|
"publishConfig": {
|
|
51
68
|
"tag": "rc",
|
package/src/index.d.ts
CHANGED
|
@@ -89,6 +89,180 @@ export interface SatpTrustPacketValidation {
|
|
|
89
89
|
errors: string[];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
export interface X402PaymentRequirement {
|
|
93
|
+
scheme?: string;
|
|
94
|
+
network?: string;
|
|
95
|
+
asset?: string;
|
|
96
|
+
payTo?: string;
|
|
97
|
+
maxAmountRequired?: string | number;
|
|
98
|
+
amountRequired?: string | number;
|
|
99
|
+
resource?: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
mimeType?: string;
|
|
102
|
+
maxTimeoutSeconds?: number;
|
|
103
|
+
extra?: unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface X402DiscoveryMetadata {
|
|
107
|
+
schemaVersion: 'satp.x402DiscoveryMetadata.v1';
|
|
108
|
+
protocol: 'x402';
|
|
109
|
+
resource: string | null;
|
|
110
|
+
endpoint: string | null;
|
|
111
|
+
action: string | null;
|
|
112
|
+
paymentRequired: boolean;
|
|
113
|
+
paymentRequirements: X402PaymentRequirement[];
|
|
114
|
+
guardrail: 'X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface X402EvidenceLookup {
|
|
118
|
+
type: 'x402';
|
|
119
|
+
endpoint: string | null;
|
|
120
|
+
maxCostUsd: number | null;
|
|
121
|
+
protocol: 'x402';
|
|
122
|
+
source: {
|
|
123
|
+
kind: string;
|
|
124
|
+
url: string | null;
|
|
125
|
+
};
|
|
126
|
+
resource: string | null;
|
|
127
|
+
paymentRequired: boolean;
|
|
128
|
+
paymentRequirements: X402PaymentRequirement[];
|
|
129
|
+
discovery: X402DiscoveryMetadata;
|
|
130
|
+
guardrail: 'X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION';
|
|
131
|
+
paymentAuthorization: false;
|
|
132
|
+
actionAuthorization: false;
|
|
133
|
+
spendAuthorized: false;
|
|
134
|
+
livePaymentRequired: false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type RuntimePolicyDecision =
|
|
138
|
+
| 'allow'
|
|
139
|
+
| 'deny'
|
|
140
|
+
| 'degrade'
|
|
141
|
+
| 'needs_approval';
|
|
142
|
+
|
|
143
|
+
export interface RuntimePolicyIdentityPayload {
|
|
144
|
+
active?: boolean;
|
|
145
|
+
satpVerified?: boolean;
|
|
146
|
+
verified?: boolean;
|
|
147
|
+
agentFolioTrustScore?: number;
|
|
148
|
+
trustScore?: number;
|
|
149
|
+
capabilities?: string[];
|
|
150
|
+
evidenceUpdatedAt?: string | number | Date | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface RuntimePolicyActionDescriptor {
|
|
154
|
+
type?: string;
|
|
155
|
+
resource?: string;
|
|
156
|
+
operation?: string;
|
|
157
|
+
requiresCapability?: string;
|
|
158
|
+
minimumTrustScore?: number;
|
|
159
|
+
allowDegraded?: boolean;
|
|
160
|
+
requiresFreshEvidence?: boolean;
|
|
161
|
+
costUsd?: number;
|
|
162
|
+
protectedTool?: boolean;
|
|
163
|
+
operatorApprovalRequired?: boolean;
|
|
164
|
+
evidenceLookup?: {
|
|
165
|
+
type?: string;
|
|
166
|
+
endpoint?: string;
|
|
167
|
+
maxCostUsd?: number;
|
|
168
|
+
} | X402EvidenceLookup;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface RuntimePolicyConfig {
|
|
172
|
+
minimumTrustScore?: number;
|
|
173
|
+
denyTrustScoreBelow?: number;
|
|
174
|
+
maxAutoSpendUsd?: number;
|
|
175
|
+
requireVerifiedIdentity?: boolean;
|
|
176
|
+
staleEvidenceAfterMs?: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface RuntimePolicyOptions {
|
|
180
|
+
now?: string | number | Date;
|
|
181
|
+
actionPaymentPreapproved?: boolean;
|
|
182
|
+
evidenceLookupPaymentPreapproved?: boolean;
|
|
183
|
+
operatorApproved?: boolean;
|
|
184
|
+
policy?: RuntimePolicyConfig;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface RuntimePolicyResult {
|
|
188
|
+
decision: RuntimePolicyDecision;
|
|
189
|
+
reasonCodes: string[];
|
|
190
|
+
message: string;
|
|
191
|
+
checks: Record<string, unknown>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export const DECISIONS: Readonly<{
|
|
195
|
+
ALLOW: 'allow';
|
|
196
|
+
DENY: 'deny';
|
|
197
|
+
DEGRADE: 'degrade';
|
|
198
|
+
NEEDS_APPROVAL: 'needs_approval';
|
|
199
|
+
}>;
|
|
200
|
+
|
|
201
|
+
export const REASON_CODES: Readonly<Record<string, string>>;
|
|
202
|
+
export const DEFAULT_POLICY: Readonly<Required<RuntimePolicyConfig>>;
|
|
203
|
+
|
|
204
|
+
export function evaluateRuntimePolicy(
|
|
205
|
+
identityPayload: RuntimePolicyIdentityPayload,
|
|
206
|
+
actionDescriptor: RuntimePolicyActionDescriptor,
|
|
207
|
+
options?: RuntimePolicyOptions
|
|
208
|
+
): RuntimePolicyResult;
|
|
209
|
+
|
|
210
|
+
export interface WalletControlChallengeOptions {
|
|
211
|
+
agentId: string;
|
|
212
|
+
wallet: PublicKey | string;
|
|
213
|
+
network?: Network;
|
|
214
|
+
domain?: string;
|
|
215
|
+
audience?: string;
|
|
216
|
+
nonce?: string;
|
|
217
|
+
issuedAt?: number;
|
|
218
|
+
expiresAt?: number;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface WalletControlChallenge {
|
|
222
|
+
schemaVersion: 'satp.walletControlChallenge.v1';
|
|
223
|
+
challengeType: 'wallet-control';
|
|
224
|
+
domain: string;
|
|
225
|
+
audience: string;
|
|
226
|
+
network: Network;
|
|
227
|
+
agentId: string;
|
|
228
|
+
wallet: string;
|
|
229
|
+
nonce: string;
|
|
230
|
+
issuedAt: number;
|
|
231
|
+
expiresAt: number;
|
|
232
|
+
agentIdHash: string;
|
|
233
|
+
genesisPda: string;
|
|
234
|
+
genesisBump: number;
|
|
235
|
+
linkedWalletPda: string;
|
|
236
|
+
linkedWalletBump: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export interface WalletControlChallengePdas {
|
|
240
|
+
agentIdHash: string;
|
|
241
|
+
genesisPda: string;
|
|
242
|
+
genesisBump: number;
|
|
243
|
+
linkedWalletPda: string;
|
|
244
|
+
linkedWalletBump: number;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface VerifyWalletControlChallengeSignatureOptions {
|
|
248
|
+
challenge: WalletControlChallenge | Record<string, unknown>;
|
|
249
|
+
signature: string | Buffer | Uint8Array | number[];
|
|
250
|
+
expectedWallet?: PublicKey | string;
|
|
251
|
+
expectedAgentId?: string;
|
|
252
|
+
expectedDomain?: string;
|
|
253
|
+
expectedAudience?: string;
|
|
254
|
+
now?: number;
|
|
255
|
+
usedNonces?: Set<string> | string[] | Record<string, boolean>;
|
|
256
|
+
replayCache?: { has(nonce: string): boolean } | string[] | Record<string, boolean>;
|
|
257
|
+
isNonceUsed?: (nonce: string, challenge: WalletControlChallenge) => boolean;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export interface WalletControlChallengeVerification {
|
|
261
|
+
ok: boolean;
|
|
262
|
+
errors: string[];
|
|
263
|
+
challengeHash: string;
|
|
264
|
+
}
|
|
265
|
+
|
|
92
266
|
// ─── V2 SDK ──────────────────────────────────────────────
|
|
93
267
|
|
|
94
268
|
export interface V2ProgramIds {
|
|
@@ -235,6 +409,73 @@ export function validateSatpTrustPacket(
|
|
|
235
409
|
packet: SatpTrustPacket | Record<string, unknown> | null | undefined
|
|
236
410
|
): SatpTrustPacketValidation;
|
|
237
411
|
|
|
412
|
+
export const X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION: 'X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION';
|
|
413
|
+
export const X402_DISCOVERY_SCHEMA_VERSION: 'satp.x402DiscoveryMetadata.v1';
|
|
414
|
+
export const RUNTIME_POLICY_ACTION_DESCRIPTOR_SCHEMA_VERSION: 'satp.runtimePolicyActionDescriptor.v1';
|
|
415
|
+
|
|
416
|
+
export function parseX402DiscoveryMetadata(
|
|
417
|
+
input: Record<string, unknown> | string
|
|
418
|
+
): X402DiscoveryMetadata;
|
|
419
|
+
|
|
420
|
+
export function buildX402EvidenceLookup(
|
|
421
|
+
input: Record<string, unknown> | string,
|
|
422
|
+
opts?: {
|
|
423
|
+
endpoint?: string;
|
|
424
|
+
maxCostUsd?: number;
|
|
425
|
+
sourceKind?: string;
|
|
426
|
+
sourceUrl?: string;
|
|
427
|
+
}
|
|
428
|
+
): X402EvidenceLookup;
|
|
429
|
+
|
|
430
|
+
export function buildRuntimePolicyActionDescriptorFromX402Discovery(
|
|
431
|
+
input: Record<string, unknown> | string,
|
|
432
|
+
opts?: {
|
|
433
|
+
type?: string;
|
|
434
|
+
resource?: string;
|
|
435
|
+
operation?: string;
|
|
436
|
+
requiresFreshEvidence?: boolean;
|
|
437
|
+
endpoint?: string;
|
|
438
|
+
maxCostUsd?: number;
|
|
439
|
+
sourceKind?: string;
|
|
440
|
+
sourceUrl?: string;
|
|
441
|
+
}
|
|
442
|
+
): RuntimePolicyActionDescriptor & {
|
|
443
|
+
schemaVersion: 'satp.runtimePolicyActionDescriptor.v1';
|
|
444
|
+
evidenceLookup: X402EvidenceLookup;
|
|
445
|
+
paymentAuthorization: false;
|
|
446
|
+
actionAuthorization: false;
|
|
447
|
+
spendAuthorized: false;
|
|
448
|
+
livePaymentRequired: false;
|
|
449
|
+
guardrail: 'X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION';
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
export const buildRuntimePolicyActionDescriptorFromX402: typeof buildRuntimePolicyActionDescriptorFromX402Discovery;
|
|
453
|
+
|
|
454
|
+
export const WALLET_CONTROL_CHALLENGE_SCHEMA_VERSION: 'satp.walletControlChallenge.v1';
|
|
455
|
+
export const WALLET_CONTROL_CHALLENGE_TYPE: 'wallet-control';
|
|
456
|
+
export const DEFAULT_WALLET_CONTROL_DOMAIN: string;
|
|
457
|
+
export const DEFAULT_WALLET_CONTROL_AUDIENCE: string;
|
|
458
|
+
|
|
459
|
+
export function buildWalletControlChallenge(
|
|
460
|
+
opts: WalletControlChallengeOptions
|
|
461
|
+
): WalletControlChallenge;
|
|
462
|
+
|
|
463
|
+
export function canonicalWalletControlChallenge(
|
|
464
|
+
challenge: WalletControlChallenge | Record<string, unknown>
|
|
465
|
+
): string;
|
|
466
|
+
|
|
467
|
+
export function hashWalletControlChallenge(
|
|
468
|
+
challenge: WalletControlChallenge | Record<string, unknown>
|
|
469
|
+
): string;
|
|
470
|
+
|
|
471
|
+
export function deriveWalletControlChallengePdas(
|
|
472
|
+
opts: Pick<WalletControlChallengeOptions, 'agentId' | 'wallet' | 'network'>
|
|
473
|
+
): WalletControlChallengePdas;
|
|
474
|
+
|
|
475
|
+
export function verifyWalletControlChallengeSignature(
|
|
476
|
+
opts: VerifyWalletControlChallengeSignatureOptions
|
|
477
|
+
): WalletControlChallengeVerification;
|
|
478
|
+
|
|
238
479
|
// ─── Borsh Deserialization Helpers ──────────────────────
|
|
239
480
|
|
|
240
481
|
export {
|
package/src/index.js
CHANGED
|
@@ -783,6 +783,14 @@ const {
|
|
|
783
783
|
buildSatpTrustPacket,
|
|
784
784
|
validateSatpTrustPacket,
|
|
785
785
|
} = require('./trust-packet');
|
|
786
|
+
const {
|
|
787
|
+
DECISIONS,
|
|
788
|
+
DEFAULT_POLICY,
|
|
789
|
+
REASON_CODES,
|
|
790
|
+
evaluateRuntimePolicy,
|
|
791
|
+
} = require('./runtime-policy-adapter');
|
|
792
|
+
const walletControlChallenge = require('./wallet-control-challenge');
|
|
793
|
+
const x402Discovery = require('./x402-discovery');
|
|
786
794
|
|
|
787
795
|
// Legacy V3 SDK wrapper — keeps string constructor compatibility while using
|
|
788
796
|
// the local extracted SATPV3SDK implementation so offline tests and consumers
|
|
@@ -926,6 +934,28 @@ module.exports = {
|
|
|
926
934
|
TRUST_PACKET_SCHEMA_VERSION,
|
|
927
935
|
buildSatpTrustPacket,
|
|
928
936
|
validateSatpTrustPacket,
|
|
937
|
+
WALLET_CONTROL_CHALLENGE_SCHEMA_VERSION: walletControlChallenge.WALLET_CONTROL_CHALLENGE_SCHEMA_VERSION,
|
|
938
|
+
WALLET_CONTROL_CHALLENGE_TYPE: walletControlChallenge.WALLET_CONTROL_CHALLENGE_TYPE,
|
|
939
|
+
DEFAULT_WALLET_CONTROL_DOMAIN: walletControlChallenge.DEFAULT_WALLET_CONTROL_DOMAIN,
|
|
940
|
+
DEFAULT_WALLET_CONTROL_AUDIENCE: walletControlChallenge.DEFAULT_WALLET_CONTROL_AUDIENCE,
|
|
941
|
+
buildWalletControlChallenge: walletControlChallenge.buildWalletControlChallenge,
|
|
942
|
+
canonicalWalletControlChallenge: walletControlChallenge.canonicalWalletControlChallenge,
|
|
943
|
+
hashWalletControlChallenge: walletControlChallenge.hashWalletControlChallenge,
|
|
944
|
+
deriveWalletControlChallengePdas: walletControlChallenge.deriveWalletControlChallengePdas,
|
|
945
|
+
verifyWalletControlChallengeSignature: walletControlChallenge.verifyWalletControlChallengeSignature,
|
|
946
|
+
X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION: x402Discovery.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION,
|
|
947
|
+
X402_DISCOVERY_SCHEMA_VERSION: x402Discovery.X402_DISCOVERY_SCHEMA_VERSION,
|
|
948
|
+
RUNTIME_POLICY_ACTION_DESCRIPTOR_SCHEMA_VERSION: x402Discovery.RUNTIME_POLICY_ACTION_DESCRIPTOR_SCHEMA_VERSION,
|
|
949
|
+
parseX402DiscoveryMetadata: x402Discovery.parseX402DiscoveryMetadata,
|
|
950
|
+
buildX402EvidenceLookup: x402Discovery.buildX402EvidenceLookup,
|
|
951
|
+
buildRuntimePolicyActionDescriptorFromX402Discovery: x402Discovery.buildRuntimePolicyActionDescriptorFromX402Discovery,
|
|
952
|
+
buildRuntimePolicyActionDescriptorFromX402: x402Discovery.buildRuntimePolicyActionDescriptorFromX402,
|
|
953
|
+
|
|
954
|
+
// Runtime policy adapter (offline/local guardrail helper)
|
|
955
|
+
DECISIONS,
|
|
956
|
+
DEFAULT_POLICY,
|
|
957
|
+
REASON_CODES,
|
|
958
|
+
evaluateRuntimePolicy,
|
|
929
959
|
|
|
930
960
|
// V3 Deserialization (local extracted scaffold)
|
|
931
961
|
// NOTE: v3sdk.deserializeGenesis has isActive field mismatch with deployed program
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DECISIONS = Object.freeze({
|
|
4
|
+
ALLOW: 'allow',
|
|
5
|
+
DENY: 'deny',
|
|
6
|
+
DEGRADE: 'degrade',
|
|
7
|
+
NEEDS_APPROVAL: 'needs_approval',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const REASON_CODES = Object.freeze({
|
|
11
|
+
ACTION_PAYMENT_NEEDS_APPROVAL: 'ACTION_PAYMENT_NEEDS_APPROVAL',
|
|
12
|
+
ACTION_PAYMENT_PREAPPROVED: 'ACTION_PAYMENT_PREAPPROVED',
|
|
13
|
+
EVIDENCE_FRESH: 'EVIDENCE_FRESH',
|
|
14
|
+
EVIDENCE_STALE_OR_MISSING: 'EVIDENCE_STALE_OR_MISSING',
|
|
15
|
+
IDENTITY_INACTIVE: 'IDENTITY_INACTIVE',
|
|
16
|
+
IDENTITY_UNVERIFIED: 'IDENTITY_UNVERIFIED',
|
|
17
|
+
INVALID_ACTION_COST_USD: 'INVALID_ACTION_COST_USD',
|
|
18
|
+
LOCAL_POLICY_ALLOW: 'LOCAL_POLICY_ALLOW',
|
|
19
|
+
MISSING_CAPABILITY: 'MISSING_CAPABILITY',
|
|
20
|
+
PROTECTED_TOOL_REQUIRES_APPROVAL: 'PROTECTED_TOOL_REQUIRES_APPROVAL',
|
|
21
|
+
TRUST_SCORE_BELOW_DENY_FLOOR: 'TRUST_SCORE_BELOW_DENY_FLOOR',
|
|
22
|
+
TRUST_SCORE_BELOW_MINIMUM: 'TRUST_SCORE_BELOW_MINIMUM',
|
|
23
|
+
TRUST_SCORE_OK: 'TRUST_SCORE_OK',
|
|
24
|
+
X402_LOOKUP_PAYMENT_PREAPPROVED: 'X402_LOOKUP_PAYMENT_PREAPPROVED',
|
|
25
|
+
X402_LOOKUP_REQUIRES_APPROVAL: 'X402_LOOKUP_REQUIRES_APPROVAL',
|
|
26
|
+
X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION: 'X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const DEFAULT_POLICY = Object.freeze({
|
|
30
|
+
minimumTrustScore: 70,
|
|
31
|
+
denyTrustScoreBelow: 25,
|
|
32
|
+
maxAutoSpendUsd: 0,
|
|
33
|
+
requireVerifiedIdentity: true,
|
|
34
|
+
staleEvidenceAfterMs: 7 * 24 * 60 * 60 * 1000,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function evaluateRuntimePolicy(identityPayload, actionDescriptor, options = {}) {
|
|
38
|
+
const policy = { ...DEFAULT_POLICY, ...(options.policy || {}) };
|
|
39
|
+
const now = options.now ? new Date(options.now) : new Date();
|
|
40
|
+
const identity = normalizeIdentity(identityPayload);
|
|
41
|
+
const action = normalizeAction(actionDescriptor);
|
|
42
|
+
const reasonCodes = [];
|
|
43
|
+
const checks = {};
|
|
44
|
+
|
|
45
|
+
checks.identityActive = identity.active === true;
|
|
46
|
+
if (!checks.identityActive) {
|
|
47
|
+
reasonCodes.push(REASON_CODES.IDENTITY_INACTIVE);
|
|
48
|
+
return decision(DECISIONS.DENY, reasonCodes, checks, 'Identity is not active.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
checks.identityVerified = !policy.requireVerifiedIdentity || identity.verified === true;
|
|
52
|
+
if (!checks.identityVerified) {
|
|
53
|
+
reasonCodes.push(REASON_CODES.IDENTITY_UNVERIFIED);
|
|
54
|
+
return decision(DECISIONS.DENY, reasonCodes, checks, 'Identity is not verified by local policy.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
checks.hasCapability = !action.requiresCapability || identity.capabilities.includes(action.requiresCapability);
|
|
58
|
+
if (!checks.hasCapability) {
|
|
59
|
+
reasonCodes.push(REASON_CODES.MISSING_CAPABILITY);
|
|
60
|
+
return decision(DECISIONS.DENY, reasonCodes, checks, 'Required capability is absent.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
checks.trustScore = identity.trustScore;
|
|
64
|
+
checks.minimumTrustScore = action.minimumTrustScore ?? policy.minimumTrustScore;
|
|
65
|
+
if (identity.trustScore < policy.denyTrustScoreBelow) {
|
|
66
|
+
reasonCodes.push(REASON_CODES.TRUST_SCORE_BELOW_DENY_FLOOR);
|
|
67
|
+
return decision(DECISIONS.DENY, reasonCodes, checks, 'Trust score is below the deny floor.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (identity.trustScore < checks.minimumTrustScore) {
|
|
71
|
+
reasonCodes.push(REASON_CODES.TRUST_SCORE_BELOW_MINIMUM);
|
|
72
|
+
if (action.allowDegraded === true) {
|
|
73
|
+
return decision(DECISIONS.DEGRADE, reasonCodes, checks, 'Trust score permits degraded access only.');
|
|
74
|
+
}
|
|
75
|
+
return decision(DECISIONS.NEEDS_APPROVAL, reasonCodes, checks, 'Trust score needs an operator decision.');
|
|
76
|
+
}
|
|
77
|
+
reasonCodes.push(REASON_CODES.TRUST_SCORE_OK);
|
|
78
|
+
|
|
79
|
+
checks.evidenceFresh = isEvidenceFresh(identity, policy, now);
|
|
80
|
+
if (action.requiresFreshEvidence && !checks.evidenceFresh) {
|
|
81
|
+
reasonCodes.push(REASON_CODES.EVIDENCE_STALE_OR_MISSING);
|
|
82
|
+
return staleEvidenceDecision(action, options, reasonCodes, checks);
|
|
83
|
+
}
|
|
84
|
+
if (action.requiresFreshEvidence) reasonCodes.push(REASON_CODES.EVIDENCE_FRESH);
|
|
85
|
+
|
|
86
|
+
checks.actionCostUsd = action.costUsd;
|
|
87
|
+
checks.actionCostUsdValid = action.costUsdValid;
|
|
88
|
+
if (!action.costUsdValid) {
|
|
89
|
+
reasonCodes.push(REASON_CODES.INVALID_ACTION_COST_USD);
|
|
90
|
+
return decision(DECISIONS.DENY, reasonCodes, checks, 'Action costUsd must be a finite non-negative number.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
checks.maxAutoSpendUsd = policy.maxAutoSpendUsd;
|
|
94
|
+
if (checks.actionCostUsd > checks.maxAutoSpendUsd && options.actionPaymentPreapproved !== true) {
|
|
95
|
+
reasonCodes.push(REASON_CODES.ACTION_PAYMENT_NEEDS_APPROVAL);
|
|
96
|
+
return decision(DECISIONS.NEEDS_APPROVAL, reasonCodes, checks, 'Paid action exceeds local auto-spend policy.');
|
|
97
|
+
}
|
|
98
|
+
if (checks.actionCostUsd > 0) {
|
|
99
|
+
reasonCodes.push(REASON_CODES.ACTION_PAYMENT_PREAPPROVED);
|
|
100
|
+
reasonCodes.push(REASON_CODES.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (action.protectedTool && action.operatorApprovalRequired && options.operatorApproved !== true) {
|
|
104
|
+
reasonCodes.push(REASON_CODES.PROTECTED_TOOL_REQUIRES_APPROVAL);
|
|
105
|
+
return decision(DECISIONS.NEEDS_APPROVAL, reasonCodes, checks, 'Protected tool requires operator approval.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
reasonCodes.push(REASON_CODES.LOCAL_POLICY_ALLOW);
|
|
109
|
+
return decision(DECISIONS.ALLOW, reasonCodes, checks, 'Local runtime policy allows the action.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function staleEvidenceDecision(action, options, reasonCodes, checks) {
|
|
113
|
+
const lookup = action.evidenceLookup || null;
|
|
114
|
+
if (!lookup || lookup.type !== 'x402') {
|
|
115
|
+
return decision(DECISIONS.DEGRADE, reasonCodes, checks, 'Evidence is stale or missing; no paid lookup path is configured.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
checks.evidenceLookup = {
|
|
119
|
+
type: lookup.type,
|
|
120
|
+
endpoint: lookup.endpoint || null,
|
|
121
|
+
maxCostUsd: lookup.maxCostUsd ?? null,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (options.evidenceLookupPaymentPreapproved !== true) {
|
|
125
|
+
reasonCodes.push(REASON_CODES.X402_LOOKUP_REQUIRES_APPROVAL);
|
|
126
|
+
reasonCodes.push(REASON_CODES.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
127
|
+
return decision(DECISIONS.NEEDS_APPROVAL, reasonCodes, checks, 'Paid x402 evidence lookup requires approval before use.');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
reasonCodes.push(REASON_CODES.X402_LOOKUP_PAYMENT_PREAPPROVED);
|
|
131
|
+
reasonCodes.push(REASON_CODES.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION);
|
|
132
|
+
return decision(DECISIONS.DEGRADE, reasonCodes, checks, 'Paid lookup may refresh evidence, but does not authorize the agent action.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function decision(value, reasonCodes, checks, message) {
|
|
136
|
+
return {
|
|
137
|
+
decision: value,
|
|
138
|
+
reasonCodes: Array.from(new Set(reasonCodes)),
|
|
139
|
+
message,
|
|
140
|
+
checks,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeIdentity(identity = {}) {
|
|
145
|
+
return {
|
|
146
|
+
agentId: identity.agentId || identity.profileId || null,
|
|
147
|
+
active: identity.active !== false,
|
|
148
|
+
verified: identity.verified === true || identity.satpVerified === true,
|
|
149
|
+
trustScore: clampScore(identity.trustScore ?? identity.agentFolioTrustScore ?? 0),
|
|
150
|
+
capabilities: Array.isArray(identity.capabilities) ? identity.capabilities.slice() : [],
|
|
151
|
+
evidenceUpdatedAt: identity.evidenceUpdatedAt || identity.lastEvidenceAt || null,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function normalizeAction(action = {}) {
|
|
156
|
+
const parsedCost = parseActionCostUsd(action);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
type: action.type || 'generic',
|
|
160
|
+
resource: action.resource || null,
|
|
161
|
+
operation: action.operation || null,
|
|
162
|
+
requiresCapability: action.requiresCapability || null,
|
|
163
|
+
minimumTrustScore: Number.isFinite(action.minimumTrustScore) ? action.minimumTrustScore : null,
|
|
164
|
+
allowDegraded: action.allowDegraded === true,
|
|
165
|
+
requiresFreshEvidence: action.requiresFreshEvidence === true,
|
|
166
|
+
evidenceLookup: action.evidenceLookup || null,
|
|
167
|
+
protectedTool: action.protectedTool === true || action.type === 'mcp_protected_tool',
|
|
168
|
+
operatorApprovalRequired: action.operatorApprovalRequired === true,
|
|
169
|
+
costUsd: parsedCost.value,
|
|
170
|
+
costUsdValid: parsedCost.valid,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseActionCostUsd(action) {
|
|
175
|
+
if (!Object.prototype.hasOwnProperty.call(action, 'costUsd') || action.costUsd == null) {
|
|
176
|
+
return { value: 0, valid: true };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof action.costUsd !== 'number' || !Number.isFinite(action.costUsd) || action.costUsd < 0) {
|
|
180
|
+
return { value: null, valid: false };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { value: action.costUsd, valid: true };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function clampScore(value) {
|
|
187
|
+
const score = Number(value);
|
|
188
|
+
if (!Number.isFinite(score)) return 0;
|
|
189
|
+
return Math.max(0, Math.min(100, score));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isEvidenceFresh(identity, policy, now) {
|
|
193
|
+
if (!identity.evidenceUpdatedAt) return false;
|
|
194
|
+
const updatedAt = new Date(identity.evidenceUpdatedAt);
|
|
195
|
+
if (Number.isNaN(updatedAt.getTime())) return false;
|
|
196
|
+
const ageMs = now.getTime() - updatedAt.getTime();
|
|
197
|
+
if (ageMs < 0) return false;
|
|
198
|
+
return ageMs <= policy.staleEvidenceAfterMs;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
DECISIONS,
|
|
203
|
+
DEFAULT_POLICY,
|
|
204
|
+
REASON_CODES,
|
|
205
|
+
evaluateRuntimePolicy,
|
|
206
|
+
};
|