@brainai/satp-client 2.0.3 → 2.0.4
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 +66 -22
- package/examples/runtime-policy-adapter.js +33 -13
- package/package.json +6 -3
- package/src/borsh-reader.d.ts +5 -0
- package/src/borsh-reader.js +25 -11
- package/src/index.d.ts +210 -1
- package/src/index.js +23 -0
- package/src/runtime-policy-adapter.js +293 -0
- package/src/signer-policy.js +210 -0
- package/src/v3-idl-discriminators.js +17 -0
- package/src/v3-pda.d.ts +26 -0
- package/src/v3-pda.js +57 -6
- package/src/v3-sdk.d.ts +85 -4
- package/src/v3-sdk.js +379 -13
package/src/index.js
CHANGED
|
@@ -787,10 +787,16 @@ const {
|
|
|
787
787
|
DECISIONS,
|
|
788
788
|
DEFAULT_POLICY,
|
|
789
789
|
REASON_CODES,
|
|
790
|
+
RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION,
|
|
791
|
+
RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION,
|
|
792
|
+
buildRuntimePolicyActionDescriptor,
|
|
793
|
+
buildRuntimePolicyAuditTrace,
|
|
794
|
+
createRuntimePolicyAdapter,
|
|
790
795
|
evaluateRuntimePolicy,
|
|
791
796
|
} = require('./runtime-policy-adapter');
|
|
792
797
|
const walletControlChallenge = require('./wallet-control-challenge');
|
|
793
798
|
const x402Discovery = require('./x402-discovery');
|
|
799
|
+
const signerPolicy = require('./signer-policy');
|
|
794
800
|
|
|
795
801
|
// Legacy V3 SDK wrapper — keeps string constructor compatibility while using
|
|
796
802
|
// the local extracted SATPV3SDK implementation so offline tests and consumers
|
|
@@ -905,7 +911,12 @@ module.exports = {
|
|
|
905
911
|
|
|
906
912
|
// V3 PDA derivation (local extracted scaffold)
|
|
907
913
|
PROGRAM_IDS: v3pda.getV3ProgramIds('devnet'),
|
|
914
|
+
V3_DEVNET_PROGRAM_IDS: v3pda.V3_DEVNET_PROGRAM_IDS,
|
|
915
|
+
V3_MAINNET_PROGRAM_IDS: v3pda.V3_MAINNET_PROGRAM_IDS,
|
|
908
916
|
getV3ProgramIds: v3pda.getV3ProgramIds,
|
|
917
|
+
V3_DEVNET_TOKEN_MINTS: v3pda.V3_DEVNET_TOKEN_MINTS,
|
|
918
|
+
SPL_TOKEN_PROGRAM_ID: v3pda.SPL_TOKEN_PROGRAM_ID,
|
|
919
|
+
ASSOCIATED_TOKEN_PROGRAM_ID: v3pda.ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
909
920
|
hashAgentId: v3pda.hashAgentId,
|
|
910
921
|
agentIdHash: v3pda.hashAgentId,
|
|
911
922
|
hashName: v3pda.hashName,
|
|
@@ -929,6 +940,8 @@ module.exports = {
|
|
|
929
940
|
deriveAttestationPda: v3pda.getV3AttestationPDA,
|
|
930
941
|
getV3EscrowPDA: v3pda.getV3EscrowPDA,
|
|
931
942
|
deriveEscrowPda: v3pda.getV3EscrowPDA,
|
|
943
|
+
getAssociatedTokenAddress: v3pda.getAssociatedTokenAddress,
|
|
944
|
+
getV3EscrowVaultATA: v3pda.getV3EscrowVaultATA,
|
|
932
945
|
deriveReviewAttestationPda: getReviewAttestationPDA,
|
|
933
946
|
prepareIdentityAttestationRequest,
|
|
934
947
|
TRUST_PACKET_SCHEMA_VERSION,
|
|
@@ -950,11 +963,21 @@ module.exports = {
|
|
|
950
963
|
buildX402EvidenceLookup: x402Discovery.buildX402EvidenceLookup,
|
|
951
964
|
buildRuntimePolicyActionDescriptorFromX402Discovery: x402Discovery.buildRuntimePolicyActionDescriptorFromX402Discovery,
|
|
952
965
|
buildRuntimePolicyActionDescriptorFromX402: x402Discovery.buildRuntimePolicyActionDescriptorFromX402,
|
|
966
|
+
SATP_SIGNER_ROLES: signerPolicy.SATP_SIGNER_ROLES,
|
|
967
|
+
OPERATIONAL_SIGNER_ALLOWED_ACTIONS: signerPolicy.OPERATIONAL_SIGNER_ALLOWED_ACTIONS,
|
|
968
|
+
OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS: signerPolicy.OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS,
|
|
969
|
+
buildSignerSeparationConfig: signerPolicy.buildSignerSeparationConfig,
|
|
970
|
+
validateSignerSeparationConfig: signerPolicy.validateSignerSeparationConfig,
|
|
953
971
|
|
|
954
972
|
// Runtime policy adapter (offline/local guardrail helper)
|
|
955
973
|
DECISIONS,
|
|
956
974
|
DEFAULT_POLICY,
|
|
957
975
|
REASON_CODES,
|
|
976
|
+
RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION,
|
|
977
|
+
RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION,
|
|
978
|
+
buildRuntimePolicyActionDescriptor,
|
|
979
|
+
buildRuntimePolicyAuditTrace,
|
|
980
|
+
createRuntimePolicyAdapter,
|
|
958
981
|
evaluateRuntimePolicy,
|
|
959
982
|
|
|
960
983
|
// V3 Deserialization (local extracted scaffold)
|
|
@@ -34,6 +34,62 @@ const DEFAULT_POLICY = Object.freeze({
|
|
|
34
34
|
staleEvidenceAfterMs: 7 * 24 * 60 * 60 * 1000,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
const RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION = 'satp.runtimePolicyAuditTrace.v1';
|
|
38
|
+
const RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION = 'satp.runtimePolicyHostActionDescriptor.v1';
|
|
39
|
+
|
|
40
|
+
function createRuntimePolicyAdapter(config = {}) {
|
|
41
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
42
|
+
throw new Error('runtime policy adapter config must be an object');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const adapterPolicy = normalizePolicy(config.policy);
|
|
46
|
+
const defaultActionType = config.defaultActionType || null;
|
|
47
|
+
const nowProvider = config.now;
|
|
48
|
+
const redact = config.redact;
|
|
49
|
+
|
|
50
|
+
if (nowProvider !== undefined && typeof nowProvider !== 'function' && !isValidDateInput(nowProvider)) {
|
|
51
|
+
throw new Error('runtime policy adapter now must be a function or valid date input');
|
|
52
|
+
}
|
|
53
|
+
if (redact !== undefined && typeof redact !== 'function') {
|
|
54
|
+
throw new Error('runtime policy adapter redact must be a function');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function adapterOptions(options = {}) {
|
|
58
|
+
if (!options || typeof options !== 'object' || Array.isArray(options)) {
|
|
59
|
+
throw new Error('runtime policy adapter method options must be an object');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...options,
|
|
64
|
+
now: resolveAdapterNow(nowProvider, options.now),
|
|
65
|
+
policy: {
|
|
66
|
+
...adapterPolicy,
|
|
67
|
+
...(options.policy || {}),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
action(input = {}, overrides = {}) {
|
|
74
|
+
const base = applyDefaultActionType(input, defaultActionType);
|
|
75
|
+
return buildRuntimePolicyActionDescriptor(base, overrides);
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
evaluate(identityPayload, actionDescriptor, options = {}) {
|
|
79
|
+
return evaluateRuntimePolicy(identityPayload, actionDescriptor, adapterOptions(options));
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
auditTrace(identityPayload, actionDescriptor, options = {}) {
|
|
83
|
+
const traceAction = redact ? attachRedactedResourceLabel(actionDescriptor, redact) : actionDescriptor;
|
|
84
|
+
return buildRuntimePolicyAuditTrace(identityPayload, traceAction, adapterOptions(options));
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
explain(result) {
|
|
88
|
+
return explainRuntimePolicyResult(result);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
37
93
|
function evaluateRuntimePolicy(identityPayload, actionDescriptor, options = {}) {
|
|
38
94
|
const policy = { ...DEFAULT_POLICY, ...(options.policy || {}) };
|
|
39
95
|
const now = options.now ? new Date(options.now) : new Date();
|
|
@@ -109,6 +165,94 @@ function evaluateRuntimePolicy(identityPayload, actionDescriptor, options = {})
|
|
|
109
165
|
return decision(DECISIONS.ALLOW, reasonCodes, checks, 'Local runtime policy allows the action.');
|
|
110
166
|
}
|
|
111
167
|
|
|
168
|
+
function buildRuntimePolicyAuditTrace(identityPayload, actionDescriptor, options = {}) {
|
|
169
|
+
const result = options.result || evaluateRuntimePolicy(identityPayload, actionDescriptor, options);
|
|
170
|
+
const identity = normalizeIdentity(identityPayload);
|
|
171
|
+
const action = normalizeAction(actionDescriptor);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
schemaVersion: RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION,
|
|
175
|
+
mode: 'offline-local-runtime-policy-trace',
|
|
176
|
+
generatedAt: safeIsoDate(options.now),
|
|
177
|
+
decision: result.decision,
|
|
178
|
+
reasonCodes: result.reasonCodes.slice(),
|
|
179
|
+
message: result.message,
|
|
180
|
+
subject: {
|
|
181
|
+
agentId: identity.agentId,
|
|
182
|
+
active: identity.active,
|
|
183
|
+
verified: identity.verified,
|
|
184
|
+
trustScoreBand: trustScoreBand(identity.trustScore),
|
|
185
|
+
evidenceUpdatedAt: identity.evidenceUpdatedAt,
|
|
186
|
+
capabilityCount: identity.capabilities.length,
|
|
187
|
+
},
|
|
188
|
+
action: {
|
|
189
|
+
type: action.type,
|
|
190
|
+
operation: action.operation,
|
|
191
|
+
resourceKind: resourceKind(action.resource),
|
|
192
|
+
resourceLabel: action.resourceLabel,
|
|
193
|
+
requiresCapability: action.requiresCapability,
|
|
194
|
+
requiresFreshEvidence: action.requiresFreshEvidence,
|
|
195
|
+
protectedTool: action.protectedTool,
|
|
196
|
+
operatorApprovalRequired: action.operatorApprovalRequired,
|
|
197
|
+
costUsd: action.costUsd,
|
|
198
|
+
evidenceLookup: action.evidenceLookup
|
|
199
|
+
? {
|
|
200
|
+
type: action.evidenceLookup.type || null,
|
|
201
|
+
configured: true,
|
|
202
|
+
maxCostUsd: action.evidenceLookup.maxCostUsd ?? null,
|
|
203
|
+
}
|
|
204
|
+
: null,
|
|
205
|
+
},
|
|
206
|
+
checks: sanitizeAuditChecks(result.checks),
|
|
207
|
+
guardrails: {
|
|
208
|
+
localDecisionOnly: true,
|
|
209
|
+
writesSolanaState: false,
|
|
210
|
+
usesKeypairs: false,
|
|
211
|
+
deploysPrograms: false,
|
|
212
|
+
publishesPackages: false,
|
|
213
|
+
authorizesPayment: false,
|
|
214
|
+
authorizesAgentActionFromPayment: false,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function buildRuntimePolicyActionDescriptor(input = {}, overrides = {}) {
|
|
220
|
+
const base = typeof input === 'string' ? { type: input } : input;
|
|
221
|
+
if (!base || typeof base !== 'object' || Array.isArray(base)) {
|
|
222
|
+
throw new Error('runtime policy action descriptor input must be an object or action type string');
|
|
223
|
+
}
|
|
224
|
+
if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) {
|
|
225
|
+
throw new Error('runtime policy action descriptor overrides must be an object');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const action = { ...base, ...overrides };
|
|
229
|
+
const type = action.type || action.surface || 'generic';
|
|
230
|
+
const descriptor = {
|
|
231
|
+
schemaVersion: RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION,
|
|
232
|
+
type,
|
|
233
|
+
resource: firstDefined(action.resource, defaultResourceForAction(type, action)),
|
|
234
|
+
operation: firstDefined(action.operation, defaultOperationForAction(type)),
|
|
235
|
+
requiresCapability: firstDefined(action.requiresCapability, action.capability, defaultCapabilityForAction(type)),
|
|
236
|
+
minimumTrustScore: firstDefined(action.minimumTrustScore, action.trustScoreMinimum, defaultMinimumTrustScoreForAction(type)),
|
|
237
|
+
allowDegraded: firstDefined(action.allowDegraded, defaultAllowDegradedForAction(type)),
|
|
238
|
+
requiresFreshEvidence: firstDefined(action.requiresFreshEvidence, defaultRequiresFreshEvidenceForAction(type)),
|
|
239
|
+
costUsd: firstDefined(action.costUsd, defaultCostUsdForAction(type)),
|
|
240
|
+
protectedTool: firstDefined(action.protectedTool, type === 'mcp_protected_tool'),
|
|
241
|
+
operatorApprovalRequired: firstDefined(action.operatorApprovalRequired, false),
|
|
242
|
+
guardrails: {
|
|
243
|
+
localDecisionOnly: true,
|
|
244
|
+
writesSolanaState: false,
|
|
245
|
+
usesKeypairs: false,
|
|
246
|
+
deploysPrograms: false,
|
|
247
|
+
publishesPackages: false,
|
|
248
|
+
livePaymentRequired: false,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (action.evidenceLookup !== undefined) descriptor.evidenceLookup = action.evidenceLookup;
|
|
253
|
+
return descriptor;
|
|
254
|
+
}
|
|
255
|
+
|
|
112
256
|
function staleEvidenceDecision(action, options, reasonCodes, checks) {
|
|
113
257
|
const lookup = action.evidenceLookup || null;
|
|
114
258
|
if (!lookup || lookup.type !== 'x402') {
|
|
@@ -158,6 +302,7 @@ function normalizeAction(action = {}) {
|
|
|
158
302
|
return {
|
|
159
303
|
type: action.type || 'generic',
|
|
160
304
|
resource: action.resource || null,
|
|
305
|
+
resourceLabel: action.resourceLabel || null,
|
|
161
306
|
operation: action.operation || null,
|
|
162
307
|
requiresCapability: action.requiresCapability || null,
|
|
163
308
|
minimumTrustScore: Number.isFinite(action.minimumTrustScore) ? action.minimumTrustScore : null,
|
|
@@ -189,6 +334,149 @@ function clampScore(value) {
|
|
|
189
334
|
return Math.max(0, Math.min(100, score));
|
|
190
335
|
}
|
|
191
336
|
|
|
337
|
+
function trustScoreBand(score) {
|
|
338
|
+
if (score >= 90) return '90-100';
|
|
339
|
+
if (score >= 80) return '80-89';
|
|
340
|
+
if (score >= 70) return '70-79';
|
|
341
|
+
if (score >= 50) return '50-69';
|
|
342
|
+
if (score >= 25) return '25-49';
|
|
343
|
+
return '0-24';
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function sanitizeAuditChecks(checks = {}) {
|
|
347
|
+
const sanitized = { ...checks };
|
|
348
|
+
if (sanitized.evidenceLookup && typeof sanitized.evidenceLookup === 'object') {
|
|
349
|
+
sanitized.evidenceLookup = {
|
|
350
|
+
type: sanitized.evidenceLookup.type || null,
|
|
351
|
+
configured: true,
|
|
352
|
+
maxCostUsd: sanitized.evidenceLookup.maxCostUsd ?? null,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return sanitized;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function firstDefined(...values) {
|
|
359
|
+
return values.find((value) => value !== undefined);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function defaultResourceForAction(type, action) {
|
|
363
|
+
if (type === 'mcp_protected_tool') return 'mcp://protected/tool';
|
|
364
|
+
if (type === 'agentfolio_trust_gate') {
|
|
365
|
+
if (action.profileId) {
|
|
366
|
+
return `https://agentfolio.bot/api/profile/${encodeURIComponent(String(action.profileId))}/trust-score`;
|
|
367
|
+
}
|
|
368
|
+
return 'https://agentfolio.bot/api/profile/:id/trust-score';
|
|
369
|
+
}
|
|
370
|
+
if (type === 'x402_endpoint') return 'x402://paid-endpoint';
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function defaultOperationForAction(type) {
|
|
375
|
+
if (type === 'mcp_protected_tool') return 'invoke';
|
|
376
|
+
if (type === 'agentfolio_trust_gate') return 'trust-score-read';
|
|
377
|
+
if (type === 'x402_endpoint') return 'lookup';
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function defaultCapabilityForAction(type) {
|
|
382
|
+
if (type === 'agentfolio_trust_gate') return 'agentfolio:trust-read';
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function defaultMinimumTrustScoreForAction(type) {
|
|
387
|
+
if (type === 'agentfolio_trust_gate') return DEFAULT_POLICY.minimumTrustScore;
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function defaultAllowDegradedForAction(type) {
|
|
392
|
+
return type === 'agentfolio_trust_gate';
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function defaultRequiresFreshEvidenceForAction(type) {
|
|
396
|
+
return type === 'mcp_protected_tool' || type === 'agentfolio_trust_gate';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function defaultCostUsdForAction(type) {
|
|
400
|
+
if (type === 'x402_endpoint') return 0;
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function normalizePolicy(policy) {
|
|
405
|
+
if (policy === undefined) return {};
|
|
406
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
407
|
+
throw new Error('runtime policy adapter policy must be an object');
|
|
408
|
+
}
|
|
409
|
+
return { ...policy };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function applyDefaultActionType(input, defaultActionType) {
|
|
413
|
+
if (!defaultActionType || typeof input === 'string') return input;
|
|
414
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) return input;
|
|
415
|
+
if (input.type || input.surface) return input;
|
|
416
|
+
return { type: defaultActionType, ...input };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function resolveAdapterNow(nowProvider, methodNow) {
|
|
420
|
+
if (methodNow !== undefined) return methodNow;
|
|
421
|
+
if (typeof nowProvider === 'function') return nowProvider();
|
|
422
|
+
return nowProvider;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function isValidDateInput(value) {
|
|
426
|
+
const date = new Date(value);
|
|
427
|
+
return !Number.isNaN(date.getTime());
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function attachRedactedResourceLabel(actionDescriptor, redact) {
|
|
431
|
+
if (!actionDescriptor || typeof actionDescriptor !== 'object' || Array.isArray(actionDescriptor)) {
|
|
432
|
+
return actionDescriptor;
|
|
433
|
+
}
|
|
434
|
+
if (typeof actionDescriptor.resource !== 'string') return actionDescriptor;
|
|
435
|
+
const redacted = redact(actionDescriptor.resource);
|
|
436
|
+
return {
|
|
437
|
+
...actionDescriptor,
|
|
438
|
+
resourceLabel: typeof redacted === 'string' ? redacted : '[redacted]',
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function explainRuntimePolicyResult(result = {}) {
|
|
443
|
+
const reasonCodes = Array.isArray(result.reasonCodes) ? result.reasonCodes : [];
|
|
444
|
+
return reasonCodes.map((code) => RUNTIME_POLICY_EXPLANATIONS[code] || `Runtime policy returned ${code}.`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const RUNTIME_POLICY_EXPLANATIONS = Object.freeze({
|
|
448
|
+
[REASON_CODES.ACTION_PAYMENT_NEEDS_APPROVAL]: 'The action cost exceeds the host auto-spend policy and needs approval.',
|
|
449
|
+
[REASON_CODES.ACTION_PAYMENT_PREAPPROVED]: 'The host preapproved payment for this action.',
|
|
450
|
+
[REASON_CODES.EVIDENCE_FRESH]: 'The identity evidence is fresh enough for this action.',
|
|
451
|
+
[REASON_CODES.EVIDENCE_STALE_OR_MISSING]: 'The identity evidence is stale or missing.',
|
|
452
|
+
[REASON_CODES.IDENTITY_INACTIVE]: 'The identity is inactive.',
|
|
453
|
+
[REASON_CODES.IDENTITY_UNVERIFIED]: 'The identity is not verified by the host policy.',
|
|
454
|
+
[REASON_CODES.INVALID_ACTION_COST_USD]: 'The action cost must be a finite non-negative number.',
|
|
455
|
+
[REASON_CODES.LOCAL_POLICY_ALLOW]: 'The local host policy allows the action.',
|
|
456
|
+
[REASON_CODES.MISSING_CAPABILITY]: 'The identity is missing the capability required by this action.',
|
|
457
|
+
[REASON_CODES.PROTECTED_TOOL_REQUIRES_APPROVAL]: 'The protected tool requires operator approval.',
|
|
458
|
+
[REASON_CODES.TRUST_SCORE_BELOW_DENY_FLOOR]: 'The trust score is below the host deny floor.',
|
|
459
|
+
[REASON_CODES.TRUST_SCORE_BELOW_MINIMUM]: 'The trust score is below the minimum for this action.',
|
|
460
|
+
[REASON_CODES.TRUST_SCORE_OK]: 'The trust score satisfies the local policy.',
|
|
461
|
+
[REASON_CODES.X402_LOOKUP_PAYMENT_PREAPPROVED]: 'The host preapproved payment for the x402 evidence lookup.',
|
|
462
|
+
[REASON_CODES.X402_LOOKUP_REQUIRES_APPROVAL]: 'The x402 evidence lookup requires payment approval.',
|
|
463
|
+
[REASON_CODES.X402_PAYMENT_IS_NOT_ACTION_AUTHORIZATION]: 'x402 payment does not authorize the agent action.',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
function safeIsoDate(value) {
|
|
467
|
+
const date = value ? new Date(value) : new Date();
|
|
468
|
+
if (Number.isNaN(date.getTime())) return new Date().toISOString();
|
|
469
|
+
return date.toISOString();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function resourceKind(resource) {
|
|
473
|
+
if (!resource || typeof resource !== 'string') return null;
|
|
474
|
+
const schemeMatch = resource.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
475
|
+
if (schemeMatch) return `${schemeMatch[1].toLowerCase()}:`;
|
|
476
|
+
if (resource.startsWith('/')) return 'path';
|
|
477
|
+
return 'opaque';
|
|
478
|
+
}
|
|
479
|
+
|
|
192
480
|
function isEvidenceFresh(identity, policy, now) {
|
|
193
481
|
if (!identity.evidenceUpdatedAt) return false;
|
|
194
482
|
const updatedAt = new Date(identity.evidenceUpdatedAt);
|
|
@@ -202,5 +490,10 @@ module.exports = {
|
|
|
202
490
|
DECISIONS,
|
|
203
491
|
DEFAULT_POLICY,
|
|
204
492
|
REASON_CODES,
|
|
493
|
+
RUNTIME_POLICY_AUDIT_TRACE_SCHEMA_VERSION,
|
|
494
|
+
RUNTIME_POLICY_HOST_ACTION_DESCRIPTOR_SCHEMA_VERSION,
|
|
495
|
+
buildRuntimePolicyActionDescriptor,
|
|
496
|
+
buildRuntimePolicyAuditTrace,
|
|
497
|
+
createRuntimePolicyAdapter,
|
|
205
498
|
evaluateRuntimePolicy,
|
|
206
499
|
};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { PublicKey } = require('@solana/web3.js');
|
|
4
|
+
|
|
5
|
+
const SATP_SIGNER_ROLES = Object.freeze({
|
|
6
|
+
OPERATIONAL_SIGNER: 'operational_signer',
|
|
7
|
+
OWNER_UPGRADE_AUTHORITY: 'owner_upgrade_authority',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const OPERATIONAL_SIGNER_ALLOWED_ACTIONS = Object.freeze([
|
|
11
|
+
'devnet_fee_payment',
|
|
12
|
+
'devnet_transaction_submission',
|
|
13
|
+
'offline_transaction_preparation',
|
|
14
|
+
'read_only_rpc',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS = Object.freeze([
|
|
18
|
+
'program_upgrade',
|
|
19
|
+
'authority_transfer',
|
|
20
|
+
'key_generation',
|
|
21
|
+
'key_rotation',
|
|
22
|
+
'mainnet_deploy',
|
|
23
|
+
'devnet_deploy',
|
|
24
|
+
'npm_publish',
|
|
25
|
+
'funds_custody',
|
|
26
|
+
'funds_transfer',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const OPERATIONAL_SIGNER_AUTHORITY_BOUNDARY =
|
|
30
|
+
'no_upgrade_authority_no_key_management_no_funds_custody';
|
|
31
|
+
|
|
32
|
+
const OWNER_UPGRADE_AUTHORITY_CUSTODY = 'owner_held';
|
|
33
|
+
|
|
34
|
+
const SECRET_FIELD_NAMES = Object.freeze([
|
|
35
|
+
'keypair',
|
|
36
|
+
'keypairPath',
|
|
37
|
+
'secretKey',
|
|
38
|
+
'privateKey',
|
|
39
|
+
'seedPhrase',
|
|
40
|
+
'mnemonic',
|
|
41
|
+
'rawEnv',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
function normalizePublicKey(value, fieldName) {
|
|
45
|
+
if (value === undefined || value === null || value === '') {
|
|
46
|
+
throw new Error(`${fieldName} is required`);
|
|
47
|
+
}
|
|
48
|
+
return new PublicKey(value).toBase58();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertNoSecretFields(input, path = 'config') {
|
|
52
|
+
if (!input || typeof input !== 'object') return;
|
|
53
|
+
|
|
54
|
+
for (const [key, value] of Object.entries(input)) {
|
|
55
|
+
const childPath = `${path}.${key}`;
|
|
56
|
+
if (SECRET_FIELD_NAMES.includes(key)) {
|
|
57
|
+
throw new Error(`${childPath} must not be present in SATP signer policy config`);
|
|
58
|
+
}
|
|
59
|
+
if (value && typeof value === 'object' && !Buffer.isBuffer(value) && !(value instanceof PublicKey)) {
|
|
60
|
+
assertNoSecretFields(value, childPath);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeOperationalActions(actions) {
|
|
66
|
+
const requested = actions === undefined ? OPERATIONAL_SIGNER_ALLOWED_ACTIONS : actions;
|
|
67
|
+
if (!Array.isArray(requested)) {
|
|
68
|
+
throw new Error('operational signer actions must be an array');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const seen = new Set();
|
|
72
|
+
for (const action of requested) {
|
|
73
|
+
if (typeof action !== 'string' || action.length === 0) {
|
|
74
|
+
throw new Error('operational signer actions must be non-empty strings');
|
|
75
|
+
}
|
|
76
|
+
if (!OPERATIONAL_SIGNER_ALLOWED_ACTIONS.includes(action)) {
|
|
77
|
+
throw new Error(`operational signer action is not low-privilege: ${action}`);
|
|
78
|
+
}
|
|
79
|
+
if (OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS.includes(action)) {
|
|
80
|
+
throw new Error(`operational signer action is owner-gated: ${action}`);
|
|
81
|
+
}
|
|
82
|
+
seen.add(action);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return Array.from(seen).sort();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function arraysMatchExactly(actual, expected) {
|
|
89
|
+
if (!Array.isArray(actual) || actual.length !== expected.length) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sortedActual = actual.slice().sort();
|
|
94
|
+
const sortedExpected = expected.slice().sort();
|
|
95
|
+
return sortedExpected.every((action, index) => sortedActual[index] === action);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildSignerSeparationConfig(opts = {}) {
|
|
99
|
+
assertNoSecretFields(opts);
|
|
100
|
+
|
|
101
|
+
const network = opts.network || 'devnet';
|
|
102
|
+
if (network !== 'devnet' && network !== 'mainnet') {
|
|
103
|
+
throw new Error('Invalid network: expected devnet or mainnet');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const operationalSigner = normalizePublicKey(
|
|
107
|
+
opts.operationalSignerPublicKey,
|
|
108
|
+
'operationalSignerPublicKey',
|
|
109
|
+
);
|
|
110
|
+
const ownerUpgradeAuthority = normalizePublicKey(
|
|
111
|
+
opts.ownerUpgradeAuthorityPublicKey,
|
|
112
|
+
'ownerUpgradeAuthorityPublicKey',
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (operationalSigner === ownerUpgradeAuthority) {
|
|
116
|
+
throw new Error('operational signer must be distinct from Owner upgrade authority');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
schemaVersion: 'satp.signerSeparation.v1',
|
|
121
|
+
network,
|
|
122
|
+
operationalSigner: {
|
|
123
|
+
role: SATP_SIGNER_ROLES.OPERATIONAL_SIGNER,
|
|
124
|
+
publicKey: operationalSigner,
|
|
125
|
+
allowedActions: normalizeOperationalActions(opts.operationalAllowedActions),
|
|
126
|
+
blockedActions: OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS.slice().sort(),
|
|
127
|
+
authorityBoundary: OPERATIONAL_SIGNER_AUTHORITY_BOUNDARY,
|
|
128
|
+
},
|
|
129
|
+
ownerUpgradeAuthority: {
|
|
130
|
+
role: SATP_SIGNER_ROLES.OWNER_UPGRADE_AUTHORITY,
|
|
131
|
+
publicKey: ownerUpgradeAuthority,
|
|
132
|
+
custody: OWNER_UPGRADE_AUTHORITY_CUSTODY,
|
|
133
|
+
operationalSignerMayUse: false,
|
|
134
|
+
},
|
|
135
|
+
flags: {
|
|
136
|
+
publicKeysOnly: true,
|
|
137
|
+
readsKeypairs: false,
|
|
138
|
+
generatesKeypairs: false,
|
|
139
|
+
transfersAuthority: false,
|
|
140
|
+
deploysPrograms: false,
|
|
141
|
+
publishesPackages: false,
|
|
142
|
+
writesSolanaState: false,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function validateSignerSeparationConfig(config) {
|
|
148
|
+
try {
|
|
149
|
+
assertNoSecretFields(config);
|
|
150
|
+
const normalized = buildSignerSeparationConfig({
|
|
151
|
+
network: config && config.network,
|
|
152
|
+
operationalSignerPublicKey: config && config.operationalSigner && config.operationalSigner.publicKey,
|
|
153
|
+
ownerUpgradeAuthorityPublicKey: config && config.ownerUpgradeAuthority && config.ownerUpgradeAuthority.publicKey,
|
|
154
|
+
operationalAllowedActions: config && config.operationalSigner && config.operationalSigner.allowedActions,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const errors = [];
|
|
158
|
+
if (!config || config.schemaVersion !== 'satp.signerSeparation.v1') {
|
|
159
|
+
errors.push('schemaVersion must be satp.signerSeparation.v1');
|
|
160
|
+
}
|
|
161
|
+
if (!config || !config.flags || config.flags.publicKeysOnly !== true) {
|
|
162
|
+
errors.push('flags.publicKeysOnly must be true');
|
|
163
|
+
}
|
|
164
|
+
if (!config || !config.operationalSigner || config.operationalSigner.role !== SATP_SIGNER_ROLES.OPERATIONAL_SIGNER) {
|
|
165
|
+
errors.push('operationalSigner.role must be operational_signer');
|
|
166
|
+
}
|
|
167
|
+
if (!config || !config.ownerUpgradeAuthority || config.ownerUpgradeAuthority.role !== SATP_SIGNER_ROLES.OWNER_UPGRADE_AUTHORITY) {
|
|
168
|
+
errors.push('ownerUpgradeAuthority.role must be owner_upgrade_authority');
|
|
169
|
+
}
|
|
170
|
+
if (!config || !config.operationalSigner || config.operationalSigner.authorityBoundary !== OPERATIONAL_SIGNER_AUTHORITY_BOUNDARY) {
|
|
171
|
+
errors.push(`operationalSigner.authorityBoundary must be ${OPERATIONAL_SIGNER_AUTHORITY_BOUNDARY}`);
|
|
172
|
+
}
|
|
173
|
+
if (!config || !config.ownerUpgradeAuthority || config.ownerUpgradeAuthority.custody !== OWNER_UPGRADE_AUTHORITY_CUSTODY) {
|
|
174
|
+
errors.push(`ownerUpgradeAuthority.custody must be ${OWNER_UPGRADE_AUTHORITY_CUSTODY}`);
|
|
175
|
+
}
|
|
176
|
+
if (
|
|
177
|
+
!config
|
|
178
|
+
|| !config.operationalSigner
|
|
179
|
+
|| !arraysMatchExactly(config.operationalSigner.blockedActions, OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS)
|
|
180
|
+
) {
|
|
181
|
+
errors.push('operationalSigner.blockedActions must list all owner-gated actions');
|
|
182
|
+
}
|
|
183
|
+
if (config && config.ownerUpgradeAuthority && config.ownerUpgradeAuthority.operationalSignerMayUse !== false) {
|
|
184
|
+
errors.push('ownerUpgradeAuthority.operationalSignerMayUse must be false');
|
|
185
|
+
}
|
|
186
|
+
for (const flagName of [
|
|
187
|
+
'readsKeypairs',
|
|
188
|
+
'generatesKeypairs',
|
|
189
|
+
'transfersAuthority',
|
|
190
|
+
'deploysPrograms',
|
|
191
|
+
'publishesPackages',
|
|
192
|
+
'writesSolanaState',
|
|
193
|
+
]) {
|
|
194
|
+
if (!config || !config.flags || config.flags[flagName] !== false) {
|
|
195
|
+
errors.push(`flags.${flagName} must be false`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { ok: errors.length === 0, errors, normalized };
|
|
199
|
+
} catch (e) {
|
|
200
|
+
return { ok: false, errors: [e.message], normalized: null };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
SATP_SIGNER_ROLES,
|
|
206
|
+
OPERATIONAL_SIGNER_ALLOWED_ACTIONS,
|
|
207
|
+
OWNER_UPGRADE_AUTHORITY_BLOCKED_ACTIONS,
|
|
208
|
+
buildSignerSeparationConfig,
|
|
209
|
+
validateSignerSeparationConfig,
|
|
210
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Generated by `node scripts/generate-v3-idls.mjs`; do not edit by hand.
|
|
4
|
+
const V3_IDL_ACCOUNT_DISCRIMINATORS = Object.freeze({
|
|
5
|
+
"Attestation": Object.freeze([152, 125, 183, 86, 36, 146, 121, 73]),
|
|
6
|
+
"EscrowV3": Object.freeze([145, 108, 37, 52, 197, 162, 232, 59]),
|
|
7
|
+
"GenesisRecord": Object.freeze([22, 160, 245, 112, 178, 126, 177, 106]),
|
|
8
|
+
"LinkedWallet": Object.freeze([43, 44, 217, 238, 93, 127, 166, 59]),
|
|
9
|
+
"MintTracker": Object.freeze([217, 230, 22, 187, 250, 88, 11, 174]),
|
|
10
|
+
"NameRegistry": Object.freeze([169, 63, 83, 240, 198, 158, 53, 11]),
|
|
11
|
+
"Review": Object.freeze([124, 63, 203, 215, 226, 30, 222, 15]),
|
|
12
|
+
"ReviewCounter": Object.freeze([89, 31, 82, 96, 68, 42, 80, 60]),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
V3_IDL_ACCOUNT_DISCRIMINATORS,
|
|
17
|
+
};
|
package/src/v3-pda.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface V3ProgramIds {
|
|
|
11
11
|
ESCROW: PublicKey;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export const SPL_TOKEN_PROGRAM_ID: PublicKey;
|
|
15
|
+
export const ASSOCIATED_TOKEN_PROGRAM_ID: PublicKey;
|
|
16
|
+
export const V3_DEVNET_TOKEN_MINTS: {
|
|
17
|
+
USDC: PublicKey;
|
|
18
|
+
};
|
|
19
|
+
|
|
14
20
|
/** Get all V3 program IDs for a network. */
|
|
15
21
|
export function getV3ProgramIds(network?: Network): V3ProgramIds;
|
|
16
22
|
|
|
@@ -70,3 +76,23 @@ export function getV3EscrowPDA(
|
|
|
70
76
|
nonce: number | bigint,
|
|
71
77
|
network?: Network
|
|
72
78
|
): [PublicKey, number];
|
|
79
|
+
|
|
80
|
+
/** Derive canonical Associated Token Account address. */
|
|
81
|
+
export function getAssociatedTokenAddress(
|
|
82
|
+
owner: PublicKey | string,
|
|
83
|
+
mint: PublicKey | string
|
|
84
|
+
): [PublicKey, number];
|
|
85
|
+
|
|
86
|
+
/** Derive Escrow V3 PDA plus its SPL vault ATA for a mint. */
|
|
87
|
+
export function getV3EscrowVaultATA(
|
|
88
|
+
client: PublicKey | string,
|
|
89
|
+
descriptionHash: Buffer,
|
|
90
|
+
nonce: number | bigint,
|
|
91
|
+
mint: PublicKey | string,
|
|
92
|
+
network?: Network
|
|
93
|
+
): {
|
|
94
|
+
escrowPDA: PublicKey;
|
|
95
|
+
escrowBump: number;
|
|
96
|
+
vaultATA: PublicKey;
|
|
97
|
+
vaultBump: number;
|
|
98
|
+
};
|