Files
qinglong/docs/adr/ADR-0184-offline-local-plugin-package-quarantine-withdrawal.md
T

179 lines
10 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-0184:本机 Plugin Package 离线隔离与能力撤出
- 状态:Accepted
- 日期:2026-07-28
- 关联:RFC D-133、D-149、D-150、D-169、D-170、D-171、D-172、D-173、
D-174ADR-0149、ADR-0150、ADR-0179、ADR-0180、ADR-0181、ADR-0182、
ADR-0183
## 背景
ADR-0183 在 compromise proposal durable 后阻断目标 signer 的新 catalog publication
和 queued stage,并在确认后删除 trust key、保存受影响 lock 集合。它刻意没有声称
已撤下 active Package
- install head 的 `activeLockDigest` 仍可指向旧 lock
- Package Task 已成为独立、不可变 TaskDefinition revision 和 current head
- Tool 已进入 Project active-vector snapshot
- 已批准的历史 Run/Tool invocation 可能绑定旧 revision/snapshot。
因此只给 install state 增加 `quarantined`,或只让 source query 忽略一个 lock,都无法
关闭完整执行面。热卸载还会与正在执行的脚本、进程和 Tool adapter 竞态。本机模型应
先停 application,再由短生命周期 Owner authority 原子撤出耐久能力。
## 提议决策
### 1. 使用永久 quarantine overlay,不改写安装历史
不扩展 `queued|staged|activating|active|failed` 的既有 install event history。新增本机
SQLite capability v35,以 append-only `QingLong3PluginPackageQuarantineEvents`
immutable `QingLong3PluginPackageWithdrawalReceipts` 和逐 Task
`QingLong3PluginPackageWithdrawalTasks` 覆盖目标
`projectId/packageName/installationId/lockDigest`。quarantine target 通过
`project/package/installation/lock/install record digest` 复合外键冻结到原安装事实,
不另建可变 quarantine head。
`plugin-package.quarantine.apply` 只接受受管 trust root 中 ADR-0183 的 exact
revocation receipt,不接受调用方自报 lock 集合。命令必须绑定 receipt/proposal/
impact digest、当前 install head、Owner、mutation、SecurityAudit 和数据库时间。
quarantine 是永久 tombstone;恢复只能安装由非撤销 key 签名的新 lock,不能把旧
head 改回 active。
`queued|staged|activating` headoverlay 使 recovery source 不再选中目标 lock
且既有精确安装事实不能被改写;不新增伪造的 install settled enum。对 `active` head
overlay 进入同一事务的 capability withdrawal。
### 2. Active withdrawal 必须同时处理 Task、Tool 与准入
一个 `BEGIN IMMEDIATE` 事务固定执行:
1. 复验 target lock 仍是 install head 或 head 的 active lock
2. 插入 exact quarantine event,目标与 mutation 都唯一,重复 mutation 只允许 exact
replay
3. 对仍由该 Package/generation 拥有的 current Task 追加 `enabled=false` revision
不删除旧 revision、Run 或审计;
4. 从 Project Tool active vector 排除该 lock,按 Profile 基于其余最多 edge 4 个、
standalone 16 个 active source 生成 immutable snapshot;空 vector 是合法
snapshot
5. 写 withdrawal receipt,绑定 quarantine、Task revision 列表、新 Tool snapshot、
原/新 active-vector digest 和计数;
6. 在事务取得写锁后、提交前执行授权 callback;callback 以同一连接复验 credential
未失效、确认者仍是 current Ownerdual-control 时提案者也仍是 current Owner
7. 提交前再次复验 install、Task、未受影响 source vector 和授权 fence。
任一步冲突或越界整体 rollback。Task/Tool 数据继续使用现有表和 pure planner;仅为
quarantine event/receipt/task 明细只增加三张事实表,不复制一套 Package 安装状态机。
### 3. 历史 revision 仍可读,但所有新 start 必须事务内失败关闭
仅更新 current Task head 或 current Tool snapshot 不足以阻止一个已经绑定历史事实的
新 start。下列本机 transaction 必须读取同一 quarantine head
- Run `dispatching/running` CAS:若 Task reconciliation generation 能回溯到已
quarantine 的 lock,在推进 Run 前拒绝;
- Tool execution start barrier:从绑定 snapshot 的 source 找到 Package lock,若已
quarantine,在写 barrier/调用 adapter 前拒绝;
- Package stage/activation/recoveryqueued/staged/activating 的精确目标被 recovery
source 排除,外键与 tombstone 阻止安装事实变体重写。
已持久进入 running 的 Run 或已经创建 start barrier 的 Tool invocation 保留原
completion/recovery 语义,不能删除或改写历史来伪造“从未执行”。D-173 的
`stop_required` 仍是撤出前的操作前置条件;本 ADR 不承诺从 SQLite 证明宿主进程已
被操作系统停止,也不实现任意脚本的安全热杀。
### 4. Crash recovery 与启动门
quarantine、Task disable、Tool snapshot 和 withdrawal receipt 在同一 SQLite 事务,
所以进程崩溃只能看到全部旧事实或完整新事实。COMMIT response loss 由 mutation/
receipt digest exact replay 返回 `existing`
application startup 在 Package recovery 前执行 capability v35 readiness relation
probe
- quarantine event 必须精确对应一张 withdrawal receipt
- Task 明细数量、内容 digest、Project/time 与 receipt 必须一致;
- active withdrawal 的新 snapshot retained source 数量必须一致,且不得再包含目标
lock
- probe 使用 `LIMIT 1` 找出首个损坏关系并失败关闭,不把全表物化进路由设备内存;
- readiness 通过后,Package recovery、Task/Tool source 和后续 Run/Tool start 仍各自
执行 transaction fence。
不增加 timer、watcher、poller 或自动 GC。一次 Owner command 的 edge/standalone
目标 lock 上限为 4/16,每个 active withdrawal 的 source 上限也为 4/16,单 receipt
最多 128 个 Task;超限要求人工拆分或停机处置,不允许无界事务。
### 5. 包边界
继续复用:
- `@qinglong/runtime-core` 的显式 quarantine/withdrawal contract subpath
- `@qinglong/local-sqlite` 的 migration、repository 和 start fence
- `@qinglong/local-owner-cli` 的现有 Package command binary
- `@qinglong/local-application` 的 startup recovery composition。
不新增 workspace package 或第三方生产依赖。owner-cli 对 runtime-core 的既有源码
导入从 devDependency 纠正为 production dependency,并由 lockfile/源码边界门精确
允许。Cluster 对等能力必须在 PostgreSQL
advisory lock、multi-replica admission、admin/runtime ACL 和 HA promotion 门全部实现
后单独接受,不能把本机 SQLite command 暴露为 Cluster transport。
## 已知限制与后续风险
1. 当前 Run 不直接保存 Package lock provenancestart fence 通过 immutable Task
reconciliation item/generation 回溯;未来应把 lock provenance 提升为 Run 建单事实;
2. quarantine 与随后 replacement install 的 previous-active/rollback 语义需要独立
恢复 ADR
3. 本轮 `SIGKILL` 覆盖 SQLite transaction window,但真实设备断电、ENOSPC、文件系统
损坏和完整 application 停机仍需要实机演练;
4. 本 ADR 只接受本机 SQLite 能力;PostgreSQL HA 门通过不代表 Cluster 已具备
quarantine adapter、ACL 或多副本管理 transport。
## 接受门禁
- pure contractexact replay/conflict、永久 tombstone、bounded withdrawal receipt
- SQLite migration/readiness/schema audit 与 capability v35
- queued/staged/activating 三状态不再被 recovery source 选中;
- active Package 的 Task 全部 disabled、Tool source 全部排除,事务失败零部分事实;
- 历史 Task revision/Tool snapshot 可读,但新 Run/Tool start 在写前被 fence
- application readiness 在 withdrawal relation incomplete/tampered 时失败关闭;
- 非 Owner、identity drift、impact/receipt 漂移和超限全部在可见变更前拒绝;
- Edge/Standalone `DELETE/FULL``WAL/FULL` 的 pre-COMMIT/post-COMMIT `SIGKILL`
matrix 和 `integrity_check=ok`
- 22-package dependency/edge import、application artifact/RSS 预算不回归;
- 实现触及 Cluster 时重跑 PostgreSQL 18 physical HA Docker 门,否则不声明 Cluster
能力。
## 验收结果
- `@qinglong/runtime-core/plugin-package-quarantine` 已提供 event/receipt schema、
exact replay/conflict、永久 tombstone、128 Task receipt 上限和确定性 mutation ID
runtime-core 349/349 通过;
- reviewed migration `0069-plugin-package-quarantine`
`0070-capability-v35` 已落地,contract v35、62 张 owned table、migration checksum、
typed schema 和 readiness relation probe 一致;
- `@qinglong/local-sqlite/plugin-package-quarantine` 在一个 `BEGIN IMMEDIATE` 中完成
exact install validation、Task consecutive disable、Tool snapshot、event/receipt/
task facts以及提交前授权 callbackexact replay 在返回前再次复验 durable facts
- Package recovery source、Task/Tool current source、Run CAS 与 Tool start barrier
均已读取 quarantine overlay;历史 revision/snapshot 仍可读;
- `ql3-package-trust revoke-confirm` 从 ADR-0183 receipt 的 impacted lock 导出目标,
在 trust snapshot 前逐项 quarantine;新建、已有 receipt 和已完成 snapshot 的精确
重放都会先执行该 hook,关闭“receipt 已存在却跳过撤出”的恢复窗口;
- credential/Owner 的完整认证在事务外完成,事务内再以同连接执行当前 credential、
confirmer Owner 和 proposer Owner fence,避免共享 authority transaction 的重入
死锁;
- crash matrix 覆盖 edge `DELETE/FULL`、standalone `WAL/FULL`,在 Task disable 后、
event 后、receipt 后、COMMIT 前和 COMMIT 后各 `SIGKILL`,共 10/108 个 pre-COMMIT
场景完整 rollback 后恢复为 `created`2 个 post-COMMIT 场景完整保留并重放为
`existing``integrity_check=ok`、foreign key check 通过;
- 全量测试通过:runtime-core 349、local-sqlite 136、local-admin 63、
local-application 32、local-owner-cli 22
- dependency/source boundary 无 findingEdge import closure 为 121 modules;四种
application 产物门通过:edge 4,915,362 bytes/615 files/90 modulesstandalone
4,915,506/615/90edge+AI 5,593,183/659/89standalone+AI
5,593,339/659/89
- 本轮未实现 Cluster quarantine,但按授权额外重跑 PostgreSQL 18.4 physical HA
Docker contractphysical streaming、`remote_apply`、partition promotion guard、
old-primary fence/`pg_rewind` rejoin、scheduler/credential/Run/tool-result
COMMIT-response-loss convergence 和 optional AI schema promotion gate 全部通过。