Files
qinglong/docs/adr/ADR-0151-sqlite-postgresql-immutable-plugin-package-materialized-revision-repository.md
T

9.0 KiB
Raw Blame History

ADR-0151SQLite/PostgreSQL 不可变 Plugin Package 物化修订仓库

  • 状态:Accepted(双方言仓库、迁移、readiness、共享合同与真实 PostgreSQL 门已实现; Task 批量发布、全局 Tool snapshot 和 consumer activation 尚未实现)
  • 日期:2026-07-25
  • 关联:ADR-0136、ADR-0137、ADR-0149、ADR-0150、QL-RFC-0001 D-144/D-145

背景

ADR-0150 已把同一 active Package generation 的 Task、Workflow、Prompt 和 Tool 文件 物化为自包含、可规范化的 qinglong/plugin-package-materialized-revision@v1。此前只有纯领域 repository port 没有耐久 adapter

  • edge/standalone 重启后必须重新打开 staging 才能取得语义;
  • Cluster 多副本无法围绕同一 generationDigest 收敛;
  • consumer 无法在读取 active generation 后取得一份可审计的 exact revision
  • 如果分别保存 source、Task、Workflow、Prompt 和 Tool,多行提交会产生同一 generation 的部分可见状态。

耐久化不能成为第二个 active pointer,也不能让 Package manager、常驻 runtime 或 Worker 获得原始 Package 语义写权限。

决策

1. 不新增 workspace package

repository port 继续位于 @qinglong/runtime-core/plugin-package-resource-materializationSQLite adapter 放入 既有 @qinglong/local-sqlite/plugin-package-materialized-revisionPostgreSQL adapter 放入 @qinglong/cluster-postgres/plugin-package-materialized-revision,并只从 Cluster package-executor 权限入口导出。

两个 adapter 都是显式 subpath,不从 Profile-neutral root 聚合导出。不新增第三方 依赖、连接池、timer、watcher、socket 或常驻缓存;workspace importer 保持 21 个。

2. 一代一行,自包含保存

双方言分别新增:

  • SQLite QingLong3PluginPackageMaterializedRevisions
  • PostgreSQL ql3.plugin_package_materialized_revisions

每行保存:

  • generation_digest 主键;
  • project_idpackage_namegenerationlock_digest
  • manifest_digestrevision_digest
  • 完整 canonical revision_json
  • 数据库观察的创建时间。

generationDigest 是 repository identityproject + package + generation 另有唯一 约束,防止同一代出现不同语义。数据库 CHECK 从 JSON 中复验上述重复 identity 避免索引列与 payload 漂移。只外键到 Project,不外键到可变 install head 或 lock history:语义修订需要在安装记录回收后仍可作为执行和审计事实存在。

完整 revision 单行保存是刻意选择。repository port 返回一份完整 revision,单行 INSERT 可以保证 publish 原子性;若拆为资源明细表,必须再引入 batch commit 标记与 复杂的 incomplete-generation 恢复协议。

3. create 或 exact replay

仓库只提供:

  • create(revision)
  • findByGenerationDigest(generationDigest)

create 先通过 runtime-core normalizer 复验完整 revision。首次写入成功;同一 generationDigest 重放时,只有 durable row 规范化后的完整值和摘要均 exact 相同才 返回既有记录。任何 identity、lock、Manifest、source 或语义差异都作为 conflict 失败关闭,不允许 update、delete、upsert overwrite 或“最后写入获胜”。

SQLite 在既有单 operation authority queue 内执行一个 INSERT ... ON CONFLICT DO NOTHING 与 exact read,不打开第二连接。PostgreSQL 使用 INSERT ... ON CONFLICT DO NOTHING RETURNING,冲突后 exact read;因为一行 append 本身即是原子发布,不要求连接 pinning 或跨网络长事务。

损坏的 durable JSON、数据库不可用和非预期 SQL 错误都映射为 unavailable;受审唯一/ CHECK/FK 约束映射为 conflict。调用方不得把不确定写入透明重放成不同 revision。

4. 明确存储与内存上限

ADR-0150 仍限制每个 resource 1 MiB、业务 resource source bytes 总计 8 MiB。 materialized revision 还包含 normalized semantic values、Manifest、lock、generation 和 JSON 编码开销,因此 durable revision_json 另设 24 MiB 硬上限,并在:

  • runtime-core normalizer
  • SQLite CHECK
  • PostgreSQL CHECK

三处保持一致。

24 MiB 是一次显式安装/物化操作的最坏输入上限,不是常驻 RSS 预算。repository 不做 目录扫描、后台预热或全表加载;consumer 必须按 active generationDigest 精确读取。

5. 双方言 schema 与能力版本

