Files
qinglong/docs/adr/ADR-0261-explicit-encrypted-durable-plugin-package-prompt-output-artifact.md

21 KiB
Raw Permalink Blame History

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-freeexact replay 不再次调用 provider,也不承诺找回正文。这个默认值适合低配路由设备,但 UI、MCP 或长时间任务有时需要在 调用断线、进程重启或 Cluster 主库晋升后继续读取结果。

现有 Artifact 不能直接改名复用:Tool Invocation Artifact 是 input/preview 双对象和审批 start barrierRemote Worker Artifact 是日志流、对象存储 promotion 与 Worker ACKLocalProcess Artifact 又依赖本地文件、range 与 truncation fact。Prompt 输出需要保存完整、最多 1 MiB 的 GenerateResult,并在 provider exactly-once 窗口修复 ModelInvocation completion。三者的原子性、 授权和 GC authority 均不同。

决策

1. 默认仍为 live-onlydurable 必须显式 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 outputdurable 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@v1 AES-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/find repository、key provider 与 read authorizer ports
  • 明文沿用 Model Gateway 的 1 MiB 上限,完整 JSON envelope 硬限 1.5 MiBretention 只允许 1 小时至 365 天。

Edge/Standalone adapter 已留在既有 @qinglong/ai SQLite storage subpathCluster adapter 已留在既有 AI PostgreSQL storage subpath。它们共享 protocol/test contract,但不共享连接或部署 实现。单个领域文件、repository 或 migration 不构成新 package;只有未来出现独立对象存储重依赖、 进程/权限域或可替换部署单元时才重新评审边界。

3. Artifact 必须先于成功 Completion,并由同一事务绑定

不能在 gateway.generate() 已写成功 Completion 并返回后再保存 Artifact:进程可能在两者之间 退出,exact replay 会正确阻止 provider,却永远失去正文。

durable 路径必须把以下事实放进同一个方言事务:

  1. 读取并复验 ModelInvocation start、Prompt plan output intent 与 Project policy fence
  2. 以 active key 构造确定性 Artifact,执行 immutable put/exact replay
  3. 让 ModelInvocation completion 的 output reference 指向 Artifact reference digest
  4. 写 usage/quota/pricing settlement、StepRun completion 与 RunEvent
  5. commit 后才向 live caller返回正文和 reference。

SQLite 使用现有单 operation authority 的短 BEGIN IMMEDIATEPostgreSQL 使用现有 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 JobGC 完成仍不能等同于所有部署的 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-artifactspg-9010-ai-plugin-package-prompt-output-tombstonespg-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_artifact intent、retention digest binding 与旧 alpha plan digest compatibility
  • SQLite 9010-ai-plugin-package-prompt-output-artifacts 与 PostgreSQL pg-9009-ai-plugin-package-prompt-output-artifacts migration
  • 两方言 immutable put/find/exact-replay repository。repository 会复验 admission plan、 Project/Run/StepRun/Invocation/requester/provider/model 和 retention mirrorlive-only plan 在 insert 前 fail closed
  • SQLite BEGIN IMMEDIATE 与 PostgreSQL SERIALIZABLE repository 已把 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 只能读 tombstonemaintenance 只能读取裁决所需事实、插入 tombstone 和删除 Artifact,不能取得 runtime/admin authority
  • key retirement preparation/completion 是 content-free append-only 事实;SQLite Artifact insert 与 preparation 共用 BEGIN IMMEDIATEPostgreSQL 两条路径共用 key-scoped advisory transaction lock 因而 preparation 一旦提交,迟到/重放的旧 key Artifact 就不能重新落库;completion 仍会复验 live ciphertext 为零,且精确重放保留首次时间戳;
  • @qinglong/ai/plugin-package-prompt-output-file-keyring 实现 Edge/Standalone 的 bounded 私有文件 material authorityactive 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/update resourceVersion CAS;拒绝 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 MiBRole 只绑定单 Secretbase 默认仅 DNS egress CloudNativePG overlay 仅再放行数据库 5432Kubernetes API /32 + port 必须由部署私有 overlay 提供;
  • cluster-ai-prompt-output Kustomize 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 一次性 CLIKubernetes Job backoffLimit=0、无 API token、只读 root、128 MiB limitNetworkPolicy 只允许 DNS 与 CloudNativePG 5432policy 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 为 1StepRun 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 bytesSQLite logical/allocated 写放大均为 1.383×;Standalone WAL/FULL peak 为 102,330,368/97,800,192 byteslogical/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/13Cluster 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 packagepnpm 输出的 20 个 workspace project 包含根项目),没有为本 能力新增 package。

接受前必须完成:

  1. output intent 已进入 plan digest、双方言 admission mirror 与 Cluster route strict body
  2. SQLite 同事务 Artifact + completion、exact replay、事务中段失败回滚与 20 点外层 SIGKILL phase matrix 已完成;仍需物理断电证据;
  3. PostgreSQL migration、最小 ACL、同事务产品 repository、exact replay 与 physical-streaming HA promotion/rewind
  4. Local/Cluster 已显式注入 key/repository/completion capabilitydisabled/live-only 零加载; 产品读取组合已接入 read authorizer、存在性屏蔽与 bounded response
  5. read service、双方言 retention tombstone/GC storage authority、PostgreSQL 最小权限角色、 digest-bound policy catalog、Local Owner maintenance CLI 与 Cluster one-shot Job
  6. 128/256 MiB arm64 durable-output resource/write-amplification gate
  7. 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 纵切面均已完成;
  8. 固定物理 Edge 存储/闪存写放大与断电证据。

被拒绝方案

  1. 新建 ql3-prompt-artifact package:没有独立部署或重依赖价值,违反 D-207。
  2. 复用 Tool Invocation 双 Artifact 表input/preview、审批和 action identity 均不相容。
  3. 复用 Worker S3 log store:会引入第二事务域和 cluster-only SDK,扩大 Edge closure。
  4. Gateway 返回后异步保存completion→Artifact crash window会永久丢正文。
  5. 把正文写入 completion/finalization receipt:扩大热表、审计、备份和 HA 泄漏面。
  6. exact replay 再调 provider:重复计费且破坏 exactly-once。
  7. runtime 自带删除 timer/DELETE:混淆执行与数据生命周期 authority,增加低配稳态成本。