Repository navigation
Releases: lza6/Aggregate-to-2api
Release list
v23.3.3 — CI 长期 flake 根治 + 退役域名清理
发布日期:2026-10-17
基线:v23.3.2 (8ae5018)
性质:缺陷修复批(无破坏性变更、无需迁移)
一句话
根治了持续制造 CI 噪声的唯一来源(test_ip_blocklist 的 TOCTOU flake),
并清除了散布在代码/模板/落地页里指向已退役域名的引用。
修复 1:test_ip_blocklist 的 flake 根治(CI 噪声的唯一持续来源)
根因:store 把「是否已建表」记成布尔标志,而连接用的是另一个属性
IPBlocklistStore 用 self._initialized(布尔)判断表是否建过,
但 _get_conn() 连的是 self._path。两者不绑定 —— 只要 _path 被改而
_initialized 仍为 True,写入就会落到未建表的库上:
sqlite3.OperationalError: no such table: ip_blocklist
触发路径有两条,都与"有没有等后台任务"无关:
tests/test_ip_blocklist.py的 fixture 换_path做每用例隔离 ——
但只要期间任何协程在旧路径上跑了init_schema()(把标志置回True),
换库后的写入就会落到新空库;api/request_guard.py:438的_sync_blocklist_cache当时走裸loop.create_task
—— 裸 task 不被background._BACKGROUND_TASKS持有,所以即使调用方
await background.await_pending()也看不见它,等也等不到。
本机确定性复现(非碰运气,报错与 CI junit 逐字一致):
await store._write_lock.acquire() # 占住写锁
t = asyncio.create_task(store.add_or_update(...))
await asyncio.sleep(0) # 让它越过 init_schema、卡在锁上
store._path = <新空库> # 换库
store._write_lock.release()
await t # → sqlite3.OperationalError: no such table: ip_blocklist两种症状是同一缺陷的两个落点:时序稍差时表现为 database is locked。
CI junit 里两种都出现过 —— 此前一直被当作"随机网络问题"。
注:v23.3.3 的第一版修复(仅
await_pending排空后台任务)未中根因 ——
CI 在该提交上仍复现no such table。本版才是根治。
修法(在 store 内加固,而非依赖调用方守规矩)
_initialized(布尔)→_initialized_path(路径快照):
短路检查变成「当前_path是否正是已建表的那条路径」。路径一换,检查自然失效 → 重走建表。init_schema()返回已建表的路径,全部 8 个调用点改用它开连接
(conn = await self._get_conn(path))——「检查/建表用的路径」与「随后读写用的路径」
是同一次读取,任何时刻换_path都不会让写入落到未建表的库上。
_get_conn(path=None)仍兼容无参调用。api/request_guard.py:438裸create_task→background.spawn
(同文件_record_auto_block_violation早已改走 spawn,这是漏改的同类点);
顺带修掉「裸 task 可被 GC 静默回收 → 封禁缓存不同步」。api/background.py新增await_pending(timeout):该模块此前只有
spawn/pending_count,缺"排空"入口(先例:sse_events.await_pending_sse_tasks)。
仍在 fixture 中用于文件级隔离(防在途写把上个用例的临时库写脏),
但现在不再是 no-such-table 的依赖项。- fixture 里
_initialized = False→_initialized_path = None
(前者在加固后已不是受控属性,会被当成普通实例属性静默创建,隔离形同失效)。 - 两处 docstring 改为描述实际所做的事。
回归覆盖
tests/test_ip_blocklist.py::TestPathSwitchRobustness(3 用例):
直接换库自动建表 / 在途写跨越换库(原 TOCTOU 时序)/ 切库数据互不串。
已做变异验证:把 _initialized_path == path 退回布尔语义 → 2/3 红
(第 3 个是防「修成始终用同一个库」的对照项,布尔语义下仍成立属预期)。
此前 v7.6 的
test_auto_block_after_repeated_429只靠「显式先建表」绕开同一缺陷,
未根治;本次才是根因消除。
修复 2:清除指向已退役域名 imagefree.tingfengai.art 的行为性引用
权威依据在仓库自身 —— deploy/README.deploy.md:3-6 明写该域名属「旧腾讯云 Docker 部署」,
当前生产为 https://imagefree.hwhcie.bond(Azure + systemd);项目 CLAUDE.md 同述。
故这不是域名分叉待拍板,而是行为性引用指向了死域名。
| 位置 | 影响 |
|---|---|
api/geo_ip.py |
生成的代理订阅链接指向已下线站点 —— 用户复制过去不可用(且原为硬编码) |
landing/src/components/SectionCode.vue / SectionFaq.vue |
落地页给用户看的 curl 示例用死域名 |
README.md |
接入方的 curl / ANTHROPIC_BASE_URL 示例 → 照抄必失败 |
deploy/.env.production.example |
生产 CORS 模板白名单到不再服务的域名 |
landing/index.html / sitemap.xml / robots.txt |
canonical / og / twitter / JSON-LD / 站点地图 → SEO 与分享卡片失效 |
frontend/src/pages/ApiGuide.tsx |
无 window 时的回退 base |
scripts/block_abuser.ps1 |
运维脚本默认 base → 打到死主机 |
deploy/docs/cloudflare-cdn.md |
CDN 指南(前置域名错) |
顺带消除硬编码:新增 IF_PUBLIC_BASE_URL
IF_BASE_URL 是上游(imagefree.net),不能复用为"自身公网地址"。
新增 IF_PUBLIC_BASE_URL(默认取当前生产域名):
settings_core.py加字段(带validation_alias)+ 取址 helper(懒导入 config 避循环依赖、去尾斜杠、取配置失败有兜底)deploy/.env.example/.env.production.example同步- 契约测试自动把关:
TestEnvExampleSync(双向一致)+TestEnvMappingContract(无 alias 必登记)
有意不改的(历史记录)
landing/public/release/* 与 docs/releases/archive/*、docs/reports/*、docs/prd/*、
docs/research/security-audit-v23.md —— 其中「线上实锤(匿名 curl https://imagefree.tingfengai.art)」
等描述的是当时确实发生的验证。改写会伪造历史,故保持原样。
判定为「不做」的设计项(附理由)
| 项 | 判定 |
|---|---|
requests.status 无 CHECK 约束 |
不做:加约束需表重建(破坏性迁移),且当前无实际缺陷 —— 状态值全部由代码写入并有白名单校验。收益 < 风险 |
| 删任务无级联清理(base64 / 幂等键 / DLQ / 向量) | 不做:① base64 孤儿由 clean_base64_files(TTL) 按 mtime 兜底;② 幂等键指向已删任务是唯一"用户可见"的,但 IF_IDEMPOTENCY_ENABLED 默认 False → 默认配置下不可能发生;③ 向量孤儿已由 /v1/gallery/similar 的过滤兜住。属设计权衡而非缺陷 |
验证
ruff check api/ tests/ scripts/ → All checks passed!
ruff format --check → 3 files already formatted
pytest tests/test_background_tasks.py → 5 passed(新增)
pytest tests/test_ip_blocklist.py → 31 passed(含新增 TestPathSwitchRobustness 3 用例)
跨文件组合(test_request_guard*.py ×4 + test_ip_blocklist + test_auth_ip)
→ 130 passed
pytest tests/test_config_validate.py → 25 passed(新字段过双向契约)
pytest tests/test_gallery_dedupe.py → 14 passed
pytest tests/test_version_consistency.py → 5 passed
sync_version.py --check → 全链一致 23.3.3(10 文件)
_public_base_url() 实跑 → 默认生产域名;设 env 后正确覆盖且去尾斜杠
npx tsc -b → exit 0
cd landing && npm run build → 成功
node scripts/e2e_mobile_smoke.cjs → 9 passed / 0 failed(真实 chromium)
CI run 38986559340 (5851c69) → ✅ success(6/6 job;前一个提交 6cd7ac9 为 failure)
针对本修复的专项压测(原始 TOCTOU 时序,非碰运气)
in_flight(占住写锁 → 换库 → 放行) → 300/300 ok
direct(直接换库后写入) → 300/300 ok
concurrent_mid_switch(120 协程并发中途换库)→ 120/120 ok
升级说明
无破坏性变更。新增可选配置 IF_PUBLIC_BASE_URL(不设则用默认生产域名)。
版本全链 10 文件 + 双 dist 已同步至 23.3.3。
v23.3.2 — CI 首次全绿:flaky 根因根治 + 进度条降级覆盖修复
v23.3.2 — CI 首次全绿:flaky 根因根治 + 进度条降级覆盖修复
发布日期:2026-10-16
基线:v23.3.1 (cf663ac)
性质:缺陷修复批(无新功能、无破坏性变更、无需迁移)
一句话
CI 六个 job 全部通过 —— 这是本仓库自 2026-09-08 以来第一次完整跑绿
(此前连续 11 次 failure)。同时修掉一个用户可见缺陷(进度条每 2 秒被抹回「运行中」)。
CI run 38887120926 → success
[OK] 前端 E2E + 自动化审计(e2e-smoke + a11y + perf + resp)
[OK] 代码检查(ruff)
[OK] 前端版本一致性门禁
[OK] 前端管理面板门禁(tsc + vitest + build) ← 此前长期为红
[OK] 测试 (3.11, ubuntu-latest) ← 此前长期为红
[OK] Docker 构建验证
修复 1(核心):前端 flaky 的根因
机制
isolate: false 让同一 worker 内的多个测试文件共享模块实例,
而 vi.mock 的假模块是按文件注册的 —— 后跑文件里被缓存的组件闭包
仍指向上一个文件遗留的 mock 实例,本文件新注册的 mock 对它完全无效。
本机强制两文件同 worker 连跑的实测(--no-file-parallelism):
| 顺序 | 结果 |
|---|---|
GalleryAlbum → Gallery |
Gallery.test.tsx 4 用例全红:Unable to find an element with the alt text: test prompt(DOM 里其实是 GalleryAlbum 的 2 项数据) |
Gallery → GalleryAlbum |
GalleryAlbum 7 用例全红:Cannot read properties of undefined (reading 'total') |
这两个文件是全仓库唯一「同一组件 + 同一 vi.mock 目标」的组合
(其余 26 文件不受影响:单 worker 全量实测 2 failed | 26 passed,失败仅此一对)。
这就是「同一 commit 时红时绿」的机制:文件→worker 的分配取决于哪个 worker 先空闲 ——
4 vCPU 的 runner 上两个 Gallery 文件偶尔落进同一 worker → 后跑者必红;
本机 16 vCPU 几乎不会同组,所以本地长期复现不到。
修法:isolate: false → true,配套 maxWorkers: 2
isolate: true 下每个文件都要重启 worker 环境,启动成本显著上升,
而 vitest worker 启动有 60s 内部硬超时、非 ASCII 路径(2api目录)下这段本就偏慢:
| 配置 | 结果 |
|---|---|
默认并发 + isolate:true |
28 errors / 0 tests(全部 worker 启动超时,套件完全不可用) |
maxWorkers:2 + isolate:true |
28 files / 300 passed(488s,稳定可跑) |
曾试过的替代方案「关掉 isolate」能让默认并发跑起来,但那正是引入跨文件 mock 串味的原因 ——
所以不关隔离,改为降低并发让 worker 能在超时前起来。CI(ubuntu-latest,4 vCPU)
并发更低,此值同样安全。
修复 2:进度条每 2 秒被轮询抹回「运行中」(用户可见)
useTaskProgress 的轮询每 tick 无条件 apply(mapStatusToDetail(t.status)),
而 GET /v1/tasks/{id} 的响应(TaskInfo)不含 status_detail / progress
—— 二者只存在于 SSE 事件负载。于是:
- SSE 推
solving/30→ 阶段徽章「求解中」+ 定值进度条 30 - 2 秒后轮询返回
processing→ 映射出{null,null}→ 被清回「运行中」+ 无定值进度条
用户看到的就是进度条每 2 秒跳一次。
实证来源:Tasks.test.tsx 失败时的 DOM dump —— aria-label="任务阶段:运行中"
- progressbar 无
aria-valuenow(即 SSE 已推过精确值,却被轮询打回粗粒度)。
修法:轮询的职责是「探测终态 + 无 SSE 时兜底」,不是用粗粒度信息覆盖精确信息
// 状态变了要更新;状态没变且映射不出更细的阶段 → 不动现有精确值
if (mapped.statusDetail === null && lastAppliedStatus === t.status) return;未做(记录边界):「刷新页面 / SSE 断开后只剩粗粒度阶段」是精度限制而非错误
(数据从未持久化)。要让刷新后也能看到阶段,需把 status_detail/progress 落库
并加进 TaskInfo —— 那是一次 API 契约变更(改 3 个发布点 + schema + 迁移),
属独立决策项,本批不做。
验证
npx tsc -b → exit 0
NODE_OPTIONS=--max-old-space-size=4096 npx vitest run
(CI 同款命令,不带任何 CLI 参数,完全读配置)
→ Test Files 28 passed | Tests 300 passed | 0 failed
pytest tests/test_shutdown_task_robustness.py tests/test_db_retention.py → 23 passed
pytest tests/test_version_consistency.py → 5 passed
ruff check api/ tests/ scripts/ → All checks passed!
# CI 侧(决定性)
GitHub Actions run 38887120926 → success(6/6 job)
变异验证(新增用例的判别力):把「进度条降级覆盖」的修复摘掉 →
该用例单独失败、原 4 条照过(1 failed | 4 passed)→ 精确命中。
顺带修复
tests/test_shutdown_task_robustness.py:_MANAGERS/_IDS被定义了两次(后者覆盖前者),
删除重复。测试语义与断言强度不变。.wolf/buglog.json按项目协议记入 bug-025(isolate 串味)/ bug-026(轮询覆盖)。
升级说明
无破坏性变更。无需迁移。版本全链 10 文件 + 双 dist 已同步至 23.3.2。
v23.3.1 — CI 转绿:三层级联失败逐个击破
v23.3.1 — CI 转绿:三层级联失败逐个击破
发布日期:2026-10-16
基线:v23.3.0 (b37f8af)
性质:缺陷修复批(无新功能、无破坏性变更、无需迁移)
一句话
修掉 3 个让 CI 后端 job 持续红的缺陷,后端 job 已全步骤通过;
同时把"CI 失败是级联的"这一事实搞清楚并记录。
背景:CI 已经红了一个多月
本批开工时的实测(GitHub Actions API):
| 项 | 事实 |
|---|---|
| 总运行次数 | 462 |
| 最近 100 次 | CI 54 failure + 1 cancelled、Deploy 45 failure → 成功 0 次 |
| 最后一次 success | 2026-09-08 之前 |
CI 的失败是级联的 —— 前一步失败会让后续步骤 [skip],
所以必须逐层修、逐层看:
| 提交 | 后端 job | 前端门禁 | 揭示/修复了什么 |
|---|---|---|---|
| 基线 | ❌ 单元测试 | ❌ Vitest | 起点:2 个 job 红 |
d1b8ae7 |
❌ 单元测试 | ✅ | DOM 泄漏修复在 CI 得到实证 |
26929d6 |
❌(database is locked 已消失) |
✅ | DB 隔离修复生效 |
f862ce6 |
✅ | shutdown 修复生效;集成步骤此前一直被 [skip] |
|
e55e8fb |
✅ 全步骤通过 | 后端 job 转绿 |
本批修复
P1 · stop() 冒泡异常,打断整个优雅关闭序列
api/provider_probe.py / free_proxy_fetcher.py / proxy_tracer.py 三处 stop() 都写成:
self.task.cancel()
try:
await self.task
except asyncio.CancelledError: # ← 只捕 CancelledError
passawait task 会重新抛出该任务的原始异常。 任务以任何非 CancelledError 终止时,
异常直接冒泡出 stop()。
关键区别在调用方式:
ProviderProbeManager.stop()由api/lifespan.py:177直接 await
→ 异常打断整个优雅关闭序列,其后的 DB flush / SSE 关闭 / provider 收尾
/ worker 停止 / 代理号池停止全部被跳过- 另两处经
shutdown_phase包装(asyncio.wait从不取结果)→ 不致命,
但留下 "Task exception was never retrieved" 噪声
CI 实证(run 38782001463 之前的 junit):
api/lifespan.py:177: in lifespan
await provider_probe.stop()
api/provider_probe.py:62: in stop
await self._task
RuntimeError: Task ... attached to a different loop
修法:三处补 except Exception + log.warning。关闭路径必须容错 ——
后台任务的失败不该让整个关机流程失败。
P1 · conftest.py 的 DB 隔离被外部注入绕过
if "IF_DB_FILE" not in os.environ: # ← 信任边界搞反了
os.environ["IF_DB_FILE"] = tempfile.mktemp(suffix=".db")它信任外部注入的 IF_DB_FILE。CI 工作流设了 IF_DB_FILE: "data/test.db"
(ci.yml:75)→ 本段整个跳过 → 测试落到共享的文件库 → 写路径随后抛
sqlite3.OperationalError: database is locked(junit 指向 ip_blocklist_store.add_or_update)。
同一处也是「测试可污染真实库」风险的来源:宿主 shell / .env 里任何残留的
IF_DB_FILE 都会让测试直接读写真库。
修法:改无条件指向临时库;调试需要固定库文件时用专用变量
IF_TEST_DB_FILE 显式指定 —— 不与应用的 IF_DB_FILE 混用。
P1 · test_honor 首页断言缺前置条件守卫
test_index_linked_to_zanshang 无条件断言 GET / == 200,但 / 是条件挂载:
_LANDING_DIR = ... / "landing" / "dist"
if _LANDING_DIR.exists(): # ← 不构建就不挂载
app.mount("/", ...)后端 CI job 只装 Python 依赖、不构建前端 → landing/dist 不存在 → 404。
本地开发机通常已 build 过,所以长期看不出来。
修法:加前置条件守卫,未构建时 skip 并写明原因
(沿用 test_openapi_contract.py::test_landing_built_dist_version_matches_source 的既有模式)。
验证
ruff check api/ tests/ scripts/ → All checks passed!
# 关闭容错(新增,含变异验证)
pytest tests/test_shutdown_task_robustness.py → 7 passed
└ 三处修复同时摘掉 → 3 用例全红 ✅ 有判别力
pytest + test_free_proxy_fetcher + test_proxy_tracer → 67 passed
# DB 隔离(模拟 CI 注入 IF_DB_FILE)
pytest tests/test_ip_blocklist.py → 28 passed
└ data/ci_sim.db 未被创建 ✅ 证明覆盖生效
6 文件回归(含全部原 CI 失败文件) → 84 passed
# test_honor 两条路径都实跑
有 landing/dist → 3 passed
移走 landing/dist(模拟后端 CI job) → 2 passed + 1 skipped(带明确理由)
CI 侧实证(GitHub Actions,e55e8fb):后端 job
「单元测试」「启动 mock cf_solver 并运行集成测试」全部 ✅。
已知未闭环
前端 vitest 的一条 flake(非本批引入):
e55e8fb 上报 TestingLibraryElementError: Unable to find an element with the alt text: test prompt
(DOM 显示 gallery-album 已渲染、计数「2 张」,且只有一个根 div —— 说明 cleanup 有效)。
Gallery.test.tsx 全部用例只 mock 1 项,而 GalleryAlbum.test.tsx:67 恰好渲染 2 项
→ 判为 isolate:false 下跨文件 DOM 残留的残余症状。
但它在 d1b8ae7/26929d6/f862ce6 三次 CI 运行中均为 ✅,且本机(Node 25)无法复现
→ 属 flake。未在无法验证的前提下做投机修改。
后续若稳定复现,优先怀疑 Gallery.test.tsx 的 vi.useFakeTimers() 与
afterEach(cleanup) 的执行顺序(假定时器期间 unmount 可能不完整)。
升级说明
无破坏性变更。无需迁移。版本全链 10 文件 + 双 dist 已同步至 23.3.1。
v23.3.0 — 终局审计批:CI 真实性 + 静默失效修复
v23.3.0 — 终局审计批:CI 真实性 + 静默失效修复
发布日期:2026-10-15
基线:v23.2.0 (79a5374)
性质:缺陷修复批(无新功能、无破坏性变更、无需迁移)
一句话
本批修掉 5 个真缺陷(含 2 个静默失效、1 个让整条 CI 前端门禁持续红的系统性缺陷),
并发现 CI 已整体失败约一个月这一此前无人注意到的事实。
本批修复
P1 · api/bg_tasks.py — 定时清理循环每次中途抛异常,后半段从未执行
_engine._queue_db 中 _engine 是模块(from .worker import engine),
而 _queue_db 是 Engine 的实例属性 → 每次必抛 AttributeError。
异常被外层 except Exception 吞成 warning,且因抛在 try 块中部,
导致其后所有清理逻辑(DAG run TTL 清理、告警评估 evaluate)被长期跳过。
- 修复:改从
api.meta取 Engine 单例(与 lifespan 注入同一实例)+ 防御式getattr - 回归:
tests/test_db_retention.py3 用例(AST 级断言——字符串匹配会被改名绕过)
P1 · 前端 Vitest 门禁持续红 — 测试套件系统性 DOM 泄漏
frontend/vite.config.ts 设了 isolate: false,并在注释中声称
「测试间通过 src/test/setup.ts 的 beforeEach 清理」——但该文件从未有任何清理逻辑。
CI 实证(annotations)多个测试文件同时报
TestingLibraryElementError: Found multiple elements with role "button"/"dialog"/"combobox"。
- 修复:
setup.ts显式注册afterEach(() => cleanup()) - 配置注释改为与实现一致,并标注「
isolate:false与该 cleanup 是配套的,改一方须检查另一方」 - ⚠ 本地无法判别:本机(Node 25)全套件 299 passed,且摘掉 cleanup 的变异测试本地也全绿 →
复现不出 CI(Node 20)症状。此修复需推送后重跑 CI 确认。
P1 · 覆盖率门槛文档与 CI 不一致(70 vs 80)
ci.yml:53 / deploy.yml:30 为 --cov-fail-under=80,而开发文档写 70。
危害具体:按文档自测 70% 通过 → 推 CI 撞 80% 意外失败(根源是 v7.6 统一到 80 时文档未同步)。
- 修复 4 处(仅
.claude/skills/权威副本;.agents/为未入库旧镜像,不动)
P2 · SSE / WebSocket 多订阅者场景误清回放缓冲
sse_events.py:246 与 ws_events.py:125 的连接收尾无条件调 clear_task。
同一任务被 2+ 客户端订阅时(多标签页、或重连旧连接尚未断开),先断开的一方会清掉
共享的事件缓冲 → 另一方之后用 Last-Event-ID 重连拿不到任何历史,且不报错(静默降级)。
- 修复:新增
clear_task_if_idle,把「判空」与「清理」放进同一把锁(避免 check-then-act 竞态) - 回归 5 用例:行为 3 + 调用点契约 2(后者必要——第一版只测方法不测调用点,变异测试证明它是伪测试)
P2 · 环境变量映射契约的结构性盲区
既有校验用 regex 扫源码文本抓 validation_alias="IF_X" 字面量 →
没有 alias 的字段根本不进集合,故「字段存在但 env 永远读不到」这类缺陷对它完全不可见。
实证:Settings.user_agent 是 202 个字段中唯一漏挂 alias 的。
- 修复:新增
TestEnvMappingContract(运行时model_fields反射 +NO_ENV_BY_DESIGN显式登记表,5 用例) - 新增字段若忘记挂 alias → 测试立刻红,强制作者做显式决策
P2 · deploy/.env.example 注释指向错误的修复文件
注释称「改 UA 需改 api/config/http.py」,但那里是死副本
(HTTPSettings.from_settings() 会无条件用 s.user_agent 覆盖);
20 处生产代码实际消费的是 config.USER_AGENT(源自 api/config/settings_core.py)。
照注释去改,等于白改一场。
- 修复:注释重写,指明权威文件 + 4 处需同步的位置 + 契约测试位置
P2 · skill 文档版本漂移
SKILL.md 写 v23.1.0,实际已是 v23.2.0(版本门禁只覆盖 10 个代码文件,不含文档)。
- 修复:版本修正 + 新增门禁
test_dev_doc_version_not_drifted(漂移即红,不做自动改写)
环境 / 基建改善
- 前端测试套件从"完全跑不起来"到 299/299:根因是
NODE_OPTIONS=--max-old-space-size=512
太小导致 vitest worker 起不来。设4096后 28 files / 299 tests 全绿(139s)。
排查顺序已固化进 skill:先堆内存 → 再路径 → 最后机器负载。 .claude/skills/imagefree-workflow/SKILL.md新增「CI 现状」告警块 + 取 CI 失败原因的方法。
文档
- 新增
docs/2026-10-15-audit-report.html(上下文 / 直觉 / 变更 / 未闭环项 / 10 题测验) workflow_status.md重建为事实台账(含 9 项"伪问题"判定记录,避免重复劳动).wolf/memory.md追加本批 15 条可复用事实
验证(真实执行)
ruff check api/ tests/ scripts/ → All checks passed!
pytest tests/test_version_consistency.py → 5 passed
pytest tests/test_config_validate.py → 25 passed
pytest tests/test_db_retention.py → 16 passed
pytest tests/test_sse_events_unit.py → 32 passed
pytest 受影响集合(5 文件) → 83 passed, 1 skipped
frontend: NODE_OPTIONS=4096 npx vitest run → 28 files / 299 tests passed
frontend: npx tsc -b → exit 0
scripts/probe_route_methods.py → 前端 31 路径全部存在,0 断链
scripts/e2e_mobile_smoke.cjs → 9 passed / 0 failed(真实 chromium)
12 端点冒烟 → 全 200
变异验证 5 组:env 契约 / 文档版本门禁 / bg_tasks 回归 / SSE 调用点契约 均有判别力;
SSE 行为测试第一版无判别力 → 已补调用点契约。
已知未闭环(诚实披露)
| 项 | 原因 |
|---|---|
| 前端 CI 修复的确认 | 本机(Node 25)复现不出 CI(Node 20)症状,需推送后重跑 CI 确认 |
| 后端 CI 失败的具体原因 | 失败 job 仅给 exit code 1;日志下载需 admin 权限 |
| 全量覆盖率真实数字 | 本机全量被 --timeout=120 中断(test_tryingopen.py::no_sleep) |
| 线上部署 | 主机 20.204.27.154 TCP 22/80/443/8100 全部超时(DNS 正常);按挂机处理 |
三项安全项:报告但未擅自修改(属生产策略决策,需授权):
/v1/logs+/v1/logs/ws匿名可读实时日志 —— 代码 docstring 明写「v7.7.8 公益只读开放」,属有意设计public_keymask()经/v1/chat/auth/status回显 Key 前 12 字符(41%)—— 当前IF_API_KEYS为空故未触发POST /v1/agent/memory/observe未授权写入 —— 匿名可写 agent 记忆(投毒面)
升级说明
无破坏性变更。无需迁移。版本全链 10 文件 + 双 dist 已同步至 23.3.0。
v23.2.0 — 加固批:CORS 安全修复 + 测试基建恢复
v23.2.0 — 加固批
本批以「终局审计 + 真实缺陷修复 + 验收能力补齐」为主题。核心是一条安全修复:
CORS 白名单从未生效。
🔒 安全修复
IF_CORS_ORIGINS环境变量静默失效。api/main.py挂载 CORSMiddleware 时读
config.CORS_ORIGINS,而该值来自constants.py中一行硬编码字面量"*"。
Settings.__init__里的global CORS_ORIGINS拆包后只写settings_core
命名空间,与包命名空间不是同一个变量 —— 环境变量始终无效,主链路恒为
allow_origins=["*"]。- 实测验证:白名单内 Origin 放行、白名单外无
access-control-allow-origin头 - 成因判定:既有缺陷,非本次拆分引入(旧版同样被模块级字面量覆盖)
- 配 5 个回归测试锁死
- 实测验证:白名单内 Origin 放行、白名单外无
🧪 测试基建恢复
- 前端 vitest 此前完全不可用:
npm test输出0 tests / 28 errors。根因是
vitest 4 隔离 worker 在非 ASCII 路径(2api目录)下启动超时。
→isolate:false+pool:'forks'+testTimeout:20000,现 28 files / 299 tests 全绿 - 修 2 处测试失败:
test_http_pool_config::test_env_overrides—— config 拆包引入的真回归
(importlib.reload不重执行子模块constants)test_mcp_server::test_tools_list_whitelist—— 白名单停在 v16,未同步
v18 P1-3 新增的 2 个工具
- ruff 基线 27 → 0(含一处真缺陷
F821 Undefined name ModuleType) - 记录了本仓 pytest 失败详情被
conftest的os._exit截断这一隐蔽问题
🎨 用户可见修复
- 门户画廊错误文案泄漏:原本把
Unexpected token '<', "<!DOCTYPE "...直接渲染给用户
→ 按失败类型归类中文文案 - 管理端 topbar 375px 文字重叠、聊天页动作按钮组 375px 溢出
📋 验收能力补齐
- 移动端 E2E 真跑通(此前脚本默认连 4590 但仓库无任何服务提供该端口,从未跑过)
→ 新增scripts/serve_static.cjs,补/admin与深链回退路径 - 压测复验:1→200 并发共 750/750 成功、0 个 5xx、进程存活;
压力释放后延迟 1 次请求内回落(无连接池耗尽/内存泄漏残留) - 全量测试:2521+ passed / 0 failed
⚠️ 已知遗留(非本批引入)
test_skill_sediment.py在特定文件组合下 teardown 报attached to a different loop
(跨文件事件循环污染)。已用实验证明与本批改动无关。
完整变更
6 主题提交 + 版本 bump,详见仓库 docs/2026-10-14-hardening-report.html(含 14 题测验)
与 workflow_status.md。
v23.1.0
v23.1.0 — 极限施压 + 防穿透 + 覆盖率猎杀
Spec-Kit 011 落地:010 US2「高并发极限施压探针」承诺未兑现 → 本轮真补。
极限施压(读路径)
- scripts/probe_stress.py(新探针):5 只读端点 × 并发 1→200 阶梯压测
- 200 并发 0 错误率、0 5xx、0 429、进程不崩;延迟双口径披露(本机 HTTP 排队噪声)
- 报告:docs/research/stress-penetration-v23.md + stress-v23.json
防穿透
- 畸形 JSON / 超长 prompt(200KB) / 5MB body → 全 422 不崩不 OOM
- 生图限流 10→5×429 + Retry-After:60 真实生效
- auto-block 403 实测(task-failure-burst 机制 = S-2 生产核对项实证)
覆盖率猎杀(T3/T4)
- v23 修复模块基线仅 61.5%(log_buffer 43.2% / auth 65.9% / alerting 62.1%)
- 补测后:auth 76.1% / alerting 95.4% / log_buffer 88.9%
- 顺带修复真实 bug:LogBufferHandler attrs 键被 k[6:] 错剥(attr_ 是 5 字符)→ attr_custom 变 ustom,已修
独立审查(T6)
- 首轮 NEEDS_CHANGES(F1 429 误判崩溃 P1 + F2-F8)→ 全部修复 → 复验 APPROVE
- F1 探针崩溃阈值排除 429(优雅降级≠崩溃);F3 webhook caplog 断言;F4 存活 poll
验证
- 36 补测全绿 + probe py_compile + ruff 0 error + 169 关键回归全绿
- 版本全链 23.1.0 + landing dist 重建
- HTML 报告增补 3 题(共 10 题测验)
🤖 Generated with Claude Code
v23.0.0
v23.0.0 — 终局闭环审计发布
7 节点终局审计(契约/SQL/安全/前端/solver/部署/独立审查)→ 修复 → 复验 → 发布。
安全
- S-1 /v1/logs/ws 广播脱敏(redis 密码/webhook 签名不再经匿名 WS 泄露)+ redis/alerting 源头只记 host;_redact 补 redis:// 与含 @ 密码模式
- S-3 chat/DAG 限流改受信代理解析真实客户端 IP(反代后不塌缩全站单桶)
- S-4 POST /v1/tasks/{id}/cancel 补 per-IP 限流(防批量恶意取消)
SQL / 数据层
- 5 个幂等补索引(status_dur/day/month/model/duration)
- gallery 负向 NOT IN 弃索引 → GALLERY_VISIBLE_STATUSES 正向白名单(单一常量源)
- 批量写失败回写带重试上限(_dead_letters 隔离,防头部阻塞/内存增长)
契约 / 前端
- F1 画廊软删前端补管理 Key(后端强制 check_admin_key)+ 删除失败项保留可见
- F2 聊天 429 retry_after_seconds 秒→分换算(提示真实生效)
- Costs 预算预测无管理 Key 降级提示(不再永久骨架屏);Security 封禁确认;Tasks 空态 react-router
部署原生化(线上 systemd 无 Docker)
- deploy/systemd/: prometheus/node_exporter/grafana/litestream 独立 unit + 生产配置 + 一键安装脚本
- SOP/litestream-restore/README 同步 + 过时警示
验证
- 后端 120+ 用例绿;前端 120+ 用例绿 + tsc 0;双 dist 重建
- 真实 E2E:livez/healthz ok、openapi 23.0.0、models/stats/metrics(70)
- solver 联邦实测分流 10/10、均时 0.18/0.27s
- 独立审查 N7 APPROVE(2 P1 + 5 P2 已返工复验)
报告:docs/v23-terminal-report.html(含 7 题测验)· 审计:docs/research/*-v23.md
🤖 Generated with Claude Code
v22.1.5
听风AI v22.1.5(终局审计收尾)
内容
- imagefree-workflow SKILL.md 更新至 v22.1.4(新工具集 uptime_probe/restore_drill/e2e_mobile_smoke/landing vitest + health-report 降级说明)
- 独立审查 APPROVE 后修正 2 处注释级微瑕(ws_events 心跳快照语义 / health_report 超时窗口说明)
验证
- 独立审查 v22.1.x 全批:APPROVE(82 passed + openapi 20 passed + 编译通过,无阻塞缺陷)
- 前后端契约审计:前端 28 端点全匹配后端 96 路由,100% 对齐
- 盲点扫描:无 TODO 欺骗/硬编码凭证/版本残留/日志泄漏
前置:v21.0.0 ~ v22.1.4(10 版本)
v22.1.4
听风AI v22.1.4 — P2 工程深化批
P2-2 画廊懒加载增强
Gallery.tsx瀑布流<img>补decoding="async"(loading="lazy"已有)→ 降解码阻塞- Gallery 测试 10/10 通过,入 dist
P2-3 config 安全拆分
- OTel 段抽离为独立模块
api/config/otlp.py(纯os.getenv,不依赖 settings 单例) __init__.pyre-export 保持三类读点零破坏:包属性config.OTEL_*/ 显式from api.config import OTEL_*/ settings 批量导入- 剩余 800+ 行因强耦合 settings 单例 + monkeypatch 契约不硬拆(诚实标注:风险 > 收益)
- config 契约测试 30 passed
P2-4 portal SEO 深化
- JSON-LD
softwareVersion15.1.1 → 22.0.0(严重过时修复,与全链一致) featureList补 AI DAG 编排 / PWA 安装;加防漂移注释(sync_version 对齐)- canonical / og 核对完整
P2-5 成本预测确认已闭环
- Costs 页已接
fetchCostForecast(projected_exceed_date / days_remaining / daily_avg_30d / disabled 四态齐全)——本项已在 v16.1 落地,确认无需重做
验证
- config 契约 30 + 全量 34 passed + Gallery 10/10 + 双 dist 重建
前置:v21.0.0 ~ v22.1.3(9 版本)
v22.1.3
听风AI v22.1.3
SSE 心跳参数化(P1-3 扩展)
IF_SSE_HEARTBEAT_INTERVAL配置装配(缺省 15s 向后兼容,0/非法值忽略)set_heartbeat_interval()/heartbeat_interval()动态读写;ws_events 同步动态读- 3 单测:默认 15s / 有效装配 / 非法忽略
health-report 邮箱源 import 阻塞降级(真实 E2E 修复 HIGH)
- 发现:真实 E2E 契约卡 16c/16d——
_collect_email_pool同步 import email_pool 构建 9 源做域名探测(每源 6-10s 串行 45-90s),_safeawait 后兜底无法中断同步 import → 拖垮 health-report - 修复:import 走
run_in_executor线程 +wait_for(8s)限时,超时降级 error 项;stats 主 loop 异步 - 3 单测:超时降级(同步阻塞模拟)/ ok / 异常降级
- 真实复测:受限网络下 health-report 200(8.4s,
email_pool:error 超时降级,其余维度正常)——修复前 30s 挂起
验证
- 相关 5 文件 71 passed + 1 skip 全绿
- 实际运行:mock solver + uvicorn 起服成功、livez 200、health-report 限时返回
前置:v21.0.0 / v22.0.0 / v22.0.1 / v22.1.0 / v22.1.1 / v22.1.2