opmsec 0.1.0 → 0.1.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.
Files changed (152) hide show
  1. package/.env.example +23 -13
  2. package/.husky/pre-commit +1 -0
  3. package/README.md +256 -173
  4. package/bun.lock +4 -4
  5. package/docs/architecture/agents.mdx +77 -0
  6. package/docs/architecture/benchmarks.mdx +65 -0
  7. package/docs/architecture/overview.mdx +58 -0
  8. package/docs/architecture/scanner.mdx +53 -0
  9. package/docs/cli/audit.mdx +35 -0
  10. package/docs/cli/check.mdx +44 -0
  11. package/docs/cli/fix.mdx +49 -0
  12. package/docs/cli/info.mdx +44 -0
  13. package/docs/cli/install.mdx +71 -0
  14. package/docs/cli/push.mdx +99 -0
  15. package/docs/cli/register-agent.mdx +80 -0
  16. package/docs/cli/view.mdx +52 -0
  17. package/docs/concepts/multi-agent-consensus.mdx +58 -0
  18. package/docs/concepts/on-chain-registry.mdx +74 -0
  19. package/docs/concepts/security-model.mdx +76 -0
  20. package/docs/concepts/zk-agent-verification.mdx +82 -0
  21. package/docs/configuration.mdx +82 -0
  22. package/docs/contract/deployment.mdx +57 -0
  23. package/docs/contract/events.mdx +115 -0
  24. package/docs/contract/functions.mdx +220 -0
  25. package/docs/contract/overview.mdx +58 -0
  26. package/docs/favicon.svg +5 -0
  27. package/docs/introduction.mdx +43 -0
  28. package/docs/logo/dark.svg +5 -0
  29. package/docs/logo/light.svg +5 -0
  30. package/docs/mint.json +106 -0
  31. package/docs/quickstart.mdx +133 -0
  32. package/package.json +7 -6
  33. package/packages/cli/src/commands/author-view.tsx +9 -1
  34. package/packages/cli/src/commands/check.tsx +318 -0
  35. package/packages/cli/src/commands/fix.tsx +294 -0
  36. package/packages/cli/src/commands/install.tsx +501 -47
  37. package/packages/cli/src/commands/push.tsx +53 -22
  38. package/packages/cli/src/commands/register-agent.tsx +227 -0
  39. package/packages/cli/src/components/AgentScores.tsx +20 -6
  40. package/packages/cli/src/components/Hyperlink.tsx +30 -0
  41. package/packages/cli/src/components/ScanReport.tsx +3 -2
  42. package/packages/cli/src/index.tsx +44 -6
  43. package/packages/cli/src/services/avatar.ts +43 -6
  44. package/packages/cli/src/services/chainpatrol.ts +20 -17
  45. package/packages/cli/src/services/contract.ts +41 -8
  46. package/packages/cli/src/services/ens.ts +3 -5
  47. package/packages/cli/src/services/fileverse.ts +12 -13
  48. package/packages/cli/src/services/typosquat.ts +166 -0
  49. package/packages/cli/src/services/version.ts +156 -5
  50. package/packages/contracts/circuits/accuracy_verifier.circom +101 -0
  51. package/packages/contracts/contracts/OPMRegistry.sol +63 -0
  52. package/packages/contracts/scripts/deploy.ts +22 -3
  53. package/packages/core/src/abi.ts +221 -0
  54. package/packages/core/src/benchmarks.ts +450 -0
  55. package/packages/core/src/constants.ts +20 -0
  56. package/packages/core/src/index.ts +2 -0
  57. package/packages/core/src/model-rankings.ts +115 -0
  58. package/packages/core/src/prompt.ts +58 -0
  59. package/packages/core/src/types.ts +41 -0
  60. package/packages/core/src/utils.ts +142 -3
  61. package/packages/scanner/src/agents/base-agent.ts +13 -3
  62. package/packages/scanner/src/index.ts +5 -2
  63. package/packages/scanner/src/queue/memory-queue.ts +8 -3
  64. package/packages/scanner/src/services/benchmark-runner.ts +114 -0
  65. package/packages/scanner/src/services/contract-writer.ts +2 -3
  66. package/packages/scanner/src/services/fileverse.ts +26 -7
  67. package/packages/scanner/src/services/openrouter.ts +61 -4
  68. package/packages/scanner/src/services/report-formatter.ts +122 -3
  69. package/packages/scanner/src/services/zk-verifier.ts +118 -0
  70. package/packages/web/.next/BUILD_ID +1 -0
  71. package/packages/web/.next/app-build-manifest.json +26 -0
  72. package/packages/web/.next/app-path-routes-manifest.json +4 -0
  73. package/packages/web/.next/build-manifest.json +33 -0
  74. package/packages/web/.next/diagnostics/build-diagnostics.json +6 -0
  75. package/packages/web/.next/diagnostics/framework.json +1 -0
  76. package/packages/web/.next/export-marker.json +6 -0
  77. package/packages/web/.next/images-manifest.json +58 -0
  78. package/packages/web/.next/next-minimal-server.js.nft.json +1 -0
  79. package/packages/web/.next/next-server.js.nft.json +1 -0
  80. package/packages/web/.next/package.json +1 -0
  81. package/packages/web/.next/prerender-manifest.json +61 -0
  82. package/packages/web/.next/react-loadable-manifest.json +1 -0
  83. package/packages/web/.next/required-server-files.json +320 -0
  84. package/packages/web/.next/routes-manifest.json +53 -0
  85. package/packages/web/.next/server/app/_not-found/page.js +2 -0
  86. package/packages/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  87. package/packages/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  88. package/packages/web/.next/server/app/_not-found.html +1 -0
  89. package/packages/web/.next/server/app/_not-found.meta +8 -0
  90. package/packages/web/.next/server/app/_not-found.rsc +16 -0
  91. package/packages/web/.next/server/app/index.html +1 -0
  92. package/packages/web/.next/server/app/index.meta +7 -0
  93. package/packages/web/.next/server/app/index.rsc +20 -0
  94. package/packages/web/.next/server/app/page.js +2 -0
  95. package/packages/web/.next/server/app/page.js.nft.json +1 -0
  96. package/packages/web/.next/server/app/page_client-reference-manifest.js +1 -0
  97. package/packages/web/.next/server/app-paths-manifest.json +4 -0
  98. package/packages/web/.next/server/chunks/611.js +6 -0
  99. package/packages/web/.next/server/chunks/778.js +30 -0
  100. package/packages/web/.next/server/functions-config-manifest.json +4 -0
  101. package/packages/web/.next/server/interception-route-rewrite-manifest.js +1 -0
  102. package/packages/web/.next/server/middleware-build-manifest.js +1 -0
  103. package/packages/web/.next/server/middleware-manifest.json +6 -0
  104. package/packages/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  105. package/packages/web/.next/server/next-font-manifest.js +1 -0
  106. package/packages/web/.next/server/next-font-manifest.json +1 -0
  107. package/packages/web/.next/server/pages/404.html +1 -0
  108. package/packages/web/.next/server/pages/500.html +1 -0
  109. package/packages/web/.next/server/pages/_app.js +1 -0
  110. package/packages/web/.next/server/pages/_app.js.nft.json +1 -0
  111. package/packages/web/.next/server/pages/_document.js +1 -0
  112. package/packages/web/.next/server/pages/_document.js.nft.json +1 -0
  113. package/packages/web/.next/server/pages/_error.js +19 -0
  114. package/packages/web/.next/server/pages/_error.js.nft.json +1 -0
  115. package/packages/web/.next/server/pages-manifest.json +6 -0
  116. package/packages/web/.next/server/server-reference-manifest.js +1 -0
  117. package/packages/web/.next/server/server-reference-manifest.json +1 -0
  118. package/packages/web/.next/server/webpack-runtime.js +1 -0
  119. package/packages/web/.next/static/2XIFCTTKVZwN_RsNE-Rrr/_buildManifest.js +1 -0
  120. package/packages/web/.next/static/2XIFCTTKVZwN_RsNE-Rrr/_ssgManifest.js +1 -0
  121. package/packages/web/.next/static/chunks/255-0dc49b7a6e8e5c05.js +1 -0
  122. package/packages/web/.next/static/chunks/4bd1b696-382748cc942d8a14.js +1 -0
  123. package/packages/web/.next/static/chunks/app/_not-found/page-0da542be7eb33a64.js +1 -0
  124. package/packages/web/.next/static/chunks/app/layout-28a489fb4398663f.js +1 -0
  125. package/packages/web/.next/static/chunks/app/page-e58ccdb78625bce6.js +1 -0
  126. package/packages/web/.next/static/chunks/framework-ac73abd125e371fe.js +1 -0
  127. package/packages/web/.next/static/chunks/main-app-dd261207182e5a23.js +1 -0
  128. package/packages/web/.next/static/chunks/main-ee293fa6aa18bdd1.js +1 -0
  129. package/packages/web/.next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  130. package/packages/web/.next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  131. package/packages/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  132. package/packages/web/.next/static/chunks/webpack-e1ae44446e7f7355.js +1 -0
  133. package/packages/web/.next/static/css/21d69157e271f2ab.css +3 -0
  134. package/packages/web/.next/trace +2 -0
  135. package/packages/web/.next/types/app/layout.ts +84 -0
  136. package/packages/web/.next/types/app/page.ts +84 -0
  137. package/packages/web/.next/types/cache-life.d.ts +141 -0
  138. package/packages/web/.next/types/package.json +1 -0
  139. package/packages/web/.next/types/routes.d.ts +57 -0
  140. package/packages/web/.next/types/validator.ts +61 -0
  141. package/packages/web/app/globals.css +75 -0
  142. package/packages/web/app/layout.tsx +26 -0
  143. package/packages/web/app/page.tsx +361 -0
  144. package/packages/web/bun.lock +300 -0
  145. package/packages/web/next-env.d.ts +6 -0
  146. package/packages/web/next.config.ts +5 -0
  147. package/packages/web/package.json +26 -0
  148. package/packages/web/postcss.config.mjs +8 -0
  149. package/packages/web/public/favicon.svg +5 -0
  150. package/packages/web/public/logo.svg +7 -0
  151. package/packages/web/tailwind.config.ts +48 -0
  152. package/packages/web/tsconfig.json +21 -0
