Files
qinglong/docs/adr/ADR-0177-active-head-driven-local-ai-application-composition.md
T

144 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0177Active Head 驱动的本机 AI Application Composition
- 状态:Accepted
- 日期:2026-07-27
- 关联:RFC D-73、D-84、D-87、D-89、D-156、D-158、D-166、D-167ADR-0167、ADR-0176
## 背景
ADR-0176 已建立显式、耐久的本机 AI feature active head 和数据库写围栏,但产品启动
尚未消费该事实。仅有管理 CLI 和底层 Profile 会留下四个缺口:
1. 部署不含 AI、含 AI 但未启用、已经启用三种状态没有统一的产品 composition
2. 若基础 application 静态导入 AI,未启用 AI 的路由设备也会安装或加载可选能力;
3. 进程启动后发生停用时,内存中的 provider 可能继续接受请求;
4. operator 不知道 activate/deactivate 后何时需要重启来完成加载或卸载。
本决策只解决 edge/standalone 本机产品 composition。它不开放 HTTP/MCP/UI route,也
不把本机 Owner ceremony 扩展为 Cluster authority。
## 决策
### 1. 复用现有 application 包,不新增小包
在既有 `@qinglong/local-application` 内增加显式
`@qinglong/local-application/ai-feature` 子路径。基础 root 保持不变;不新增
workspace package、第三方生产依赖、进程、listener、watcher、后台 timer 或缓存。
基础 application 制品不安装 `@qinglong/ai`。只有显式选择 `*-application-ai`
制品时才把 AI package 纳入 production archive;选择该制品仍不代表启动时加载 AI。
`@qinglong/local-sqlite/optional-feature-runtime` 复用现有
`packageManagement.ts`,只增加窄 subpath 和返回类型,不创建“一文件一包”。
### 2. 启动顺序由 durable active head 决定
产品入口固定执行:
```text
base application ready
→ deployment included?
→ open optional-feature read authority
→ read exact 9007 head
→ active 时才 dynamic import AI
→ validate full feature history/schema/checksum and exact generation/digest
→ assemble shared invocation/pricing storage
→ bounded recovery
→ load provider credentials
→ active
```
以下路径不得加载 AI 或 provider
- base application disabled
- deployment 明确 `excluded`
- 9007 schema 不存在;
- head 为 `inactive`
active head 存在但 AI package、完整 schema/history、storage、recovery 或 provider
不可用时,整个组合失败关闭,并停止已经启动的 base application,不能降级成一个
表面 ready、实际丢失已启用能力的进程。
### 3. 每次请求复验 generation/digest
启动时读取的 active generation 和 transition digest 不是永久内存开关。每次
`list_models``generate``stream` 和 usage read 在占用 operation slot 前,都通过
同一个 SQLite operation authority 复验 exact active head。
若 head 已停用或漂移:
- 当前请求以 `ModelGatewayProfileDrainingError` 失败;
- capability 立即停止接收新操作;
- 没有 active operation 时按 provider → storage 顺序释放;
- 有 active operation 时等待最后一个 operation 完成后自动释放,不需要 timer。
数据库事务内的 admission fence 仍是防止新 durable invocation 的最终权威;这里的
请求前 fence 用来及时撤销进程内 provider authority,二者不能互相替代。
### 4. 有界 drain 和重启语义
组合根只暴露一个统一 `stop()`。停止顺序为 AI admission/drain、provider、AI
storage,再停止 base application。轮询只发生在显式 `stop()` 调用栈内,默认超时
5 秒、默认间隔 25 毫秒;没有常驻 timer。超时返回 `timed_out`,不把仍在执行的调用
伪装成已停止;最后一个调用完成时 Profile 仍会释放 authority。
`ql3-ai-feature activate|deactivate` 的成功响应必须返回
`runtimeAction: "restart_required"``inspect` 返回 `"none"`
- activate 后重启,产品入口才会加载 AI;
- deactivate 的数据库写围栏立即生效,当前进程的下一次 AI 操作触发 drain;
- 重启是 operator 可验证的最终卸载边界,不实现隐式 watcher 或热重启。
### 5. Readiness 与可选 schema
本机基础 readiness 继续精确验证全部 owned base 表、列、索引和 trigger。其 adoption
`tableCount` 只排除已经受审的 14 张 AI optional feature 表;未知表仍计数并造成
manifest evidence 漂移,不能用前缀通配符隐藏扩展。
AI active composition 另外验证独立 90019007 history、checksum、schema 与 head。
因此“基础 readiness 不把受审 optional 表当作 legacy drift”不等于“基础 readiness
替 AI 证明可用”。
## 拒绝方案
1. **新建 `local-ai-application` package**:没有独立部署或依赖收益,只增加 importer
和单文件包,拒绝。
2. **基础 application 静态依赖 AI**:让禁用 AI 的低配设备承担安装和加载成本,拒绝。
3. **schema presence 即启用**:绕过 Owner active head,拒绝。
4. **只在启动时读一次 head**:停用后进程内 provider 仍可接受请求,拒绝。
5. **用 watcher/timer 热装卸**:增加空载资源、竞态和不可预测运维语义,拒绝。
6. **active AI 启动失败时继续 base ready**:部署声明与 durable operator intent 不一致,
拒绝。
7. **为 optional 表使用名称前缀豁免**:会隐藏未知插件或漂移表,拒绝。
## 当前证据
- local-application 18/18,覆盖 disabled/excluded 零 AI module、schema absent 零
provider、真实 active startup、请求前停用 drain、inactive restart 和 provider
启动失败后的完整 authority 释放;
- AI 98 项:96 pass、2 条 PostgreSQL 条件 skip;新增 active-head fence 与最后 operation
自动释放测试;
- local-sqlite 127/127、local-admin 57/57、local-adopted-profile 7/7、
local-owner-cli 22/2222-package 全量门在允许本机 listener 的环境退出 0
- 22-importer dependency/source audit `findings=[]`local-application 4 个源码文件;
精确允许 AI composition subpathAI root/internal 和其它 SQLite runtime subpath
均有负向门禁;
- edge/standalone 基础制品为 3,913,976/3,914,036 bytes、478 files、40 modules
application 为 4,578,408/4,578,540 bytes、591 files、87 modules
application-ai 为 5,256,223/5,256,367 bytes、635 files、87 modules。后者安装
`@qinglong/ai`,但入口初始 require 没有加载它;
- disabled AI benchmark 仅加载 1 个 AI moduleRSS 增量 475,136 bytes,三种 Profile
的 storage/provider/management loader 均为 0。
- PostgreSQL 18.4 arm64 physical HA Docker 门重新通过:timeline 1→2、旧主 fencing、
`pg_rewind` 只读同步重入、双 control replica 恢复和全部具体 gate 均为 true;
pg-9001pg-9006、11 张 AI 表及 ACL 在 promotion 前后精确一致。本地 composition
没有修改 Cluster schema,该门只证明既有 Cluster 边界未回归。
## 后续门禁
1. ADR-0178 已由真实 edge/standalone headless executable 选择该 composition root
通用 CLI 的 installed provider ceremony 仍保持失败关闭;
2. 在固定低配 Linux 路由设备上记录冷启动、峰值 RSS、断电、ENOSPC 与备份恢复;
3. activation/price mutation 的专属 COMMIT-response-loss/SIGKILL 矩阵;
4. provider 配置 ceremony 与认证产品 route;在这些 authority 完成前不开放 HTTP/MCP/UI
5. Cluster 继续使用 TLS identity、平台 Policy/quota 和职责分离,不复用本机 9007。