Files
qinglong/docs/adr/ADR-0260-generation-bound-content-free-plugin-package-prompt-execution.md
T

250 lines
15 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-0260Generation-bound、Content-free 的 Plugin Package Prompt 执行
- 状态:Proposed
- 日期:2026-08-02
- 关联:D-144、D-157、D-161、D-207、D-211、D-212、D-243
## 背景
Plugin Package 已经能够把 Prompt 文件物化为 generation-bound 语义资源,并通过
automation publication 发布 exact active definition。但“定义已发布”不等于“可以安全
调用模型”。如果执行端临时读取 current head、直接渲染后调用 provider,会出现以下问题:
- Package 在渲染、admission 和 provider I/O 之间发生升级、disable、quarantine 或
publisher revocation,导致一次请求混合两代事实;
- provider 已产生费用,但 Run、StepRun、quota 或 audit 尚未提交;
- COMMIT 已成功但响应丢失时,重试再次调用 provider;
- 为了方便回放,把 template、参数或模型正文复制到数据库、日志、备份和 HA 副本;
- 把 Prompt 伪装成 Task 或单步 Workflow,引入错误的执行 authority,或让路由设备承担
frontier、Attempt、timer、watcher 等不必要常驻成本;
- 为协议、SQLite adapter、PostgreSQL adapter 分别新增 workspace package,继续放大
importer、构建、SBOM 和发布边界。
QingLong 同时面向小型路由设备和多节点集群,因此该能力必须满足两端同一语义:低配设备
按请求付费且禁用时零加载;集群在并发、响应丢失和数据库主从切换下仍可围栏和对账。
## 决策
### 1. Prompt 是显式 model execution
Plugin Package Prompt 不是 Task、Workflow、Trigger 或 scheduler。每次调用创建:
- 一个父 `Run`,从 admission 起为 `running`
- 一个 `StepRun.kind=model`,从 `ready` 经唯一 ModelInvocation 链进入终态;
- 一个 content-free admission receipt
- 一个由 ModelInvocation Completion 或 Resolution 支撑的 finalization receipt。
不创建 RunAttempt、child Run、Workflow frontier、后台 cadence 或 per-Prompt worker。
### 2. 执行计划不可变且不含内容
执行前从 exact `PluginPackageAutomationPublication` 生成最多 32 KiB 的 immutable plan。
plan 必须绑定:
- Project、Package、installation、lock
- generation、generation digest、materialized revision digest
- publication digest、Prompt ID、Prompt definition digest
- request、invocation、Run、StepRun、trace、发起 Subject 与 Project Policy fence
- provider、model、max output tokens、temperature、deadline
- parameter digest、model request digest 和 input bytes。
plan、receipt、RunEvent 和数据库索引列不得包含 template、参数值、渲染后的 message 或模型
输出正文。
### 3. 内容只做一次瞬态 exact rendering
Prompt template 与参数只在调用栈内存中渲染:
- required 参数缺失失败关闭;
- optional 参数缺失表示省略,不等于显式空字符串;
- 参数值不会再次递归解释其中的 placeholder
- 不接受未声明参数;
- 单参数值与单 message 均受 64 KiB 上限;
- 生成后的输入字节、token 和 deadline 继续受 Model Gateway 硬预算约束。
渲染结果仅交给本次 Gateway 调用,admission repository 永远看不到正文。
### 4. Admission 必须先于 provider I/O
首次 admission 在单个存储事务中完成:
1. 先检查相同 request ID 的 durable receipt
2. 对新请求复验 exact active publication
3. 在同一事务内复验发起 Subject 的 current Project/RoleBinding fence
4. 复验 current install/lock、lifecycle、quarantine、publisher start guard
5. 复验 exact materialized revision 中恰好存在一个同 digest Prompt
6. 原子写入 Run、admission event、model StepRun、StepRun mutation 和 admission receipt。
任何 guard、identity、digest、外键或计数冲突都整体回滚。exact replay 必须先于 current
guard,使已提交 winner 在 Package 后续 disable、withdraw 或 upgrade 后仍能收敛。
### 5. ModelInvocation 是唯一 provider fence
Prompt executor 不直接持久化 provider 状态,也不建立第二套 invocation 状态机。所有
provider I/O 必须经过 D-157 的 ModelInvocation
- `ready→running`、Run/Event/Mutation 和 ModelInvocationStart 原子提交;
- quota reservation、price quote、usage 和 completion 沿用既有 Gateway policy
- `running→succeeded|failed|timed_out|lost` 由唯一 StepRun mutation chain 决定;
- durable start 已存在但 completion 缺失时禁止 provider replay
- completion 为 `outcome_unknown` 且没有人工 Resolution 时保持不可判定;
- Resolution 为 retry 时父 Prompt Run 保持运行,cancel/fail 才能终态化。
### 6. 父 Run 从终态证据收敛
finalization repository 在一个事务中读取 exact admission、Run/StepRun 与
ModelInvocation Completion/Resolution
- Completion `succeeded|failed|timed_out` 直接决定父 Run 状态;
- `outcome_unknown` 只接受绑定同一 completion digest 的终态 Resolution
- Run version 与 event sequence 必须保持相等并 CAS 增加一次;
- final RunEvent 只保存 evidence digest、final StepRun digest、plan digest 和低敏状态;
- finalization receipt 与 Run/Event/StepRun/evidence 必须可双向复验。
### 7. Replay 不承诺返回模型正文
首次 live 调用可以把 `GenerateResult` 返回当前 caller。exact replay 返回 admission 与
finalization receipts,但 `result``null`,绝不再次调用 provider。
如果产品需要跨请求读取输出,必须后续引入显式、受保留期与加密策略约束的 Artifact sink
并只在 ModelInvocation completion 中保存 bounded output ref/hash/bytes。不得把正文补进
Prompt admission/finalization 表。
### 8. 双方言与权限边界
SQLite
- AI feature 未 active 时不加载 Prompt executor、不建新进程或连接;
- 与 Local ModelInvocation 共享同一 operation authority
- admission/finalization 使用短 `BEGIN IMMEDIATE`
- 两张 Prompt 表属于可选 `ModelInvocation*` schema allowlist,不污染主 schema readiness
- 停用 AI 时既有 exact replay 可读,但新 mutation 受 feature fence。
PostgreSQL
- 使用独立 `ql3_ai` migration stream 的 9007/9008
- `model_invocation_prompt_admissions`
`model_invocation_prompt_finalizations` 为 append-only
- `ql3_runtime` 只有 `SELECT, INSERT`,没有 `UPDATE, DELETE`
- AI schema 提供 Prompt 专属 `SECURITY DEFINER`
`plugin_package_prompt_admission_snapshot`,不借用名称错误的 Workflow 入口;
- snapshot 复用核心 `plugin_package_automation_start_allowed`,并锁定 publication 与
materialized revision;它同时按 Subject type/ID、Project version 与最新 RoleBinding
version 复核 API admission 的 immutable policy fence
- mutation 使用带 5 秒 statement、2 秒 lock timeout 的短 SERIALIZABLE transaction
对 serialization/deadlock 最多重试三次。
### 9. Package 与部署边界
实现全部留在既有 `@qinglong/ai` package 的显式 subpath
- `plugin-package-prompt-execution`
- `plugin-package-prompt-executor`
- `local-plugin-package-prompt-admission-storage`
- `postgres-plugin-package-prompt-admission-storage`
- `postgres-plugin-package-prompt-application`
这些是同一可选 AI capability 的协议和 adapter,不满足独立部署、进程、权限域或重依赖
隔离价值,因此不得新增 workspace package。
Edge/Standalone 通过 Local application 在 AI active 后动态装配。Cluster 通过显式、默认
关闭的 PostgreSQL application composition 装配;disabled 分支不得打开 Pool、执行 readiness
或加载 provideractive 分支必须按“只读 migration/ACL readiness → bounded recovery → provider
credential load”顺序启动。该 composition 不增加进程、listener、端口或 workspace package。
Cluster Control 只在显式注入 `promptExecution` capability 时增加
`POST /api/v3/projects/{projectId}/packages/{packageName}/prompts/{promptId}/executions`
默认 allowlist 仍只有 Run read/cancel。该 route 复用现有 bearer authentication、认证前
rate shield、Project Policy、durable security audit、TLS/body/response/in-flight/request
timeout,并使用独立 `model.invoke` permission。客户端只能提交 publication digest,不能
提交 publication JSON;服务端从受限 snapshot 解析 exact publication,随后 admission
事务再次复核 publication 与 policy fence。route 不新增进程、listener、端口、依赖或包。
## 已实现证据
- immutable Prompt plan、非递归 rendering、digest/size/identity normalizer
- SQLite admission/finalization repository 与 Local AI lazy composition
- PostgreSQL 9007/9008、Prompt 专属 snapshot 与 SERIALIZABLE repository
- Prompt executor 的 first execution、safe resume、completion repair 与 exact replay
- SQLite 真库覆盖原子 admission、publication withdrawal 后 replay、target drift rollback、
parent Run finalization、provider exactly-once 与内容排除;
- 原生 Linux arm64 Node 24.18.0 的 `router-stress-ci`128 MiB、0 swap、0.5 CPU)与
`edge-release-ci`256 MiB、0 swap、1 CPU)已直接运行 Local AI active product vertical
正式 install/materialize/publication → Prompt execute → `succeeded@v5` → exact replay
provider 恰好一次、零 RunAttempt、durable SQLite 不含私有输入/输出。两档 Prompt process
peak RSS 分别为 `92282880`/`90951680` bytes,数据库 logical/allocated growth 均为 `0`
cgroup `memory.peak` 分别为 `128229376`/`129253376` bytes,零 max/OOM
- 同一两档门已纳入 ModelInvocation start/completion 的 7 点/profile、14 场景
`SIGKILL → reopen → exact replay` 矩阵;Prompt admission/finalization 外层事务再覆盖
10 点/profile、20 场景,其中 16 个 COMMIT 前 crash 全回滚、4 个 COMMIT 后 crash durable
exact replay/content-free/integrity/foreign key 全绿并报告
`promptAdmissionFinalizationCrashProven=true`。所有 CI 证据仍固定
`physicalPowerLossProven=false`,不把进程崩溃或 tmpfs 文件增长冒充闪存写放大或真实断电;
- crash fixture 通过 `@qinglong/ai` 的 test-only workspace devDependency 使用正式
`@qinglong/local-sqlite` baseline migration 与 Package repositoriesAI production
dependencies 仍只有 `@qinglong/runtime-core`,没有新增 importer、生产依赖或部署闭包;
- AI suite 共 115 项:112 pass、3 个无 URL 条件 skip、0 fail;新增覆盖 Prompt 外层事务
20 点 crash matrix、Cluster disabled
零加载、exact migration/ACL readiness、先 recovery 后 provider、幂等资源释放与 readiness
失败不开 provider
- runtime-core 430/430 通过,`model.invoke` 仅 owner/admin/operator 可直接使用,viewer
拒绝,agent 保持 `require_approval`Cluster Control 167 项为 165 pass、2 个外部服务
条件 skip、0 fail,覆盖 strict body、subject/fence 传递、request abort、低敏错误、live
result 与 content-free replay receipt,以及未注入时 route 不存在;
- 独立 PostgreSQL 18 真库使用 migration、package-executor、runtime 三个非特权账号,完成
Cluster application readiness、Package install/materialize/publish、Prompt execute、
ModelInvocation completion、父 Run finalization 与 replayprovider 调用一次,Run 收敛到
`succeeded@v5`durable JSON 不含私有参数和模型输出;
- PostgreSQL 18.4 arm64 physical-streaming HA 门在最新 9008 checksum 上
`gates.passed=true`9001—9008 history/ACL、runtime 只读 migration history、Prompt
admission/finalization、provider exactly-once、content-free durable facts 在晋升前复制并在
timeline 1→2 后完全一致;旧主先 fencing,再经 `pg_rewind` 以只读同步 standby 重加入,
新 product service 从数据库解析 publicationRoleBinding 撤销后旧 policy fence 被拒绝且
provider 调用数保持一次;`gates.passed=true`,临时容器/网络/卷零残留,用户现有
evidence control-plane 未被触碰;
- 通用三节点 K3s/CloudNativePG 1.30/PostgreSQL 18.4 HA 门继续全绿,覆盖 TLS 1.3、
identity/certificate rotation、failover/outage recovery、CNI/RBAC 与 durable facts。
## 接受门与非目标
本 ADR 保持 Proposed,直到以下门完成:
1. [x] 在 PostgreSQL physical streaming timeline promotion、旧主 fencing、rewind/rejoin
后,独立证明 9007/9008 migration、ACL、admission/finalization receipt 完全一致;
2. [x] Cluster application 显式装配 Prompt executor,且不存在 repository 可用即等于
route 上线的隐式行为;
3. [x] 产品 API transport 完成受认证 Principal、Project Policy、`model.invoke`、rate/body/
timeout budget、request abort 和低敏错误映射;agent 自动化仍必须先通过 approval 产品链;
4. [x] API 入口明确区分 live result 与 content-free receipt replayUI/MCP 可复用该 API
但不作为本 ADR 接受前提;
5. [ ] durable output 已由 ADR-0261 单独冻结 envelope、transaction、authorization、retention
与 GC 边界;双方言原子 adapter、产品读取和 maintenance authority 完成前保持不可达;
6. [x] 在固定 128/256 MiB 原生 Linux CI envelope 中补充 active application、RSS、SQLite
logical/allocated growth、exact replay、内容排除、ModelInvocation 与 Prompt admission/
finalization 外层事务 SIGKILL/reopen 矩阵;disabled 零数据库/provider 加载继续由组合测试
证明;
7. [ ] 在固定物理低配路由设备上采集最终 application artifact 的 active RSS、真实数据盘/
闪存写放大与受控断电重启矩阵。CI 报告必须保持
`physicalPowerLossProven=false`,完成前不得形成最低支持声明。
以下不属于本 ADR
- Prompt 定时执行或 Workflow 内嵌 Prompt
- Agent、多模型编排、Tool calling 或向量检索;
- Secret 自动插值;
- provider 自动重试;
- Prompt/输出正文默认采集;
- 新 workspace package、daemon、watcher、listener 或 per-Prompt timer。
## 被拒绝方案
1. **把 Prompt 包装成单步 Workflow**:引入不需要的 frontier、Attempt、cancellation 与
recovery ownership,放大路由设备成本。
2. **把 Prompt 当普通 Task**:绕过 Model Gateway policy、quota、price、usage 与唯一
model StepRun fence。
3. **provider I/O 后补 Run**:数据库故障时产生无 durable owner 的外部费用和结果。
4. **exact replay 自动再调 provider**COMMIT response loss 或 caller retry 会重复计费。
5. **持久化 template、参数和输出**:扩大 Secret、个人数据、日志、备份和 HA 副本泄漏面。
6. **复用 Workflow snapshot 函数**:实现可行但 authority 名称错误,会把 Prompt 生命周期
隐式耦合到 Workflow admission;因此改为 AI schema 内 Prompt 专属入口。
7. **为每个 adapter 拆 package**:没有独立部署或依赖价值,违反 D-207 并增加供应链成本。