(pluginId: string)
| 145 | * error. |
| 146 | */ |
| 147 | export async function runPluginScan(pluginId: string): Promise<void> { |
| 148 | logInfo(pluginId, "scan start"); |
| 149 | |
| 150 | const loaded = await loadPlugin(pluginId); |
| 151 | if (!loaded) { |
| 152 | logInfo(pluginId, "loadPlugin returned null; nothing to scan"); |
| 153 | return; |
| 154 | } |
| 155 | |
| 156 | const tag = loaded.plugin.slug; |
| 157 | |
| 158 | if (loaded.permanentlyBlocked) { |
| 159 | logInfo(tag, "permanently blocked; short-circuiting"); |
| 160 | await applyBlockedShortCircuit(pluginId); |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | await markScanning(pluginId); |
| 165 | try { |
| 166 | const similar = await findSimilarPlugins(pluginId); |
| 167 | if (similar.length > 0) { |
| 168 | logInfo(tag, "candidate duplicates", { |
| 169 | count: similar.length, |
| 170 | topSimilarity: similar[0].similarity, |
| 171 | }); |
| 172 | } |
| 173 | const verdict = await runSecurityAgent(loaded.plugin, similar); |
| 174 | await applyVerdict(pluginId, loaded.prevActive, verdict); |
| 175 | logInfo(tag, "scan complete", { |
| 176 | verdict: verdict.verdict, |
| 177 | severity: verdict.severity, |
| 178 | }); |
| 179 | } catch (err) { |
| 180 | // Compensation: without this the row stays at scan_status='scanning' |
| 181 | // forever (no scan_run_id, no flag), which is invisible to admins |
| 182 | // until the 15-min staleness threshold in getStuckScans() trips. |
| 183 | // Surfacing as 'error' lets the admin re-scan or delete it. |
| 184 | const message = err instanceof Error ? err.message : String(err); |
| 185 | logError(tag, "scan failed; marking scan_status=error", err); |
| 186 | await markScanFailed(pluginId, message); |
| 187 | throw err; |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | async function loadPlugin(pluginId: string): Promise<LoadResult | null> { |
| 192 | const supabase = await createClient(); |
no test coverage detected