# 审计 — 运维时区全局北京（2026-06-09）

**Changelog**: `docs/changelog/2026-06-09-operator-timezone-beijing.md` · `docs/changelog/2026-06-09-timezone-followups-ws-install-dates.md`  
**报告**: `docs/reports/2026-06-09-operator-timezone-beijing-audit.md`

## 变更文件清单

| 文件 | 说明 |
|------|------|
| `server/utils/display_time.py` | `operator_wall`、`beijing_day_range_utc` |
| `server/background/schedule_runner.py` | 北京墙钟 cron 匹配 |
| `server/infrastructure/database/audit_log_repo.py` | 审计日期北京日界 |
| `server/api/settings.py` | 告警 date 筛选、`utcnow` 修复 |
| `frontend/src/utils/datetime.ts` | `beijingWallParts` |
| `frontend/src/utils/scheduleCycle.ts` | 与 runner 对齐 |
| `ScheduleCyclePicker.vue` | 北京时间提示 |
| `tests/test_operator_tz.py` | 运维时区单测 |
| `tests/test_schedule_next_run.py` | 更新北京语义断言 |
| `server/api/websocket.py` | WS 告警/恢复消息增加 `at`（UTC ISO） |
| `frontend/src/composables/useWebSocket.ts` | 使用 `msg.at` 展示北京时刻 |
| `server/api/install.py` | 安装时区固定 `Asia/Shanghai` 校验 |
| `web/app/install.html` | 移除误导性时区下拉，固定说明文案 |
| `web/app/index.html` | 前端构建入口 hash 更新 |
| `tests/test_ws_alert_at.py` | WS `at` 字段单测 |
| `tests/integration/test_alerts_audit.py` | 非法日期 422 集成测 |
| `frontend/e2e/pages/schedules-timezone.spec.mjs` | 调度北京时间 E2E |

## Step 3 规则扫描

| 规则 | 结论 |
|------|------|
| PY-11 naive/aware | PASS — `to_naive_utc` / `ensure_utc` |
| 调度双触发 | PASS — 60s 冷却保留 |
| SQL 注入 | PASS — 日期 `fromisoformat`，无拼接 |
| 静默吞错 | PASS — 非法 `date_from`/`date_to` 返回 422 |
| 存量 cron 语义 | RISK-P2 — 无迁移；部署后人工核对调度列表 |

## Closure

| 项 | 结果 |
|----|------|
| `test_operator_tz.py` | passed |
| `test_schedule_next_run.py` | passed |
| `test_ws_alert_at.py` | passed |
| `test_alerts_audit.py` (422) | passed |
| `scripts/local_verify.sh` | All local checks passed |
| `npm run type-check` | passed |
| ruff F | 0 violations |

## DoD

- [x] cron 按北京时间匹配，`next_run` 与表单时:分一致
- [x] `cycleFromSchedule` 用 `parseApiDateTime` + 北京时:分
- [x] 审计 `date_from` 覆盖北京当日 00:00–23:59
- [x] design spec + plan + changelog
- [x] WS 告警 `at`、安装时区固定、日期 422
- [ ] 生产部署后人工核对启用中的调度（用户确认几乎无）