@@ -1,13 +1,11 @@
1
- import type { ScanReport } from '@opm/core';
2
- import { getEnvOrDefault } from '@opm/core';
3
- import { formatReportAsMarkdown } from './report-formatter';
4
-
5
- const DEFAULT_API_URL = 'http://localhost:8001';
1
+ import type { ScanReport, CheckReport } from '@opm/core';
2
+ import { getEnvOrDefault, FILEVERSE_DEFAULT_URL } from '@opm/core';
3
+ import { formatReportAsMarkdown, formatCheckReportAsMarkdown } from './report-formatter';
6
4
  const POLL_INTERVAL_MS = 3000;
7
5
  const POLL_TIMEOUT_MS = 60_000;
8
6
 
9
7
  function getApiConfig() {
10
- const apiUrl = getEnvOrDefault('FILEVERSE_API_URL', DEFAULT_API_URL);
8
+ const apiUrl = getEnvOrDefault('FILEVERSE_API_URL', FILEVERSE_DEFAULT_URL);
11
9
  const apiKey = process.env.FILEVERSE_API_KEY;
12
10
  if (!apiKey) throw new Error('FILEVERSE_API_KEY is required (generate at ddocs.new → Settings → Developer Mode)');
13
11
  return { apiUrl, apiKey };
@@ -56,11 +54,32 @@ async function pollForSync(apiUrl: string, apiKey: string, ddocId: string): Prom
56
54
  return `https://ddocs.new/pending/${ddocId}`;
57
55
  }
