From 13dea1cb2c7587f044fd54d9ef04b2d46bcc9272 Mon Sep 17 00:00:00 2001 From: xxm Date: Sun, 30 Aug 2026 13:38:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20sync=5Fprofile=5Fscripts=E8=B7=A8?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=94=B9cp=20-u(=E7=A1=AC=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=85=A8=E7=81=AD=E8=87=B451=E8=84=9A=E6=9C=AC=E5=88=86?= =?UTF-8?q?=E5=8F=89,8-26~28=E4=BF=AE=E5=A4=8D=E6=9C=AA=E8=BE=BE=E7=94=9F?= =?UTF-8?q?=E4=BA=A7);=20feat:=20cron=E4=BB=BB=E5=8A=A1=E5=81=A5=E5=BA=B7?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1;=20docs:=208-30=E5=AE=A1=E8=AE=A1=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E6=93=8D=E4=BD=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/profile-scripts/cron_disabled_audit.py | 120 ++++++++++++++++++ .../profile-scripts/sync_profile_scripts.sh | 6 +- docs/ops/2026-08-30-cron-disabled-审计恢复.md | 105 +++++++++++++++ 3 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 deploy/profile-scripts/cron_disabled_audit.py create mode 100644 docs/ops/2026-08-30-cron-disabled-审计恢复.md diff --git a/deploy/profile-scripts/cron_disabled_audit.py b/deploy/profile-scripts/cron_disabled_audit.py new file mode 100644 index 00000000..a3e85ba0 --- /dev/null +++ b/deploy/profile-scripts/cron_disabled_audit.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""cron_disabled_audit.py — hermes cron 任务健康审计(流程约束③落地, 2026-08-30 老莫批准) + +背景:2026-08-12 LLM故障期批量止血后,18 个任务被遗忘在 disabled 18 天(含 4 环节 +之一的盘前批量重评),无任何告警。根因:健康检查只看"跑着的是否正常",不看 +"该跑的是否被启用"。 + +每日审计: +1. 4 环节关键任务启用状态(选股/买/卖/每日重评——任一 disabled 即报警) +2. disabled > 7 天的任务清单(无 paused_reason 的标"违规",有 reason 的附原因) +3. enabled 但 last_status=error 的任务清单 + +输出:broadcast health 频道(异常时),正常时静默(只在全部健康时发一条 OK)。 +调度:每日 08:20(盘前体检时段)。 + +维护:4 环节关键任务清单硬编码在下面 CRITICAL_JOBS,新增关键任务时需同步维护。 +""" +import json, os, sqlite3, sys +from datetime import datetime, timedelta + +JOBS_JSON = "/home/hmo/.hermes/profiles/position-analyst/cron/jobs.json" +DB = "/home/hmo/MoFin/data/mofin.db" +STALE_DAYS = 7 + +# ── 4 环节关键任务(选股/买/卖/每日重评)——新增关键任务必须维护此清单 ── +CRITICAL_JOBS = { + "选股": ["主力建仓扫描-每15分", "候选股过滤管道-每30分", "候选股自动提拔-每30分"], + "买": ["自选买入区提醒-盘前午间尾盘"], + "卖": ["自选自动退出-盘前", "持仓异动监控-每5分"], + "每日重评": ["盘前全量重评-自选退出", "盘前批量处理"], +} + + +def load_jobs(): + d = json.load(open(JOBS_JSON)) + items = d if isinstance(d, list) else d.get("jobs", list(d.values()) if isinstance(d, dict) else []) + if isinstance(items, dict): + items = list(items.values()) + return [j for j in items if isinstance(j, dict)] + + +def broadcast(conn, title, content): + conn.execute( + "INSERT INTO broadcast_messages (ts, category, title, content, source, created_at) VALUES (?,?,?,?,?,?)", + (datetime.now().isoformat(timespec="seconds"), "health", title, content, + "cron_disabled_audit", datetime.now().isoformat(timespec="seconds"))) + conn.commit() + + +def main(): + jobs = load_jobs() + now = datetime.now() + problems = [] + + # 1) 4 环节关键任务状态 + crit_bad = [] + for ring, names in CRITICAL_JOBS.items(): + for n in names: + hit = [j for j in jobs if j.get("name") == n] + if not hit: + crit_bad.append(f"[{ring}] {n}: **jobs.json 中不存在!**") + elif not hit[0].get("enabled"): + crit_bad.append(f"[{ring}] {n}: disabled!") + if crit_bad: + problems.append("🔴 4环节关键任务异常:\n" + "\n".join(crit_bad)) + + # 2) disabled > 7 天(跳过已结案的正式废弃项——它们不是待办) + RETIRED_MARKS = ("正式废弃", "归档", "被取代", "取代", "收编") + stale = [] + for j in jobs: + if j.get("enabled"): + continue + name = j.get("name", "?") + pa = j.get("paused_at") + reason = j.get("paused_reason") + if reason and any(m in str(reason) for m in RETIRED_MARKS): + continue # 已结案废弃, 不报警 + if pa: + try: + days = (now - datetime.fromisoformat(str(pa).replace("Z", "+00:00")).replace(tzinfo=None)).days + except Exception: + days = -1 + else: + days = -1 # 无 paused_at 的长期 disabled 也要列出 + if days < 0 or days >= STALE_DAYS: + tag = "⚠️无reason(违规)" if not reason else f"reason: {str(reason)[:60]}" + stale.append(f" {name} (paused {days if days >= 0 else '?'}天) {tag}") + if stale: + problems.append(f"🟡 disabled>{STALE_DAYS}天任务 {len(stale)} 个:\n" + "\n".join(stale[:15])) + + # 3) enabled 但 error + errs = [f" {j.get('name')}: {str(j.get('last_error'))[:60]}" for j in jobs + if j.get("enabled") and j.get("last_status") == "error"] + if errs: + problems.append(f"🟠 enabled 但 last_status=error {len(errs)} 个:\n" + "\n".join(errs[:10])) + + conn = sqlite3.connect(DB, timeout=30) + conn.execute("PRAGMA busy_timeout=30000") + today = now.strftime("%Y-%m-%d") + if problems: + content = "\n\n".join(problems) + # 当日幂等:同标题已发则更新内容(防重复刷屏) + row = conn.execute( + "SELECT id FROM broadcast_messages WHERE source='cron_disabled_audit' AND created_at LIKE ?", + (today + "%",)).fetchone() + if row: + conn.execute("UPDATE broadcast_messages SET content=? WHERE id=?", (content, row[0])) + conn.commit() + else: + broadcast(conn, f"cron任务审计: {len(problems)} 类异常", content) + print(f"[AUDIT] {len(problems)} 类异常已发 broadcast") + for p in problems: + print(p[:200]) + else: + print("[AUDIT] 全部健康(关键任务启用+无 stale disabled+无 error), 静默") + conn.close() + + +if __name__ == "__main__": + main() diff --git a/deploy/profile-scripts/sync_profile_scripts.sh b/deploy/profile-scripts/sync_profile_scripts.sh index 5c89d23b..298a7ee8 100644 --- a/deploy/profile-scripts/sync_profile_scripts.sh +++ b/deploy/profile-scripts/sync_profile_scripts.sh @@ -8,10 +8,10 @@ count=0 for f in "$SRC"/*.py "$SRC"/*.sh; do [ -e "$f" ] || continue name=$(basename "$f") - ln -f "$f" "$DST/$name" + cp -u "$f" "$DST/$name" count=$((count+1)) done -ln -f "$SRC/sync_profile_scripts.sh" "$DST/sync_profile_scripts.sh" 2>/dev/null || true +cp -u "$SRC/sync_profile_scripts.sh" "$DST/sync_profile_scripts.sh" 2>/dev/null || true # ── 库文件 SSOT:canonical 在 MoFin 根目录,其他位置只许硬链(红线#6)── # git checkout/merge 会重写文件破坏硬链,这里强制恢复 @@ -21,7 +21,7 @@ for lib in mofin_db.py mo_data.py; do ln -f "$ROOT/$lib" "$ROOT/deploy/profile-scripts/$lib" 2>/dev/null || true ln -f "$ROOT/$lib" "$ROOT/scripts/$lib" 2>/dev/null || true ln -f "$ROOT/$lib" "/home/hmo/web-dashboard/$lib" 2>/dev/null || true - ln -f "$ROOT/$lib" "$DST/$lib" 2>/dev/null || true + cp -u "$ROOT/$lib" "$DST/$lib" 2>/dev/null || true fi done diff --git a/docs/ops/2026-08-30-cron-disabled-审计恢复.md b/docs/ops/2026-08-30-cron-disabled-审计恢复.md new file mode 100644 index 00000000..02cfd255 --- /dev/null +++ b/docs/ops/2026-08-30-cron-disabled-审计恢复.md @@ -0,0 +1,105 @@ +# Cron Disabled 任务审计与恢复操作单 + +> 日期:2026-08-30 | 操作人:莫笑笑(小小莫)| 触发:老莫健康检查质询 +> 流程依据:流程约束①(变更必须写 reason)②(批量操作必须落操作单) + +## 一、背景:8-12 事件还原(证据链) + +| 时间 | 事件 | 证据 | +|---|---|---| +| 7-02~8-07 | 旧评估进化体系自然坏死(feedback 7-02/evaluations 7-08/lessons 8-01/evolution 8-07 断供) | DB 各表 MAX(created_at) | +| 8-11 | cron 架构大审查(docs/cron-architecture-review-20260811.md);**"策略到期评估"job 缺 id 字段被引入→调度器 tick KeyError 隐疾** | 8-21 知微定位记录 | +| 8-12 09:43 | cron 异常 7 个(LLM 上游故障+调度器隐疾叠加) | 知微开盘简报自检行 | +| 8-12 15:38 | 知微批量暂停 5 个"报错"任务止血(市场精选/基本面午间/背离周末/宏观周末/策略评估每日) | jobs.json paused_at 同秒 | +| 8-12 16:08/23:09 | 停自选12维补全、parallel_batch | jobs.json paused_at | +| 8-12~22 | 分支自成长/剪枝/元自成长/元监控L4/策略评估每周/建议对账/周末重评/cron推XMPP 被直接 disable(无 paused_at) | jobs.json 版本对比(8-11 全 E→8-22 全 X) | +| 8-15~18 | 进化闭环设计(未批准)→ 两方向(8-16)→ AB路线 v2(8-18);手动跑 3 天 | docs/decisions/2026-08-15 + strategy_research_log | +| 8-20 下午~8-21 13:37 | gateway 重启后调度器全瘫(每次 tick KeyError,所有 job 静默失败)→ 知微定位缺 id 修复 | 知微 state.db 8-21 13:37 消息 | +| 8-21 | evolution-cleanup 归档整个 evolution/+meta_growth+meta_watchdog(归档原因无文档) | archive/evolution-cleanup-20260821/ | +| 8-24 | parallel_batch.sh 升级 N=6(重启意图),但 job 漏启用 | 脚本注释 | + +**根因教训**:止血不写 reason + 无恢复清单 + 事件无文档 → 18 个任务被遗忘在 disabled 18 天(含 4 环节之一的盘前批量重评)。 + +## 二、本次恢复清单(10 个,脚本均验证存在) + +| # | 任务 | 调度 | 恢复理由 | +|---|---|---|---| +| 1 | 盘前批量处理 parallel_batch | 10 8 * * 1-5 | 4 环节之一(每日重评);8-24 升级 N=6 的重启意图明确 | +| 2 | 基本面刷新-午间 | 47 12 * * 1-5 | 8-11 审查判定"有意义保留",8-12 止血被误停 | +| 3 | 跨市场背离检测-周末 | 30 8-16/2 * * 0,6 | 同上 | +| 4 | 宏观风险扫描-周末 | 0 10 * * 0,6 | 同上 | +| 5 | 宏观风险扫描(早间主版) | 30 8 * * 1-5 | LLM 深度分析层(误报修正核心);8/13-14 漏跑系上游 LLM 故障,故障已过 | +| 6 | 自选股自动重评-周末 | 30 10 * * 0,6 | 周末重评独立价值(stale_detector --watchlist-only) | +| 7 | 分支自成长-盘中 | 15,30,45,00 9-15 * * 1-5 | 8-11 审查明确"活跃",自成长体系 | +| 8 | 分支剪枝-每日 | 0 21 * * 1-5 | 同上 | +| 9 | 元自成长-每日 | 45 0,12 * * 1-5 | 同上 | +| 10 | 元监控-自检系统的自检-L4 | 5 * * * * | 同上 | +| 11 | 策略健康度监控-每日 | 45 16 * * 1-5 | 8-15 仍在维护(硬编码→数据驱动修复),进化闭环退化信号数据源 | + +## 三、正式废弃清单(13 个,reason 已写入 jobs.json) + +| # | 任务 | 废弃依据 | +|---|---|---| +| 1 | 策略评估-每日/每周 | evaluations 7-08 断供;8-11 判每日版可停;脚本 8-20 已归档;职能由 strategy_effectiveness(到期评估,enabled)承接 | +| 2 | 小果市场筛选-全市场 | 8-11 合理停用 + 8-19 大扫除标注"小果系列可能已废弃" + 脚本已归档 | +| 3 | 市场精选推荐-每日 | 选股主链路已换代为候选管道(accumulation→filter→promote,均 enabled) | +| 4 | 自选12维分析补全-每日午间 | 12 维已有正规路径(per_stock_reassess 买卖触发 + premarket 盘前 + parallel_batch 批量) | +| 5 | cron报告推XMPP-每5分 | 简报推送已由 hermes 原生 delivery + kanban_xmpp_bridge(*/2,enabled)承接 | +| 6 | 系统健康检查-开盘前 | 8-11 判定被新健康体系(Tier1/morning/intraday/functional/self_repair)替代 | +| 7 | 300308入场信号紧盯/午后紧盯 | 个股临时盯盘,使命完成 | +| 8 | 宏观风险扫描-午间 | 与早间版重复(8-11 已停用) | +| 9 | 行业富集-cninfo-午间 | 早间幂等已补全,午间冗余(8-11 P2 停用) | +| 10 | S2恐慌买扫描-独立 | 已由 strategy_executor 温区统一调度收编(同 v_weak/p_oversold 已有 reason) | +| 11 | 自选买入区提醒+触发重评-盘中 | 旧路径,已由"自选买入区提醒-盘前午间尾盘"(per_stock_reassess.py,enabled)取代 | + +## 四、待确认(2 个) + +| 任务 | 状态 | 待确认点 | +|---|---|---| +| 建议对账-每周(advice_reconciliation) | 脚本已不在 deploy | 查 archive 是否有副本;知微确认是否仍有价值 | +| 策略时效性检查(日)(strategy-staleness-check) | state=error | 与 strategy_effectiveness(到期评估)功能重叠?确认取代关系 | + +## 五、待老莫拍板(2 个:进化体系去留) + +**策略进化引擎-每周 + AB路线每日研究**: +- 体系:8-15 设计(未批准未否决)→ 8-16 老莫"两方向"(A 引擎变体/B B组挖掘)→ 8-18 老莫 AB路线 v2(LLM 研究层)→ 8-21 evolution-cleanup 全部归档(无文档) +- 本体位置:`archive/evolution-cleanup-20260821/evolution/`(evolution_engine.py + b_group_miner.py + precompute_evolution.py,可还原) +- 还原动作:目录移回 `/home/hmo/MoFin/evolution/` → 启用 evolution_daily(周六 22:00)+ ab_research_daily(每日 21:00)→ evolution_center.json 重新预计算(断供 14 天) +- 选项 A:还原启用(你三度推动的体系) +- 选项 B:正式废弃(补 reason,archive 留底) + +## 六、变更结果 + +- enabled:79 → 89(+10) +- disabled 无 reason:30 → 4(剩余=待确认 2 + 待拍板 2) +- jobs.json 备份:bak_enable_batch_20260830 / bak_restore_20260830 / bak_restore2_20260830 + +## 七、后续跟踪 + +- [ ] 2026-08-31 08:10 parallel_batch 首跑验证(18 天后首次)+ 与 premarket Step 1.5 双入口幂等观察 +- [ ] 2026-08-31 各恢复任务首跑观察(分支自成长 9:00/元监控 12:05/宏观风险 8:30 等) +- [ ] 策略到期评估(strategy_effectiveness)表 0 行原因确认(8-28 ok 但无写入——空跑 or 逻辑问题) +- [ ] 8 个 last_status=error 的 enabled job 排查(清单待拉) +- [ ] 流程约束③落地:健康检查加 disabled 审计维度(disabled>7 天清单 + 4 环节关键任务启用状态) + +--- + +## 八、追加发现(同日 13:30):deploy↔hermes 硬链接全灭 + 51 脚本分叉 + +**发现**:deploy/profile-scripts 在 data 卷(/mnt/data),hermes scripts 在系统盘(/dev/sdb3), +跨设备导致 sync_profile_scripts.sh 的 `ln -f` 硬链接方案**全部物理失效(存活 0/187)**。 + +**后果**:**51 个脚本内容分叉**——8-26~8-28 的全部修复(busy_timeout 回归修复 d04d9d50、 +batch_reassess try/except 997dcae4、数据分层 bd1ef0c9 等、hk_rate DB 优先 7fc6116a) +**均未到达生产**;hermes cron 一直在跑 8-21~8-26 旧版(含带 bug 的 batch_reassess—— +premarket_full_review 的 Popen 调 hermes 路径,盘前 12 维重评生产环境仍跑旧版)。 +deploy_guard 对 hk_rate 的持续报警同源(仓库已修,生产未更新)。 + +**处置**(已完成): +1. 51 个分叉旧版备份到 `archive/hermes-scripts-diverged-bak-20260830/`(可回滚) +2. 全量同步 deploy→hermes(187 文件,分叉归 0,md5 验证一致) +3. sync_profile_scripts.sh 修复:`ln -f` → `cp -u`(跨设备兼容,随 commit 入库) +4. hermes 侧反向修改核查:0 个(51 个分叉全部 deploy 更新,同步方向安全) + +**铁律补充**:今后改 deploy 后必须确认 hermes scripts 同步(cp 后 md5 抽查), +不能假设 sync 机制在工作——本次分叉存在约 2-4 天无人察觉。