Skip to content

⚠️ 自动镜像 · 此页由 docs-site/scripts/mirror-changelog.mjsdocs/changelogs/0.8.x.md 生成,请勿直接编辑此处;改源文件后 pnpm docs:build 会自动同步。

Changelog — 0.8.x

9 个版本:v0.8.0(文档细化与补全)→ v0.8.1(治理 / 合规收口)→ v0.8.2(文档深度优化)→ v0.8.3(治理 / 测试基建闭环)→ v0.8.4 + 4-hotfix(效率看板 / 人员绩效 epic)→ v0.8.5(fabric 清理 / 直方图 / 单测 25%)→ v0.8.6(v0.9.x 准备版)→ v0.8.7(防机器人 / 指标深化 / 工作台 UX 收口)→ v0.8.8(0.9.x 前最后清债版 Sparkling Tome)。

[0.8.8] - 2026-05-07

0.9.x 前最后一个清债版(Sparkling Tome)。 把 ROADMAP 中所有 P3 杂项 + 几个体量适中的 P2 一次性收齐,让 v0.9.x Grounded-SAM-2 主轴启动时不再被外围小问题打断。一次推进 12 件互相独立的事:① HTTPS / HSTS / CSP middleware(production-only,宽松基线 CSP 兼容 Vite shim + Cloudflare Turnstile,ADR-0010);② _task_with_urlTaskOut.model_validate(schema 漂移自动跟随 v0.8.7 skip_reason 类增量字段,不再漏注入);③ 失败预测「永久放弃」全链路failed_predictions.dismissed_at + POST /admin/failed-predictions/{id}/dismiss|restore + admin 列表 toggle / 紫色 dismiss 按钮 / 灰色已放弃行);④ /auth/refresh + WebSocket 鉴权过期重连(7 天 grace、jti 黑名单 + gen + is_active 三段闭环、useNotificationSocket onclose 1008/4001 自动 refresh,长会话标注员不再被踢,ADR-0011);⑤ OpenAPI snapshot pre-commit hook(schemas/api 路径变更自动重生成 snapshot 并 git add,CI 不再因忘 export 红灯);⑥ Sentry DSN 缺失 production 启动 WARN(lifespan 检测 environment+DSN,error tracking 失踪不再悄悄发生);⑦ reviewer 端 skip badge + reject modal 预填(task.skip_reason 非空时紫色 SKIP badge + 顶部说明条 + RejectReasonModal 默认选「其他」预填「标注员跳过:xxx」);⑧ CommentsPanel keyset 分页(base64-urlsafe cursor,useAnnotationCommentsInfinite + 「加载更早评论」按钮,单标注 100+ 评论不再初始化卡顿);⑨ Polygon 顶点 hit-test 改进(视觉 radius 5→6、命中 hitStrokeWidth 9,z-order 顺势按绘制顺序优先选最后落点);⑩ 前端工程化:bundle size budget(.size-limit.json + pnpm size + CI 进 vitest 后阻断;当前 main 549/600KB / vendor-konva 283/320 / vendor-markdown 123/150)+ vite envDir 收口仓库根 .env SoT;⑪ 可观测性 stack(docker-compose monitoring profile 加 prometheus + grafana service,infra/grafana/dashboards/anno-overview.json 5 panel 覆盖 HTTP / ML / Celery,新增 docs-site/dev/monitoring.md);⑫ 前端单测从 22.04% 推回 25.17%(5 个新 test 文件 ~35 case:turnstile / useCanvasDraftPersistence / RejectReasonModal / FailedPredictionsPage / useNotificationSocket / AnnotationHistoryTimeline),加 review-approve-loop.spec.ts E2E 闭环(reviewer approve → annotator notification)。

