Files
efi-kernel/文档/06-架构与不变量.md
lou 2155f65c76 加"技能执行"示例驱动: 把 skill 真跑一遍 (选模型 / 拼 prompt / 校验输出 / 重试)
- 驱动/技能执行/: oneshot + system 解释器. 读自带 技能/*.json -> 按 tier 选模型 ->
  拼 prompt -> 调 OpenAI 兼容端点 -> 剥 JSON -> 逐条按档校验 -> 落盘 + 写 events
  main 档只锁外层 (plan 是数组 + 每步有 want); small 档锁死 (单值 want + 必须在契约
  清单里 + args 键白名单 + confidence 只许 0/0.5/1)
- 契约清单只读 PG 的 drivers 表拿 (不 import 别的驱动、不翻别人的文件夹);
  也支持 EFI_SKILL_CONTRACTS 显式给一份 (手工跑 / PG 不在时)
- 思考型模型把 max_tokens 吃光时, 把预算翻倍重试一次; 本机端点强制不走代理
- 驱动/Json解码: 补 provides ["Json解码:解析"] -- skill 样例里一直在用这个契约名, 之前没人提供
- 文档同步: README / 文档 02 / 文档 06 / 文档 10 (坑 27~30) / 设计 01 / 设计 05 (补记执行侧)
- .gitignore: 盖住 驱动/*/输出/ (运行时产物不进 git)
2026-09-17 12:39:10 +08:00

12 KiB
Raw Permalink Blame History

06 · 架构与不变量(动手改底座前先读)

这份文档是给"要改这套底座的人(含以后的我)"看的:分层必须守住的不变量数据流、 以及几个"为什么故意不做"。改动如果违反下面任何一条不变量,就不是重构,是换架构 —— 要先跟老板对齐。

1. 分层(一层管一层,谁都不越级)

                    ┌──────────────────────────────────────────┐
      人 / 脚本 ───► │  引导器  UEFI.boot.py   (内核的管家)       │
                    │  体检 6 项 / venv 重建 / 包台账 / 记账     │
                    │  管内核进程的: 启动 停止 重启 状态 日志     │
                    └───────────────────┬──────────────────────┘
                                        │ 起/停/判活(走 进程.py)
                    ┌───────────────────▼──────────────────────┐
                    │  内核  内核/内核.py   (常驻总调度, 甲)      │
                    │  命令消费 / 调用仲裁 / 依赖巡检 / 心跳      │
                    │  扫描 + 校验 + 契约匹配 + 拓扑排序          │
                    └───────────────────┬──────────────────────┘
                                        │ 启停/判活(同一个 进程.py)
                    ┌───────────────────▼──────────────────────┐
                    │  驱动  驱动/<名>/  (确定性代码能力接入层)   │
                    │  有 配置.efi.json 才算; 源码+venv 或 exec   │
                    └───────────────────┬──────────────────────┘
                                        │ 只经 PG 表(events 总线 / calls 调用)
                    ┌───────────────────▼──────────────────────┐
                    │  程序 / Skill  (声明式, 模型驱动, v0.2)    │  ← 本版不实现
                    └──────────────────────────────────────────┘

        共享内存(唯一的"内核内存"):PostgreSQL 库 efi_kernel8 张表,见 08

职责边界(越界就是架构腐化):

只做 绝不做
引导器 环境体检 / 内核进程的生命周期 / 记账 不实现内核的子命令(只透传)、不改驱动配置、不自动修环境
内核 驱动注册与校验、契约匹配、启停与收尸、调用仲裁、巡检 不碰驱动内部(不解析 stdout)、不替引导器体检环境
驱动 声明契约、干活、往 PG 汇报 不 import 别的驱动、不写 drivers/driver_state/commands、不互相调用

2. 硬不变量(破了架构就塌)

