@svsprotocol/solana 0.1.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.
- package/LICENSE +158 -0
- package/README.md +365 -0
- package/dist/action-production-proof-evidence.js +553 -0
- package/dist/adapter-catalog.d.ts +29 -0
- package/dist/adapter-catalog.js +146 -0
- package/dist/adapter-core.d.ts +48 -0
- package/dist/adapter-core.js +249 -0
- package/dist/approval-signature.js +197 -0
- package/dist/base58.js +69 -0
- package/dist/bot-auth.js +50 -0
- package/dist/bot-certification-evidence.js +342 -0
- package/dist/bot-first-action-runbook.js +299 -0
- package/dist/bot-integration-contract.js +41 -0
- package/dist/certified-submit-status.js +176 -0
- package/dist/common.d.ts +1135 -0
- package/dist/elizaos.d.ts +43 -0
- package/dist/elizaos.js +227 -0
- package/dist/goat.d.ts +47 -0
- package/dist/goat.js +261 -0
- package/dist/index.d.ts +330 -0
- package/dist/index.js +128 -0
- package/dist/protocol.d.ts +205 -0
- package/dist/protocol.js +900 -0
- package/dist/receipt.js +51 -0
- package/dist/signed-proof-read-protection.js +495 -0
- package/dist/solana-agent-kit.d.ts +35 -0
- package/dist/solana-agent-kit.js +151 -0
- package/dist/svs-client.js +1232 -0
- package/dist/vercel-ai.d.ts +47 -0
- package/dist/vercel-ai.js +266 -0
- package/dist/verified-agent-adoption-kit.js +471 -0
- package/dist/verified-agent-profile.js +329 -0
- package/dist/verified-agent-registry-consumer.js +421 -0
- package/dist/verified-agent-registry.d.ts +36 -0
- package/dist/verified-agent-registry.js +826 -0
- package/dist/verified-agent-trust-score.js +335 -0
- package/dist/webhooks.js +834 -0
- package/package.json +72 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
3
|
+
import { hashObject } from "./receipt.js";
|
|
4
|
+
import {
|
|
5
|
+
createVerifiedAgentBadgeSvg,
|
|
6
|
+
verifyVerifiedAgentProfile
|
|
7
|
+
} from "./verified-agent-profile.js";
|
|
8
|
+
import {
|
|
9
|
+
createVerifiedAgentTrustScore,
|
|
10
|
+
VERIFIED_AGENT_TRUST_SCORE_HIGH_TRUST_MIN,
|
|
11
|
+
VERIFIED_AGENT_TRUST_SCORE_POLICY
|
|
12
|
+
} from "./verified-agent-trust-score.js";
|
|
13
|
+
|
|
14
|
+
export const VERIFIED_AGENT_REGISTRY_VERSION = "svs.verified-agent-registry.v1";
|
|
15
|
+
export const VERIFIED_AGENT_REGISTRY_BUILD_VERSION = "svs.verified-agent-registry-build.v1";
|
|
16
|
+
export const VERIFIED_AGENT_REGISTRY_VERIFICATION_VERSION = "svs.verified-agent-registry-verification.v1";
|
|
17
|
+
export const VERIFIED_AGENT_REGISTRY_TRUST_MANIFEST_VERSION = "svs.verified-agent-registry-trust-manifest.v1";
|
|
18
|
+
export const DEFAULT_VERIFIED_AGENT_REGISTRY_DIR = "./data/verified-agent-registry";
|
|
19
|
+
|
|
20
|
+
export async function buildVerifiedAgentRegistry({
|
|
21
|
+
profiles = [],
|
|
22
|
+
outputDir = DEFAULT_VERIFIED_AGENT_REGISTRY_DIR,
|
|
23
|
+
title = "SVS Verified Agent Registry",
|
|
24
|
+
generatedAt = new Date(),
|
|
25
|
+
staleAfterMs = null,
|
|
26
|
+
requireVerified = true,
|
|
27
|
+
now = generatedAt
|
|
28
|
+
} = {}) {
|
|
29
|
+
if (!Array.isArray(profiles) || profiles.length === 0) {
|
|
30
|
+
throw new Error("At least one verified agent profile is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await mkdir(outputDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
const generatedAtIso = generatedAt instanceof Date
|
|
36
|
+
? generatedAt.toISOString()
|
|
37
|
+
: new Date(generatedAt).toISOString();
|
|
38
|
+
const agents = [];
|
|
39
|
+
const profileByBotId = new Map();
|
|
40
|
+
|
|
41
|
+
for (const entry of profiles) {
|
|
42
|
+
const profile = await loadProfileEntry(entry);
|
|
43
|
+
const verification = verifyVerifiedAgentProfile(profile, {
|
|
44
|
+
requireVerified,
|
|
45
|
+
staleAfterMs,
|
|
46
|
+
now
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (requireVerified && verification.ok !== true) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Verified agent profile ${profile?.agent?.botId ?? entry?.profilePath ?? "unknown"} failed verification.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const botId = profile.agent.botId;
|
|
56
|
+
const safeBotId = toSafePathSegment(botId);
|
|
57
|
+
const profileText = `${JSON.stringify(profile, null, 2)}\n`;
|
|
58
|
+
const badgeText = entry?.badgeText ?? await loadOptionalText(entry?.badgePath) ?? createVerifiedAgentBadgeSvg(profile);
|
|
59
|
+
const profileRelativePath = `agents/${safeBotId}/profile.json`;
|
|
60
|
+
const badgeRelativePath = `agents/${safeBotId}/badge.svg`;
|
|
61
|
+
const pageRelativePath = `agents/${safeBotId}/index.html`;
|
|
62
|
+
const agentDir = join(outputDir, "agents", safeBotId);
|
|
63
|
+
const trustScore = createRegistryAgentTrustScore({
|
|
64
|
+
profile,
|
|
65
|
+
profileVerification: verification,
|
|
66
|
+
generatedAt: generatedAtIso
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await mkdir(agentDir, { recursive: true });
|
|
70
|
+
await Promise.all([
|
|
71
|
+
writeFile(join(outputDir, profileRelativePath), profileText),
|
|
72
|
+
writeFile(join(outputDir, badgeRelativePath), badgeText)
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const agent = {
|
|
76
|
+
botId,
|
|
77
|
+
name: profile.agent.name ?? botId,
|
|
78
|
+
status: profile.status.value,
|
|
79
|
+
badgeText: profile.display.badgeText,
|
|
80
|
+
profileHash: profile.profileHash,
|
|
81
|
+
certificationHash: profile.certification.certificationHash,
|
|
82
|
+
recordId: profile.certification.recordId,
|
|
83
|
+
qualityTarget: profile.qualityTarget,
|
|
84
|
+
proofs: profile.proofs,
|
|
85
|
+
trustScore,
|
|
86
|
+
generatedAt: profile.generatedAt,
|
|
87
|
+
profilePath: profileRelativePath,
|
|
88
|
+
badgePath: badgeRelativePath,
|
|
89
|
+
pagePath: pageRelativePath,
|
|
90
|
+
verification: {
|
|
91
|
+
ok: verification.ok,
|
|
92
|
+
status: verification.status,
|
|
93
|
+
failedCheckCount: verification.failedCheckCount,
|
|
94
|
+
checkedAt: verification.checkedAt
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
agents.push(agent);
|
|
99
|
+
profileByBotId.set(botId, profile);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const unsignedRegistry = {
|
|
103
|
+
version: VERIFIED_AGENT_REGISTRY_VERSION,
|
|
104
|
+
generatedAt: generatedAtIso,
|
|
105
|
+
title,
|
|
106
|
+
profileCount: agents.length,
|
|
107
|
+
agents
|
|
108
|
+
};
|
|
109
|
+
const registry = {
|
|
110
|
+
...unsignedRegistry,
|
|
111
|
+
registryHash: hashVerifiedAgentRegistry(unsignedRegistry)
|
|
112
|
+
};
|
|
113
|
+
const trustManifest = createVerifiedAgentRegistryTrustManifest({
|
|
114
|
+
title,
|
|
115
|
+
generatedAt: generatedAtIso,
|
|
116
|
+
registry
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
await Promise.all([
|
|
120
|
+
writeFile(join(outputDir, "registry.json"), `${JSON.stringify(registry, null, 2)}\n`),
|
|
121
|
+
writeFile(join(outputDir, "trust-manifest.json"), `${JSON.stringify(trustManifest, null, 2)}\n`),
|
|
122
|
+
writeFile(join(outputDir, "index.html"), renderRegistryIndex({ title, generatedAt: generatedAtIso, registry, trustManifest })),
|
|
123
|
+
...registry.agents.map((agent) => writeFile(
|
|
124
|
+
join(outputDir, agent.pagePath),
|
|
125
|
+
renderAgentPage({
|
|
126
|
+
title,
|
|
127
|
+
generatedAt: generatedAtIso,
|
|
128
|
+
agent,
|
|
129
|
+
profile: profileByBotId.get(agent.botId),
|
|
130
|
+
registryHash: registry.registryHash
|
|
131
|
+
})
|
|
132
|
+
))
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
version: VERIFIED_AGENT_REGISTRY_BUILD_VERSION,
|
|
137
|
+
ok: true,
|
|
138
|
+
status: "built",
|
|
139
|
+
outputDir,
|
|
140
|
+
registryPath: join(outputDir, "registry.json"),
|
|
141
|
+
trustManifestPath: join(outputDir, "trust-manifest.json"),
|
|
142
|
+
indexPath: join(outputDir, "index.html"),
|
|
143
|
+
registryHash: registry.registryHash,
|
|
144
|
+
trustManifestHash: trustManifest.trustManifestHash,
|
|
145
|
+
profileCount: agents.length,
|
|
146
|
+
agents,
|
|
147
|
+
files: [
|
|
148
|
+
"registry.json",
|
|
149
|
+
"trust-manifest.json",
|
|
150
|
+
"index.html",
|
|
151
|
+
...agents.flatMap((agent) => [
|
|
152
|
+
agent.profilePath,
|
|
153
|
+
agent.badgePath,
|
|
154
|
+
agent.pagePath
|
|
155
|
+
])
|
|
156
|
+
]
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function hashVerifiedAgentRegistry(registry) {
|
|
161
|
+
if (!registry || typeof registry !== "object") {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { registryHash: _registryHash, ...unsigned } = registry;
|
|
166
|
+
|
|
167
|
+
return hashObject(unsigned);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function hashVerifiedAgentRegistryTrustManifest(manifest) {
|
|
171
|
+
if (!manifest || typeof manifest !== "object") {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { trustManifestHash: _trustManifestHash, ...unsigned } = manifest;
|
|
176
|
+
|
|
177
|
+
return hashObject(unsigned);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function createVerifiedAgentRegistryTrustManifest({
|
|
181
|
+
title = "SVS Verified Agent Registry",
|
|
182
|
+
generatedAt = new Date(),
|
|
183
|
+
registry
|
|
184
|
+
} = {}) {
|
|
185
|
+
if (!registry || typeof registry !== "object") {
|
|
186
|
+
throw new Error("registry is required.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const generatedAtIso = generatedAt instanceof Date
|
|
190
|
+
? generatedAt.toISOString()
|
|
191
|
+
: new Date(generatedAt).toISOString();
|
|
192
|
+
const unsigned = {
|
|
193
|
+
version: VERIFIED_AGENT_REGISTRY_TRUST_MANIFEST_VERSION,
|
|
194
|
+
title,
|
|
195
|
+
generatedAt: generatedAtIso,
|
|
196
|
+
registry: {
|
|
197
|
+
version: registry.version ?? null,
|
|
198
|
+
title: registry.title ?? title,
|
|
199
|
+
profileCount: registry.profileCount ?? null,
|
|
200
|
+
registryHash: registry.registryHash ?? null,
|
|
201
|
+
registryPath: "registry.json",
|
|
202
|
+
indexPath: "index.html"
|
|
203
|
+
},
|
|
204
|
+
hostedPaths: {
|
|
205
|
+
registryJson: "registry.json",
|
|
206
|
+
trustManifestJson: "trust-manifest.json",
|
|
207
|
+
registryIndex: "index.html"
|
|
208
|
+
},
|
|
209
|
+
verifierInstructions: {
|
|
210
|
+
cli: "npm run verify:verified-agent-registry-url -- --url REGISTRY_JSON_URL --expected-hash REGISTRY_HASH",
|
|
211
|
+
localCli: "npm run verify:verified-agent-registry -- --file ./data/verified-agent-registry/registry.json",
|
|
212
|
+
sdkImport: "import { verifyVerifiedAgentRegistryUrl } from '@svsprotocol/solana/verified-agent-registry';",
|
|
213
|
+
sdkCall: "await verifyVerifiedAgentRegistryUrl({ registryUrl, expectedRegistryHash });",
|
|
214
|
+
requiredChecks: [
|
|
215
|
+
"registry hash matches pinned expected hash",
|
|
216
|
+
"linked profile hashes match registry entries",
|
|
217
|
+
"linked profiles verify as current SVS verified profiles",
|
|
218
|
+
"agent trust-score summaries are registry-visible and hash-pinned",
|
|
219
|
+
"profile, registry, and trust manifest exclude secret material"
|
|
220
|
+
]
|
|
221
|
+
},
|
|
222
|
+
trustScorePolicy: {
|
|
223
|
+
version: VERIFIED_AGENT_TRUST_SCORE_POLICY.version,
|
|
224
|
+
scoreVersion: VERIFIED_AGENT_TRUST_SCORE_POLICY.scoreVersion,
|
|
225
|
+
highTrustMinimumScore: VERIFIED_AGENT_TRUST_SCORE_POLICY.highTrustMinimumScore,
|
|
226
|
+
maxScore: VERIFIED_AGENT_TRUST_SCORE_POLICY.maxScore,
|
|
227
|
+
highTrustRequiresEvidenceComplete: VERIFIED_AGENT_TRUST_SCORE_POLICY.highTrustRequiresEvidenceComplete,
|
|
228
|
+
requiredSignalIds: VERIFIED_AGENT_TRUST_SCORE_POLICY.requiredSignalIds,
|
|
229
|
+
optionalSignalIds: VERIFIED_AGENT_TRUST_SCORE_POLICY.optionalSignalIds
|
|
230
|
+
},
|
|
231
|
+
trustStates: {
|
|
232
|
+
verified: "Registry hash, profile hashes, certification hashes, and linked profiles all verify.",
|
|
233
|
+
stale: "Profile or proof freshness is outside the relying party window.",
|
|
234
|
+
failed: "At least one hash, profile, certification, or linked proof check failed."
|
|
235
|
+
},
|
|
236
|
+
agents: Array.isArray(registry.agents)
|
|
237
|
+
? registry.agents.map((agent) => ({
|
|
238
|
+
botId: agent.botId,
|
|
239
|
+
name: agent.name,
|
|
240
|
+
status: agent.status,
|
|
241
|
+
profileHash: agent.profileHash,
|
|
242
|
+
certificationHash: agent.certificationHash,
|
|
243
|
+
recordId: agent.recordId,
|
|
244
|
+
profilePath: agent.profilePath,
|
|
245
|
+
badgePath: agent.badgePath,
|
|
246
|
+
pagePath: agent.pagePath,
|
|
247
|
+
trustScore: agent.trustScore
|
|
248
|
+
? {
|
|
249
|
+
status: agent.trustScore.status,
|
|
250
|
+
score: agent.trustScore.score,
|
|
251
|
+
maxScore: agent.trustScore.maxScore,
|
|
252
|
+
evidenceComplete: agent.trustScore.evidenceComplete,
|
|
253
|
+
blockingSignalCount: agent.trustScore.blockingSignalCount,
|
|
254
|
+
trustScoreHash: agent.trustScore.trustScoreHash
|
|
255
|
+
}
|
|
256
|
+
: null,
|
|
257
|
+
proofFreshness: {
|
|
258
|
+
generatedAt: agent.generatedAt ?? null,
|
|
259
|
+
verificationCheckedAt: agent.verification?.checkedAt ?? null
|
|
260
|
+
}
|
|
261
|
+
}))
|
|
262
|
+
: [],
|
|
263
|
+
reportSafety: {
|
|
264
|
+
secretsIncluded: false,
|
|
265
|
+
redactedFields: [
|
|
266
|
+
"SVS_BOT_API_KEY",
|
|
267
|
+
"SVS_BOT_REQUEST_SIGNING_SECRET",
|
|
268
|
+
"SVS_BOT_PENDING_REQUEST_SIGNING_SECRET",
|
|
269
|
+
"apiKey",
|
|
270
|
+
"requestSigningSecret",
|
|
271
|
+
"pendingRequestSigningSecret",
|
|
272
|
+
"webhookSecret"
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
...unsigned,
|
|
279
|
+
trustManifestHash: hashVerifiedAgentRegistryTrustManifest(unsigned)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function verifyVerifiedAgentRegistry({
|
|
284
|
+
registry = null,
|
|
285
|
+
registryPath = null,
|
|
286
|
+
baseDir = null,
|
|
287
|
+
requireVerified = true,
|
|
288
|
+
staleAfterMs = null,
|
|
289
|
+
now = new Date()
|
|
290
|
+
} = {}) {
|
|
291
|
+
const loadedRegistry = registry ?? await loadRegistryFromPath(registryPath);
|
|
292
|
+
const effectiveBaseDir = baseDir ?? (registryPath ? dirname(registryPath) : process.cwd());
|
|
293
|
+
const checks = [
|
|
294
|
+
check(
|
|
295
|
+
"Verified agent registry version is supported",
|
|
296
|
+
loadedRegistry?.version === VERIFIED_AGENT_REGISTRY_VERSION,
|
|
297
|
+
loadedRegistry?.version ?? "missing"
|
|
298
|
+
),
|
|
299
|
+
check(
|
|
300
|
+
"Verified agent registry hash is valid",
|
|
301
|
+
loadedRegistry?.registryHash === hashVerifiedAgentRegistry(loadedRegistry),
|
|
302
|
+
`declared=${loadedRegistry?.registryHash ?? "missing"} computed=${hashVerifiedAgentRegistry(loadedRegistry) ?? "missing"}`
|
|
303
|
+
),
|
|
304
|
+
check(
|
|
305
|
+
"Verified agent registry profile count matches entries",
|
|
306
|
+
loadedRegistry?.profileCount === loadedRegistry?.agents?.length,
|
|
307
|
+
`declared=${loadedRegistry?.profileCount ?? "missing"} actual=${loadedRegistry?.agents?.length ?? "missing"}`
|
|
308
|
+
),
|
|
309
|
+
check(
|
|
310
|
+
"Verified agent registry has at least one agent",
|
|
311
|
+
Array.isArray(loadedRegistry?.agents) && loadedRegistry.agents.length > 0,
|
|
312
|
+
`count=${loadedRegistry?.agents?.length ?? "missing"}`
|
|
313
|
+
)
|
|
314
|
+
];
|
|
315
|
+
const trustManifestStatus = await readAndVerifyRegistryTrustManifest({
|
|
316
|
+
baseDir: effectiveBaseDir,
|
|
317
|
+
registry: loadedRegistry
|
|
318
|
+
});
|
|
319
|
+
checks.push(...trustManifestStatus.checks);
|
|
320
|
+
const agents = [];
|
|
321
|
+
|
|
322
|
+
if (Array.isArray(loadedRegistry?.agents)) {
|
|
323
|
+
for (const [index, agent] of loadedRegistry.agents.entries()) {
|
|
324
|
+
const agentChecks = [];
|
|
325
|
+
let profile = null;
|
|
326
|
+
let profileVerification = null;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const profilePath = resolveRegistryRelativePath(effectiveBaseDir, agent.profilePath);
|
|
330
|
+
|
|
331
|
+
profile = JSON.parse(await readFile(profilePath, "utf8"));
|
|
332
|
+
profileVerification = verifyVerifiedAgentProfile(profile, {
|
|
333
|
+
requireVerified,
|
|
334
|
+
expectedBotId: agent.botId,
|
|
335
|
+
staleAfterMs,
|
|
336
|
+
now
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
agentChecks.push(check(
|
|
340
|
+
"Verified agent registry linked profile can be loaded",
|
|
341
|
+
false,
|
|
342
|
+
error.message
|
|
343
|
+
));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (profile) {
|
|
347
|
+
agentChecks.push(
|
|
348
|
+
check(
|
|
349
|
+
"Verified agent registry linked profile verifies",
|
|
350
|
+
profileVerification?.ok === true,
|
|
351
|
+
`status=${profileVerification?.status ?? "missing"} failed=${profileVerification?.failedCheckCount ?? "missing"}`
|
|
352
|
+
),
|
|
353
|
+
check(
|
|
354
|
+
"Verified agent registry bot id matches linked profile",
|
|
355
|
+
agent.botId === profile.agent?.botId,
|
|
356
|
+
`registry=${agent.botId ?? "missing"} profile=${profile.agent?.botId ?? "missing"}`
|
|
357
|
+
),
|
|
358
|
+
check(
|
|
359
|
+
"Verified agent registry profile hash matches linked profile",
|
|
360
|
+
agent.profileHash === profile.profileHash,
|
|
361
|
+
`registry=${agent.profileHash ?? "missing"} profile=${profile.profileHash ?? "missing"}`
|
|
362
|
+
),
|
|
363
|
+
check(
|
|
364
|
+
"Verified agent registry status matches linked profile",
|
|
365
|
+
agent.status === profile.status?.value,
|
|
366
|
+
`registry=${agent.status ?? "missing"} profile=${profile.status?.value ?? "missing"}`
|
|
367
|
+
),
|
|
368
|
+
check(
|
|
369
|
+
"Verified agent registry certification hash matches linked profile",
|
|
370
|
+
agent.certificationHash === profile.certification?.certificationHash,
|
|
371
|
+
`registry=${agent.certificationHash ?? "missing"} profile=${profile.certification?.certificationHash ?? "missing"}`
|
|
372
|
+
),
|
|
373
|
+
check(
|
|
374
|
+
"Verified agent registry verification summary matches linked profile",
|
|
375
|
+
agent.verification?.ok === profileVerification?.ok &&
|
|
376
|
+
agent.verification?.status === profileVerification?.status,
|
|
377
|
+
`registry=${agent.verification?.status ?? "missing"} profile=${profileVerification?.status ?? "missing"}`
|
|
378
|
+
)
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (profileVerification?.failedChecks?.length) {
|
|
382
|
+
agentChecks.push(...profileVerification.failedChecks.map((item) => ({
|
|
383
|
+
name: `Profile check: ${item.name}`,
|
|
384
|
+
ok: false,
|
|
385
|
+
detail: item.detail
|
|
386
|
+
})));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
agents.push({
|
|
391
|
+
index,
|
|
392
|
+
botId: agent?.botId ?? null,
|
|
393
|
+
profilePath: agent?.profilePath ?? null,
|
|
394
|
+
ok: agentChecks.every((item) => item.ok),
|
|
395
|
+
failedCheckCount: agentChecks.filter((item) => !item.ok).length,
|
|
396
|
+
failedChecks: agentChecks.filter((item) => !item.ok),
|
|
397
|
+
checks: agentChecks,
|
|
398
|
+
profileHash: profile?.profileHash ?? null
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
checks.push(check(
|
|
404
|
+
"Verified agent registry linked profiles verify",
|
|
405
|
+
agents.every((agent) => agent.ok),
|
|
406
|
+
`failed=${agents.filter((agent) => !agent.ok).length}`
|
|
407
|
+
));
|
|
408
|
+
|
|
409
|
+
const failedChecks = [
|
|
410
|
+
...checks.filter((item) => !item.ok),
|
|
411
|
+
...agents.flatMap((agent) => agent.failedChecks.map((item) => ({
|
|
412
|
+
...item,
|
|
413
|
+
agentIndex: agent.index,
|
|
414
|
+
botId: agent.botId
|
|
415
|
+
})))
|
|
416
|
+
];
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
version: VERIFIED_AGENT_REGISTRY_VERIFICATION_VERSION,
|
|
420
|
+
ok: failedChecks.length === 0,
|
|
421
|
+
status: failedChecks.length === 0 ? "verified" : "failed",
|
|
422
|
+
checkedAt: now instanceof Date ? now.toISOString() : new Date(now).toISOString(),
|
|
423
|
+
registryPath: registryPath ?? null,
|
|
424
|
+
registryHash: loadedRegistry?.registryHash ?? null,
|
|
425
|
+
computedRegistryHash: hashVerifiedAgentRegistry(loadedRegistry),
|
|
426
|
+
trustManifest: trustManifestStatus.summary,
|
|
427
|
+
profileCount: loadedRegistry?.profileCount ?? null,
|
|
428
|
+
verifiedAgentCount: agents.filter((agent) => agent.ok).length,
|
|
429
|
+
failedCheckCount: failedChecks.length,
|
|
430
|
+
failedChecks,
|
|
431
|
+
checks,
|
|
432
|
+
agents
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function loadRegistryFromPath(registryPath) {
|
|
437
|
+
if (!registryPath) {
|
|
438
|
+
throw new Error("registryPath is required.");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return JSON.parse(await readFile(registryPath, "utf8"));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function readAndVerifyRegistryTrustManifest({
|
|
445
|
+
baseDir,
|
|
446
|
+
registry
|
|
447
|
+
}) {
|
|
448
|
+
const manifestPath = resolveRegistryRelativePath(baseDir, "trust-manifest.json");
|
|
449
|
+
const checks = [];
|
|
450
|
+
let manifest = null;
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
454
|
+
} catch (error) {
|
|
455
|
+
checks.push(check(
|
|
456
|
+
"Verified agent registry trust manifest can be loaded",
|
|
457
|
+
false,
|
|
458
|
+
error.message
|
|
459
|
+
));
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
summary: {
|
|
463
|
+
found: false,
|
|
464
|
+
path: manifestPath,
|
|
465
|
+
trustManifestHash: null,
|
|
466
|
+
computedTrustManifestHash: null,
|
|
467
|
+
registryHash: null
|
|
468
|
+
},
|
|
469
|
+
checks
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const computedTrustManifestHash = hashVerifiedAgentRegistryTrustManifest(manifest);
|
|
474
|
+
|
|
475
|
+
checks.push(
|
|
476
|
+
check(
|
|
477
|
+
"Verified agent registry trust manifest version is supported",
|
|
478
|
+
manifest.version === VERIFIED_AGENT_REGISTRY_TRUST_MANIFEST_VERSION,
|
|
479
|
+
manifest.version ?? "missing"
|
|
480
|
+
),
|
|
481
|
+
check(
|
|
482
|
+
"Verified agent registry trust manifest hash is valid",
|
|
483
|
+
manifest.trustManifestHash === computedTrustManifestHash,
|
|
484
|
+
`declared=${manifest.trustManifestHash ?? "missing"} computed=${computedTrustManifestHash ?? "missing"}`
|
|
485
|
+
),
|
|
486
|
+
check(
|
|
487
|
+
"Verified agent registry trust manifest pins registry hash",
|
|
488
|
+
manifest.registry?.registryHash === registry?.registryHash,
|
|
489
|
+
`manifest=${manifest.registry?.registryHash ?? "missing"} registry=${registry?.registryHash ?? "missing"}`
|
|
490
|
+
),
|
|
491
|
+
check(
|
|
492
|
+
"Verified agent registry trust manifest agent count matches registry",
|
|
493
|
+
manifest.registry?.profileCount === registry?.profileCount &&
|
|
494
|
+
Array.isArray(manifest.agents) &&
|
|
495
|
+
manifest.agents.length === registry?.agents?.length,
|
|
496
|
+
`manifest=${manifest.registry?.profileCount ?? "missing"}/${manifest.agents?.length ?? "missing"} registry=${registry?.profileCount ?? "missing"}/${registry?.agents?.length ?? "missing"}`
|
|
497
|
+
),
|
|
498
|
+
check(
|
|
499
|
+
"Verified agent registry trust manifest has verifier instructions",
|
|
500
|
+
typeof manifest.verifierInstructions?.cli === "string" &&
|
|
501
|
+
manifest.verifierInstructions.cli.includes("verify:verified-agent-registry-url") &&
|
|
502
|
+
typeof manifest.verifierInstructions?.sdkCall === "string" &&
|
|
503
|
+
manifest.verifierInstructions.sdkCall.includes("verifyVerifiedAgentRegistryUrl"),
|
|
504
|
+
`cli=${manifest.verifierInstructions?.cli ?? "missing"}`
|
|
505
|
+
),
|
|
506
|
+
check(
|
|
507
|
+
"Verified agent registry trust manifest excludes secrets",
|
|
508
|
+
manifest.reportSafety?.secretsIncluded === false && !containsSecretMaterial(manifest),
|
|
509
|
+
`secretsIncluded=${manifest.reportSafety?.secretsIncluded ?? "missing"}`
|
|
510
|
+
)
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
return {
|
|
514
|
+
summary: {
|
|
515
|
+
found: true,
|
|
516
|
+
path: manifestPath,
|
|
517
|
+
trustManifestHash: manifest.trustManifestHash ?? null,
|
|
518
|
+
computedTrustManifestHash,
|
|
519
|
+
registryHash: manifest.registry?.registryHash ?? null
|
|
520
|
+
},
|
|
521
|
+
checks
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function resolveRegistryRelativePath(baseDir, relativePath) {
|
|
526
|
+
if (!relativePath || typeof relativePath !== "string") {
|
|
527
|
+
throw new Error("registry agent profilePath is required.");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (isAbsolute(relativePath)) {
|
|
531
|
+
throw new Error("registry agent profilePath must be relative.");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const resolvedBase = resolve(baseDir);
|
|
535
|
+
const resolvedPath = resolve(resolvedBase, normalize(relativePath));
|
|
536
|
+
|
|
537
|
+
if (resolvedPath !== resolvedBase && !resolvedPath.startsWith(`${resolvedBase}/`)) {
|
|
538
|
+
throw new Error(`registry agent profilePath escapes registry directory: ${relativePath}`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return resolvedPath;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function loadProfileEntry(entry) {
|
|
545
|
+
if (entry?.profile) {
|
|
546
|
+
return entry.profile;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!entry?.profilePath) {
|
|
550
|
+
throw new Error("profilePath is required.");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return JSON.parse(await readFile(entry.profilePath, "utf8"));
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function loadOptionalText(path) {
|
|
557
|
+
if (!path) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return readFile(path, "utf8");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function createRegistryAgentTrustScore({
|
|
565
|
+
profile,
|
|
566
|
+
profileVerification,
|
|
567
|
+
generatedAt
|
|
568
|
+
} = {}) {
|
|
569
|
+
const profileProofs = profile?.proofs ?? {};
|
|
570
|
+
const actionProofOk = profileProofs.actionRecordVerificationOk === true &&
|
|
571
|
+
profileProofs.portableSetVerified === true;
|
|
572
|
+
|
|
573
|
+
return createVerifiedAgentTrustScore({
|
|
574
|
+
profile,
|
|
575
|
+
profileVerification,
|
|
576
|
+
registryVerification: {
|
|
577
|
+
ok: profileVerification?.ok === true,
|
|
578
|
+
status: profileVerification?.ok === true ? "verified" : "failed",
|
|
579
|
+
botId: profile?.agent?.botId ?? profileVerification?.botId ?? null
|
|
580
|
+
},
|
|
581
|
+
actionProductionProofVerification: actionProofOk
|
|
582
|
+
? {
|
|
583
|
+
ok: true,
|
|
584
|
+
recordId: profile?.certification?.recordId ?? null,
|
|
585
|
+
evidenceHash: profileProofs.verificationSetHash ?? profile?.certification?.evidenceHash ?? null
|
|
586
|
+
}
|
|
587
|
+
: null,
|
|
588
|
+
generatedAt
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function renderRegistryIndex({ title, generatedAt, registry, trustManifest }) {
|
|
593
|
+
const cards = registry.agents.map((agent) => `<article class="agent-card">
|
|
594
|
+
<img src="${escapeHtml(agent.badgePath)}" alt="${escapeHtml(agent.badgeText)} badge" />
|
|
595
|
+
<div>
|
|
596
|
+
<h2>${escapeHtml(agent.name)}</h2>
|
|
597
|
+
<p class="bot-id">${escapeHtml(agent.botId)}</p>
|
|
598
|
+
<dl>
|
|
599
|
+
<div><dt>Status</dt><dd>${escapeHtml(agent.status)}</dd></div>
|
|
600
|
+
<div><dt>Quality</dt><dd>${escapeHtml(formatQuality(agent.qualityTarget))}</dd></div>
|
|
601
|
+
<div><dt>Trust score</dt><dd>${renderTrustScoreInline(agent.trustScore)}</dd></div>
|
|
602
|
+
<div><dt>Profile hash</dt><dd><code>${escapeHtml(shortHash(agent.profileHash))}</code></dd></div>
|
|
603
|
+
<div><dt>Certification</dt><dd><code>${escapeHtml(shortHash(agent.certificationHash))}</code></dd></div>
|
|
604
|
+
</dl>
|
|
605
|
+
<nav>
|
|
606
|
+
<a href="${escapeHtml(agent.pagePath)}">Open agent page</a>
|
|
607
|
+
<a href="${escapeHtml(agent.profilePath)}">Profile JSON</a>
|
|
608
|
+
</nav>
|
|
609
|
+
</div>
|
|
610
|
+
</article>`).join("\n");
|
|
611
|
+
|
|
612
|
+
return renderHtmlDocument({
|
|
613
|
+
title,
|
|
614
|
+
body: `<main class="shell">
|
|
615
|
+
<header class="hero">
|
|
616
|
+
<p class="eyebrow">SVS Agent Verification</p>
|
|
617
|
+
<h1>${escapeHtml(title)}</h1>
|
|
618
|
+
<p>Static registry of verified Solana agents. Each entry links to profile JSON, badge, certification hashes, and proof summary data for protocols and auditors.</p>
|
|
619
|
+
<dl class="summary">
|
|
620
|
+
<div><dt>Agents</dt><dd>${registry.profileCount}</dd></div>
|
|
621
|
+
<div><dt>Generated</dt><dd>${escapeHtml(generatedAt)}</dd></div>
|
|
622
|
+
<div><dt>Registry hash</dt><dd><code>${escapeHtml(registry.registryHash)}</code></dd></div>
|
|
623
|
+
<div><dt>Trust manifest</dt><dd><code>${escapeHtml(shortHash(trustManifest.trustManifestHash))}</code></dd></div>
|
|
624
|
+
</dl>
|
|
625
|
+
</header>
|
|
626
|
+
<section class="trust-panel">
|
|
627
|
+
<h2>Verifier Instructions</h2>
|
|
628
|
+
<p>Pin the registry hash before trusting badges or profile pages. A badge image alone is not proof.</p>
|
|
629
|
+
<pre>${escapeHtml(trustManifest.verifierInstructions.cli)}</pre>
|
|
630
|
+
<nav>
|
|
631
|
+
<a href="registry.json">Registry JSON</a>
|
|
632
|
+
<a href="trust-manifest.json">Trust manifest JSON</a>
|
|
633
|
+
</nav>
|
|
634
|
+
</section>
|
|
635
|
+
<section class="trust-panel">
|
|
636
|
+
<h2>Trust Checklist</h2>
|
|
637
|
+
<ul>
|
|
638
|
+
${trustManifest.verifierInstructions.requiredChecks.map((item) => ` <li>${escapeHtml(item)}</li>`).join("\n")}
|
|
639
|
+
</ul>
|
|
640
|
+
<p class="score-note">High trust requires ${VERIFIED_AGENT_TRUST_SCORE_HIGH_TRUST_MIN}/${VERIFIED_AGENT_TRUST_SCORE_POLICY.maxScore}, complete required evidence, and zero blocking signals. Registry pages show the mechanical score so protocols can compare agents by evidence instead of badge text alone.</p>
|
|
641
|
+
</section>
|
|
642
|
+
<section class="agent-grid">
|
|
643
|
+
${cards}
|
|
644
|
+
</section>
|
|
645
|
+
</main>`
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function renderAgentPage({ title, generatedAt, agent, profile, registryHash }) {
|
|
650
|
+
const proofRows = Object.entries(agent.proofs ?? {}).map(([key, value]) => `<tr>
|
|
651
|
+
<th>${escapeHtml(key)}</th>
|
|
652
|
+
<td>${escapeHtml(String(value))}</td>
|
|
653
|
+
</tr>`).join("\n");
|
|
654
|
+
const profileJson = JSON.stringify(profile, null, 2);
|
|
655
|
+
const trustScoreRows = (agent.trustScore?.signals ?? []).map((signal) => `<tr>
|
|
656
|
+
<th>${escapeHtml(signal.label)}</th>
|
|
657
|
+
<td>
|
|
658
|
+
<strong>${signal.ok ? "passed" : signal.required ? "blocking" : "missing optional"}</strong>
|
|
659
|
+
<span>${escapeHtml(signal.earned)}/${escapeHtml(signal.weight)} pts</span>
|
|
660
|
+
<p>${escapeHtml(signal.detail)}</p>
|
|
661
|
+
</td>
|
|
662
|
+
</tr>`).join("\n");
|
|
663
|
+
|
|
664
|
+
return renderHtmlDocument({
|
|
665
|
+
title: `${agent.name} - ${title}`,
|
|
666
|
+
body: `<main class="shell">
|
|
667
|
+
<header class="hero">
|
|
668
|
+
<p class="eyebrow">SVS Verified Agent</p>
|
|
669
|
+
<h1>${escapeHtml(agent.name)}</h1>
|
|
670
|
+
<p class="bot-id">${escapeHtml(agent.botId)}</p>
|
|
671
|
+
<img src="badge.svg" alt="${escapeHtml(agent.badgeText)} badge" />
|
|
672
|
+
<dl class="summary">
|
|
673
|
+
<div><dt>Status</dt><dd>${escapeHtml(agent.status)}</dd></div>
|
|
674
|
+
<div><dt>Quality</dt><dd>${escapeHtml(formatQuality(agent.qualityTarget))}</dd></div>
|
|
675
|
+
<div><dt>Trust score</dt><dd>${renderTrustScoreInline(agent.trustScore)}</dd></div>
|
|
676
|
+
<div><dt>Record</dt><dd><code>${escapeHtml(agent.recordId ?? "missing")}</code></dd></div>
|
|
677
|
+
<div><dt>Generated</dt><dd>${escapeHtml(generatedAt)}</dd></div>
|
|
678
|
+
</dl>
|
|
679
|
+
</header>
|
|
680
|
+
<section>
|
|
681
|
+
<h2>Verifier Instructions</h2>
|
|
682
|
+
<p>Verify the registry hash and linked profile before trusting this agent page or badge.</p>
|
|
683
|
+
<pre>npm run verify:verified-agent-registry-url -- --url REGISTRY_JSON_URL --expected-hash ${escapeHtml(registryHash ?? "REGISTRY_HASH")}</pre>
|
|
684
|
+
<p><a href="../../trust-manifest.json">Open trust-manifest.json</a></p>
|
|
685
|
+
</section>
|
|
686
|
+
<section>
|
|
687
|
+
<h2>Trust Score Explanation</h2>
|
|
688
|
+
<p>High trust requires ${VERIFIED_AGENT_TRUST_SCORE_HIGH_TRUST_MIN}/${VERIFIED_AGENT_TRUST_SCORE_POLICY.maxScore}, complete required evidence, and zero blocking signals. This score is deterministic and hash-pinned; it explains evidence quality but does not replace verifier checks.</p>
|
|
689
|
+
<dl class="summary">
|
|
690
|
+
<div><dt>Status</dt><dd>${escapeHtml(agent.trustScore?.status ?? "missing")}</dd></div>
|
|
691
|
+
<div><dt>Score</dt><dd>${escapeHtml(agent.trustScore?.score ?? "missing")}/${escapeHtml(agent.trustScore?.maxScore ?? VERIFIED_AGENT_TRUST_SCORE_POLICY.maxScore)}</dd></div>
|
|
692
|
+
<div><dt>Required evidence</dt><dd>${agent.trustScore?.evidenceComplete === true ? "complete" : "incomplete"}</dd></div>
|
|
693
|
+
<div><dt>Score hash</dt><dd><code>${escapeHtml(shortHash(agent.trustScore?.trustScoreHash))}</code></dd></div>
|
|
694
|
+
</dl>
|
|
695
|
+
<table class="trust-score-table">
|
|
696
|
+
<tbody>
|
|
697
|
+
${trustScoreRows}
|
|
698
|
+
</tbody>
|
|
699
|
+
</table>
|
|
700
|
+
<p class="score-note">${escapeHtml(agent.trustScore?.nextAction?.message ?? "No trust score next action available.")}</p>
|
|
701
|
+
</section>
|
|
702
|
+
<section>
|
|
703
|
+
<h2>Proof Summary</h2>
|
|
704
|
+
<table>
|
|
705
|
+
<tbody>
|
|
706
|
+
${proofRows}
|
|
707
|
+
</tbody>
|
|
708
|
+
</table>
|
|
709
|
+
</section>
|
|
710
|
+
<section>
|
|
711
|
+
<h2>Hashes</h2>
|
|
712
|
+
<dl class="summary">
|
|
713
|
+
<div><dt>Profile hash</dt><dd><code>${escapeHtml(agent.profileHash)}</code></dd></div>
|
|
714
|
+
<div><dt>Certification hash</dt><dd><code>${escapeHtml(agent.certificationHash ?? "missing")}</code></dd></div>
|
|
715
|
+
</dl>
|
|
716
|
+
</section>
|
|
717
|
+
<section>
|
|
718
|
+
<h2>Profile JSON</h2>
|
|
719
|
+
<p><a href="profile.json">Open profile.json</a></p>
|
|
720
|
+
<pre>${escapeHtml(profileJson)}</pre>
|
|
721
|
+
</section>
|
|
722
|
+
<p><a href="../../index.html">Back to registry</a></p>
|
|
723
|
+
</main>`
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function renderHtmlDocument({ title, body }) {
|
|
728
|
+
return `<!doctype html>
|
|
729
|
+
<html lang="en">
|
|
730
|
+
<head>
|
|
731
|
+
<meta charset="utf-8" />
|
|
732
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
733
|
+
<title>${escapeHtml(title)}</title>
|
|
734
|
+
<style>
|
|
735
|
+
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #08111f; color: #edf7ff; }
|
|
736
|
+
body { margin: 0; background: #08111f; }
|
|
737
|
+
.shell { width: min(1120px, calc(100vw - 32px)); margin: 0 auto; padding: 40px 0; }
|
|
738
|
+
.hero { border-bottom: 1px solid rgba(255,255,255,0.14); padding-bottom: 24px; margin-bottom: 24px; }
|
|
739
|
+
.eyebrow { color: #14f195; font-weight: 800; text-transform: uppercase; font-size: 12px; }
|
|
740
|
+
h1 { font-size: 34px; line-height: 1.1; margin: 0 0 12px; letter-spacing: 0; }
|
|
741
|
+
h2 { font-size: 18px; margin-top: 28px; }
|
|
742
|
+
a { color: #14f195; font-weight: 700; }
|
|
743
|
+
code, pre { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; }
|
|
744
|
+
code { padding: 2px 5px; }
|
|
745
|
+
pre { overflow: auto; padding: 16px; line-height: 1.45; }
|
|
746
|
+
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin: 20px 0 0; }
|
|
747
|
+
.summary div, .agent-card { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; padding: 14px; }
|
|
748
|
+
dt { color: #9fb2c7; font-size: 12px; font-weight: 800; text-transform: uppercase; }
|
|
749
|
+
dd { margin: 4px 0 0; overflow-wrap: anywhere; }
|
|
750
|
+
.agent-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
|
|
751
|
+
.agent-card img { margin-bottom: 14px; }
|
|
752
|
+
.agent-card h2 { margin: 0; }
|
|
753
|
+
.bot-id { color: #9fb2c7; overflow-wrap: anywhere; }
|
|
754
|
+
.agent-card nav { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }
|
|
755
|
+
.score-note { color: #b9c7d8; line-height: 1.55; }
|
|
756
|
+
table { width: 100%; border-collapse: collapse; background: rgba(255,255,255,0.04); border-radius: 8px; overflow: hidden; }
|
|
757
|
+
th, td { text-align: left; border-bottom: 1px solid rgba(255,255,255,0.1); padding: 10px; overflow-wrap: anywhere; }
|
|
758
|
+
th { color: #9fb2c7; width: 280px; }
|
|
759
|
+
.trust-score-table td strong { display: inline-block; margin-right: 10px; color: #14f195; }
|
|
760
|
+
.trust-score-table td span { color: #9fb2c7; font-size: 13px; font-weight: 800; }
|
|
761
|
+
.trust-score-table td p { margin: 6px 0 0; color: #b9c7d8; line-height: 1.45; }
|
|
762
|
+
</style>
|
|
763
|
+
</head>
|
|
764
|
+
<body>
|
|
765
|
+
${body}
|
|
766
|
+
</body>
|
|
767
|
+
</html>
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function renderTrustScoreInline(trustScore) {
|
|
772
|
+
if (!trustScore) {
|
|
773
|
+
return "missing";
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return `${escapeHtml(trustScore.score)}/${escapeHtml(trustScore.maxScore)} ${escapeHtml(trustScore.status)} <code>${escapeHtml(shortHash(trustScore.trustScoreHash))}</code>`;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function formatQuality(qualityTarget) {
|
|
780
|
+
if (!qualityTarget) {
|
|
781
|
+
return "missing";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return `${qualityTarget.score ?? "?"}/${qualityTarget.targetScore ?? "?"} ${qualityTarget.status ?? "unknown"}`;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function shortHash(hash) {
|
|
788
|
+
if (!hash) {
|
|
789
|
+
return "missing";
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return hash.length > 18 ? `${hash.slice(0, 10)}...${hash.slice(-8)}` : hash;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function containsSecretMaterial(value) {
|
|
796
|
+
const text = JSON.stringify(value ?? {});
|
|
797
|
+
|
|
798
|
+
return /(svs_live|svs_req_live|BEGIN PRIVATE KEY|secret-value)/i.test(text);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function toSafePathSegment(value) {
|
|
802
|
+
const safe = String(value ?? "agent")
|
|
803
|
+
.trim()
|
|
804
|
+
.toLowerCase()
|
|
805
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
806
|
+
.replace(/^-+|-+$/g, "");
|
|
807
|
+
|
|
808
|
+
return safe || `agent-${Date.now()}`;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function escapeHtml(value) {
|
|
812
|
+
return String(value ?? "")
|
|
813
|
+
.replaceAll("&", "&")
|
|
814
|
+
.replaceAll("<", "<")
|
|
815
|
+
.replaceAll(">", ">")
|
|
816
|
+
.replaceAll("\"", """)
|
|
817
|
+
.replaceAll("'", "'");
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function check(name, ok, detail) {
|
|
821
|
+
return {
|
|
822
|
+
name,
|
|
823
|
+
ok: Boolean(ok),
|
|
824
|
+
detail
|
|
825
|
+
};
|
|
826
|
+
}
|