fix: sync_profile_scripts跨设备改cp -u(硬链接全灭致51脚本分叉,8-26~28修复未达生产); feat: cron任务健康审计; docs: 8-30审计恢复操作单

This commit is contained in:
xxm
2026-08-30 13:38:20 +08:00
parent 6a4ce1f565
commit 13dea1cb2c
3 changed files with 228 additions and 3 deletions
@@ -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()
@@ -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
# ── 库文件 SSOTcanonical 在 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
@@ -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路线 v28-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*/2enabled)承接 |
| 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.pyenabled)取代 |
## 四、待确认(2 个)
| 任务 | 状态 | 待确认点 |
|---|---|---|
| 建议对账-每周(advice_reconciliation | 脚本已不在 deploy | 查 archive 是否有副本;知微确认是否仍有价值 |
| 策略时效性检查(日)(strategy-staleness-check | state=error | 与 strategy_effectiveness(到期评估)功能重叠?确认取代关系 |
## 五、待老莫拍板(2 个:进化体系去留)
**策略进化引擎-每周 + AB路线每日研究**
- 体系:8-15 设计(未批准未否决)→ 8-16 老莫"两方向"A 引擎变体/B B组挖掘)→ 8-18 老莫 AB路线 v2LLM 研究层)→ 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:正式废弃(补 reasonarchive 留底)
## 六、变更结果
- enabled79 → 89+10
- disabled 无 reason30 → 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→hermes187 文件,分叉归 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 天无人察觉。