Added

  • production 安全响应头 middlewareapps/api/app/middleware/security_headers.py):HSTS max-age=31536000; includeSubDomains + X-Content-Type-Options=nosniff + X-Frame-Options=DENY + Referrer-Policy=strict-origin-when-cross-origin + 宽松基线 CSP(兼容现有 inline style + Cloudflare Turnstile)。if settings.environment == "production" 包裹注册,dev/staging 零影响。/metrics 由独立 ASGI 子应用挂载,不经过本中间件——内网 scrape 无需 HSTS/CSP。详见 docs/adr/0010-security-headers-middleware.md
  • POST /auth/refreshapps/api/app/api/v1/auth.py:284-380):用即将 / 已过期的 token 换新 token,7 天 grace。校验链:jwt.decode(verify_exp=False)now > exp + 7d 返 401 grace_expired → jti 黑名单返 401 token_revoked → user.is_active=False 返 401 user_inactive → gen 不匹配返 401 generation_outdated → 通过则换发新 token + 写 audit auth.token_refresh(detail 含 old_jti / expired_seconds_ago)+ 更新 last_seen_at。slowapi 5/min/IP 限流。详见 docs/adr/0011-websocket-token-reauth.md
  • WebSocket 鉴权过期重连apps/web/src/hooks/useNotificationSocket.ts):onclose 检测 close code 1008/4001 → authApi.refresh() → 写 authStore + 用新 token scheduleRetry;refresh 失败 → ApiError 401 → client.ts 自动 logout → 路由跳 /loginrefreshing flag 防止单次过期触发多次 refresh 打 5/min 限流。
  • 失败预测 dismiss / restore 端点apps/api/app/api/v1/predictions.py):POST /admin/failed-predictions/{id}/dismissdismissed_at = now() + audit failed_prediction.dismissed/restore 清空 dismissed_at;列表 ?include_dismissed=false(默认隐藏)。dismiss 后 retry 返 409 防误用。前端 FailedPredictionsPage 加 toggle / 紫色「放弃」按钮(confirm 二次确认)+ 已放弃行灰色背景 + 「恢复」按钮。
  • reviewer 端 skip 反馈apps/web/src/pages/Review/ReviewWorkbench.tsx + RejectReasonModal.tsx):task.skip_reason 非空时 ReviewWorkbench 顶部紫色说明条 + display_id 旁紫色 SKIP badge;ReviewPage 单任务退回时透传 skipReasonHint 到 RejectReasonModal,自动选「其他」并预填「标注员跳过:{reason}」。
  • CommentsPanel keyset 分页:后端 GET /annotations/{id}/comments/page?cursor&limit=50(base64-urlsafe cursor,DESC(created_at, id))+ schema AnnotationCommentListPage;前端 useAnnotationCommentsInfinite + 「加载更早评论」按钮;保留旧 /comments 端点向后兼容;mutation 同步 invalidate 新 / 旧 query key。
  • Polygon 顶点 hit-testapps/web/src/pages/Workbench/stage/ImageStage.tsx):编辑态顶点 Circle radius 5→6 + 显式 hitStrokeWidth 9(命中区域比视觉大),顶点重叠时 Konva 按 z-order 自然选最后绘制者,与 PolygonTool 落点顺序一致。
  • OpenAPI pre-commit hook.pre-commit-config.yaml):apps/api/app/api/**/*.pyapps/api/app/schemas/**/*.py 变更时自动 cd apps/api && uv run python ../../scripts/export_openapi.py + git add openapi.snapshot.json docs-site/api/openapi.json
  • Sentry DSN 启动 WARNapps/api/app/main.py lifespan):environment=="production" && !sentry_dsn 时 logger.warning 不阻断启动,运维忘配置不再悄悄丢线上错误。
  • bundle size budgetapps/web/.size-limit.json + apps/web/scripts/check-bundle-size.mjs + pnpm size):纯 Node std lib 无第三方依赖,main 600KB / vendor-konva 320KB / vendor-markdown 150KB 三档预算。CI vitest 之后跑 pnpm size,超阈值非零退出阻断 PR。当前实际 main 549 KB / vendor-konva 283 KB / vendor-markdown 123 KB。
  • 可观测性 stack(docker-compose monitoring profile + infra/{prometheus,grafana}/):默认不启动避免 dev 多吃 ~200MB;docker compose --profile monitoring up -d prometheus grafanahttp://localhost:3001 admin/admin。Grafana 自动 provision datasource Prometheus + dashboard 文件夹 Annoanno-overview.json 5 panel 覆盖 HTTP rate / HTTP p95 / ML p50/p95/p99 / Celery queue / Worker heartbeat。新增 docs-site/dev/monitoring.md + sidebar 入口。
  • 前端单测推到 25.17%apps/web/vitest.config.ts 阈值 22→25):5 个新 test 文件、~35 case:turnstile.test.ts(5)、useCanvasDraftPersistence.test.ts(8)、RejectReasonModal.test.tsx(4)、FailedPredictionsPage.test.tsx(9)、useNotificationSocket.test.tsx(6,含 ws reauth 关键路径用 MockWebSocket + fakeTimers + auth refresh mock)、AnnotationHistoryTimeline.test.tsx(9);vitest config 加 scripts/** 排除(build-time 脚本不算覆盖率分母)。
  • review-approve-loop.spec.ts E2E:reviewer 通过 UI approve → 后端校验 task.status=completed + annotator 视角 /notifications 取到 task.approved 通知,与 v0.8.7 reject feedback loop 镜像。
  • 新增 ADR0010-security-headers-middleware.md(HSTS/CSP 折衷与 follow-up)+ 0011-websocket-token-reauth.md(refresh grace 设计与攻击面分析)。
  • 新增后端测试 ~14 casetest_auth_refresh.py(6,含 happy path / grace_expired / inactive / generation_outdated / malformed / 速率限制 limiter.reset 兼容) + test_failed_predictions.py 加 5(dismiss / dismiss_blocks_retry / restore / 幂等 / RBAC) + test_annotation_comments_paged.py(2,cursor 串联 + invalid_cursor 400)。

Changed

  • _task_with_url 改用 TaskOut.model_validate(task, from_attributes=True)apps/api/app/api/v1/tasks.py:1025-1080):手写 30 字段 dict 改为 ORM 直读 + 4 个动态字段(file_url / thumbnail_url / image_dim+blurhash / assignee+reviewer brief)显式 patch。schema 加新字段(如 v0.8.7 skip_reason/skipped_at)只需改 TaskOut,本 helper 自动跟随,不再漏字段。
  • apps/web/vite.config.tsenvDir: "../../":仓库根 .env 收口为前后端共用 SoT,避免 apps/web/.env 与根 .env 漂移。apps/web/.env.example 已删(v0.8.7 落地)。
  • apps/web/package.json version 0.7.6 → 0.8.8:v0.8.x 起前端版本号同步主仓 release(之前漂移到 0.7.6)。
  • apps/api/app/main.py FastAPI version "0.8.3" → "0.8.8":同上。

Database

  • 0047_failed_prediction_dismissed_atfailed_predictions.dismissed_at TIMESTAMPTZ NULL + ix_failed_predictions_dismissed_at(部分索引 WHERE NOT NULL,「显示已放弃」筛选快速过滤)

Fixed

  • CommentsPanel 单标注 100+ 评论时初始化卡顿:旧 /annotations/{id}/comments 全量返回,CommentsPanel maxHeight: 240 + overflowY: auto 仅控制视觉,不限制 DOM 节点数。改 keyset 分页后首屏只渲 50 条,按需追加。

Notes

  • v0.8.8 完成后 0.9.x Grounded-SAM-2 主轴可专注 GPU backend 容器化,不被「P3 杂项 / 小动作」打断。剩余推迟项:截图自动化 14 张实跑回填、首次登录 onboarding tooltip、LoginPage progressive CAPTCHA、邮箱验证 / OAuth2 / 系统设置 admin UI、API 密钥模型、批次状态机 admin-locked 实施。
  • security headers CSP 当前为「宽松基线版」(允许 'unsafe-inline');下一步 nonce-based 收紧留作 v0.10.x 与 ProjectSettingsPage 重构同窗口做。
  • /auth/refresh 7 天 grace 是「出差一周回来 token 过期但身份还在」的体感线;超过强制重登。攻击者 stoles 一个未过期 token 后能 7 天内不停 refresh —— is_active=False + 主动 logout 仍可斩断。
  • bundle main 当前 549 KB(接近 600 KB 上限):v0.9.x 接 SAM 工具时若主包再涨需要拆 vendor 子 chunk;记录在 ROADMAP P3。
  • v0.8.8 是 v0.9.x(Grounded-SAM-2 主轴 ~5 周)启动前的最后一个准备版。

[0.8.7] - 2026-05-07

防机器人 / 指标深化 / E2E 续作 / 截图自动化 / 工作台 UX 收口。 一次性收 8 件可独立推进、互不依赖的事,让 v0.9.x Grounded-SAM-2 主轴启动前的最后一个准备版彻底干净:① CAPTCHA / Cloudflare Turnstile(注册 + 忘记密码两路防分布式刷号;TURNSTILE_ENABLED=False 时 service 层 short-circuit,dev/CI 透传不阻断);② Celery / ML Backend 指标(Prometheus 加 ml_backend_request_duration_seconds Histogram + celery_queue_length / celery_worker_heartbeat_seconds Gauge;/health/celery 扩 response 含 queues + workers 心跳;prediction_cost_stats 加 P50/P95/P99 PERCENTILE_CONT;AdminDashboard mini-stat 显示 P95);③ E2E review 反馈环 spec(reviewer 通过 UI reject → 后端校验 task.status/reject_reason;annotation.spec 升级为监听 POST /annotations 网络断言);④ Playwright 截图自动化pnpm screenshots 跑 14 场景写到 docs-site/user-guide/images,scenes.ts 配置驱动;keypoint 两张延后;不进 CI 避免 flaky);⑤ C.2 收口:阈值控件 Topbar [ ] 主控统一 + AIInspectorPanel 改 read-only 显示 + ReviewerMiniPanel(今日通过/退回/平均耗时 3 mini-stat,20s 自动 refetch);⑥ C.3 Shift 锁纵横比 / Alt 中心 resize(applyResize 加 modifiers,6 例单测);⑦ C.3 任务跳过与原因(Task.skip_reason + skipped_at + POST /tasks/{id}/skip + Topbar「跳过」按钮 + SkipTaskModal 4 项预设原因);⑧ C.3 History 持久化(useAnnotationHistory 加 sessionStorage 5min TTL persist/restore,刷新后撤销栈不丢)。

Added

  • Cloudflare Turnstile CAPTCHA 全栈apps/api/app/services/captcha_service.pyapps/web/src/lib/turnstile.tsapps/web/src/components/Captcha.tsx):后端 verify_turnstile_token 3s 超时、fail-closed;前端动态注入官方 api.js + 受控组件;TURNSTILE_ENABLED=False 时 short-circuit 通过,dev/CI 不阻断;启用后注册(/auth/register-open)与忘记密码(/auth/forgot-password)必须携带 captcha_token,校验失败 400 captcha_failed
  • Prometheus 指标三件apps/api/app/observability/metrics.py):ml_backend_request_duration_seconds(Histogram,labels=backend_id+outcome;MLBackendClient.predict / predict_interactive 计时 observe)+ celery_queue_length(Gauge,labels=queue)+ celery_worker_heartbeat_seconds(Gauge,labels=worker);/health/celery 同步暴露 queues + workers 心跳明细。
  • 预测延迟分位数/dashboard/admin/prediction-cost-stats response 加 p50/p95/p99_inference_time_ms(PostgreSQL PERCENTILE_CONT(0.5/0.95/0.99) WITHIN GROUP (ORDER BY prediction_metas.inference_time_ms));AdminDashboard MLBackendsAndCostCard 在「平均耗时」mini-stat 下方显示 P95 hint。
  • GET /dashboard/reviewer/today-miniapps/api/app/api/v1/dashboard.py):返当日 approved_today / rejected_today / avg_review_seconds 3 个数(基于 Task.reviewer_id == me + reviewed_at >= today UTC + reject_reason 区分通过/退回);前端 ReviewerMiniPanel 在 ReviewWorkbench 右侧栏顶部渲染,useReviewerTodayMini 20s 自动 refetch。
  • POST /tasks/{id}/skipapps/api/app/api/v1/tasks.py):标注员跳过任务接口,body {reason: "image_corrupt"|"no_target"|"unclear"|"other", note?: str};状态 pending/in_progress → review,其他 409;非法 reason 422;同时落 audit_logs task.skip + 设 task.skip_reason + skipped_at;前端 SkipTaskModal 4 项预设原因 + 可选 note,Topbar 提交按钮旁加「跳过」入口(仅非 review/completed 状态显示)。
  • applyResize 修饰键apps/web/src/pages/Workbench/stage/ResizeHandles.tsx):{shiftKey, altKey} 入参;shift 锁起始 aspect ratio(newW/newH = origW/origH,按 |dx| 与 |dy*aspect| 较大轴选主导方向);alt 以 bbox 中心为 anchor 反向 mirror(一边变化 dw → 总宽度变 2dw);两键叠加。ImageStage resize handler 透传 e.shiftKey/e.altKey
  • History 持久化apps/web/src/pages/Workbench/state/useAnnotationHistory.ts):sessionStorage key wb:hist:{taskId}{undo, redo, ts},TTL 5 分钟(与 prediction id 替换窗口对齐);taskId 切换 / 初始化时尝试 restore;写时机 throttle 50ms;JSON 损坏 / quota 异常静默忽略,不破坏内存 history。
  • Playwright 截图自动化apps/web/e2e/screenshots/):scenes.ts 14 场景配置(getting-started login/forgot-password、bbox toolbar/iou/bulk-edit、polygon vertex-edit/close-hint、projects create-entry/wizard-steps、review workbench/reject-form、export format-select/progress;keypoint 两张延后);screenshots.spec.ts 主入口固定 1440×900 视口、动画 disable、networkidle 等待;pnpm --filter web screenshots 触发,playwright.config.ts testIgnore 排除让 test:e2e 不跑。
  • 新增前端测试 4 个文件 / ~20 caseCaptcha.test.tsx(3)、SkipTaskModal.test.tsx(4)、ResizeHandles.test.ts(6)、ReviewerMiniPanel.test.tsx(1)+ useAnnotationHistory.test.ts 加 sessionStorage 6 case;前端单测从 277 → 297。
  • 新增后端测试 4 个文件 / 17 casetest_captcha_service.py(7,含 disabled / no-secret / 网络异常 / siteverify 200/200-fail / 超时 / 503)、test_metrics_celery.py(3,含 queue 聚合 / no-workers 503 / Prometheus Gauge 写入)、test_ml_client_metrics.py(3,含 success/error Histogram observe + backend 自报值优先 wall-clock fallback)、test_dashboard_reviewer_mini.py(4,含权限 / 多 reviewer 隔离 / today only)+ test_open_registration.py 加 2(CAPTCHA 缺失 400 / token 通过)+ test_prediction_cost_stats.py 加 1(PERCENTILE_CONT 100 样本断言)+ test_task_skip.py(5,含合法/非法 reason / 状态 409 / audit / 自分派)。
  • .env.example / apps/web/.env.example:新增 TURNSTILE_ENABLED / SITE_KEY / SECRET_KEY + VITE_TURNSTILE_SITE_KEY 占位 + 注释指向 Cloudflare 控制台。

Changed

  • AIInspectorPanel 阈值控件改为 read-only 数值apps/web/src/pages/Workbench/shell/AIInspectorPanel.tsx):原生 <input type="range"> slider 移除,改为「在工具栏使用 [/] 调整」提示文案 + 滚轮微调 fallback;阈值百分比与刻度尺保留作视觉反馈。Topbar [/] 仍是主控(行为零变化)。
  • /health/celery response shape 升级apps/api/app/api/health.py):workerslist[str] 升级为 list[{name, last_heartbeat_seconds_ago, pool_max}];新增 queues: list[{name, length}];旧测试桩(仅 mock ping())通过 try/except 降级为空 active/reserved/stats 兼容。
  • vitest coverage thresholds 临时降到 22:v0.8.7 引入 8 个新组件/hook,分母增长大于新单测覆盖(实测 22.04%,297 case);下一版优先补 ProjectSettingsPage / AuditPage / WorkbenchShell hook,目标推回 ≥ 25 → 30。

Database

  • 0046_task_skip_reasontasks.skip_reason VARCHAR(50) + tasks.skipped_at TIMESTAMPTZ(NULL 默认;现有数据无需迁移)

Fixed

  • pydantic-settings 2.13 默认 extra=forbid 导致本地启动炸apps/api/app/config.py):.env 中含 VITE_* 前端变量与 Settings 字段不匹配,pydantic 2.13 起拒绝;显式 class Config: extra = "ignore" 让前后端共用 .env。

Notes

  • v0.8.7 是 v0.9.x(Grounded-SAM-2 接入)启动前最后一个准备版;CAPTCHA 与指标体系到位后 v0.9.x M0/M1 可专注 GPU backend 容器化。
  • Turnstile 占位 sitekey 1x00000000000000000000AA(永远成功)/ 2x00000000000000000000AB(永远失败)官方测试用,便于 production 部署前自查链路。
  • E2E review-feedback-loop.spec.tsannotation.spec.ts bbox 落库网络断言均依赖完整启动栈(docker + api + dev),CI workers=1 + fullyParallel=false 串行跑无并发风险。
  • 截图自动化 14 张为基线,部分场景(iou.png 双框 / progress.png 真实 50%)需 maintainer 准备数据后人工抓帧覆盖;keypoint 两张随非 image-det 工作台 epic 一起补。

[0.8.6] - 2026-05-07

v0.9.x 准备版。 Grounded-SAM-2 主轴(v0.9.0 起 ~5 周)启动前,把不依赖 GPU 的 6 件事提前做完:① 协议 §2.2 context.typetext(schema docstring + 文档同步,exemplar 留给 v0.10.x SAM 3);② ML Backend 周期健康检查(Celery beat 每 60s,串行 + 0-3s 抖动错峰,新增 ml_backends.last_checked_at);③ Project.ml_backend_id 真实绑定 + GeneralSection / CreateWizard 改造(ai_model 保留作 display hint,绑定 backend 时自动用 backend.name 覆盖;ON DELETE SET NULL);④ AdminDashboard 预测成本卡片(/admin/prediction-cost-stats?range=7d|30d + 4 mini-stat:调用数 / 平均耗时 / 失败率 / 总成本,by_backend 维度);⑤ apps/_shared/mask_utils/ 共享 Python 包骨架(mask_to_polygon cv2.findContours + shapely.simplify + 顶点归一化 + 单测,v0.9.0 grounded-sam2-backend 与 v0.10.x sam3-backend 共用);⑥ 失败预测重试管理页(Celery 异步 + WebSocket 推 failed_prediction.retry.{started,succeeded,failed} 进度,单条 max 3 次,超过返 409)。一次性收 ROADMAP §A 四条 + 提前完成 v0.9.5 M5 三件,让 v0.9.0 一开张直接做 GPU backend 容器化主线,不被外围杂事打断。

Added

  • 协议 §2.2 context.typetextdocs-site/dev/ml-backend-protocol.md):v0.9.x Grounded-SAM-2 文本批量 prompt 入口;InteractiveRequest.context 仍为开放 dict 不锁死,docstring 列出 4 种 type 与示例。
  • ML Backend 周期健康检查 Celery taskapps/api/app/workers/ml_health.py):check_ml_backends_health 每 60s 触发,串行扫所有 backend,每个调用前 random.uniform(0, 3) s 抖动错峰,避免同 GPU 节点 CUDA 上下文 contention。复用 MLBackendService.check_healthstate + last_checked_atMLBackendOut.last_checked_at 暴露给前端。
  • Project.ml_backend_id 外键绑定:Alembic 0044_project_ml_backend_id(FK ml_backends.id ON DELETE SET NULL + 部分索引 WHERE NOT NULL);ProjectCreate/Update/Out schema 同步;MLBackendService.get_project_backend(project_id) 优先返回显式绑定,否则 fallback 到 is_interactive=True && state=connected 任选;POST/PATCH /projects handler _apply_backend_display_hintbackend.name 覆盖 ai_model
  • GeneralSection MLBackend 下拉apps/web/src/pages/Projects/sections/GeneralSection.tsx):开启 AI 后展示「实际 ML Backend」下拉,列出当前项目已注册 backends + 在线状态徽标 + 是否交互式;选「未绑定」也允许(仅显示 ai_model hint)。CreateProjectWizard 步骤 3 加引导文案「项目创建后到 ML 模型 tab 注册 backend 再回基本信息绑定」。
  • AdminDashboard 预测成本卡片GET /admin/prediction-cost-stats?range=7d|30d 端点基于 predictions × prediction_metas × failed_predictions × ml_backends 聚合;前端 MLBackendsAndCostCard 替换原「ML 后端状态」单卡,4 mini-stat(调用数 / 平均耗时 / 失败率 / 总成本)+ Range toggle;异常时降级返回零值避免 Dashboard 黑屏。
  • apps/_shared/mask_utils/ Python 包pyproject.toml (anno-mask-utils@0.1.0, py>=3.10, numpy + opencv-python-headless + shapely)、src/mask_utils/{polygon,normalize}.pytests/test_polygon.py(圆 / 方 / 空 mask × IoU≥0.95)。本期 apps/api 不依赖此包;为 v0.9.0 M0/M3 grounded-sam2-backend 与 v0.10.x sam3-backend 共用而提前就位。
  • 失败预测重试管理页 /admin/failed-predictionsapps/web/src/pages/Admin/FailedPredictionsPage.tsx):表格列 项目 / 任务 / Backend / 错误类型 / 消息 / 重试 N/3 / 时间 / 重试按钮。后端新路由 apps/api/app/api/v1/predictions.pyGET /admin/failed-predictions?page&page_size(分页 + 关联 task / project / backend)+ POST /admin/failed-predictions/{id}/retry(202 投递 Celery task)。PageKeyadmin-failed-predictions;super_admin / project_admin 可见。
  • retry_failed_prediction Celery taskapps/api/app/workers/predictions_retry.py,路由 ml 队列):读 failed_prediction → 推 ws failed_prediction.retry.started → 调 backend.predict → 成功则插 predictions + 删 failed + 推 succeeded;失败则 retry_count += 1 + last_retry_at + 推 failed。max=3 软上限由路由层判断(HTTP 409)。
  • WebSocket retry 事件 invalidateapps/web/src/hooks/useNotificationSocket.ts):消息 typefailed_prediction.retry. 起头时同时 invalidate ["admin", "failed-predictions"] query,列表自动刷新无需轮询。
  • AdminDashboard 失败预测入口卡:跳转 /admin/failed-predictions
  • 20 个新单测test_ml_backend_schemas(6)、test_ml_health_worker(6,含 worker 异常隔离)、test_projects_ml_backend_binding(6,含 ON DELETE SET NULL)、test_prediction_cost_stats(5)、test_failed_predictions(6,含 max 3 次返 409)、mask_utils 独立包 test_polygon(7)。

Changed

  • Project.ai_model 语义重定义为 display hint:保留字段不删除,绑定 backend 时自动用 backend.name 覆盖;未绑定时仍可手填作为 Badge 显示(5 处 Dashboard / ProjectGrid 渲染逻辑零改动)。
  • MLBackendService.check_healthlast_checked_at:除原有的 state 更新外,每次调用都打上时间戳,便于 UI 显示心跳新鲜度。

Database

  • 0043_ml_backend_last_checked_atml_backends.last_checked_at TIMESTAMPTZ(周期健康检查时间戳)
  • 0044_project_ml_backend_idprojects.ml_backend_id UUID FK ml_backends(id) ON DELETE SET NULL + ix_projects_ml_backend_id 部分索引
  • 0045_failed_prediction_retry_countfailed_predictions.retry_count INT NOT NULL DEFAULT 0 + last_retry_at TIMESTAMPTZ + extra JSONB

Notes

  • v0.8.6 提前完成的 v0.9.5 M5 任务(ROADMAP/[archived]0.9.x.md 同步标记):周期健康检查、失败预测重试 UI、预测成本卡片。M5 工时从 3d 降到 1.5d,仅剩 backend 显存监控 + ADR-0010/0011 + deploy 文档。
  • mask_utils 包 ~50MB cv2 依赖暂不进 apps/api 镜像;v0.9.x backend 镜像本就有 cv2 可复用。
  • 30d 内 predictions 数据量级 < 10K,predictions.created_at 暂不加 BRIN 索引;CHANGELOG 留 TODO「数据量上 10 万后再加」。

[0.8.5] - 2026-05-07

fabric 清理 / 24-bar 专注时段直方图 / 单测 25% 硬阻断 / E2E 写实化。 收口 v0.7.x ~ v0.8.4 三个尾巴:① 清掉 fabric@^6.5.0 dead dep(apps/web/src/ 全量零引用,仅 App.tsx:22 注释提到,ADR-0004 评估通过);② v0.8.4 已造好的 <Histogram> 组件接到个人 dashboard,标注员可看到当日 0-23 时的标注分钟数节律;③ 前端单测覆盖率从 v0.8.3 的 10.88% 推到 25.28%,CI 阈值同步上调到 25 严格阻断回退;④ E2E annotation/batch-flow 从「最小 happy path」升级为带 bbox 拖框 + 多角色串联的写实场景,工作台引入 4 处 data-testid 提供稳定 selector,后端补 _test_seed.advance_task 辅助端点跳过 UI 链路串联多角色。

Added

  • AnnotatorDashboard「今日专注时段分布」24-bar 直方图:后端 /dashboard/annotator 新增 hour_buckets: list[int] 字段,按 EXTRACT(hour, TaskEvent.started_at) GROUP BY 聚合 duration_ms / 60_000;前端复用 v0.8.4 的 <Histogram> 组件,xLabels=["00:00", "23:00"]
  • 工作台 data-testid(4 处)tool-btn-{id}(ToolDock 工具按钮,含 box / hand / polygon / canvas)、workbench-stage(ImageStage 容器)、workbench-submit(Topbar 提交质检按钮)、settings-tab-{key}(ProjectSettingsPage 8 个 section tab)。
  • POST /api/v1/__test/seed/advance_task 测试辅助端点:直接置 task 到目标状态(pending / annotating / submitted / review / completed / rejected),可同时绑定 annotator / reviewer,绕过 UI 链路。E2E batch-flow.spec 用此跳过画框 / 提交流程,专注验证多角色交接。
  • 9 个新前端测试文件(277 case):Histogram / AnnotatorDashboard / ReviewerDashboard / AdminDashboard / ViewerDashboard / LoginPage / RegisterPage / PasswordPages(Forgot + Reset)/ InviteUserModal / useDashboard。
  • vitest setup localStorage / sessionStorage polyfill:jsdom 在 about:blank(opaque origin)下不提供 storage,导致 zustand persist 在 setState 时炸 storage.setItem is not a function,统一注入内存 storage 兜底,afterEach 清理。

Changed

  • apps/web/vite.config.ts coverage thresholds:lines / statements 10 → 25,functions / branches 维持。pnpm test:coverage 实测 lines=25.28%(277 case,0.28pp 容差)。
  • E2E annotation.spec:保留 smoke「路由可达」case;新增 bbox 拖框 case(getByTestId tool-btn-boxboundingBox() 计算坐标 → mouse.move/down/up 模拟拖框 → seed.advanceTask(submitted) 模拟提交结果 → 断言 URL 未崩溃)。
  • E2E batch-flow.spec:保留 smoke「项目设置页 200」case;新增三角色串联(annotator submit → reviewer 看到 /review → admin 切到 batches tab)。
  • apps/web/package.json:删 fabric@^6.5.0(lockfile 重生);apps/web/src/App.tsx:22 注释里 konva / fabrickonva

Fixed

  • vitest jsdom 环境 zustand persist 崩溃:v0.8.3 起 11 个 case(usePermissions / stores)依赖 zustand persist 写 localStorage,但 jsdom 默认 opaque origin 抛 SecurityError: localStorage is not available,导致 storage.setItem is not a function。setup 注入内存 storage polyfill 解除。
  • tests/factory.create_project classes 字段类型不匹配:写的是 [{"name": c}]ProjectOut.classes: list[str],导致 GET /projects/:id 在 ResponseValidationError 上 500。E2E 写实化暴露的预先存在 bug。修为 list(classes or ["car", "person"])
  • _test_seed.seed_reset 非特权用户工作台空任务:seed 创建的 task 无 batch_id,非特权用户在 list_tasksbatch_visibility_clause 过滤后看不到孤儿任务,工作台显示「该项目暂无任务」。reset 现在创建 status='annotating' + annotator_id/reviewer_id 双绑定的 default batch,并把 5 个 task 全归入;同时补 ProjectMember 行(annotator + reviewer),否则 RequireProjectMember/projects/:id/annotate 拦回 dashboard。
  • playwright fullyParallel seed/reset 互相覆盖:seed/reset 是数据库 TRUNCATE 全局操作,多 spec 并发会让 fixture 拿到对方的造数结果而 500。playwright.config.ts 改为 fullyParallel: false, workers: 1

Removed

  • fabric@^6.5.0:v0.7.x ~ v0.8.0 ADR-0004 评估时已确认 apps/web/src/ 全量零引用,本期清理。bundle 减少 ~150KB / 一项 supply-chain 风险面。

[0.8.4 hotfix] - 2026-05-06

接通 v0.8.4 的活跃维度占位字段。 v0.8.4 与 v0.8.3 并行开发,落地时心跳基座尚未合并,因此 active_minutes_today / streak_days / activity_score 三处保持 None / 50 占位待下一版接通;v0.8.3 合并后即解除阻塞,本热修复直接基于 task_events 把三处真实化,前端组件不动(schema 字段类型不变)。

变更

  • GET /dashboard/annotator
    • active_minutes_todaySUM(task_events.duration_ms) / 60000 WHERE started_at >= today UTC
    • streak_days ← 从今天倒推 (started_at AT TIME ZONE 'UTC')::date distinct 连续天数(30 天上限),口径与 mv_user_perf_daily.day 一致。
  • GET /dashboard/admin/people
    • activity_score ← 7d 窗口 SUM(task_events.duration_ms) 按用户分组后的团队百分位(沿用 _percentile_rank),替换固定 50 占位。直查 task_events 而非 mv 视图,避免 1h 刷新 lag 让活跃用户被打回中位。
  • 测试tests/test_task_events_batch.py 新增 2 例(共 8 例):annotator 接通后 active_minutes_today / streak_days 数值正确;admin/people 活跃用户 activity_score 严格 > 非活跃用户。

修复

  • apps/api/alembic 版本号漂移:开发环境出现"relation "task_events" already exists",因 0.8.3/0.8.4 并行 worktree 中已 apply 0039–0042 但主仓 alembic_version 停留在 0038。运行时无影响,需 alembic stamp head 同步(schema 已正确)。

[0.8.4] - 2026-05-06

效率看板 / 人员绩效 epic。 一次性把 ROADMAP P1「Layer 1 数据沉淀 + Layer 2 个人 dashboard 强化 + Layer 3 管理员人员看板」三层落地:标注员/审核员能自查产能/质量/投入;管理员有 /admin/people 卡片网格 + 抽屉下钻看全员效率。

数据沉淀Task.assigned_at 写入点全量改派(_cascade_task_assignee / task.submit 兜底 / 用户注销改派);新表 task_events(工作台 useSessionStats 每 20 条 flush);物化视图 mv_user_perf_daily 每小时 refresh。

L2/L3 占位字段active_minutes_today / streak_days / activity_score 当前后端返 None / 50。v0.8.3 已落 last_seen_at 心跳基座,下一版只需切 dashboard 端点的占位逻辑即可点亮,前端组件无需改动。

新增

  • 迁移 0039-0042(4 个,链在 v0.8.3 的 0038_user_last_seen_at 之后):
    • 0039_task_assigned_attasks.assigned_at TIMESTAMPTZ + 部分索引 (assignee_id, assigned_at DESC) WHERE assigned_at IS NOT NULL。idempotent(ADD COLUMN IF NOT EXISTS)。
    • 0040_task_events:新表 task_events(id PK, task_id, user_id, project_id, kind ENUM(annotate|review), started_at, ended_at, duration_ms, annotation_count, was_rejected) + 三个时序索引 + CHECK 约束。
    • 0041_mv_user_perf_daily:物化视图 (user_id, project_id, kind, day) → throughput / median_duration_ms / p95_duration_ms / rejected_n / active_minutes,UNIQUE 索引支持 CONCURRENTLY refresh。
    • 0042_user_weekly_targetusers.weekly_target_default + project_members.weekly_target,替换 AnnotatorDashboard.tsx weeklyTarget = 200 硬编码。
  • 后端端点(4 个):
    • POST /auth/me/task-events:batch:单批 ≤ 200 条,user_id 强制覆盖为登录用户(防伪造);走 Celery 异步队列 app.workers.task_events.persist_task_events_batch,broker 不可用 → sync fallback。
    • GET /dashboard/admin/people?role=&period=&sort=&q=:super_admin only,返回每人 {main_metric, throughput_score, quality_score, activity_score, sparkline_7d, alerts},告警 chip:被退回率 > 15% / 周环比降 > 30%。
    • GET /dashboard/admin/people/{user_id}?period=4w:详情 — 4 周趋势 / 项目分布 / 耗时直方图(10 桶 + p50/p95)/ 最近 50 条 timeline。
    • GET /dashboard/annotator / /reviewer 扩展:annotator 增加 median_duration_ms / rejected_rate / reopened_avg / weekly_compare_pct / weekly_target;reviewer 增加 median_review_duration_ms / reopen_after_approve_rate / weekly_compare_pct / daily_review_counts
  • Celery beat hourly refreshrefresh_user_perf_mv crontab minute=5REFRESH MATERIALIZED VIEW CONCURRENTLY mv_user_perf_daily;端点优先读视图,当日窗口直查 task_events 兜底。
  • 前端原子组件<SectionDivider> / <RadialProgress> / <Histogram>(仿 RegistrationSourceCard SVG bar 风格,不引图表库)。
  • AnnotatorDashboard 5 卡 → 9 卡三段:产能(待标 / 今日 / 本周 + 周环比 + sparkline / 单题中位耗时)+ 质量(原创比例 / 退回率 / 重审次数 avg)+ 投入(活跃时长 / streak / 累计),心跳依赖项暂显
  • ReviewerDashboard 5 卡 → 6 卡两段:产能(待审 / 今日 + 周环比 + sparkline / 平均审核耗时 / 累计)+ 质量(24h 通过率 / 历史通过率 / 二次返修率)。
  • AdminPeoplePage(/admin/people:sticky 筛选栏(角色 / 时间 / 排序 / 搜索 → URL search params 同步)+ 响应式卡片网格(auto-fill minmax 280px)+ 右侧抽屉个人详情(4 hero KPI + 4 周趋势双 sparkline + 耗时直方图 + 项目分布 + timeline)。super_admin RBAC 双重门(路由 + endpoint)。
  • AdminDashboard 入口卡:顶部 4 总量 StatCard 下方加「成员绩效 →」可点击 Card,跳 /admin/people
  • 工作台埋点useSessionStats(currentTaskId, projectId, kind) 现支持 pendingEvents 缓冲,每 20 条或 unmount/pagehide 时 flush 到 meApi.submitTaskEvents。失败静默丢弃避免雪崩。
  • ADR-0009docs/adr/0009-task-events-table-and-partition.md 记录 task_events 月分区两阶段方案:Stage 1(本期)普通表 + 时序索引;Stage 2(行数 > 1M 或单月 INSERT > 100k 触发)按 started_at RANGE 分区,参考 ADR-0006 / ADR-0008 模式。
  • 测试tests/test_task_events_batch.py(6 例)覆盖 sync fallback INSERT 落库、ended_at < started_at 422、/admin/people 403 / 200 / 404 / detail 200。

变更

  • alembic env.pyconfig.set_main_option("sqlalchemy.url", ...) 改为只在调用方未注入 URL 时设置,否则 conftest 注入的 test DB URL 会被覆盖到 dev DB。
  • app/services/batch.py:_cascade_task_assigneeuser_id 非空时 assigned_at = func.now(),否则置 NULL。
  • app/api/v1/tasks.py:548 / app/api/v1/users.py:587:兜底分派 / 注销改派路径同步写 assigned_at
  • app/api/v1/dashboard.py:annotator/reviewer 端点新增字段(详见上文)。
  • OpenAPI snapshot 同步:新增 7 个 schema(AdminPeopleList / AdminPersonItem / AdminPersonDetail / TaskEventIn / TaskEventBatchIn / TaskEventBatchOut + reviewer/annotator 字段)。

推迟(下一版收口)

  • 接通活跃时长 / streak / activity_score占位 None / 50 → 已在 v0.8.4.1 hotfix 接通,口径切到 task_events 而非 last_seen_at(埋点累计耗时比心跳点位更贴合"投入维度"语义)。
  • L3 leaderboard TabGET /dashboard/admin/people/leaderboard 端点占位未实现,UI 也未暴露。

[0.8.3] - 2026-05-06

治理 / 测试基建闭环。 把 ROADMAP 中四块 P1/P2 一次性收齐:在线状态心跳机制(替代旧的 status==online 偏差)、审计不可变 trigger 测试覆盖(兜底 security.md 的可靠声明)、前端单测推到稳定 baseline 并切硬阻断(v0.7.6 8.68% → 10.88%)、E2E 三 spec 写实 + 摘 continue-on-error。后续效率看板 / SAM 接入等 P1 大件不再被基建空缺挡路。

新增

  • 在线状态心跳机制(A · UsersPage):迁移 0038 给 users 表加 last_seen_at 列(带索引);POST /auth/me/heartbeat 端点(前端 30s 周期触发,仅 visible tab 跑);Celery beat 任务 mark_inactive_offline(每 2 分钟扫描,把超 OFFLINE_THRESHOLD_MINUTES=5min 未活跃的 online 用户置 offline);GET /users/stats 返回 {total, online, weekly_active}weekly_active 基于 last_seen_at >= now-7d。前端 useHeartbeat hook 挂在 AppShell + FullScreenWorkbench;UsersPage 顶部「本周活跃」改读后端聚合。修复了「关浏览器永远在线」的偏差,同时为下一版「效率看板 P1.投入 维度」打底。
  • 审计日志不可变 trigger 测试覆盖(B · 治理合规)tests/test_audit_immutability.py 5 条 case 覆盖 ① UPDATE 阻断 ② DELETE 阻断 ③ SET LOCAL "app.allow_audit_update" = 'true' 豁免后允许 ④ 豁免不跨 SAVEPOINT 泄漏 ⑤ COPY 路径(pg_restore 用)走 BEFORE ROW trigger 不触发。security.md 已声称可靠,本版补测试兜底,防止后续分区 / trigger 重写时回归。
  • E2E 基础设施(B · 测试/DX)apps/api/tests/factory.pycreate_user / create_project / create_task / create_batch,幂等可重入)+ apps/api/app/api/v1/_test_seed.py(仅 environment != 'production' 挂载,POST /api/v1/__test/seed/reset truncate + 重建 admin/annotator/reviewer + 1 项目 + 5 task;POST /__test/seed/login 跳密码发 JWT)+ apps/web/e2e/fixtures/seed.ts(Playwright fixture,提供 seed.reset() / seed.injectToken(page, email) / seed.loginViaUI(page, email, pwd))。
  • 前端单测 8 个新测试文件 + 切硬阻断
    • useSessionStats.test.ts(ring buffer 边界 + dt 过滤)
    • useHeartbeat.test.ts(visible / hidden 切换 + 401 静默)
    • usePermissions.test.ts(5 角色权限分支)
    • auditLabels.test.ts(业务动作过滤 / 中文标签 fallback)
    • iou.test.ts + polygonGeom.test.ts(几何工具)
    • transforms.test.ts(bbox / polygon / prediction 映射)
    • useAnnotationHistory.hook.test.ts(栈状态机 + 切任务清栈 + tmpId 替换)
    • stores.test.ts(authStore / appStore / bugDrawerStore 三个 zustand store)

变更

  • apps/api/app/api/v1/auth.py:99-104login 成功时同步写 user.last_seen_at = now,与 last_login_at 同步。logout/logout_all 保持仅切 status='offline'(last_seen_at 不重置,登出仍是活跃)。
  • apps/api/app/api/v1/me.py:新增 POST /heartbeat(无 body,204 返回,刷新 last_seen_at + status='online')。
  • apps/api/app/workers/celery_app.py:beat_schedule 新增 mark-inactive-offlinecrontab(minute="*/2"));includeapp.workers.presence
  • apps/api/app/config.py:新增 offline_threshold_minutes: int = 5 配置(30s 心跳 × 10 容差)。
  • apps/web/src/pages/Users/UsersPage.tsx:150:「本周活跃」从本地 filter(status==="online") 改读 useUsersStats().weekly_active
  • apps/web/src/App.tsx:AppShell 与 FullScreenWorkbench 各调一次 useHeartbeat(),覆盖工作台与主壳两条路径。
  • apps/web/vite.config.ts:coverage 配置加 thresholds(lines/statements 10%、branches 60%、functions 30%)+ exclude(types / mock data / Konva 工具 / bugReportCapture 等不可单元测部分)。低于阈值时 vitest 退出非 0。
  • codecov.yml 切硬阻断backend.informational: false(target 60%,已稳定)+ frontend.informational: false(target 10%,实测 10.88% 留 0.88pp 容差)。下一版逐步推 frontend 向 ROADMAP P2 列出的 25%。
  • .github/workflows/ci.yml e2e job:摘掉 continue-on-error: true;env 加 ENVIRONMENT=development 暴露 _test_seed router,加 PLAYWRIGHT_API_BASE 让 fixture 知道后端地址。
  • apps/web/e2e/tests/{auth,annotation,batch-flow}.spec.ts 三个 spec 全部写实:
    • auth.spec.ts:登录 → dashboard / 错密码留页 + 错误条 / 未登录访问 /dashboard 跳 /login
    • annotation.spec.ts:annotator 跳登录后 /annotate 路由可达
    • batch-flow.spec.ts:admin 跳登录后项目设置页可达