# 不变量 为什么 在哪守
1 内存只有 PG 一处:不引入文件状态源、socket、消息队列、共享内存 两处真相 = 迟早不一致;"不用通信协议,直接用 pgsql 当内存"是老板定调 全项目(db.py 是唯一碰 SQL 的文件)
2 判活一律回 /proc 复核,库里 pid 只当账 pid 会被系统复用,拿旧 pid 发信号会杀到别人 进程.判活 + 状态.复核(每次动作/渲染前都调)
3 发信号前必须校验 cmdline;宁可不杀,不可误杀 老板的 GUI 程序被误杀过一次(点名批评) 进程.停止(入口=…) 一律传入口
4 一个文件只装一种内容 混装过:命令输出混进日志文件,按级别筛不了 三条日志道 / 快照 / 配置 分家
5 一份实现不复制 两套实现 = 各有一套坑,还各自以为对方对 进程.py日志.py文本.py状态.py 一份
6 驱动之间零耦合 耦合了就打架,而且"认识对方"会让替换/复用全断 只有 provides/needs;寻址在 calls 表里由内核做
7 驱动只许写 events / calls 否则驱动能去停别人的进程,照样打架 约定 + 内核校验(角色级硬隔离留 v0.2)
8 隔离失败:一个驱动坏只标它自己 一个坏配置不能让整台机器停摆 校验单个 就地失败、invalid + 原因
9 不吞错、不静默降级 出错必须留痕(last_error / events / 日志三处) 内核对 PG 连不上直接退出(不降级到文件);引导器自己的命令才允许 WARN + 快照
10 状态取值固定 7 个stopped/starting/running/exited/crashed/failed/invalid 状态是各方共用的词汇表,加一个要想清楚全链路 状态.py 常量 + 中文映射
11 一次只动一件事:启动与停止分两条命令,没停干净不启 反复起/杀会攒孤儿互抢(踩过 13 份隧道孤儿) 内核 重启 先停干净、试跑引导器.py 每步复查
12 日志轮转只在拉起进程之前做 运行中改名 → 进程继续写老 inode = 日志丢 内核.拉起一个 / 引导器 内核启动
13 常驻内核独一份PG 咨询锁 0x65666901,会话级) 两个调度器 = 打架;会话级锁进程一死自动放,不留死锁 db.试锁
14 契约匹配的确定性:同契约多提供方按名字取先、启动顺序按名字排 结果要可复现,不能靠文件系统顺序碰运气 扫描.取契约 / 扫描.排顺序

3. 操作纪律(破了不塌,但会难查)

