"""Seed the monthly audit-evidence pre-compile cron. Revision ID: 498 Revises: 490 审计季取证慢的根因不是查询慢,是**证据要到审计季才开始收集**。这个 cron 把 目录里所有「无必填参数」的公司级问题按**上一个完整自然月**跑一遍并冻结,于是 「5 月谁持有过生产云权限」这类问题在 6 月初就已经有一份带 hash 的答案, 到 11 月外部审计要材料时直接取,而不是在 deadline 下现场重建。 跑哪些查询是从目录的参数声明**推导**的,不是在这里写死清单 —— 写死的清单 会在有人注册第 13 个查询的那天开始悄悄漏掉它。需要主体的问题(单人足迹 / 回收证明 / 对象生命周期)必填参数非空,自然留在按需触发一侧。 排在每月 2 号 03:20:1 号可能还有跨月落库的尾巴,而 03:20 避开 03:00 那批 定时任务的父行锁竞争(2026 年两起 1205 都是 03:00 撞出来的)。 只读:不打任何厂商接口、不调用模型,只对 Helix 自己的台账做确定性查询。 """ import uuid import sqlalchemy as sa from alembic import op revision = "498" down_revision = "490" branch_labels = None depends_on = None _KEY = "auditor.evidence_precompile" _DISPLAY = "审计证据月度预编译" _DESCRIPTION = ( "每月 2 号 03:20(Asia/Shanghai)把证据查询目录里所有【无必填参数】的公司级" "问题按上一个完整自然月跑一遍并冻结成 bundle,供审计季直接取用 —— 证据在" "所属周期内就已存在,而不是等到审计季在 deadline 下现场重建。\n" "跑哪些查询是从目录的参数声明推导的,不是写死清单:任何必填参数为空的条目" "都是公司级问题。需要主体的问题(单人足迹 / 回收证明 / 对象生命周期)保持" "按需触发。\n" "只读:不打任何厂商接口、不调用模型,只对 Helix 自己的台账做确定性查询。\n" "跑的是【已结束的】自然月 —— 对当月跑会冻结一张残缺的快照并给它盖 hash," "下次再跑又不一样,而 hash 的差异本该只保留给「底层数据变了」这一个含义。\n" "任一查询失败即整体记 failed 并在摘要里点名(已成功的 bundle 保留)—— " "少一份证物却记 ok,会让这个月度控制看起来健康好几个月。\n" 'config={"month":"YYYY-MM"} 覆写窗口(补跑历史月份);' '{"query_keys":[...]} 限定只跑指定查询。' ) # 2 号而不是 1 号:跨月落库还有尾巴。03:20 而不是 03:00:避开 03:00 那批 # 定时任务的父行锁竞争。 _CRON = "20 3 2 * *" def upgrade() -> None: op.get_bind().execute( sa.text( "INSERT INTO scheduled_jobs " "(id, name, handler_key, display_name, description, " " trigger_type, cron_expr, timezone, enabled, config, " " created_at, updated_at) " "SELECT :id, :name, :k, :dn, :desc, 'cron', :cron, " " 'Asia/Shanghai', 1, '{}', NOW(), NOW() " "FROM DUAL WHERE NOT EXISTS (" " SELECT 1 FROM scheduled_jobs WHERE handler_key = :k)" ), { "id": str(uuid.uuid4()), "name": _KEY, "k": _KEY, "dn": _DISPLAY, "desc": _DESCRIPTION, "cron": _CRON, }, ) def downgrade() -> None: op.get_bind().execute( sa.text("DELETE FROM scheduled_jobs WHERE handler_key = :k"), {"k": _KEY}, )