推迟(明确不在本期)

  • 效率看板 Layer 1/2/3:依赖本版心跳但工作量独立(2-3 + 1-2 + 3-4 天),留给 v0.8.4+
  • ADR-0008 admin-locked 实施:v0.8.2 ADR 仍 Proposed,scheduler 测试覆盖未补
  • OAuth / CAPTCHA / 邮箱验证:开放注册基座已稳,加固延后
  • fabric.js dead dep 清理:非心跳 / 测试主题
  • 前端单测推到 25%:8 个新测试 + 167 case 把 baseline 从 8.68% 推到 10.88%;25% 需再 6000 行覆盖,留给 v0.8.4+

[0.8.2] - 2026-05-06

文档深度优化。 把 v0.8.0 / v0.8.1 留下的四处文档机制缝隙以自动化方式补齐:ADR 不再孤悬 GitHub、pnpm docs:build 进 PR gate、how-to 与源码漂移即报错、ML Backend 协议有可跑样板。后续文档随代码自然漂移即被 CI 拦下,免人工巡检。

新增

  • pnpm docs:build 进 CI gate.github/workflows/ci.yml 新增 docs-build job,所有 PR 都跑(不带 paths 过滤),约 5s 成本;dead-link / hotkeys SoT 漂移 / snippet 不一致即时阻断。原 docs.yml 保留为 GitHub Pages 发布触发,无重复构建影响。
  • docs-site/scripts/check-doc-snippets.mjs + snippet 标记机制:扫 .md 中的 <!-- snippet:PATH:START-END --> ... <!-- /snippet --> 块,逐行比对源文件区间与代码块内容,不一致即打印 diff 并 exit 1。docs-site/package.jsonprebuild 链中追加;pnpm check:snippets 顶层别名可直接调用。
  • add-api-endpoint.md 加 snippet 标记:logout 代码块绑定 apps/api/app/api/v1/auth.py:239-266;同时把 v0.8.0 写入时已漂移的内容(漏 current_user.status = "offline"、AuditService.log 压缩单行)对齐到当前真实源码。后续 logout 函数任一字符变化 prebuild 即报。
  • docs-site/scripts/mirror-adr.mjs + ADR 接入 sidebar:把 docs/adr/*.md 镜像到 docs-site/dev/adr/ 让 VitePress 渲染,文件头注入"自动镜像"警告条;同时输出 sidebar.generated.jsondocs-site/.vitepress/config.ts 顶部读取该 JSON,在 /dev/ 侧边栏底部新增「ADR(架构决策)」可折叠组(默认 collapsed)。.gitignore 排除 docs-site/dev/adr/,避免 mirror 产物入库。
  • docs-site/dev/examples/echo-ml-backend/ 可执行样板(5 文件):main.py(协议 §1-3 四端点完整 FastAPI 实现)+ requirements.txt + Dockerfile(python:3.11-slim + uvicorn)+ test.sh(curl 三连击 health/setup/predict)+ README.md(uvicorn / docker 两种启动方式 + 接入平台步骤)。ml-backend-protocol.md §8 改为 <!-- snippet --> 引用 main.py:1-63,inline 示例与样板永远同步。
  • ADR-0008(Proposed)批次 admin-locked 字段docs/adr/0008-batch-admin-locked-status.md 把 ROADMAP A §批次状态机二阶段「annotating → active 暂停」难点(scheduler 死锁)文字化。决定引入正交 admin_locked: bool 字段(独立于 7 态枚举),check_auto_transitions 起始处短路返回;包含表迁移 SQL、API 端点设计(lock/unlock)、状态机 mermaid 图、3 种被拒绝方案(PAUSED 枚举 / task 级锁 / 借 ARCHIVED)的取舍理由。仅设计,实现推迟到 v0.9 评估窗口。

变更

  • docs-site/package.json prebuild 链:从 sync-openapi && generate-hotkeys 扩为 sync-openapi && mirror-adr && generate-hotkeys && check-doc-snippets;新增 check:snippets / mirror:adr 顶层 script。
  • .vitepress/config.ts/dev/ 侧边栏底部新增 ADR(架构决策) 折叠组,items 由 mirror-adr 输出的 sidebar.generated.json 注入;缺文件时降级为空数组让 VitePress 仍可启动。

推迟(明确不在本期)

  • 截图自动化(Playwright + IMAGE_CHECKLIST 16 处):与 E2E spec 写实共建 fixture,1-2 天深活,本期窗口不足
  • getting-started 3 张 GIF 录屏:等截图自动化方案落地后批量产出
  • fabric.js dead dep 清理:非文档主题,留给下次依赖清理 PR

[0.8.1] - 2026-05-06

治理 / 合规向收口 epic。 一次性把 ROADMAP 中「系统设置可编辑、注册统计、自助注销、管理员重置密码、审计分区归档、数据导出审计」6 项硬残缺收齐。

新增

  • 系统设置 DB 化(PR 1):新表 system_settings(迁移 0034)+ SystemSettingsService(30s LRU)+ PATCH /settings/system(super_admin only)。白名单字段 allow_open_registration / invitation_ttl_days / frontend_base_url / smtp_*;黑名单字段(SECRET_KEY / DATABASE_URL / ENVIRONMENT 等)永不可在 UI 编辑。smtp_password 在 GET 响应掩码(password_set: bool),audit_log detail 仅记录 {changed: True} 不含明文。
  • SMTP 测试发送POST /settings/system/test-smtp3/min 限流),用 stdlib smtplib 即时给当前管理员发一封测试邮件,无新依赖。
  • SettingsPage 系统设置区改可编辑:受控表单 + 「立即生效 / 需新会话生效」标注 + SMTP 密码独立「设置 / 更换」按钮 + 「发送测试邮件到我」按钮。
  • 注册统计仪表卡(PR 2A)/dashboard/admin 响应增加 registration_by_day(30 天,邀请 vs 开放注册分别聚合 audit_logs.detail_json.method/invitation_id)。AdminDashboard 新增「30 天注册来源」双柱条形图(沿用现有 <StatusBar> 风格,不引入图表库)。
  • 管理员重置低等级用户密码(PR 2B)POST /users/{id}/admin-reset-password(super_admin / project_admin,3/min 限流,角色等级校验 + project_admin 项目内限制)。生成 16 字符强临时密码(大小写 + 数字 + 符号),audit_log detail 不记录密码本身。User 模型新增 password_admin_reset_at(迁移 0035),用户首次登录后自助 change_password 成功时清空。UsersPage 新增「重置密码」按钮 + 二次确认 + 临时密码展示 Modal(带复制按钮)。
  • 账号自助注销冷静期(PR 3)POST/DELETE /auth/me/deactivation-request(迁移 0036 加 deactivation_requested_at / reason / scheduled_at)。提交时通知所有 super_admin,给 7 天处理窗口(如转交任务);冷静期内可撤销。Celery beat process_deactivation_requests(每日 04:00 UTC)扫描到期用户,复用 GDPR 软删路径(is_active=False + audit_logs 脱敏)。SettingsPage <DangerZoneCard>:未申请态「申请注销」/ 已申请态「已于 X 申请,将于 Y 自动生效 + 撤销」。
  • audit_logs 月分区表(PR 4,迁移 0037)PARTITION BY RANGE (created_at),PK 改为 (id, created_at)。复用 v0.7.8 不可变 trigger,挂在分区父表上(PG13+ 自动级联到所有子分区)。覆盖 [min(legacy.created_at), now+3m] 子分区,原数据零丢失迁移。原 7 个索引(含 GIN on detail_json)按相同名重建为分区局部索引。详见 ADR-0007(已从「延期」更新为「已实施」+ 实施记录)。
  • 审计冷数据归档(PR 4):Celery beat ensure_future_audit_partitions(每月 25 日提前建未来 3 月分区)+ archive_old_audit_partitions(每月 2 日把 > AUDIT_RETENTION_MONTHS 的子分区 stream-gzip 上传 MinIO audit-archive/{YYYY}/{MM}.jsonl.gz,成功后 DROP TABLE)。新 audit action audit.archive,detail 含分区名 + 行数 + S3 key。
  • 数据导出审计强化(PR 4):4 个导出端点(projects / batches / audit-logs / users)的 AuditService.log 调用统一走新 helper export_detail(),detail_json 增 actor_email / ip / request_id / filter_criteria。CSV 文件首部插入 # Exported by / Exported at / Request ID 注释行;JSON 文件包装 _export_meta 顶层字段(pandas/Excel comment='#' 自动跳过)。
  • AUDIT_RETENTION_MONTHS env.env.example 新增,默认 12 个月。

变更

  • 邀请流程(InvitationService.create / resend + 用户/邀请管理路由)改读 SystemSettingsService.get(...),env 仅作启动 fallback。
  • /auth/forgot-password 重置链接生成同样改走 SystemSettingsService,便于同 PATCH 即时切换前端域名。
  • tests/conftest.py db_session fixture 在 teardown 时清 SystemSettingsService 模块级缓存,防跨测试 PATCH 值泄漏。
  • tests/test_alembic_drift.py 豁免 audit_logs_y* 分区子表(不在 ORM metadata 中)。

[0.8.0] - 2026-05-06

文档细化与补全。 v0.7.x 收口的特性都齐了,本期把文档站从「骨架完整、内容大纲为主」推进到「可作为新人 onboarding 与运维交付物」。

新增

  • docs-site/dev/deploy.md(部署指南):拓扑图、必填/推荐 env 一览、nginx 反代示例(含 WS 长连接 + proxy_read_timeout)、首次 bootstrap_admin 步骤、备份恢复(pg_dump + MinIO sync + Redis 不需备份说明)、升级 runbook、健康检查端点表、常见 production 启动错误。
  • docs-site/dev/security.md(安全模型):威胁模型表 + 缓解一览、5 级 RBAC + 全局能力矩阵、JWT 生命周期 + jti/gen 黑名单时序图、邀请流程 mermaid、审计日志字段释义 + 不可变 trigger 说明、CORS 收紧维度对比。
  • docs-site/dev/ml-backend-protocol.md(ML Backend 协议契约):4 个端点 schema(/health / /predict 同步+交互式 / /setup / /versions)、鉴权约定、is_interactive 语义、错误格式、prediction_metas token/cost 透传字段表、50 行 FastAPI echo backend 最小可跑示例。
  • docs-site/dev/ws-protocol.md(WebSocket 协议):两个频道(/ws/notifications JWT query + /ws/projects/{pid}/preannotate cookie)、消息格式(NotificationOut + ping 心跳 + 进度 payload)、断线兜底、前端指数退避策略、Redis ConnectionPool 上限、扩展新频道的 4 步 how-to。
  • ADR 0002-0005 回填
    • 0002-backend-stack-fastapi-sqlalchemy-alembic.md — FastAPI + SQLAlchemy 2.0 async + Alembic 选型,对比 DRF / Tortoise / Node Prisma
    • 0003-openapi-client-codegen.md@hey-api/openapi-ts 选型,对比 orval / swagger-typescript-api / openapi-generator-cli
    • 0004-canvas-stack-konva.md — Konva 4 Layer(v0.6.4 起 5 Layer)选型,对比 Fabric.js / 原生 Canvas / PixiJS / SVG;含 package.json 仍含 dead fabric 依赖的备注
    • 0005-task-lock-and-review-matrix.md — 5min TTL + 60s 心跳 + 接管阈值 TTL/2 的取值理由 + 5 状态机 + 角色权限矩阵
  • docs-site/scripts/generate-hotkeys.mjs:从 apps/web/src/pages/Workbench/state/hotkeys.ts regex 解析 HOTKEYS + GROUP_LABEL,生成 docs-site/user-guide/workbench/hotkeys.generated.mdpredev / prebuild 自动跑,新增 pnpm docs:hotkeys 顶层别名。
  • docs-site/user-guide/IMAGE_CHECKLIST.md:截图回填清单 + 拍摄约定(分辨率、脱敏、红框规范);汇总 16 处 <!-- TODO(0.8.1) --> 占位。

改进

  • docs-site/dev/architecture/data-flow.md:4 个 mermaid 序列图全部加上代码路径标注(apps/api/...:行号),读者可点 GitHub 跳具体函数;新增「实时通知」详细序列图(含 30s 心跳)。
  • docs-site/dev/how-to/add-api-endpoint.md:从 widgets 占位例改成 v0.7.8 真实落地的 POST /auth/logout 全链路(路由 + token_blacklist service + 测试覆盖正常路径/错误路径/副作用断言 + snapshot + 前端 wrapper + PR checklist)。
  • docs-site/user-guide/workbench/index.md:删除手抄快捷键表(与代码漂移:W/R/Ctrl+Enter 都不存在),改为 <!--@include: ./hotkeys.generated.md-->,与 hotkeys.ts 38 条 SoT 对齐。
  • docs-site/user-guide/getting-started.md:基础快捷键 6 项与代码 SoT 对齐(B/P/V/Space+drag/Ctrl+Z/E);加 3 处截图占位(登录、忘记密码、端到端 GIF)。
  • bbox / polygon / keypoint / projects / review / export 页:合计 13 处截图占位,均含拍摄要求注释,配合 IMAGE_CHECKLIST 回填。

配置

  • docs-site/.vitepress/config.ts/dev/ 侧边栏新增「部署与协议」分组(4 项),位于「架构」与「How-to」之间。
  • docs-site/package.jsonpredev / prebuild 改为 sync-openapi && generate-hotkeys;新增 hotkeys 脚本。
  • package.json:新增 pnpm docs:hotkeys 顶层别名。

占位(不阻断发布)

  • 16 处用户手册截图(docs-site/user-guide/images/<page>/*.png|gif)目前为 1×1 透明 PNG,build 通过;真实图按 IMAGE_CHECKLIST 在 0.8.1 回填。

Released under the MIT License.