53e7d1e5f2
文档/ (2026-09-16; 老板定调: 这是 agent 底座, 所以必须扎实, 现在越扎实以后开发越简单)
00-索引 文档地图 + 30 秒概念速查 + 三条命令跑起来 + 事实源优先级
01-快速上手 体检 -> 建库 -> 起内核 -> 起驱动 -> 收工, 全带实测输出; 第一次最易踩的四个坑
02-写一个驱动 五分钟最小驱动 / 形态选择 / 能碰哪些表 / 汇报与调用两份模板 / 交付检查表
03-命令手册 两层每条命令 + 日志选项 + 退出码约定 + --json 样例 + 日常十条
04-契约与调用 一次调用的完整生命周期 / 六条仲裁 / 锁与按需拉起 / 排障表
05-日志与排障 三条道怎么读 + "症状->判据->处置"总表 + 断电收尸语义
06-架构与不变量 分层 / 14 条硬不变量 / 主流程表 / 双真相 / 为什么故意不做 / 已知薄弱点
07-模块与接口 逐模块职责与公开接口 + "想改 X -> 动哪几处"连带清单
08-数据模型 8 张表逐字段 (谁写谁读) + events.kind 字典 + 状态机 + 快照 + 排查 SQL
09-扩展指南 六个配方 (加子命令/加字段/加表/加日志来源/加自测/改判定) + 同步清单
10-验收与质量门 四道门 + 五份自测明细 + pyright 严格档 + 26 条已知坑总表 + 发布 checklist
规矩: 不重复设计文档 / 每条命令实测过再写 (含 jq 表达式) / 代码>设计>文档 的事实源优先级 /
改代码必须同步文档 (清单在 09 末尾) / 暂时没做到的事写成"已知边界"不含糊过去
修复: 同锁串行化原来是死的 (实测抓到的真缺陷)
旧行为: db.领调用 只领 pending (waiting 没人再碰) + db.同锁在跑 把 waiting 也算"占着锁"
-> 同一把锁上两条请求互相排队, 双双停在 waiting 谁也不跑 (实测 id 16/17);
而 收权超时 只收 running -> 排队连超时都没有 = 死锁
修法: ① db.领调用 的 SQL 改 state IN ('pending','waiting') -- 每轮把排队的领回来重判, 锁一空就推进
② db.同锁在跑 只认 state='running' (排队的还没拿到锁, 不挡人)
③ 内核.转发调用 waiting 分支补 deadline (排队也立期限); 内核.收权超时 遍历 running + waiting
④ 抽出 内核.期限文本() 统一算 deadline
实测: 两条同锁调用串行跑完 (19.started_at == 18.finished_at); 排队者超时被收权 (events 有记录)
回归: 自测db.py 调用组 +4 条断言 (waiting 不算占着锁 / waiting 会被重新领 / ...);
去掉一条依赖生产库全局计数的脆弱断言
其它: 内核 与 引导器 的 用法() 末尾加文档指引
验收: uvx pyright 0 errors / 0 warnings; 五份自测全过 (进程/内核 58/配置/db/日志 86);
试跑引导器.py PASS 11 / FAIL 0 / 残留无; 残留进程 0
13 KiB
13 KiB
07 · 模块与接口(改哪一层、动哪几处)
一套职责一个文件,不合并;一份实现不复制(
进程/日志/文本/状态都是唯一实现,内核与引导器共用)。 这份文档给"要改代码的人":每个文件管什么、对外接口是什么、改一件事要连带改哪些地方。 函数级细节看源码里的 docstring(每个函数都写了"为什么")。
1. 依赖方向(只能自上往下,不许反向)
UEFI.boot.py ──┬─► 进程.py 通用进程库: 启停 / 判活 / 收子树 / 日志读取
├─► 日志.py 日志系统: 写 / 解析 / 过滤 / 轮转 / 实时跟
├─► 文本.py CJK 宽度感知的对齐 / 表格 / 横线
└─► db.py 唯一碰 SQL 的文件 (只建引导器那两张表, 写 kernel_env/kernel_runs)
内核/内核.py ──┬─► 扫描.py ──► 状态.py ──► 进程.py
├─► 状态.py
├─► db.py 唯一碰 SQL 的文件 (建全部 8 张表)
├─► 日志.py / 文本.py / 进程.py
└─► 汇总: 命令分发 / 单驱动动作 / 常驻调度
驱动/<名>/*.py ──► psycopg2 直连 PG (只写 events / calls; 不 import 项目里任何模块)
规矩:db.py 是唯一写 SQL 的地方(别处一律不出现 SQL 字符串);进程.py / 日志.py / 文本.py
是唯一实现(哪一层要用都 import 它,不许抄一遍)。
2. 各模块职责与接口
UEFI.boot.py — 引导器(纯 stdlib)
| 组 | 关键接口 | 说明 |
|---|---|---|
| 配置 | 读环境() -> (配置, 警告) |
缺文件按默认模板生成;解析失败不覆盖用户文件,用默认值继续 + WARN |
| 体检 | 体检(配置) -> 体检报告 / 打印体检 / 报告快照 / 例行体检(配置, 详细) |
6 项(1-5 阻塞、6 PG 只 WARN);例行体检 = 体检 + 写快照 + 记台账 + 按需打印 |
| 包 | 读包要求 / 读实装包 / 核包 / 解析版本 / 满足要求 |
自己撸的版本比较(不引 packaging);uv pip list 失败回落 importlib.metadata |
| venv | venv路径 / 检查venv健康 / 环境重建 |
重建 = 旧 venv 改名留退路(.venv.bak-<时间戳>)→ uv venv / python -m venv → 复检 |
| 内核进程 | 内核状态 / 内核启动 / 内核停止 / 内核重启 / 内核日志 |
全部走 进程.py;--守护 用 进程.启动(stdout/stderr → 内核.out.log,EFI_LOG_CONSOLE=0) |
| 记账 | PG连接 / PG记体检 / 写快照 |
引导器自己的命令 PG 不通只 WARN(降级写 环境状态.efi.json) |
| 日志 | 记日志(级别, 消息, 安静=False) / 起日志(配置) / 打日志(路径, 选, 称呼) |
引导器动作落 引导器.log;每次跑先轮转一次自己的 |
| 分发 | main(argv) → 分发(argv, 配置) |
顶层 5 词:自检/环境/包/内核/日志;其余原样透传内核 |
不做:不实现内核的子命令、不改驱动配置、不自动修环境/装包/重建。
内核/内核.py — 常驻总调度 + 单驱动动作
| 组 | 关键接口 | 说明 |
|---|---|---|
| 日志/配置 | 说(级别, 消息) / 用环境(环境) / 读环境 / 驱动根 / 日志行数·日志上限·日志保留 / 轮转日志 |
说() 是薄包装(落 内核.log;是否打 stderr 看 EFI_LOG_CONSOLE) |
| 连库 | 连库 / 重连 |
连不上直接退出(不降级);重连 失败 10 次退出,让看门狗发现 |
| 拼命令 | 拼命令(驱动, 环境) -> (argv, env, 警告表) |
解释器解析 + args/env/EFI_DB 注入;cwd = 驱动根 |
| 单驱动动作 | 拉起一个 / 停一个 / 收僵尸 / 下游名 / 停序 |
拉起一个 是启动/重启/按需/自动重拉唯一实现;停一个 级联停下游 |
| 调用仲裁 | 取链 / 校验调用 / 期限文本 / 转发调用 / 收权超时 |
六条仲裁;转发 = 写 running + pg_notify driver_<提供方> |
| 巡检 | 巡检(连接, 环境) |
收尸 → 重拉 → 级联标"依赖失效" → 收权超时 |
| CLI | 执行命令(连接, 环境, 命令, 参数) + 命令列表/扫描/启动/停止/重启/状态/日志/事件/清单 |
CLI 与常驻内核共用这一份实现(不复制) |
| 日志命令 | 命令日志 / 打印某个日志 / 打印全部驱动日志 / 打印日志台账 / 用法日志 |
四形态:台账 / 单驱动 / --全部 / --内核·--引导器·--输出 |
| 常驻 | 命令调度(连接, 环境) |
独一份锁 → 收尸 → 扫描 → autostart → LISTEN → 主循环(命令/调用/巡检/心跳/定期重扫) |
不做:不解析驱动 stdout(只重定向)、不替引导器体检环境。
内核/扫描.py — 认文件夹 + 校验 + 契约
| 接口 | 说明 |
|---|---|
扫目录(驱动根) |
只认根目录有 配置.efi.json 的文件夹(按名字排序,结果稳定) |
读配置(目录) |
json 读不了给 (空, 原因) |
校验单个(目录) -> 注册表行 |
第 2-6 条校验(JSON/版本/runtime/entry 路径安全/entry 存在+x 位);不过也返回(带 valid=False + error) |
查重名(记录表) |
第 7 条(后到的 invalid) |
取契约 / 定契约 |
第 8 条(收 provides / 查 needs / DFS 找环),最多 3 轮 |
排顺序(记录表, 契约) |
拓扑排序(提供方在消费者前,无依赖按名字) |
扫描(连接, 驱动根, 内核版本) -> 扫描结果 |
全流程 + 收尸 + 落快照 + 记 scans/events(scan) |
扫描结果(dataclass) |
驱动/状态表/顺序/契约/问题/该拉起/清单版本/总数/有效/无效/在跑 |
不做:不碰 SQL(走 db.py)、不起进程(那是内核的单驱动动作)。
内核/状态.py — 状态机 + 快照 + 收尸判定
| 接口 | 说明 |
|---|---|
| 常量 | 配置名/快照名/日志目录名/快照版本;7 个状态常量 + 中文表 + 显示状态() |
| 路径 | 驱动根/入口路径/日志目录/日志路径(从注册表行算,唯一路径来源) |
| 快照 | 读快照 / 写快照(原子:.tmp + os.replace)/ 组装快照 / 待重启 |
| 判定 | 复核(驱动, 状态行) -> (状态, 原因)(判活回 /proc)/ 该拉起(驱动, 状态行, 判定) -> 理由 |
不做:不碰 SQL(快照是文件、判定是纯逻辑)。
内核/db.py — 唯一碰 SQL 的文件(43 个函数 / 8 张表)
| 组 | 关键接口 |
|---|---|
| 连接 | 数据库(dataclass) / 从配置 / 连(autocommit) / 建表 / 建引导器表 / 库存在 / 查(公开转手,给自测/诊断) |
| 注册表 | 记驱动(upsert) / 清不在 / 取驱动 / 取全部驱动 |
| 状态 | 确保状态行 / 写状态(白名单动态 UPDATE,只改传进来的列) / 取状态 / 取全部状态 |
| 事件 | 写事件(连接, source, kind, message, driver=None, level="info", data=None) / 读事件(条数, driver=None) |
| 扫描 | 记扫描批次 / 取最近扫描 |
| 命令 | 记命令 / 领命令(pending→running,FOR UPDATE SKIP LOCKED) / 记命令结果 / 读命令 / 收尸命令 |
| 调用 | 领调用(pending/waiting→waiting) / 写调用 / 读调用 / 取调用(state) / 同锁在跑(只认 running) |
| 通知/锁 | 监听 / 收通知 / 通知 / 试锁(会话级咨询锁) |
| 台账 | 记体检 / 记运行开始 / 记运行结束 / 记内核pid / 最近运行 / 未结束运行 / 收尾未结束 / 今日运行次数 |
规矩:text[] 列传 list;jsonb 列传 "json.dumps 出来的串 + ::jsonb 强转"(写调用 的 result
给 dict/list 时也会自动 dumps);写状态/写调用 都有列白名单,拼错的列名会被挡(自测里有)。
内核/进程.py — 通用进程库(内核管驱动、引导器管内核,都用这一份)
| 接口 | 说明 |
|---|---|
读cmdline / 读stat / 读进程信息 / 读开机秒 |
直读 /proc(不用 ps:busybox 截断、`ps |
判活(pid, 入口) -> 状态字符串 |
running/stopped/crashed/exited/zombie;入口决定是否认领 |
匹配入口 / 按入口找进程 / 进程组成员 |
cmdline 精确点名;组员跳僵尸(Z 杀不动) |
启动(argv, cwd, 日志, env, 入口, 探活秒, 分隔="") |
独立会话 + stdio 重定向 + 分隔头 + 探活;秒退不抛,返回 ok=False + 本次日志尾巴 |
停止(pid, 入口, 超时) |
校验 cmdline → SIGTERM 进程组 → 超时升 SIGKILL → 复查 |
从位置读日志 / 尾日志 / 跟日志 |
进程层通用读取(带过滤/轮转感知的看 日志.py) |
跑命令 / 跑并转发 |
前台跑并回收输出(引导器透传内核用) |
内核/日志.py — 日志系统(见 设计/04)
| 接口 | 说明 |
|---|---|
| 路径 | 内核日志路径/内核输出路径/引导器日志路径/驱动日志路径(集中定义,别再各处拼) |
| 级别 | 级别表 / 规范化级别 / 级别序号 / 达标 |
| 写 | 记(路径, 级别, 来源, 消息, 门槛, 控制台) / 追加 |
| 解析 | 解析行(拆字段)/ 猜级别(驱动裸行)/ 命中(过滤判据) |
| 读 | 尾(路径, 行数, 级别, 关键词, 含轮转) / 跟(路径, 级别, 关键词)(按行缓冲 + 轮转感知) |
| 轮转 | 轮转(路径, 上限字节, 保留份数) / 轮转名单 / 带序路径 |
| CLI | 选项(dataclass) / 解析选项(参数, 默认行数) / 打印(行表, json输出) / 转json行 |
| 杂 | 大小文本 / 现在文本 / 控制台开() |
内核/文本.py — CJK 宽度感知的输出
左/右(对齐字面量)、字符宽、显示宽度、截断、填充、横线、表格(表头, 行表, 对齐=None)、打印。
中文是双宽 —— 直接用 len() 排表格是歪的,所有表格都过它。
验收与自测文件
| 文件 | 覆盖 |
|---|---|
内核/自测进程.py |
真起进程、真收子树(11 项) |
内核/自测内核.py |
58 项纯逻辑:脏配置 / 契约 / 环 / 状态机 / 拼命令 |
内核/自测配置.py |
配置解析 / 版本比较 |
内核/自测db.py |
真库真 SQL 182+ 项(11 组,三层隔离:前缀 / 事务回滚 / 两道终检) |
内核/自测日志.py |
86 项:真写文件 / 真轮转 / 真起子进程跟日志 / 真起进程验分隔头 |
内核/自测AST等价.py |
工具:改注释后证明"逻辑一行没动" |
试跑引导器.py |
端到端 11 项(老板亲自跑的验收) |
3. 想改 X → 动哪几处(连带清单,漏一处就是坑)
| 想改 | 动 | 必须同步 |
|---|---|---|
| 加/改一个内核子命令 | 内核/内核.py 的 执行命令 + 命令Xxx + 用法() |
UEFI.boot.py 用法()(透传清单)、文档/03、自测内核.py(纯逻辑那部分) |
| 加/改驱动配置字段 | 扫描.校验单个 + db.drivers DDL |
设计/01 字段表、状态.组装快照(要进快照的话)、自测db.py(注册表组)、文档/02 |
| 加状态取值 | 状态.py 常量 + 中文表 |
db.driver_state(text,不用改)、状态.复核/该拉起、自测内核.py、设计/01 状态机、设计/02 判定表、文档/05 |
| 加一张表 | db.内核表 里的 DDL 字符串 + 读写函数 |
自测db.py 的两道终检(8 张表 → 9 张)、文档/08、设计/02 |
| 改调用仲裁某条 | 内核/内核.py 的 校验调用(+ db.同锁在跑 之类) |
自测db.py 调用组、文档/04 六条表、设计/02 |
| 改日志格式/选项 | 内核/日志.py(解析/格式化/选项) |
内核/内核.py 命令日志 + 引导器 内核日志/命令日志(共用解析)、设计/04、文档/03/05、自测日志.py |
| 改体检项 | UEFI.boot.py 的 体检 + 检查项 |
设计/03、试跑引导器.py(第 2 项查 6 项齐全)、文档/01 |
| 改轮转策略 | 内核/日志.py 轮转 + 调用点(内核 轮转日志 / 引导器启动段) |
设计/04、自测日志.py 第 5 组、.gitignore(如果加了新文件后缀) |
| 改"什么时候自动拉起" | 状态.该拉起 |
自测内核.py、设计/02 第 5 节、文档/05 断电表 |
| 加一个样板驱动 | 驱动/<新名>/(配置 + 入口) |
设计/01 若有新字段、文档/02 的样板清单 |
改完一律跑:uvx pyright + 五份自测 + python3 试跑引导器.py(明细见 10-验收与质量门.md)。