mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
226 lines
15 KiB
Markdown
226 lines
15 KiB
Markdown
# ADR-0481:Committed Legacy Data Receipt 的本机部署 Lineage
|
||
|
||
- 状态:Accepted
|
||
- 日期:2026-08-21
|
||
- 关联 RFC:QL-RFC-0001 D-05、D-06、D-17、D-64、D-87、D-184、D-259、D-388
|
||
- 关联 ADR:ADR-0194、ADR-0309、ADR-0310、ADR-0313、ADR-0314、ADR-0362、ADR-0476、ADR-0477、ADR-0478、ADR-0479、ADR-0480
|
||
|
||
## 背景
|
||
|
||
ADR-0480 已把 Legacy `config/db/ssh.d` prepared model 原子提交到 Local SQLite,并在 transformation root
|
||
留下内容无关、可精确验证的 `commit.json`。但是现有部署链只绑定 SQLite activation、Legacy silence
|
||
commitment、Application config、进程 identity 与 service/Compose outcome。D-387 的 `commitDigest` 和数据库
|
||
`receiptDigest` 尚未进入 systemd、OpenRC 或 Compose 的启动事实。
|
||
|
||
这不是日志字段缺失,而是 authority 缺口:现有 adopted Application v3 不认识 data application receipt,部署者
|
||
可以先完成 D-387,再继续用一个只绑定 SQLite activation 的 v3 config 启动目标;相反,若把 D-387 apply 与
|
||
service start 合并,又会让数据迁移 credential 获得 activation 权限,并破坏 ADR-0480 已明确的职责分离。
|
||
|
||
Compose 还有一个更早的产品缺口。fresh v2 固定把 deployment root 映射到 `/var/lib/qinglong3`;SQLite activation
|
||
却绑定宿主机 canonical source/target path digest。直接把 adopted config 的路径改成容器路径会让真实 Application
|
||
校验失败,继续依赖测试中的合成 mount evidence 也不能称为受审 adopted Compose create/config。
|
||
|
||
本决策只处理本机 Edge/Standalone。Cluster 必须使用 PostgreSQL、separation-of-duty、控制面 rollout 与集群专用
|
||
fencing;不能复用本机私有文件或 Owner credential ceremony。
|
||
|
||
## 决策
|
||
|
||
### 1. Receipt 是启动前置事实,不是启动授权
|
||
|
||
D-387 的 `commit.json` 证明一个 prepared transformation 已经完成数据库提交和明文逻辑回收。它不表示:
|
||
|
||
- 配置、SSH binding、Task 或 Trigger 已启用;
|
||
- Legacy 已静默;
|
||
- systemd/OpenRC unit 已安装、enable 或 start;
|
||
- Compose target 可以 `up`;
|
||
- target 可以回退,或 Legacy 可以重新启动。
|
||
|
||
因此 data apply、deployment bundle prepare、service/Compose activation 和 rollback 继续是四个独立命令与 durable
|
||
事实。任何一步成功都不隐式调用下一步。
|
||
|
||
### 2. Application v4 精确绑定 committed application
|
||
|
||
新增只允许 `storage.mode=adopted` 的
|
||
`qinglong/local-application-process@v4`。它保留 v3 的 `storage` 与 `cutover`,并增加 exact 字段:
|
||
|
||
```json
|
||
{
|
||
"legacyDataApplication": {
|
||
"commitPath": "/absolute/private/transformation/commit.json",
|
||
"expectedCommitDigest": "64-hex",
|
||
"expectedReceiptDigest": "64-hex"
|
||
}
|
||
}
|
||
```
|
||
|
||
Application 在取得 signal、SQLite、Secret、Plugin Package、AI 或 lifecycle authority 前,按以下顺序失败关闭:
|
||
|
||
1. 解析 exact v4 config,要求所有 authority path 规范、非 root、互异且有界;
|
||
2. 使用 no-follow descriptor 有界读取私有 `commit.json`,复核 owner、mode、link count、inode 与读前后 stat;
|
||
3. 校验 D-387 exact schema、`state=committed`、reclamation 三项固定值与 payload-domain digest;
|
||
4. 同时匹配 config 的 `expectedCommitDigest`、`expectedReceiptDigest`、Profile,以及 storage activation 所绑定的
|
||
transformation/target 事实;
|
||
5. 再验证既有 Legacy silence commitment,然后才进入 adopted storage startup。
|
||
|
||
v2 fresh 行为不变。v3 仍表示“只接管 SQLite、没有声明完整 data-directory application”的兼容模式;完成 D-387
|
||
并要把其结果作为部署前置事实的路径必须使用 v4,部署 prepare 不得为该路径生成 v3。
|
||
|
||
### 3. Canonical commit codec 下沉到既有 SQLite adoption domain
|
||
|
||
`commit.json` 的 payload 本来由 SQLite adoption receipt 派生。canonical type、exact normalizer 与 digest 计算放在
|
||
现有 `@qinglong/local-sqlite/data-directory-adoption` 子路径,由 D-387 cleanup、Application startup gate 和 Owner
|
||
deployment consumer 共同复用。文件身份读取仍由各自 authority owner 负责。
|
||
|
||
这不是新 package,也不让 Local SQLite 读取 deployment journal、调用 init/Docker 或获得 service authority;它只
|
||
共享内容无关的纯数据 contract,避免 Application 与 Owner 各自维护一份易漂移的 schema。
|
||
|
||
### 4. 新增 adopted bundle prepare/verify,但不激活服务
|
||
|
||
既有 `ql3-local-deploy` 增加 exact、私有 command-file 操作:
|
||
|
||
```text
|
||
local.deployment.adopted.prepare
|
||
local.deployment.adopted.verify
|
||
```
|
||
|
||
命令精确绑定 Profile、instance/cutover、deployment root、SQLite source/target/recovery/manifest/activation、Legacy
|
||
silence commitment、D-387 commit/receipt,以及 systemd、OpenRC、Compose 三选一 service contract。prepare 在发布
|
||
任何文件前验证全部源证据,随后 no-replace 发布 Application v4 config、描述符和一个内容无关 bundle receipt;verify
|
||
只验证终态,绝不修复、install、enable、start、stop、pull image 或连接 socket。
|
||
|
||
该操作不执行 `local.setup`,因为 target SQLite、Owner pepper、Secret keyring 和 D-387 receipt 已存在。fresh
|
||
`local.deployment.prepare` 保持 v2/`local.setup` 语义,避免把两种 authority 混成大量 optional 字段。
|
||
|
||
### 5. systemd/OpenRC 保持 Owner → root bridge → Owner consumer
|
||
|
||
Application v4 config 的文件 SHA-256 继续进入 service-manager intent,因此短生命周期 root bridge 可传递绑定而无需
|
||
解析 D-387 schema、打开 SQLite 或读取 transformation root。root bridge 仍只执行固定 manager/argv 并发布 outcome。
|
||
|
||
Owner consumer 必须重新读取 v4 config、commit 和既有 activation/commitment/data evidence。新的 cutover journal
|
||
版本显式记录:
|
||
|
||
- `legacyDataApplicationCommitDigest`;
|
||
- `legacyDataApplicationReceiptDigest`;
|
||
- 既有 `applicationConfigDigest`、`activationDigest` 与 `commitmentDigest`。
|
||
|
||
restart/stop 必须保持同一 commit/receipt binding;发生漂移时不能调用 manager。Legacy rollback preparation 可以读取
|
||
该 lineage,但 `commitDigest` 绝不替代既有 stopped evidence、双阶段授权、barrier 和 readiness proof。
|
||
|
||
### 6. adopted Compose 使用 identity-preserving bind
|
||
|
||
fresh v2 Compose 继续把 deployment root 映射到 `/var/lib/qinglong3`。adopted v4 不复用这一映射,而要求:
|
||
|
||
- target、recovery、manifest、activation、cutover commitment、D-387 commit 与运行时可写目录均位于私有
|
||
deployment root;
|
||
- deployment root 以 source 与 target 相同的 canonical absolute path 绑定进容器;
|
||
- Legacy SQLite source 作为唯一额外 bind,同样映射到相同 canonical absolute path;
|
||
- 描述符固定 read-only rootfs、无网络、cap-drop、no-new-privileges、UID:GID 和 Profile memory/PID 上限;
|
||
- adopted target 不使用隐式 `unless-stopped` 绕过 generation/Legacy reproof。
|
||
|
||
这样 Application 看到的 path 字节与 activation 中的 path digest 相同,不需要重写已签定的证据,也不扩大为任意
|
||
caller-supplied mount 表。Compose config inspection 必须精确验证两条 mount、config path、restart policy、image digest、
|
||
generation 和 mutation labels。
|
||
|
||
Compose preflight 在任何 `compose up` 前验证 v4 commit/receipt,并把二者加入 rollout receipt。apply 的 exact replay、
|
||
失败回收、restore 与 evidence collection 必须继承同一 binding;restore 仍只恢复目标 SQLite generation,不删除 D-387
|
||
提交、不重新创建已回收明文,也不自动启动 Legacy。
|
||
|
||
### 7. 低配与大节点使用同一协议、不同预算
|
||
|
||
本能力不新增 workspace package、production dependency、daemon、timer、watcher、listener、数据库连接或后台 retry。
|
||
Application 常驻路径只增加一次不超过 64 KiB 的私有 JSON 读取和 SHA-256;Owner/Compose 验证均为人工触发的一次性
|
||
操作,不扫描历史。
|
||
|
||
Edge 保持 128 MiB/64 PID 描述符预算,Standalone 保持 256 MiB/256 PID。两档共享同一 receipt/lineage schema,避免
|
||
低配设备成为弱协议;Cluster 节点不因此获得本机模式的规模或高可用声明。
|
||
|
||
## 故障与重放语义
|
||
|
||
- commit 在 prepare 前漂移:不发布 bundle;
|
||
- prepare 在文件发布中崩溃:原命令只按 deterministic content/no-replace 收敛,未知额外文件失败关闭;
|
||
- service/Compose activation 前漂移:不调用 manager/Docker;
|
||
- manager/Docker side effect 后响应丢失:沿用既有 barrier 与 inspect-only recovery,不因 receipt 重新执行副作用;
|
||
- Application 读取 commit 后、storage 前 commit 被替换:descriptor identity/stat 复核失败;
|
||
- 已 active 后 commit 被删除或漂移:下一次 restart/stop consumer 失败关闭并进入既有人工诊断路径;当前进程不引入
|
||
watcher,因此不冒充运行期间持续 attestation;
|
||
- rollback:必须执行既有显式 prepare/authorize/consume 或 Compose restore ceremony,receipt 本身没有回退权限。
|
||
|
||
## 被拒绝的替代方案
|
||
|
||
### 只把 `commitDigest` 加进 stdout 或 service intent
|
||
|
||
拒绝。Application 仍可用 v3 直接启动,root/Compose side effect 也可能发生在 Owner 复核前。
|
||
|
||
### D-387 apply 成功后自动 start
|
||
|
||
拒绝。数据迁移 credential 会获得 deployment authority,且 COMMIT-response-loss 会让自动副作用无法安全重放。
|
||
|
||
### Application 导入 Owner CLI 或直接调用 systemd/Docker
|
||
|
||
拒绝。会把短生命周期部署 authority 带入低配常驻闭包,并破坏 root bridge 的最小解析面。
|
||
|
||
### adopted Compose 继续映射到 `/var/lib/qinglong3`
|
||
|
||
拒绝。会改变 activation 已绑定的 path identity;重新计算 digest 等于伪造一份没有原 ceremony 支持的新证据。
|
||
|
||
### 为 receipt 建立新 workspace package或常驻 watcher
|
||
|
||
拒绝。canonical codec 已有 SQLite adoption owner;watcher 既不能回滚外部 side effect,也会为路由设备增加常驻成本。
|
||
|
||
### 用 receipt 自动允许 Legacy rollback
|
||
|
||
拒绝。receipt 证明新数据已提交,反而意味着回退更需要数据 reconciliation;它不证明 target stopped 或 Legacy ready。
|
||
|
||
## 验收条件
|
||
|
||
1. v4 config、commit schema/digest/Profile/receipt/path 任一漂移,Application 在 signal/storage 前失败;v2/v3 既有语义不变。
|
||
2. adopted prepare/verify 对 systemd、OpenRC、Compose 均可 exact replay,且不产生 install/start/socket/network 副作用。
|
||
3. systemd/OpenRC root bridge 不解析 commit;Owner cutover journal 显式绑定 commit/receipt,restart/stop 漂移失败。
|
||
4. adopted Compose 真实 config 使用 identity-preserving mount,preflight/apply/restore/evidence lineage 保留 commit/receipt。
|
||
5. activation 与 rollback 仍需既有独立命令;D-387 apply 或 bundle prepare 单独成功不会改变服务状态。
|
||
6. 覆盖成功、exact replay、commit/config/mount 漂移、发布崩溃、manager/Docker 响应丢失、ENOSPC 与低配资源边界。
|
||
7. 完整 package/backend、架构、distribution、artifact 门通过;workspace package 数、浅包审计和常驻依赖闭包不退化。
|
||
|
||
## 实现与验收证据
|
||
|
||
- Application v4、canonical data application commit codec、adopted bundle、systemd/OpenRC Owner consumer 与 Compose
|
||
lineage 均已落地。Compose rollout receipt 为 v3,restore/evidence collection receipt 为 v2,三者携带相同
|
||
`applicationConfigDigest/activationDigest/commitmentDigest/legacyDataApplicationCommitDigest/
|
||
legacyDataApplicationReceiptDigest`,fresh receipt 使用同一 schema 但 adopted 字段为 `null`。
|
||
- adopted bundle 聚焦套件 `12/12` 通过:systemd、OpenRC、Compose prepare/verify exact replay,不产生 service intent;
|
||
commit/config/mount 漂移失败关闭;真实 `docker compose config --format json` 证明 identity-preserving 双 bind;Docker
|
||
`up` response loss 只 inspect;restore 保持 activation 已绑定的 target device/inode;evidence collection 继承同一
|
||
lineage。新增发布故障矩阵覆盖“stage 已 fsync、target 尚未 link”与“target 已 link、stage 尚未清理”,原命令均以
|
||
no-replace bytes 收敛且不激活服务。
|
||
- SQLite rollout safety 聚焦套件 `8/8`、完整 Local SQLite `239/239` 通过。adopted restore 在同一 inode 内有界改写,
|
||
ENOSPC/部分写保留 exact source stage 与 old-snapshot recovery evidence,重放后恢复原字节并清理中间材料;fresh restore
|
||
继续使用原 replace 语义。
|
||
- 完整 Local Owner 为 `222 total / 217 pass / 5 root-service conditional skip / 0 fail`。tracked backend 在新增
|
||
release contract 回归后为 `1540 total / 1538 pass / 2 conditional skip / 0 fail`;loopback/TLS 用例在允许本机
|
||
listener 的环境运行。18 个 QL3 workspace package 均完成 clean build 与逐包测试。
|
||
- package boundary schema v6 保持 `workspacePackageCount=18`、`singleSourcePackages=[]`、
|
||
`shallowSourcePackages=[]`;Local Owner 为 `135 source / 134 nested / 1 root binary entry`。没有新增 workspace
|
||
package、production dependency、daemon、timer、watcher、listener 或数据库连接。
|
||
- 十四档 Edge/Standalone artifact audit 全 compatible。基础 Edge/Standalone 为
|
||
`2,611,978 / 2,612,056` bytes、319 files、58 loaded modules;Adopted 为
|
||
`2,831,713 / 2,831,836` bytes、339 files、59 modules;Application 为
|
||
`3,669,436 / 3,669,556` bytes;Application+AI 为 `4,529,710 / 4,529,842` bytes;MCP 为
|
||
`7,337,910 / 7,338,018` bytes,均保留预算 headroom。Edge/Standalone Compose 继续分别固定
|
||
`128 MiB/64 PID` 与 `256 MiB/256 PID`。
|
||
- release live preflight/rollout 不再硬编码过期 SQLite v44/v37,而从已构建 Local SQLite contract 读取 v50;最终
|
||
deployment-readiness 不复制实现版本号,只要求有界正整数并要求 Edge/Standalone 对同一 selection 完全一致。catalog
|
||
selection、deployment readiness、publication closure 与 tag finalizer 相关回归 `28/28` 通过,image release、local
|
||
image、package boundary、cluster dependency、Edge import、service bridge、deployment lock 与 cluster deployment 审计均
|
||
compatible。
|
||
- PostgreSQL/Cluster 语义未由本 ADR 修改,也不由本地 receipt 重新声明。独立 PostgreSQL 18.6 arm64 HA Docker 门仍以
|
||
timeline `1 → 2`、`146` gates 和无 finding 的 evidence audit 通过,用于证明本切片没有破坏既有集群基线,而不是把
|
||
Local lineage 当作 Cluster authority。
|
||
|
||
## 未包含
|
||
|
||
- Cluster/PostgreSQL/Kubernetes 的 prepared-model application 与 rollout;
|
||
- 运行期间持续文件 attestation 或 tamper remediation;
|
||
- `scripts/upload` 的最终启用、Task/Trigger activation、SSH host-key 审核;
|
||
- 数据产生后的自动 reconciliation 或 Legacy restart;
|
||
- 固定物理 Edge/NAS 的断电、FTL 写放大和加密卷销毁证明。
|