纪律 说明
配置外置一份 环境.efi.json 是项目级唯一配置(引导器写、内核只读);内核不再自持配置文件
配置外置:驱动字段 驱动自己的配置只有 配置.efi.json 一份,内核只读
界面纯中文 库里存英文(值稳定),给人看的一律过 显示状态() 转中文
CLI 可重定向 输出要能 > 文件--json 只给机器读(人读的表格不混进去)
改完代码必跑 uvx pyright + 五份自测 + 试跑引导器.py(细节见 10-验收与质量门.md
注释写"为什么" 代码里的 【…】 段落是决策记录;改逻辑时把理由一起改,别只改代码

4. 主流程(谁在什么时候碰哪张表)

流程 步骤 涉及
体检移交 UEFI.boot.py <命令> → 体检 6 项 → 写 环境状态.efi.json + kernel_env → 透传内核(前台 跑并转发 / 后台 进程.启动 记账 kernel_runs kernel_envkernel_runs
扫描 扫目录 → 9 条校验 → 收 provides → 查 needs → 查环 → 拓扑排序 → upsert drivers → 清幽灵 → 收尸(状态.复核)→ 写 driver_state → 落快照 → 记 scans + events(scan) driversdriver_statescansevents
启动 commands(CLI) 或直接调 → 拼命令(解释器/args/env/EFI_DB)→ 轮转日志 → 进程.启动(独立会话、分隔头、探活)→ 写 driver_state(running) + events(start) → 失败则 failed + 日志尾巴 driver_stateevents
停止 校验 cmdline → SIGTERM 进程组 → 等 stop_timeout → 升 SIGKILL → 收子树 → 写 stopped + events(stop)级联停下游 同上
调用 驱动 INSERT calls(pending) → 内核领 → 六条仲裁 → 转发(running + pg_notify driver_<提供方>) → 提供方干活回填 done/failed;超时内核收权 timeout callsevents
巡检(每 10s 收尸(复核 + 记 exit)→ 按 autostart/restart 重拉 → 级联标"依赖失效" → 收权超时 driver_statecallsevents
心跳(每 300s 写一条 heartbeat(断电时间线的最后一格) events

5. 双真相(最容易搞混的地方)

活真相 落盘快照
位置 PG driver_state / drivers 驱动/<名>/运行.efi.json
谁写 内核 内核(每次扫描/启停/收尸整份重写,原子替换)
用途 一切判定与决策 文件夹自包含:拷走驱动目录、PG 不在时也能看上次状态
不要做 不要把快照当状态源去判活;驱动作者手改它没意义(下次刷新覆盖)

同理配置:环境.efi.json(项目级,引导器写/内核读)与 驱动/<名>/配置.efi.json(驱动作者写/内核读) 是两份不同层级的配置,不要合并 —— 合并后"谁的配置"就说不清了。

6. 常驻调度(甲)在干什么,为什么必须常驻

四件事,都是"只有在运行时看着才知道"的:

  1. 命令消费CLI 客户端把命令写进 commandsstate=pending),常驻内核 LISTEN/NOTIFY 领走执行回填; CLI 自己执行过的命令落 state=running,所以常驻内核不会重复领
  2. 调用仲裁calls 表的六条(越权/成环/无人提供/按需拉起/同锁排队/超时收权)。
  3. 依赖巡检(每 10s):崩了按策略重拉、上游崩了给下游标"依赖失效"(上游只是没启动不算)。
  4. 心跳(每 300s):断电时间线。

退出时不顺便停驱动("内核可以随时死"),下次启动先收尸认领。

7. 为什么故意不做

没做 理由(老板定调 / 工程判断)
TUI / 图形界面 "内核要什么界面,肯定是纯 cli 啊和日志" —— 给脚本/自动化用的东西,输出要能 > 重定向
自造通信协议(socket / 消息格式) "不用什么通信协议,直接用 pgsql 当内存"events 表就是总线,驱动直插
supervisor / systemd / RPC 框架 内存层就是 PG;多一层框架 = 多一处真相与依赖(引导器本身是纯 stdlib,坏了还能报错)
自动修环境 / 自动装包 / 自动重建 venv "手动控制"口径:只有 环境 重建包 安装 两个显式命令才动手
开机自启 网络/系统服务手动控制是老板偏好;爬虫管线那种长跑任务是例外
驱动自动重试 failed 配置错了反复撞墙只会把日志刷满;restart=on-failure 只针对崩了
日志落库 结构化汇报走 events;日志文件管"翻旧账 + tail";两份真相没意义(细节 设计/04

8. 已知薄弱点(诚实清单,不是 bug 但要知道)

现状 什么时候要处理
驱动权限隔离 约定 + 内核校验,不是 PG 角色级硬隔离:驱动拿到的连接串理论上能改 drivers v0.2(多用户/不可信驱动时)
调用没有优先级/公平性 锁上按 id 先到先得,无权重、无抢占 出现"重要任务被长任务堵住"时
调用结果没有大小限制 result 是 jsonb;大对象该走"提供方写自己的表 + result 只放引用" 出现大结果时
依赖巡检 10s 粒度 崩了最多 10s 才被发现/重拉 需要更快时(但会更吵)
单 PG 单库 没有分片/跨库 不在本版范围
程序/Skill 层由驱动执行 skill 归驱动管:声明在 驱动/<名>/技能/*.json(两档样例 驱动/样板技能/,规范 设计/05),驱动自己读自己解释;内核不认识它 执行样例见 驱动/技能执行/2026-09-17 落地:选模型 / 拼 prompt / 逐条校验 / 预算不够重试一次)