58
56
 
57
+ export async function uploadCheckReportToFileverse(report: CheckReport): Promise<string> {
58
+ const { apiUrl, apiKey } = getApiConfig();
59
+ const title = `OPM Check Report: ${report.project} (${report.totalDeps} deps)`;
60
+ const content = formatCheckReportAsMarkdown(report);
61
+
62
+ const res = await fetch(`${apiUrl}/api/ddocs?apiKey=${encodeURIComponent(apiKey)}`, {
63
+ method: 'POST',
64
+ headers: { 'Content-Type': 'application/json' },
65
+ body: JSON.stringify({ title, content }),
66
+ });
67
+
68
+ if (!res.ok) {
69
+ const body = await res.text();
70
+ throw new Error(`Fileverse create failed (${res.status}): ${body}`);
71
+ }
72
+
73
+ const { data } = await res.json() as { data: { ddocId: string; syncStatus: string; link?: string } };
74
+ if (data.syncStatus === 'synced' && data.link) return data.link;
75
+ return pollForSync(apiUrl, apiKey, data.ddocId);
76
+ }
77
+
59
78
  export async function fetchReportFromFileverse(reportURI: string): Promise<ScanReport | null> {
60
79
  if (!reportURI || reportURI.startsWith('local://')) return null;
61
80
 
62
81
  const apiKey = process.env.FILEVERSE_API_KEY;
63
- const apiUrl = getEnvOrDefault('FILEVERSE_API_URL', DEFAULT_API_URL);
82
+ const apiUrl = getEnvOrDefault('FILEVERSE_API_URL', FILEVERSE_DEFAULT_URL);
64
83
 
65
84
  const ddocId = extractDdocId(reportURI);
66
85
  if (ddocId && apiKey) {
@@ -1,6 +1,6 @@
1
1
  import { OPENROUTER_API_URL, OPENAI_API_URL } from '@opm/core';
2
2
  import type { AgentScanResult } from '@opm/core';
3
- import { validateScanResult, safeJsonParse } from '@opm/core';
3
+ import { validateScanResult, normalizeScanResult, safeJsonParse } from '@opm/core';
4
4
 
5
5
  function getProvider(): { apiUrl: string; apiKey: string; kind: 'openai' | 'openrouter' } {
6
6
  const forcedProvider = process.env.LLM_PROVIDER;
@@ -53,6 +53,10 @@ export async function callLLM(
53
53
  headers['X-Title'] = 'OPM Security Scanner';
54
54
  }
55
55
 
56
+ const tokenLimit = kind === 'openai'
57
+ ? { max_completion_tokens: 4096 }
58
+ : { max_tokens: 4096 };
59
+
56
60
  const res = await fetch(apiUrl, {
57
61
  method: 'POST',
58
62
  headers,
@@ -64,7 +68,7 @@ export async function callLLM(
64
68
  ],
65
69
  response_format: { type: 'json_object' },
66
70
  temperature: 0.1,
67
- max_tokens: 4096,
71
+ ...tokenLimit,
68
72
  }),
69
73
  });
70
74
 
@@ -78,9 +82,62 @@ export async function callLLM(
78
82
  if (!raw) throw new Error(`Empty response from ${kind}/${model}`);
79
83
 
80
84
  const parsed = safeJsonParse<AgentScanResult>(raw);
81
- if (!parsed || !validateScanResult(parsed)) {
82
- throw new Error(`Invalid scan result JSON from ${model}: ${raw.slice(0, 200)}`);
85
+ if (!parsed) throw new Error(`Unparseable JSON from ${model}: ${raw.slice(0, 200)}`);
86
+
87
+ if (validateScanResult(parsed)) return parsed;
88
+
89
+ const normalized = normalizeScanResult(parsed);
90
+ if (!normalized) throw new Error(`Cannot normalize scan result from ${model}: ${raw.slice(0, 200)}`);
91
+
92
+ return normalized;
93
+ }
94
+
95
+ export async function callLLMRaw<T = unknown>(
96
+ model: string,
97
+ systemPrompt: string,
98
+ userPrompt: string,
99
+ ): Promise<T> {
100
+ const { apiUrl, apiKey, kind } = getProvider();
101
+
102
+ const headers: Record<string, string> = {
103
+ 'Authorization': `Bearer ${apiKey}`,
104
+ 'Content-Type': 'application/json',
105
+ };
106
+
107
+ if (kind === 'openrouter') {
108
+ headers['HTTP-Referer'] = 'https://opm.dev';
109
+ headers['X-Title'] = 'OPM Security Scanner';
83
110
  }
84
111
 
112
+ const tokenLimit = kind === 'openai'
113
+ ? { max_completion_tokens: 4096 }
114
+ : { max_tokens: 4096 };
115
+
116
+ const res = await fetch(apiUrl, {
117
+ method: 'POST',
118
+ headers,
119
+ body: JSON.stringify({
120
+ model,
121
+ messages: [
122
+ { role: 'system', content: systemPrompt },
123
+ { role: 'user', content: userPrompt },
124
+ ],
125
+ response_format: { type: 'json_object' },
126
+ temperature: 0.1,
127
+ ...tokenLimit,
128
+ }),
129
+ });
130
+
131
+ if (!res.ok) {
132
+ const body = await res.text();
133
+ throw new Error(`${kind} ${res.status}: ${body}`);
134
+ }
135
+
136
+ const data = await res.json() as { choices: Array<{ message: { content: string } }> };
137
+ const raw = data.choices?.[0]?.message?.content;
138
+ if (!raw) throw new Error(`Empty response from ${kind}/${model}`);
139
+
140
+ const parsed = safeJsonParse<T>(raw);
141
+ if (!parsed) throw new Error(`Invalid JSON from ${model}: ${raw.slice(0, 200)}`);
85
142
  return parsed;
86
143
  }
@@ -1,4 +1,4 @@
1
- import type { ScanReport, AgentEntry, Vulnerability, SupplyChainIndicators } from '@opm/core';
1
+ import type { ScanReport, AgentEntry, Vulnerability, SupplyChainIndicators, CheckReport } from '@opm/core';
2
2
 
3
3
  function riskEmoji(score: number): string {
4
4
  if (score >= 70) return '🔴';
@@ -49,10 +49,15 @@ function formatAgent(agent: AgentEntry, index: number): string {
49
49
  const { result } = agent;
50
50
  const emoji = riskEmoji(result.risk_score);
51
51
 
52
+ const modelLines = [`- **Model:** \`${agent.model}\``];
53
+ if (agent.model_intelligence || agent.model_coding) {
54
+ modelLines.push(`- **AI Intelligence Index:** ${agent.model_intelligence || '—'}/100 | **Coding Index:** ${agent.model_coding || '—'}/100 | **Weight:** ${agent.model_weight || '—'}`);
55
+ }
56
+
52
57
  return [
53
58
  `### Agent ${index + 1}: \`${agent.agent_id}\``,
54
59
  '',
55
- `- **Model:** \`${agent.model}\``,
60
+ ...modelLines,
56
61
  `- **Risk Score:** ${emoji} **${result.risk_score}/100** (${result.risk_level})`,
57
62
  `- **Recommendation:** ${result.recommendation}`,
58
63
  '',
@@ -101,10 +106,12 @@ export function formatReportAsMarkdown(report: ScanReport): string {
101
106
  '',
102
107
  '---',
103
108
  '',
104
- '## Aggregate Risk',
109
+ '## Aggregate Risk (Intelligence-Weighted)',
105
110
  '',
106
111
  `\`${riskBar(report.aggregate_risk_score)}\` **${report.aggregate_risk_score}/100** — ${report.consensus}`,
107
112
  '',
113
+ `> Scores are weighted by each model's AI Intelligence and Coding indices from [Artificial Analysis](https://artificialanalysis.ai).`,
114
+ '',
108
115
  report.aggregate_risk_score < 40
109
116
  ? '> ✅ This package appears safe based on multi-agent consensus.'
110
117
  : report.aggregate_risk_score < 70
@@ -132,3 +139,115 @@ export function formatReportAsMarkdown(report: ScanReport): string {
132
139
 
133
140
  return sections.join('\n');
134
141
  }
142
+
143
+ export function formatCheckReportAsMarkdown(report: CheckReport): string {
144
+ const timestamp = new Date(report.timestamp).toLocaleString('en-US', {
145
+ dateStyle: 'long', timeStyle: 'short',
146
+ });
147
+
148
+ const typosquats = report.deps.filter((d) => d.typosquat);
149
+ const cveBlocked = report.deps.filter((d) => d.cveCritical > 0);
150
+ const cveWarned = report.deps.filter((d) => d.cveCount > 0 && d.cveCritical === 0);
151
+ const highRisk = report.deps.filter((d) => d.onChainScore !== null && d.onChainScore >= 70);
152
+ const safeCount = report.totalDeps - typosquats.length - cveBlocked.length - cveWarned.length - highRisk.length;
153
+
154
+ const sections: string[] = [
155
+ `# OPM Dependency Check Report`,
156
+ '',
157
+ `- **Project:** ${report.project}`,
158
+ `- **Scanned:** ${timestamp}`,
159
+ `- **Total dependencies:** ${report.totalDeps}`,
160
+ '',
161
+ '---',
162
+ '',
163
+ '## Summary',
164
+ '',
165
+ `| Category | Count |`,
166
+ `|---|---|`,
167
+ `| ${typosquats.length > 0 ? '🔴' : '🟢'} Typosquats | ${typosquats.length} |`,
168
+ `| ${cveBlocked.length > 0 ? '🔴' : '🟢'} Critical CVEs | ${cveBlocked.length} |`,
169
+ `| ${cveWarned.length > 0 ? '🟡' : '🟢'} CVE Warnings | ${cveWarned.length} |`,
170
+ `| ${highRisk.length > 0 ? '🔴' : '🟢'} High On-chain Risk | ${highRisk.length} |`,
171
+ `| 🟢 Safe | ${Math.max(0, safeCount)} |`,
172
+ '',
173
+ ];
174
+
175
+ if (typosquats.length > 0) {
176
+ sections.push('---', '', '## Typosquat Risks', '');
177
+ for (const d of typosquats) {
178
+ sections.push(
179
+ `### \`${d.name}\`@${d.version}`,
180
+ '',
181
+ `- **Likely intended package:** \`${d.typosquat!.likelyTarget}\``,
182
+ `- **Confidence:** ${d.typosquat!.confidence}`,
183
+ `- **Reason:** ${d.typosquat!.reason}`,
184
+ '',
185
+ );
186
+ }
187
+ }
188
+
189
+ if (cveBlocked.length > 0) {
190
+ sections.push('---', '', '## Critical Vulnerabilities', '');
191
+ for (const d of cveBlocked) {
192
+ sections.push(
193
+ `### \`${d.name}\`@${d.version}`,
194
+ '',
195
+ `- **Critical:** ${d.cveCritical} | **High:** ${d.cveHigh}`,
196
+ `- **CVEs:** ${d.cveIds.join(', ')}`,
197
+ d.fixVersion ? `- **Fix:** upgrade to \`${d.fixVersion}\`` : '',
198
+ '',
199
+ );
200
+ }
201
+ }
202
+
203
+ if (cveWarned.length > 0) {
204
+ sections.push('---', '', '## CVE Warnings', '');
205
+ for (const d of cveWarned) {
206
+ sections.push(
207
+ `- \`${d.name}\`@${d.version} — ${d.cveCount} CVE(s)${d.fixVersion ? ` → \`${d.fixVersion}\`` : ''}`,
208
+ );
209
+ }
210
+ sections.push('');
211
+ }
212
+
213
+ if (highRisk.length > 0) {
214
+ sections.push('---', '', '## High On-chain Risk', '');
215
+ for (const d of highRisk) {
216
+ sections.push(`- \`${d.name}\`@${d.version} — risk score **${d.onChainScore}/100**`);
217
+ }
218
+ sections.push('');
219
+ }
220
+
221
+ if (report.agents.length > 0) {
222
+ sections.push('---', '', '## AI Agent Analysis', '');
223
+ for (const a of report.agents) {
224
+ const flags = a.findings.filter((f) => f.issue !== 'safe' && f.severity !== 'NONE');
225
+ sections.push(
226
+ `### \`${a.agentId}\` — ${a.model}`,
227
+ '',
228
+ `- **AI Intelligence:** ${a.intelligence}/100 | **Coding:** ${a.coding}/100`,
229
+ `- **Risk Score:** ${a.riskScore}/100`,
230
+ '',
231
+ );
232
+ if (flags.length > 0) {
233
+ for (const f of flags) {
234
+ sections.push(
235
+ `- **[${f.severity}]** \`${f.package}\` — ${f.issue}: ${f.explanation}` +
236
+ (f.suggested_replacement ? ` → \`${f.suggested_replacement}\`` : ''),
237
+ );
238
+ }
239
+ } else {
240
+ sections.push(`- ✅ No issues found`);
241
+ }
242
+ sections.push('', `> ${a.overall}`, '');
243
+ }
244
+ }
245
+
246
+ sections.push(
247
+ '---',
248
+ '',
249
+ `*Report generated by [OPM](https://github.com/dhananjaypai08/opm) — On-chain Package Manager*`,
250
+ );
251
+
252
+ return sections.filter((l) => l !== undefined).join('\n');
253
+ }
@@ -0,0 +1,118 @@
1
+ import { createHash, randomBytes } from 'crypto';
2
+
3
+ /**
4
+ * Zero-knowledge accuracy verification via hash commitments.
5
+ *
6
+ * The scheme works as follows:
7
+ * 1. A trusted authority generates benchmark test cases with expected outputs
8
+ * 2. Expected outputs are hashed with a secret salt → commitment
9
+ * 3. The candidate agent runs against the benchmarks
10
+ * 4. Actual outputs are hashed with the same salt
11
+ * 5. A proof is generated: hash(commitment || result_hashes || accuracy_flag)
12
+ * 6. The verifier checks the proof without seeing individual test results
13
+ *
14
+ * This ensures:
15
+ * - Test cases remain private (can't be gamed)
16
+ * - Individual results aren't disclosed
17
+ * - Only a binary pass/fail is revealed
18
+ * - The proof is deterministic and verifiable
19
+ */
20
+
21
+ export interface ZKCommitment {
22
+ salt: string;
23
+ expectedHash: string;
24
+ caseCount: number;
25
+ }
26
+
27
+ export interface ZKProof {
28
+ commitment: ZKCommitment;
29
+ resultHash: string;
30
+ accuracyProof: string;
31
+ passed: boolean;
32
+ timestamp: number;
33
+ }
34
+
35
+ export interface AccuracyWitness {
36
+ expectedVerdicts: number[];
37
+ actualVerdicts: number[];
38
+ salt: string;
39
+ }
40
+
41
+ function poseidonHash(...inputs: string[]): string {
42
+ const h = createHash('sha256');
43
+ for (const input of inputs) {
44
+ h.update(input);
45
+ }
46
+ return h.digest('hex');
47
+ }
48
+
49
+ export function generateCommitment(expectedVerdicts: number[]): ZKCommitment {
50
+ const salt = randomBytes(32).toString('hex');
51
+ const verdictStr = expectedVerdicts.join(',');
52
+ const expectedHash = poseidonHash(salt, verdictStr);
53
+
54
+ return {
55
+ salt,
56
+ expectedHash,
57
+ caseCount: expectedVerdicts.length,
58
+ };
59
+ }
60
+
61
+ export function generateProof(
62
+ commitment: ZKCommitment,
63
+ expectedVerdicts: number[],
64
+ actualVerdicts: number[],
65
+ ): ZKProof {
66
+ if (expectedVerdicts.length !== actualVerdicts.length) {
67
+ throw new Error('Verdict arrays must have equal length');
68
+ }
69
+
70
+ const expectedStr = expectedVerdicts.join(',');
71
+ const commitmentCheck = poseidonHash(commitment.salt, expectedStr);
72
+ if (commitmentCheck !== commitment.expectedHash) {
73
+ throw new Error('Commitment verification failed — expected verdicts do not match');
74
+ }
75
+
76
+ const actualStr = actualVerdicts.join(',');
77
+ const resultHash = poseidonHash(commitment.salt, actualStr);
78
+
79
+ const allMatch = expectedVerdicts.every((e, i) => e === actualVerdicts[i]);
80
+
81
+ const accuracyProof = poseidonHash(
82
+ commitment.expectedHash,
83
+ resultHash,
84
+ allMatch ? '1' : '0',
85
+ commitment.salt,
86
+ );
87
+
88
+ return {
89
+ commitment,
90
+ resultHash,
91
+ accuracyProof,
92
+ passed: allMatch,
93
+ timestamp: Date.now(),
94
+ };
95
+ }
96
+
97
+ export function verifyProof(proof: ZKProof): boolean {
98
+ const recomputedProof = poseidonHash(
99
+ proof.commitment.expectedHash,
100
+ proof.resultHash,
101
+ proof.passed ? '1' : '0',
102
+ proof.commitment.salt,
103
+ );
104
+
105
+ return recomputedProof === proof.accuracyProof;
106
+ }
107
+
108
+ export function proofToOnChainBytes(proof: ZKProof): string {
109
+ const payload = JSON.stringify({
110
+ commitment: proof.commitment.expectedHash,
111
+ resultHash: proof.resultHash,
112
+ accuracyProof: proof.accuracyProof,
113
+ passed: proof.passed,
114
+ timestamp: proof.timestamp,
115
+ caseCount: proof.commitment.caseCount,
116
+ });
117
+ return '0x' + Buffer.from(payload).toString('hex');
118
+ }
@@ -0,0 +1 @@
1
+ 2XIFCTTKVZwN_RsNE-Rrr
@@ -0,0 +1,26 @@
1
+ {
2
+ "pages": {
3
+ "/_not-found/page": [
4
+ "static/chunks/webpack-e1ae44446e7f7355.js",
5
+ "static/chunks/4bd1b696-382748cc942d8a14.js",
6
+ "static/chunks/255-0dc49b7a6e8e5c05.js",
7
+ "static/chunks/main-app-dd261207182e5a23.js",
8
+ "static/chunks/app/_not-found/page-0da542be7eb33a64.js"
9
+ ],
10
+ "/layout": [
11
+ "static/chunks/webpack-e1ae44446e7f7355.js",
12
+ "static/chunks/4bd1b696-382748cc942d8a14.js",
13
+ "static/chunks/255-0dc49b7a6e8e5c05.js",
14
+ "static/chunks/main-app-dd261207182e5a23.js",
15
+ "static/css/21d69157e271f2ab.css",
16
+ "static/chunks/app/layout-28a489fb4398663f.js"
17
+ ],
18
+ "/page": [
19
+ "static/chunks/webpack-e1ae44446e7f7355.js",
20
+ "static/chunks/4bd1b696-382748cc942d8a14.js",
21
+ "static/chunks/255-0dc49b7a6e8e5c05.js",
22
+ "static/chunks/main-app-dd261207182e5a23.js",
23
+ "static/chunks/app/page-e58ccdb78625bce6.js"
24
+ ]
25
+ }
26
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "/_not-found/page": "/_not-found",
3
+ "/page": "/"
4
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "polyfillFiles": [
3
+ "static/chunks/polyfills-42372ed130431b0a.js"
4
+ ],
5
+ "devFiles": [],
6
+ "ampDevFiles": [],
7
+ "lowPriorityFiles": [
8
+ "static/2XIFCTTKVZwN_RsNE-Rrr/_buildManifest.js",
9
+ "static/2XIFCTTKVZwN_RsNE-Rrr/_ssgManifest.js"
10
+ ],
11
+ "rootMainFiles": [
12
+ "static/chunks/webpack-e1ae44446e7f7355.js",
13
+ "static/chunks/4bd1b696-382748cc942d8a14.js",
14
+ "static/chunks/255-0dc49b7a6e8e5c05.js",
15
+ "static/chunks/main-app-dd261207182e5a23.js"
16
+ ],
17
+ "rootMainFilesTree": {},
18
+ "pages": {
19
+ "/_app": [
20
+ "static/chunks/webpack-e1ae44446e7f7355.js",
21
+ "static/chunks/framework-ac73abd125e371fe.js",
22
+ "static/chunks/main-ee293fa6aa18bdd1.js",
23
+ "static/chunks/pages/_app-7d307437aca18ad4.js"
24
+ ],
25
+ "/_error": [
26
+ "static/chunks/webpack-e1ae44446e7f7355.js",
27
+ "static/chunks/framework-ac73abd125e371fe.js",
28
+ "static/chunks/main-ee293fa6aa18bdd1.js",
29
+ "static/chunks/pages/_error-cb2a52f75f2162e2.js"
30
+ ]
31
+ },
32
+ "ampFirstPages": []
33
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "buildStage": "static-generation",
3
+ "buildOptions": {
4
+ "useBuildWorker": "true"
5
+ }
6
+ }
@@ -0,0 +1 @@
1
+ {"name":"Next.js","version":"15.5.12"}
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 1,
3
+ "hasExportPathMap": false,
4
+ "exportTrailingSlash": false,
5
+ "isNextImageImported": false
6
+ }
@@ -0,0 +1,58 @@
1
+ {
2
+ "version": 1,
3
+ "images": {
4
+ "deviceSizes": [
5
+ 640,
6
+ 750,
7
+ 828,
8
+ 1080,
9
+ 1200,
10
+ 1920,
11
+ 2048,
12
+ 3840
13
+ ],
14
+ "imageSizes": [
15
+ 16,
16
+ 32,
17
+ 48,
18
+ 64,
19
+ 96,
20
+ 128,
21
+ 256,
22
+ 384
23
+ ],
24
+ "path": "/_next/image",
25
+ "loader": "default",
26
+ "loaderFile": "",
27
+ "domains": [],
28
+ "disableStaticImages": false,
29
+ "minimumCacheTTL": 60,
30
+ "formats": [
31
+ "image/webp"
32
+ ],
33
+ "maximumResponseBody": 50000000,
34
+ "dangerouslyAllowSVG": false,
35
+ "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;",
36
+ "contentDispositionType": "attachment",
37
+ "remotePatterns": [],
38
+ "unoptimized": false,
39
+ "sizes": [
40
+ 640,
41
+ 750,
42
+ 828,
43
+ 1080,
44
+ 1200,
45
+ 1920,
46
+ 2048,
47
+ 3840,
48
+ 16,
49
+ 32,
50
+ 48,
51
+ 64,
52
+ 96,
53
+ 128,
54
+ 256,
55
+ 384
56
+ ]
57
+ }
58
+ }
@@ -0,0 +1 @@
1
+ {"version":1,"files":["../node_modules/@swc/helpers/_/_interop_require_default/package.json","../node_modules/@swc/helpers/cjs/_interop_require_default.cjs","../node_modules/@swc/helpers/package.json","../node_modules/client-only/index.js","../node_modules/client-only/package.json","../node_modules/next/dist/client/components/app-router-headers.js","../node_modules/next/dist/compiled/@opentelemetry/api/index.js","../node_modules/next/dist/compiled/@opentelemetry/api/package.json","../node_modules/next/dist/compiled/babel-code-frame/index.js","../node_modules/next/dist/compiled/babel-code-frame/package.json","../node_modules/next/dist/compiled/babel/code-frame.js","../node_modules/next/dist/compiled/babel/package.json","../node_modules/next/dist/compiled/bytes/index.js","../node_modules/next/dist/compiled/bytes/package.json","../node_modules/next/dist/compiled/next-server/server.runtime.prod.js","../node_modules/next/dist/compiled/source-map/package.json","../node_modules/next/dist/compiled/source-map/source-map.js","../node_modules/next/dist/compiled/stacktrace-parser/package.json","../node_modules/next/dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js","../node_modules/next/dist/compiled/ws/index.js","../node_modules/next/dist/compiled/ws/package.json","../node_modules/next/dist/experimental/testmode/context.js","../node_modules/next/dist/experimental/testmode/fetch.js","../node_modules/next/dist/experimental/testmode/server-edge.js","../node_modules/next/dist/lib/client-and-server-references.js","../node_modules/next/dist/lib/constants.js","../node_modules/next/dist/lib/interop-default.js","../node_modules/next/dist/lib/is-error.js","../node_modules/next/dist/lib/picocolors.js","../node_modules/next/dist/next-devtools/server/shared.js","../node_modules/next/dist/server/after/builtin-request-context.js","../node_modules/next/dist/server/app-render/after-task-async-storage-instance.js","../node_modules/next/dist/server/app-render/after-task-async-storage.external.js","../node_modules/next/dist/server/app-render/async-local-storage.js","../node_modules/next/dist/server/app-render/work-async-storage-instance.js","../node_modules/next/dist/server/app-render/work-async-storage.external.js","../node_modules/next/dist/server/app-render/work-unit-async-storage-instance.js","../node_modules/next/dist/server/app-render/work-unit-async-storage.external.js","../node_modules/next/dist/server/body-streams.js","../node_modules/next/dist/server/lib/cache-handlers/default.external.js","../node_modules/next/dist/server/lib/incremental-cache/memory-cache.external.js","../node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js","../node_modules/next/dist/server/lib/incremental-cache/tags-manifest.external.js","../node_modules/next/dist/server/lib/lru-cache.js","../node_modules/next/dist/server/lib/parse-stack.js","../node_modules/next/dist/server/lib/router-utils/instrumentation-globals.external.js","../node_modules/next/dist/server/lib/router-utils/instrumentation-node-extensions.js","../node_modules/next/dist/server/lib/router-utils/router-server-context.js","../node_modules/next/dist/server/lib/source-maps.js","../node_modules/next/dist/server/lib/trace/constants.js","../node_modules/next/dist/server/lib/trace/tracer.js","../node_modules/next/dist/server/load-manifest.external.js","../node_modules/next/dist/server/patch-error-inspect.js","../node_modules/next/dist/server/response-cache/types.js","../node_modules/next/dist/server/route-modules/app-page/module.compiled.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/amp-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/router-context.js","../node_modules/next/dist/server/route-modules/app-page/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/route-modules/pages/module.compiled.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/amp-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/html-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/loadable-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/loadable.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/router-context.js","../node_modules/next/dist/server/route-modules/pages/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/web/utils.js","../node_modules/next/dist/shared/lib/constants.js","../node_modules/next/dist/shared/lib/deep-freeze.js","../node_modules/next/dist/shared/lib/error-source.js","../node_modules/next/dist/shared/lib/invariant-error.js","../node_modules/next/dist/shared/lib/is-internal.js","../node_modules/next/dist/shared/lib/is-plain-object.js","../node_modules/next/dist/shared/lib/is-thenable.js","../node_modules/next/dist/shared/lib/modern-browserslist-target.js","../node_modules/next/dist/shared/lib/no-fallback-error.external.js","../node_modules/next/dist/shared/lib/runtime-config.external.js","../node_modules/next/dist/shared/lib/server-reference-info.js","../node_modules/next/package.json","../node_modules/react/cjs/react.production.js","../node_modules/react/index.js","../node_modules/react/package.json","../node_modules/styled-jsx/dist/index/index.js","../node_modules/styled-jsx/index.js","../node_modules/styled-jsx/package.json","../node_modules/styled-jsx/style.js","package.json"]}