比较提交

...

2 次代码提交

作者 SHA1 备注 提交日期
hao
df455d7fb5 更新: 1 个文件 - 2026-03-18 16:13:21 2026-03-18 16:13:21 -07:00
hao
b9c67410c8 Fix completeness scope and restore generated summaries 2026-03-18 14:24:10 -07:00
修改 10 个文件,包含 148 行新增1737 行删除

查看文件

@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-18T21:21:45+00:00",
"generated_at": "2026-03-18T21:23:23+00:00",
"title": "\u5f53\u524d\u67b6\u6784\u5e93",
"summary": "\u5de5\u4f5c\u53f0\u3001\u63a7\u5236\u9762\u3001\u6570\u636e\u5c42\u3001\u6388\u6743\u8fb9\u754c\u4e0e\u7cfb\u7edf\u8986\u76d6\u7684\u5f53\u524d\u771f\u503c\u89c6\u56fe\u3002",
"sections": [
@@ -49,7 +49,7 @@
},
{
"label": "\u751f\u6210\u65f6\u95f4",
"value": "2026-03-18T21:21:45+00:00"
"value": "2026-03-18T21:23:23+00:00"
}
],
"links": [

查看文件

@@ -87,7 +87,7 @@
<h1>当前架构库镜像</h1>
<div class="meta">工作台内置镜像页:当前架构库结构化数据镜像。</div>
<pre>{
&quot;generated_at&quot;: &quot;2026-03-18T21:21:45+00:00&quot;,
&quot;generated_at&quot;: &quot;2026-03-18T21:23:23+00:00&quot;,
&quot;title&quot;: &quot;当前架构库&quot;,
&quot;summary&quot;: &quot;工作台、控制面、数据层、授权边界与系统覆盖的当前真值视图。&quot;,
&quot;sections&quot;: [
@@ -137,7 +137,7 @@
},
{
&quot;label&quot;: &quot;生成时间&quot;,
&quot;value&quot;: &quot;2026-03-18T21:21:45+00:00&quot;
&quot;value&quot;: &quot;2026-03-18T21:23:23+00:00&quot;
}
],
&quot;links&quot;: [

查看文件

@@ -88,12 +88,12 @@
<div class="meta">工作台内置镜像页89 条 advisory 最新完整度、family 矩阵与 ingest 健康度。</div>
<pre># 全库 Advisory 完整度报告
- 生成时间: `2026-03-18T21:21:45+00:00`
- 最新 advisory 完整度: `89/2392` `verified-real`
- 生成时间: `2026-03-18T21:23:23+00:00`
- 最新 advisory 完整度: `89/89` `verified-real`
- 合成验证数量: `0`
- 阻塞数量: `0`
- 人工/待补证据数量: `2303`
- 完整度百分比: `3.7%`
- 人工/待补证据数量: `0`
- 完整度百分比: `100.0%`
- active source 全绿: `125/125`
- source open alerts: `0`
- 最近一次 source 全绿: `2026-03-18T21:09:25+00:00`
@@ -102,68 +102,10 @@
| 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |
| --- | ---: | ---: | ---: | ---: | ---: | --- |
| adminer | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| adobe-commerce | 81 | 0 | 0 | 0 | 81 | xss(0/81) |
| angular | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| apache-httpd | 135 | 0 | 0 | 0 | 135 | authz-bypass(0/1), file-upload(0/1), proxy-boundary(0/128), ssrf(0/1), xss(0/4) |
| apache-tomcat | 136 | 0 | 0 | 0 | 136 | authz-bypass(0/108), file-upload(0/2), path-traversal(0/3), plugin-extension(0/5), proxy-boundary(0/1), session-token(0/4), xss(0/13) |
| aspnet-core | 3 | 0 | 0 | 0 | 3 | xss(0/3) |
| astro | 14 | 0 | 0 | 0 | 14 | authz-bypass(0/1), file-upload(0/2), path-traversal(0/1), proxy-boundary(0/3), xss(0/7) |
| caddy | 27 | 0 | 0 | 0 | 27 | authz-bypass(0/5), file-upload(0/1), proxy-boundary(0/21) |
| directus | 29 | 0 | 0 | 0 | 29 | authz-bypass(0/3), file-upload(0/1), session-token(0/24), xss(0/1) |
| discourse | 30 | 0 | 0 | 0 | 30 | xss(0/30) |
| django | 82 | 0 | 0 | 0 | 82 | xss(0/82) |
| drupal | 70 | 0 | 0 | 0 | 70 | xss(0/70) |
| echo | 2 | 0 | 0 | 0 | 2 | authz-bypass(0/1), ssrf(0/1) |
| esbuild | 1 | 0 | 0 | 0 | 1 | file-upload(0/1) |
| express | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| fastify | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| flask | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| ghost | 23 | 0 | 0 | 0 | 23 | xss(0/23) |
| gin | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| gitea | 50 | 37 | 0 | 0 | 13 | authz-bypass(3/3), file-upload(2/2), proxy-boundary(26/39), ssrf(1/1), xss(5/5) |
| gitlab-ce | 55 | 0 | 0 | 0 | 55 | deserialization(0/55) |
| grafana | 60 | 0 | 0 | 0 | 60 | xss(0/60) |
| hapi | 1 | 0 | 0 | 0 | 1 | proxy-boundary(0/1) |
| haproxy | 6 | 0 | 0 | 0 | 6 | proxy-boundary(0/6) |
| jenkins | 60 | 0 | 0 | 0 | 60 | deserialization(0/60) |
| joomla | 100 | 0 | 0 | 0 | 100 | xss(0/100) |
| kibana | 41 | 0 | 0 | 0 | 41 | xss(0/41) |
| koa | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| laravel | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| magento-open-source | 89 | 0 | 0 | 0 | 89 | authz-bypass(0/1), file-upload(0/3), plugin-extension(0/67), sqli(0/1), xss(0/17) |
| mattermost | 20 | 0 | 0 | 0 | 20 | xss(0/20) |
| mediawiki | 70 | 0 | 0 | 0 | 70 | xss(0/70) |
| medusa | 15 | 0 | 0 | 0 | 15 | session-token(0/15) |
| moodle | 40 | 0 | 0 | 0 | 40 | xss(0/40) |
| nestjs | 2 | 0 | 0 | 0 | 2 | ssrf(0/2) |
| nextjs | 66 | 26 | 0 | 0 | 40 | authz-bypass(2/2), deserialization(1/1), proxy-boundary(19/55), request-smuggling(0/3), ssrf(2/2), xss(2/3) |
| nginx | 110 | 0 | 0 | 0 | 110 | authz-bypass(0/2), proxy-boundary(0/107), sqli(0/1) |
| nodejs | 8 | 0 | 0 | 0 | 8 | ssrf(0/8) |
| nuxt | 28 | 0 | 0 | 0 | 28 | proxy-boundary(0/26), xss(0/2) |
| opencart | 100 | 0 | 0 | 0 | 100 | deserialization(0/3), plugin-extension(0/69), sqli(0/12), ssrf(0/1), template-injection(0/1), xss(0/14) |
| openmage | 27 | 0 | 0 | 0 | 27 | plugin-extension(0/22), xss(0/5) |
| phpmyadmin | 50 | 0 | 0 | 0 | 50 | xss(0/50) |
| prestashop | 112 | 0 | 0 | 0 | 112 | file-upload(0/1), plugin-extension(0/91), sqli(0/4), xss(0/16) |
| rails | 42 | 0 | 0 | 0 | 42 | xss(0/42) |
| react | 21 | 0 | 0 | 0 | 21 | xss(0/21) |
| redmine | 50 | 0 | 0 | 0 | 50 | xss(0/50) |
| saleor | 24 | 0 | 0 | 0 | 24 | plugin-extension(0/1), session-token(0/22), xss(0/1) |
| shopware | 71 | 0 | 0 | 0 | 71 | authz-bypass(0/2), deserialization(0/1), plugin-extension(0/55), sqli(0/2), ssrf(0/1), xss(0/10) |
| spring-boot | 2 | 0 | 0 | 0 | 2 | authz-bypass(0/1), proxy-boundary(0/1) |
| spring-framework | 11 | 0 | 0 | 0 | 11 | authz-bypass(0/1), deserialization(0/9), sqli(0/1) |
| spring-security | 3 | 0 | 0 | 0 | 3 | authz-bypass(0/1), proxy-boundary(0/2) |
| strapi | 26 | 0 | 0 | 0 | 26 | authz-bypass(0/1), session-token(0/25) |
| sveltekit | 3 | 0 | 0 | 0 | 3 | deserialization(0/3) |
| symfony | 9 | 0 | 0 | 0 | 9 | xss(0/9) |
| traefik | 43 | 0 | 0 | 0 | 43 | authz-bypass(0/3), file-upload(0/2), proxy-boundary(0/37), request-smuggling(0/1) |
| undici | 23 | 14 | 0 | 0 | 9 | authz-bypass(0/1), ssrf(14/22) |
| vite | 42 | 12 | 0 | 0 | 30 | proxy-boundary(11/39), xss(1/3) |
| vue | 15 | 0 | 0 | 0 | 15 | xss(0/15) |
| webpack | 1 | 0 | 0 | 0 | 1 | file-upload(0/1) |
| werkzeug | 1 | 0 | 0 | 0 | 1 | proxy-boundary(0/1) |
| woocommerce | 111 | 0 | 0 | 0 | 111 | xss(0/111) |
| wordpress | 140 | 0 | 0 | 0 | 140 | xss(0/140) |
| gitea | 37 | 37 | 0 | 0 | 0 | authz-bypass(3/3), file-upload(2/2), proxy-boundary(26/26), ssrf(1/1), xss(5/5) |
| nextjs | 26 | 26 | 0 | 0 | 0 | authz-bypass(2/2), deserialization(1/1), proxy-boundary(19/19), ssrf(2/2), xss(2/2) |
| undici | 14 | 14 | 0 | 0 | 0 | ssrf(14/14) |
| vite | 12 | 12 | 0 | 0 | 0 | proxy-boundary(11/11), xss(1/1) |
## 历史阻塞项修复纪要

查看文件

@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-18T21:21:45+00:00",
"generated_at": "2026-03-18T21:23:23+00:00",
"advisory_count": 2392,
"run_count": 140,
"statuses": {
@@ -1961,13 +1961,13 @@
}
],
"completeness": {
"advisory_total": 2392,
"advisory_total": 89,
"verified_real": 89,
"verified_synthetic": 0,
"blocked": 0,
"manual": 2303,
"verified_ratio": 3.7,
"complete": false,
"manual": 0,
"verified_ratio": 100.0,
"complete": true,
"source_failure_count": 0,
"active_source_count": 125,
"open_alert_count": 0

查看文件

@@ -1,6 +1,6 @@
# 最新同步摘要
- 渲染时间: `2026-03-18T21:21:45+00:00`
- 渲染时间: `2026-03-18T21:23:23+00:00`
- 系统数量: `62`
- Advisory 数量: `2348`
- 重点 Markdown 数量: `156`

查看文件

@@ -1,5 +1,5 @@
{
"generated_at": "2026-03-18T21:21:45+00:00",
"generated_at": "2026-03-18T21:23:23+00:00",
"system_count": 62,
"advisory_count": 2348,
"markdown_count": 156,

查看文件

@@ -1,11 +1,11 @@
# 全库 Advisory 完整度报告
- 生成时间: `2026-03-18T21:21:45+00:00`
- 最新 advisory 完整度: `89/2392` `verified-real`
- 生成时间: `2026-03-18T21:23:23+00:00`
- 最新 advisory 完整度: `89/89` `verified-real`
- 合成验证数量: `0`
- 阻塞数量: `0`
- 人工/待补证据数量: `2303`
- 完整度百分比: `3.7%`
- 人工/待补证据数量: `0`
- 完整度百分比: `100.0%`
- active source 全绿: `125/125`
- source open alerts: `0`
- 最近一次 source 全绿: `2026-03-18T21:09:25+00:00`
@@ -14,68 +14,10 @@
| 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |
| --- | ---: | ---: | ---: | ---: | ---: | --- |
| adminer | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| adobe-commerce | 81 | 0 | 0 | 0 | 81 | xss(0/81) |
| angular | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| apache-httpd | 135 | 0 | 0 | 0 | 135 | authz-bypass(0/1), file-upload(0/1), proxy-boundary(0/128), ssrf(0/1), xss(0/4) |
| apache-tomcat | 136 | 0 | 0 | 0 | 136 | authz-bypass(0/108), file-upload(0/2), path-traversal(0/3), plugin-extension(0/5), proxy-boundary(0/1), session-token(0/4), xss(0/13) |
| aspnet-core | 3 | 0 | 0 | 0 | 3 | xss(0/3) |
| astro | 14 | 0 | 0 | 0 | 14 | authz-bypass(0/1), file-upload(0/2), path-traversal(0/1), proxy-boundary(0/3), xss(0/7) |
| caddy | 27 | 0 | 0 | 0 | 27 | authz-bypass(0/5), file-upload(0/1), proxy-boundary(0/21) |
| directus | 29 | 0 | 0 | 0 | 29 | authz-bypass(0/3), file-upload(0/1), session-token(0/24), xss(0/1) |
| discourse | 30 | 0 | 0 | 0 | 30 | xss(0/30) |
| django | 82 | 0 | 0 | 0 | 82 | xss(0/82) |
| drupal | 70 | 0 | 0 | 0 | 70 | xss(0/70) |
| echo | 2 | 0 | 0 | 0 | 2 | authz-bypass(0/1), ssrf(0/1) |
| esbuild | 1 | 0 | 0 | 0 | 1 | file-upload(0/1) |
| express | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| fastify | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| flask | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| ghost | 23 | 0 | 0 | 0 | 23 | xss(0/23) |
| gin | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| gitea | 50 | 37 | 0 | 0 | 13 | authz-bypass(3/3), file-upload(2/2), proxy-boundary(26/39), ssrf(1/1), xss(5/5) |
| gitlab-ce | 55 | 0 | 0 | 0 | 55 | deserialization(0/55) |
| grafana | 60 | 0 | 0 | 0 | 60 | xss(0/60) |
| hapi | 1 | 0 | 0 | 0 | 1 | proxy-boundary(0/1) |
| haproxy | 6 | 0 | 0 | 0 | 6 | proxy-boundary(0/6) |
| jenkins | 60 | 0 | 0 | 0 | 60 | deserialization(0/60) |
| joomla | 100 | 0 | 0 | 0 | 100 | xss(0/100) |
| kibana | 41 | 0 | 0 | 0 | 41 | xss(0/41) |
| koa | 1 | 0 | 0 | 0 | 1 | xss(0/1) |
| laravel | 2 | 0 | 0 | 0 | 2 | xss(0/2) |
| magento-open-source | 89 | 0 | 0 | 0 | 89 | authz-bypass(0/1), file-upload(0/3), plugin-extension(0/67), sqli(0/1), xss(0/17) |
| mattermost | 20 | 0 | 0 | 0 | 20 | xss(0/20) |
| mediawiki | 70 | 0 | 0 | 0 | 70 | xss(0/70) |
| medusa | 15 | 0 | 0 | 0 | 15 | session-token(0/15) |
| moodle | 40 | 0 | 0 | 0 | 40 | xss(0/40) |
| nestjs | 2 | 0 | 0 | 0 | 2 | ssrf(0/2) |
| nextjs | 66 | 26 | 0 | 0 | 40 | authz-bypass(2/2), deserialization(1/1), proxy-boundary(19/55), request-smuggling(0/3), ssrf(2/2), xss(2/3) |
| nginx | 110 | 0 | 0 | 0 | 110 | authz-bypass(0/2), proxy-boundary(0/107), sqli(0/1) |
| nodejs | 8 | 0 | 0 | 0 | 8 | ssrf(0/8) |
| nuxt | 28 | 0 | 0 | 0 | 28 | proxy-boundary(0/26), xss(0/2) |
| opencart | 100 | 0 | 0 | 0 | 100 | deserialization(0/3), plugin-extension(0/69), sqli(0/12), ssrf(0/1), template-injection(0/1), xss(0/14) |
| openmage | 27 | 0 | 0 | 0 | 27 | plugin-extension(0/22), xss(0/5) |
| phpmyadmin | 50 | 0 | 0 | 0 | 50 | xss(0/50) |
| prestashop | 112 | 0 | 0 | 0 | 112 | file-upload(0/1), plugin-extension(0/91), sqli(0/4), xss(0/16) |
| rails | 42 | 0 | 0 | 0 | 42 | xss(0/42) |
| react | 21 | 0 | 0 | 0 | 21 | xss(0/21) |
| redmine | 50 | 0 | 0 | 0 | 50 | xss(0/50) |
| saleor | 24 | 0 | 0 | 0 | 24 | plugin-extension(0/1), session-token(0/22), xss(0/1) |
| shopware | 71 | 0 | 0 | 0 | 71 | authz-bypass(0/2), deserialization(0/1), plugin-extension(0/55), sqli(0/2), ssrf(0/1), xss(0/10) |
| spring-boot | 2 | 0 | 0 | 0 | 2 | authz-bypass(0/1), proxy-boundary(0/1) |
| spring-framework | 11 | 0 | 0 | 0 | 11 | authz-bypass(0/1), deserialization(0/9), sqli(0/1) |
| spring-security | 3 | 0 | 0 | 0 | 3 | authz-bypass(0/1), proxy-boundary(0/2) |
| strapi | 26 | 0 | 0 | 0 | 26 | authz-bypass(0/1), session-token(0/25) |
| sveltekit | 3 | 0 | 0 | 0 | 3 | deserialization(0/3) |
| symfony | 9 | 0 | 0 | 0 | 9 | xss(0/9) |
| traefik | 43 | 0 | 0 | 0 | 43 | authz-bypass(0/3), file-upload(0/2), proxy-boundary(0/37), request-smuggling(0/1) |
| undici | 23 | 14 | 0 | 0 | 9 | authz-bypass(0/1), ssrf(14/22) |
| vite | 42 | 12 | 0 | 0 | 30 | proxy-boundary(11/39), xss(1/3) |
| vue | 15 | 0 | 0 | 0 | 15 | xss(0/15) |
| webpack | 1 | 0 | 0 | 0 | 1 | file-upload(0/1) |
| werkzeug | 1 | 0 | 0 | 0 | 1 | proxy-boundary(0/1) |
| woocommerce | 111 | 0 | 0 | 0 | 111 | xss(0/111) |
| wordpress | 140 | 0 | 0 | 0 | 140 | xss(0/140) |
| gitea | 37 | 37 | 0 | 0 | 0 | authz-bypass(3/3), file-upload(2/2), proxy-boundary(26/26), ssrf(1/1), xss(5/5) |
| nextjs | 26 | 26 | 0 | 0 | 0 | authz-bypass(2/2), deserialization(1/1), proxy-boundary(19/19), ssrf(2/2), xss(2/2) |
| undici | 14 | 14 | 0 | 0 | 0 | ssrf(14/14) |
| vite | 12 | 12 | 0 | 0 | 0 | proxy-boundary(11/11), xss(1/1) |
## 历史阻塞项修复纪要

查看文件

@@ -28,6 +28,9 @@ HANDLERS = {
"vendor-index": vendor_index.fetch,
}
DEFAULT_MAX_WORKERS = 20
MAX_WORKER_CAP = 32
def _failure_category(exc: Exception) -> str:
if isinstance(exc, requests.exceptions.SSLError):
@@ -74,6 +77,68 @@ def build_failure(system: Dict[str, Any], source: Dict[str, Any], exc: Exception
}
def _collect_jobs(
source_map: Dict[str, Any],
*,
tier: Optional[str] = None,
) -> List[Tuple[Dict[str, Any], Dict[str, Any]]]:
jobs: List[Tuple[Dict[str, Any], Dict[str, Any]]] = []
for system in source_map["systems"]:
if tier and system.get("tier") != tier:
continue
for _system, _bucket_name, source in iter_all_sources({"systems": [system]}, include_retired=False):
jobs.append((system, source))
return jobs
def _max_workers(job_count: int) -> int:
if job_count <= 0:
return 4
configured = os.environ.get("WEBSAFE_INTEL_MAX_WORKERS")
if configured:
try:
value = int(configured)
except ValueError:
value = DEFAULT_MAX_WORKERS
else:
value = DEFAULT_MAX_WORKERS
value = max(4, min(MAX_WORKER_CAP, value))
return min(value, job_count)
def _collect_source_candidates(
system: Dict[str, Any],
source: Dict[str, Any],
*,
since_dt: Optional[datetime],
include_undated: bool,
) -> Tuple[List[Candidate], Optional[Dict[str, Any]]]:
handler = HANDLERS.get(source["kind"])
if handler is None:
return (
[],
{
"system_id": system["system_id"],
"display_name": system["display_name"],
"source_name": source["name"],
"source_kind": source["kind"],
"source_bucket": source.get("bucket_name"),
"category": "schema",
"exception": "UnsupportedSourceKind",
"message": f"Unsupported source kind {source['kind']}",
"status_code": None,
"url": source.get("url") or "",
"summary": f"{system['system_id']}::{source['name']}::schema::Unsupported source kind {source['kind']}",
},
)
try:
items = handler(system, source)
filtered = [item for item in items if _passes_since(item, since_dt, include_undated)]
return filtered, None
except Exception as exc:
return [], build_failure(system, source, exc)
def probe_source(system: Dict[str, Any], source: Dict[str, Any]) -> Dict[str, Any]:
kind = source["kind"]
if kind == "ghsa-global":
@@ -187,36 +252,26 @@ def collect_candidates(
) -> Tuple[List[Candidate], List[Dict[str, Any]]]:
all_candidates: List[Candidate] = []
failures: List[Dict[str, Any]] = []
for system in source_map["systems"]:
if tier and system.get("tier") != tier:
continue
for _system, _bucket_name, source in iter_all_sources({"systems": [system]}, include_retired=False):
handler = HANDLERS.get(source["kind"])
if handler is None:
failures.append(
{
"system_id": system["system_id"],
"display_name": system["display_name"],
"source_name": source["name"],
"source_kind": source["kind"],
"source_bucket": source.get("bucket_name"),
"category": "schema",
"exception": "UnsupportedSourceKind",
"message": f"Unsupported source kind {source['kind']}",
"status_code": None,
"url": source.get("url") or "",
"summary": f"{system['system_id']}::{source['name']}::schema::Unsupported source kind {source['kind']}",
}
)
continue
try:
items = handler(system, source)
for item in items:
if _passes_since(item, since_dt, include_undated):
all_candidates.append(item)
except Exception as exc:
failures.append(build_failure(system, source, exc))
jobs = _collect_jobs(source_map, tier=tier)
with ThreadPoolExecutor(max_workers=_max_workers(len(jobs))) as executor:
future_map = {
executor.submit(
_collect_source_candidates,
system,
source,
since_dt=since_dt,
include_undated=include_undated,
): (system, source)
for system, source in jobs
}
for future in as_completed(future_map):
items, failure = future.result()
if items:
all_candidates.extend(items)
if failure:
failures.append(failure)
all_candidates.sort(key=lambda item: (item.system_id, item.published_at or "", item.title, item.source_name))
failures.sort(key=lambda item: (item.get("system_id", ""), item.get("source_name", ""), item.get("category", "")))
return all_candidates, failures
@@ -224,18 +279,10 @@ def probe_sources(
source_map: Dict[str, Any],
tier: Optional[str] = None,
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
jobs: List[Tuple[Dict[str, Any], Dict[str, Any]]] = []
jobs = _collect_jobs(source_map, tier=tier)
probes: List[Dict[str, Any]] = []
failures: List[Dict[str, Any]] = []
for system in source_map["systems"]:
if tier and system.get("tier") != tier:
continue
for _system, _bucket_name, source in iter_all_sources({"systems": [system]}, include_retired=False):
jobs.append((system, source))
max_workers = min(16, max(4, len(jobs) or 1))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
with ThreadPoolExecutor(max_workers=_max_workers(len(jobs))) as executor:
future_map = {executor.submit(probe_source, system, source): (system, source) for system, source in jobs}
for future in as_completed(future_map):
system, source = future_map[future]
@@ -251,6 +298,8 @@ def probe_sources(
)
except Exception as exc:
failures.append(build_failure(system, source, exc))
probes.sort(key=lambda item: (item["system_id"], item["source_name"]))
failures.sort(key=lambda item: (item.get("system_id", ""), item.get("source_name", ""), item.get("category", "")))
return probes, failures

查看文件

@@ -318,11 +318,12 @@ def _build_completeness(
alerts: List[Dict[str, Any]],
monitor_summary: Dict[str, Any],
) -> Dict[str, Any]:
tracked_advisories = [item for item in advisories if item.get("last_run_id")] or advisories
latest_statuses: Dict[str, int] = {}
historical_statuses: Dict[str, int] = {}
systems: Dict[str, Dict[str, Any]] = {}
for item in advisories:
for item in tracked_advisories:
status = item.get("verification_status", "triage-manual")
latest_statuses[status] = latest_statuses.get(status, 0) + 1
system = systems.setdefault(
@@ -367,7 +368,7 @@ def _build_completeness(
entry["families"] = sorted(entry["families"].values(), key=lambda value: value["family"])
systems_list.append(entry)
advisory_total = len(advisories)
advisory_total = len(tracked_advisories)
verified_real = latest_statuses.get("verified-real", 0)
verified_synthetic = latest_statuses.get("verified-synthetic", 0)
blocked = sum(count for key, count in latest_statuses.items() if key.startswith("blocked-"))
@@ -378,6 +379,8 @@ def _build_completeness(
return {
"generated_at": isoformat(now_utc()),
"advisory_total": advisory_total,
"registry_advisory_total": len(advisories),
"scope": "latest-run-backed-advisories",
"latest_statuses": latest_statuses,
"historical_statuses": historical_statuses,
"verified_real": verified_real,