21 KiB
ADR-0261:显式加密的 Durable Plugin Package Prompt 输出 Artifact
- 状态:Proposed
- 日期:2026-08-02
- 关联:QL-RFC-0001 D-207/D-243/D-244、ADR-0026、ADR-0027、ADR-0159、 ADR-0160、ADR-0260、ADR-0262
上下文
ADR-0260 默认只向首次 live caller 返回模型正文。Prompt plan、RunEvent、ModelInvocation、 admission/finalization receipt 和数据库镜像列全部 content-free;exact replay 不再次调用 provider,也不承诺找回正文。这个默认值适合低配路由设备,但 UI、MCP 或长时间任务有时需要在 调用断线、进程重启或 Cluster 主库晋升后继续读取结果。
现有 Artifact 不能直接改名复用:Tool Invocation Artifact 是 input/preview 双对象和审批
start barrier;Remote Worker Artifact 是日志流、对象存储 promotion 与 Worker ACK;LocalProcess
Artifact 又依赖本地文件、range 与 truncation fact。Prompt 输出需要保存完整、最多 1 MiB 的
GenerateResult,并在 provider exactly-once 窗口修复 ModelInvocation completion。三者的原子性、
授权和 GC authority 均不同。
决策
1. 默认仍为 live-only,durable 必须显式 opt-in
Prompt execution plan 后续只允许 live_only 或带精确 retention policy revision/digest 的
durable_artifact。默认 live_only 不解析 key provider、不构造 Artifact repository、不执行
Artifact find/put/read,也不增加数据库行、文件、对象存储请求或后台状态。
当前 plan contract 已加入上述显式 output intent。为保持 alpha 孵化期间已经落盘的 receipt 可重放,
旧 plan 缺少 output 字段时按 live_only 解释,但计算旧 plan digest 时仍保持字段缺失;新建 plan
始终写入显式 output。这是一条仅用于既有 alpha 数据的 digest compatibility 规则,不允许新调用方
省略 output intent。
同一 request ID 的 output mode 和 retention digest 必须进入 immutable plan digest。live-only 完成后不能通过 retry 补取 durable output;durable request 也不能降级成 live-only exact replay。 调用方若改变模式或 policy,得到稳定 conflict,而不是隐式复用旧费用或正文。
2. Envelope、Reference 与端口留在现有 AI package
共享 contract 位于
@qinglong/ai/plugin-package-prompt-output-artifact,不新增 workspace package。首个 v1 已实现:
qinglong/plugin-package-prompt-output-artifact@v1AES-256-GCM envelope;- Project/Run/StepRun/ModelInvocation/User、provider/model、content digest、output bytes、key ID、 retention revision/digest/eligible time全部进入 AAD 和 Artifact digest;
- Artifact ID 由 ModelInvocation ID 的 domain-separated SHA-256 确定性派生;
- ciphertext 打开后必须重新规范化
GenerateResult,复验 provider/model、UTF-8 output bytes 与 content digest; - content-free reference 只含 opaque identity、digest、bytes、key 与 retention metadata;
- immutable
put/findrepository、key provider 与 read authorizer ports; - 明文沿用 Model Gateway 的 1 MiB 上限,完整 JSON envelope 硬限 1.5 MiB;retention 只允许 1 小时至 365 天。
Edge/Standalone adapter 已留在既有 @qinglong/ai SQLite storage subpath,Cluster adapter
已留在既有 AI PostgreSQL storage subpath。它们共享 protocol/test contract,但不共享连接或部署
实现。单个领域文件、repository 或 migration 不构成新 package;只有未来出现独立对象存储重依赖、
进程/权限域或可替换部署单元时才重新评审边界。
3. Artifact 必须先于成功 Completion,并由同一事务绑定
不能在 gateway.generate() 已写成功 Completion 并返回后再保存 Artifact:进程可能在两者之间
退出,exact replay 会正确阻止 provider,却永远失去正文。
durable 路径必须把以下事实放进同一个方言事务:
- 读取并复验 ModelInvocation start、Prompt plan output intent 与 Project policy fence;
- 以 active key 构造确定性 Artifact,执行 immutable put/exact replay;
- 让 ModelInvocation completion 的 output reference 指向 Artifact reference digest;
- 写 usage/quota/pricing settlement、StepRun completion 与 RunEvent;
- commit 后才向 live caller返回正文和 reference。
SQLite 使用现有单 operation authority 的短 BEGIN IMMEDIATE;PostgreSQL 使用现有 AI schema
的短 SERIALIZABLE transaction。v1 不把最多 1 MiB 的密文转移到 Worker 本地文件或 S3,因为
那会在数据库 completion 与对象 promotion 之间引入第二个事务域。若未来 Cluster 改用对象存储,
必须先增加 durable staged→bound→published bridge、响应丢失裁决和 orphan GC,不能只替换 URI。
4. exact replay、恢复和错误语义
- completion + matching Artifact:返回 content-free receipt/reference;只有再次通过 read policy、 key resolution 和 retention 状态后才可打开正文,provider 调用数保持不变;
- Artifact + incomplete completion:同事务复验 Artifact 后补 completion,再 finalization;绝不调 provider;
- start 已存在但 Artifact/completion 都缺失:沿用 in-progress/outcome-unknown,不能假定 provider 未执行;
- completion 声称 durable 但 Artifact 缺失、digest/key/identity 漂移:corrupt/unavailable,不能 回退 live-only 或再次调用 provider;
- 同 Artifact ID 不同规范化内容:conflict;相同内容:exact replay。
该顺序把 crash window 收敛为“全回滚”或“Artifact 与 completion 同时 durable”。SIGKILL fixture 必须覆盖加密前、Artifact insert 后、completion/settlement/StepRun/Event 后和 COMMIT response loss; 物理断电仍需独立设备证据,不能由进程 crash 冒充。
5. 读取授权、Retention 与 GC 是独立 authority
执行 runtime 只有 insert/select,不取得 delete 或任意跨 Project read。产品读取只接受
subject + Project + Run + opaque Artifact identity,不接受 key、路径、URI 或 ciphertext;固定顺序为
metadata identity → artifact.read Project Policy → retention/tombstone → key resolve/decrypt。deny、
require-approval 与 not-found 对非可信 transport 默认屏蔽存在性。
Artifact 保存创建时 policy revision/digest 和 eligible time,但这不是自行删除授权。GC 由显式、
短生命周期 maintenance authority 执行 bounded keyset page:重新验证 retention policy、终态
Prompt/Run,并在同一写事务中先写 content-free immutable tombstone、再按精确 Artifact digest 删除
密文。v1 不增加读取租约:repository 单次读取取得完整 immutable row,授权、retention inspection 与
解密只处理该次调用已持有的内存副本;GC 若在 retention inspection 后提交,不得使已经开始的读取在
同一进程内凭空失效。读取在 inspection 前观察到 tombstone 则只返回 retained,不会重算正文。
这条线性化规则避免为每次读增加写事务、租约表、续租 timer 与 WAL。
Edge 不增加 timer;由 Owner CLI 或现有单 maintenance cadence 显式触发。Cluster 使用独立
ql3_ai_maintenance 短生命周期 role/Job,不把 DELETE 授给 control runtime。GC 对每个候选重新读取
精确 row、终态与 policy revision/digest,使用方言内互斥并把 tombstone+DELETE 原子提交;崩溃只能
留下“密文仍在”或“tombstone 已在且密文已删”两种状态,不能先删后丢失 retained 证据。
key rotation 允许新写使用 active key、旧读按 key ID resolve。key retirement 现固定为“不改写
immutable Artifact”的两阶段协议:先把旧 key 切为 inactive,再以方言内事务追加 preparation fence、
在同一 key advisory/SQLite write authority 下证明 live ciphertext 为零,外部删除 key material 后追加
completion;崩溃后由 preparation、material absence 与确定性 absence proof 恢复。共享 coordinator、双方言
repository/migration 与 PostgreSQL HA 已实现。Edge/Standalone 已有私有 POSIX file-keyring material
authority:限定当前 UID、目录私有、文件 0600、非 symlink、有界 generation/key/retirement,并以
CAS + atomic rename + directory fsync 完成 rotation/retirement;同一 preparation 的删除响应丢失可由
持久 absence proof 精确恢复。既有 ql3-owner-gc 已提供一次性 owner.prompt-output-key.retire 命令;
Cluster Admin 的注入式 one-shot retirement process 已由 ADR-0262 接入固定 UID/resourceVersion 的
Kubernetes Secret material authority、command-file-only CLI 与 opt-in Job/RBAC/NetworkPolicy。这个
adapter 只关闭 Kubernetes Secret 上的单对象 retirement CAS,不等同于 KMS/HSM;运行时同源读取已由
独立只读投影组件闭环,首次 provision/active rotation 管理面、外部 wrapping/non-exportable key、
遗失 key 人工恢复仍须闭环。read
service、双方言 GC adapter 与 storage authority 已实现;Local Owner
maintenance 命令使用 private durable command file 携带最多 128 条 Project+revision+digest 绑定的
策略目录,每次只执行一个 bounded page。Cluster 使用同一目录协议和独立 maintenance role 的显式
one-shot Job;GC 完成仍不能等同于所有部署的 key 生命周期已经完成。
低配与 Cluster 影响
- live-only 路径的目标增量是 0 Artifact I/O、0 常驻资源、0 package;
- durable 单请求最多额外持有一个 bounded result JSON、ciphertext 和数据库事务;128/256 MiB arm64 门现以 512 KiB 输出分别运行 Edge DELETE/FULL 与 Standalone WAL/FULL 产品纵切面,并硬校验 peak RSS、SQLite logical/allocated growth 与 WAL 写放大;
- SQLite 写入大 envelope 只对明确 opt-in 请求发生,不能成为默认日志策略;
- PostgreSQL 密文会进入 WAL/HA replica,因此需要独立 9009/9010/9011 migration、ACL、角色与 timeline
promotion/rewind 门。
pg-9009-ai-plugin-package-prompt-output-artifacts和pg-9010-ai-plugin-package-prompt-output-tombstones、pg-9011-ai-plugin-package-prompt-output-key-retirements已在 PostgreSQL 18.4 arm64 physical-streaming HA 门完成remote_apply、timeline 1→2、旧主 fencing/rewind、fresh control replicas、GC 后 exact replay 与 promotion 后 schema/ACL 复验;用户现有 evidence control-plane 容器未被修改。
当前实现与接受门
当前已完成以下显式 opt-in 的写入闭环:
- 共享 envelope/reference/key/read-authorizer contract,以及加密 round-trip、content-free reference、 tamper fail-closed 和 retention/key budget 四项定向测试;
- plan 的显式
live_only|durable_artifactintent、retention digest binding 与旧 alpha plan digest compatibility; - SQLite
9010-ai-plugin-package-prompt-output-artifacts与 PostgreSQLpg-9009-ai-plugin-package-prompt-output-artifactsmigration; - 两方言 immutable put/find/exact-replay repository。repository 会复验 admission plan、 Project/Run/StepRun/Invocation/requester/provider/model 和 retention mirror,live-only plan 在 insert 前 fail closed;
- SQLite
BEGIN IMMEDIATE与 PostgreSQLSERIALIZABLErepository 已把 Artifact、 ModelInvocation completion/usage/quota/pricing settlement、StepRun/Event output reference 放入同一 方言事务;已有 Artifact 可修复缺失 completion,已有 completion 但 Artifact 缺失或漂移会失败关闭; - Model Gateway 只在部署组合显式提供同一个 successful-completion capability 时启用 durable hook; Prompt executor 会做 capability identity handshake,缺少任一 key/repository/hook 时都在 admission/provider 前失败关闭;
- Local application 与 Cluster Prompt application 均只在显式注入
promptOutputKeys后装配该能力; live-only 路径不读取 active key,也不增加第二个 provider registry、连接、timer、watcher 或 package; - Cluster Prompt route 已以 exact body 开放
live_only|durable_artifact,严格校验 1 小时至 365 天的 retention policy,并只返回 content-free Artifact reference; - PostgreSQL runtime 仅有 artifact table
SELECT/INSERT,无UPDATE/DELETE,其余管理/执行/Worker roles 无权访问;该边界已通过 physical-streaming HA promotion/rewind; - Local/Cluster 产品读取 service 已固定执行 metadata identity → Project Policy → retention/tombstone → key/decrypt,使用 opaque Artifact identity、屏蔽 forbidden/not-found,并对响应施加硬上限;
- SQLite tombstone migration 与 PostgreSQL
pg-9010均只保存 identity/digest/policy/删除时间等 content-free 事实;双方言 GC 都是 caller-driven bounded page,逐项复验终态与 policy 漂移,并在 同一事务内先插入 tombstone、再删除精确密文; - PostgreSQL 新增非继承、短连接的
ql3_ai_maintenance角色。runtime 只能读 tombstone,maintenance 只能读取裁决所需事实、插入 tombstone 和删除 Artifact,不能取得 runtime/admin authority; - key retirement preparation/completion 是 content-free append-only 事实;SQLite Artifact insert 与
preparation 共用
BEGIN IMMEDIATE,PostgreSQL 两条路径共用 key-scoped advisory transaction lock, 因而 preparation 一旦提交,迟到/重放的旧 key Artifact 就不能重新落库;completion 仍会复验 live ciphertext 为零,且精确重放保留首次时间戳; @qinglong/ai/plugin-package-prompt-output-file-keyring实现 Edge/Standalone 的 bounded 私有文件 material authority;active key 不可退役,inactive material 的 catalog/material proof 必须与 durable preparation 完全一致,删除后保存 content-free preparation、retired catalog digest 与确定性 absence proof。死进程 lock 可回收,不增加 timer、watcher、daemon 或 workspace package;- 既有
ql3-owner-gc新增owner.prompt-output.collect,只从 mode 0600 private command file 读取 bounded digest-bound policy catalog,一次执行一页并只输出 scanned/tombstoned/skipped/hasMore; 非该命令分支不加载 AI 模块,未新增 package、daemon、timer 或 watcher; - 同一个
ql3-owner-gc又新增owner.prompt-output-key.retire,命令文件只携带 key/retirement/request/ mutation identity,返回只含状态、identity、prepare/complete digest 与数据库时间;真实 SQLite feature、 file-keyring 删除和 exact replay 已贯通。Cluster Admin 的prompt-output-key-retirement-process使用ql3_ai_maintenance短连接、启动时复验 retirement 表的 append-only ACL,并只接受显式注入的 material authority;它不把 Kubernetes Secret 删除伪装为 KMS 一致性,也不让常驻 control plane 获得该权限; @qinglong/cluster-admin/prompt-output-kubernetes-secret-keyring只对固定 namespace/name/UID/data key 的 mutable Opaque Secret 执行 get/updateresourceVersionCAS;拒绝 list/watch/create/delete/patch、 Secret 重建、active key、last-applied material 副本和 annotation/manifest 漂移。响应丢失与并发相同 retirement 通过 winner 重读收敛;ql3-prompt-output-key-retire在 material 读取前用 SelfSubjectAccessReview 证明 exact Secret get/update 且扩权矩阵被拒绝,再打开ql3_ai_maintenance短连接。对应 opt-in Job 为backoffLimit=0、非 root、只读根、128 MiB;Role 只绑定单 Secret,base 默认仅 DNS egress, CloudNativePG overlay 仅再放行数据库 5432,Kubernetes API/32 + port必须由部署私有 overlay 提供;cluster-ai-prompt-outputKustomize Component 现在把同一个ql3-prompt-output-keyring/keyring.json以 required、单文件、0440、read-only volume 装配到既有 Cluster AI runtime,并只注入已有 projected-keyring adapter 的两个环境变量。默认 Cluster 和默认cluster-ai仍保持 live-only;该组件不创建 Secret、不挂载 ServiceAccount token、不授予 RBAC,也不 新增 package、进程、listener、watcher 或 timer;- 三节点 K3s v1.34.3/Flannel arm64 实跑已证明真实 Kubernetes atomic-writer symlink、Secret generation
1→2 与 revision 变化会被同一 Pod/同一进程重新打开。新 active key 生效后,历史 key 仍能解开轮换前
创建的真实 encrypted Artifact;运行时无 Kubernetes 凭据,投影仍为 exact
keyring.json/0440/ read-only。轮换窗口中真实命中的中间代竞态返回PLUGIN_PACKAGE_PROMPT_OUTPUT_PROJECTED_KEYRING_UNAVAILABLE,没有读取混合代数据;正式门允许该 fail-closed 操作重试后再验证稳定新代; - 既有 Cluster Admin image 增加同协议的
ql3-prompt-output-gc一次性 CLI;Kubernetes JobbackoffLimit=0、无 API token、只读 root、128 MiB limit,NetworkPolicy 只允许 DNS 与 CloudNativePG 5432,policy ConfigMap immutable 且故意不进入默认 Kustomization; - SQLite 故障注入已证明 Artifact insert 之后、usage ledger 之前失败会整事务回滚,Run/StepRun 不会
冒充成功;recovery 将已调用 provider 但无终态的 invocation 收敛为
outcome_unknown; - PostgreSQL 18.4 arm64 HA 门已用真实 durable Prompt execution 证明 GC 前 Artifact 为 1、GC 后为 0
且 tombstone 为 1,StepRun output reference 与 Artifact ID 一致,GC 后 exact replay 仍不再次调用
provider;随后旧 key preparation/material deletion/completion 精确重放、迟到密文拒绝、同步复制和
promotion 后逐字段一致也已通过。Prompt/输出明文不进入 durable JSON;原子 Artifact、
tombstone-before-delete、GC 后 replay、key retirement durable fence、maintenance least-privilege
专用 gate 与总
gates.passed均为 true。 - Node 24.18.0 arm64 的 128 MiB/0.5 CPU/64 PID 压力门和 256 MiB/1 CPU/128 PID release 门均在 零 swap、非 root、只读 root/workspace、seccomp、NoNewPrivs 下通过,且零 max/OOM。512 KiB durable output 的 Edge DELETE/FULL process peak RSS 为 103,616,512/104,517,632 bytes,SQLite logical/allocated 写放大均为 1.383×;Standalone WAL/FULL peak 为 102,330,368/97,800,192 bytes,logical/allocated/WAL 写放大为 2.185×/2.188×/2.185×。 两档都同时证明 provider 两次(live 与 durable 各一次)、各自 exact replay 零重调、live-only 零 key load、durable key load/resolve 各一次、零 RunAttempt 与 durable bytes 无明文;门禁会对任一证据 漂移失败关闭。这是 tmpfs/cgroup CI 写入放大,不是物理闪存 FTL 或最低配置承诺。
AI suite 当前为 142 项、139 通过、3 项仅因未提供外部 PostgreSQL 环境而条件跳过;Local
application 为 42 项、39 通过、3 项平台条件跳过,Local Owner maintenance 为 13/13;Cluster
control 为 172 项、170 通过、2 项外部服务条件跳过,Cluster Admin 为 206 项、204 通过、2 项
外部服务条件跳过;Cluster PostgreSQL 为 272 项、271 通过、1 项真库条件跳过。本切片的 package
closure/dependency/SBOM/deployment/DR 组合门禁为 75/75,独立依赖审计为 compatible: true。
workspace 仍为 19 个 QL3 package(pnpm 输出的 20 个 workspace project 包含根项目),没有为本
能力新增 package。
接受前必须完成:
- output intent 已进入 plan digest、双方言 admission mirror 与 Cluster route strict body;
- SQLite 同事务 Artifact + completion、exact replay、事务中段失败回滚与 20 点外层 SIGKILL phase matrix 已完成;仍需物理断电证据;
- PostgreSQL migration、最小 ACL、同事务产品 repository、exact replay 与 physical-streaming HA promotion/rewind;
- Local/Cluster 已显式注入 key/repository/completion capability,disabled/live-only 零加载; 产品读取组合已接入 read authorizer、存在性屏蔽与 bounded response;
- read service、双方言 retention tombstone/GC storage authority、PostgreSQL 最小权限角色、 digest-bound policy catalog、Local Owner maintenance CLI 与 Cluster one-shot Job;
- 128/256 MiB arm64 durable-output resource/write-amplification gate;
- key retirement 的共享状态机、双方言 durable fence/repository、POSIX file-keyring authority、 Local Owner 产品命令、Cluster Kubernetes Secret adapter/CLI/Job 与 PostgreSQL HA 已完成;仍需 首次 provision/active rotation 的受审管理面、具体 KMS/HSM backend 与遗失 key 人工恢复。Cluster 运行时同源 read-only projection、同进程 active rotation/historical decrypt 和 retirement 的真实 Kubernetes API 纵切面均已完成;
- 固定物理 Edge 存储/闪存写放大与断电证据。
被拒绝方案
- 新建
ql3-prompt-artifactpackage:没有独立部署或重依赖价值,违反 D-207。 - 复用 Tool Invocation 双 Artifact 表:input/preview、审批和 action identity 均不相容。
- 复用 Worker S3 log store:会引入第二事务域和 cluster-only SDK,扩大 Edge closure。
- Gateway 返回后异步保存:completion→Artifact crash window会永久丢正文。
- 把正文写入 completion/finalization receipt:扩大热表、审计、备份和 HA 泄漏面。
- exact replay 再调 provider:重复计费且破坏 exactly-once。
- runtime 自带删除 timer/DELETE:混淆执行与数据生命周期 authority,增加低配稳态成本。