SQLite

  • 0045-plugin-package-materialized-revisions 建表和索引;
  • 0046-capability-v23 把 local contract 推进至 v23
  • capability 增加 plugin_package_materialized_revision:1

PostgreSQL

  • pg-0025-plugin-package-materialized-revisions 建表、索引和角色授权;
  • control-core capability 从 v23 推进至 v24
  • migration stream 为 25 条,catalog 为 38 张表;
  • capability 增加 plugin_package_materialized_revision:1

SQLite readiness 同时要求 migration、capability、表和两个业务索引。PostgreSQL readiness 同时要求 migration、capability、表、索引、CHECK/FK 与精确 ACL。

6. PostgreSQL authority 只属于 Package executor

pg-0025 撤销 PUBLIC、runtime、admin、package-manager 和 worker-ingress 对新表的 全部权限,只授予 ql3_package_executor

  • SELECT
  • INSERT

executor 没有 UPDATE/DELETE/TRUNCATE authority。Package manager 继续只负责公开管理 准入;常驻 runtime、Worker ingress 和 admin 不能读取或改写原始物化语义。迁移仍由 短生命周期 ql3_migration authority 执行。

7. durable revision 仍不是 active pointer 或执行发布

consumer 的顺序固定为:

  1. 从 ADR-0149 source 读取一次 active generation
  2. 以其 generationDigest 精确查 revision
  3. 规范化并复验完整 identity
  4. 由后续受审 publisher 执行原子业务发布;
  5. 发布前再次确认 active generation 未漂移。

repository 不提供 current 查询,不更新 active pointer,也不逐项创建 TaskDefinition、 注册 Tool handler、执行 Workflow/Prompt 或绑定 Secret。仓库完成只关闭“耐久语义事实” 缺口,不关闭“生产执行可见性”缺口。

Profile 影响

  • edge/standalone:复用现有单 SQLite operation authority;无新连接、线程或后台任务。 只有显式 Package 物化/读取操作承担最多 24 MiB JSON 的短生命周期成本。
  • cluster:多 executor 可以竞争同一 append;数据库唯一约束和 exact replay 使结果 收敛。RPO-0/切换语义继续由既有 PostgreSQL HA 约束承担。
  • worker:不导入 repository,不取得表权限,只消费后续 execution revision 绑定结果。

被否决方案

  1. 新增 plugin-package-revision-store workspace package:没有独立部署或依赖 生命周期,会继续细碎化 packages/
  2. 按 Task/Workflow/Prompt/Tool 拆多张表:当前 consumer 需要完整 revision, 多表会引入部分发布和额外恢复协议。
  3. 把 revision 塞进 active pointer/ConfigMap:会突破 pointer 预算并合并文件发布、 语义 authority 与业务激活。
  4. 只存 digest、每次从 staging/OCI 重建:无法给重启和多副本提供耐久 exact evidence,也把外部制品可用性带入执行读路径。
  5. 给 manager 或 runtime 读写表权限:会把公开 transport 或常驻控制面升级为 原始 Package 语义 authority。
  6. 允许 update/delete 以便升级或 GC:升级本来就应产生新 generation;在引用感知 retention 设计完成前删除会破坏执行与审计事实。

验证

  • 共享 repository contract:首次 create、exact replay、按 generation digest 查询、 absence
  • SQLite:真实 Node 24 DatabaseSync、单 operation authority、损坏 JSON fail-closed、root/subpath 隔离、typed schema/catalog/readiness lockstep
  • PostgreSQLSQL 生成、数据库时钟、SQLSTATE conflict、损坏 JSON fail-closed、 package-executor/root entrypoint 隔离;
  • PostgreSQL 18.4 arm64 真库:25 条 migration 后完成 revision create、exact replay 与 find
  • PostgreSQL 18.4 physical HApg-0025/v24/38 表和角色 readiness 在 timeline 1→2 promotion 前后通过,旧主 fencing、pg_rewind 只读同步重入及既有 24 项领域/HA 门全部通过;
  • architecture21-package 全量测试、edge import audit 和 cluster dependency audit 必须保持无 finding。

后续

  1. 设计 Package TaskDefinition 多资源单事务 reconciliation,以 generation 为批次, 防止一代内部分可见;
  2. 为全部 active generation 构造一次性 immutable Tool registry snapshotDefinition 与受信 handler/preview publisher 分离;
  3. 为 Workflow/Prompt 建立独立版本仓库和执行协议;
  4. 建立 Package Secret requirement 到 Project SecretRef 的审批、rotation 和审计 ceremony
  5. 在 execution revision/Run 引用建立后,再设计引用感知 retention;当前禁止 GC durable materialized revision。

上述 Gate 完成前,durable revision 仍不可直接进入生产执行路径。