Files
qinglong/docs/adr/ADR-0477-bounded-legacy-data-directory-inventory.md
T

141 lines
8.5 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-0477:有界 Legacy Data Directory 盘点
- 状态:Accepted
- 日期:2026-08-21
- 关联:QL-RFC-0001、ADR-0476
## 上下文
ADR-0476 已经证明单个生产形态 QingLong 2.x `database.sqlite` 可以经过 inspect、stage、verify、activation 和 clean/write-after
双态回滚分类接管到 3.0,但真实部署的 `data` 目录不只有主数据库。现行 2.x 配置和实际用户目录可包含:
- `config/``scripts/``db/``upload/``ssh.d/`
- `log/``syslog/``bak/`
- `repo/``raw/``dep_cache/`、历史 `deps/`
- 用户或插件创建的未知顶层条目。
直接复用 2.x `SystemService.exportData/importData` 不满足 3.0 接管要求。该路径以 shell 拼接 `tar`,默认只覆盖数据库和上传目录,
也没有固定资产分类、no-follow、硬链接拒绝、内容上限、确定性计划或敏感输出约束。直接打包整个 `data` 目录还会把日志、备份、
跨架构依赖缓存、仓库 checkout、SSH 材料和未知插件资产混成一个不可审核恢复单元;对低内存路由设备尤其危险。
完整目录复制之前,需要一个短生命周期、只读、确定性、按 Profile 有界的产品入口先回答:哪些资产存在、哪些可进入后续复制、哪些必须
转换、哪些只保留为外部恢复资产、哪些应在 3.0 重新生成,以及是否存在必须人工处理的不安全或未知条目。
## 决策
### 1. 复用现有一次性产品入口
`@qinglong/local-owner-cli``lifecycle/data-directory-adoption/` 内增加:
- exact operation`local-data-directory.adoption.inspect`
- exact options`dataRoot``profile`
- 既有 `ql3-adoption run --command-file ...` 私有 command-file 入口。
不新增 workspace package、第三方依赖、binary、daemon、listener、watcher、timer、数据库连接或部署对象。实现不得进入 Edge、
Standalone、Application、AI 或 MCP 常驻闭包。
### 2. 固定资产处置矩阵
| 类别 | 处置 | 盘点深度 | 原因 |
| --- | --- | --- | --- |
| `config` | `transform` | `recursive_content` | 需要迁移到 3.0 配置模型,不能盲拷旧配置 |
| `scripts` | `copy_reviewed` | `recursive_content` | 用户脚本是业务资产,但必须先审核安全和兼容性 |
| `db` | `transform` | `recursive_content` | 主库由 ADR-0476 迁移,Keyv/sidecar 需独立识别 |
| `upload` | `copy_reviewed` | `recursive_content` | 用户上传内容可保留,但必须受大小和文件类型边界约束 |
| `ssh.d` | `transform` | `recursive_content` | 属于敏感凭据材料,后续必须进入专用私有交付协议 |
| `log``syslog``bak` | `retain_external` | `root_only` | 作为历史/恢复资产保留,不进入默认 3.0 运行目录 |
| `repo``raw``dep_cache``deps` | `regenerate` | `root_only` | checkout、原始缓存和依赖缓存应按目标架构重建 |
未知顶层条目只记录数量和名称摘要,不返回原始名称,计划状态固定为 `manual_review`。本阶段不允许 caller 覆盖处置矩阵或增加任意
include/exclude glob。
### 3. No-follow、稳定身份与内容脱敏
盘点要求 `dataRoot` 为当前 UID 拥有、canonical、非符号链接、group/world 不可写的非根目录。递归类别按 UTF-8 字节序确定性遍历,
使用 `lstat` 且不跟随符号链接;只有当前 UID、group/world 不可写的普通单链接文件或目录可继续读取。符号链接、硬链接、多链接文件、
特殊文件、错误 owner 和可被组/其他用户写入的条目只计为 unsafe,不读取其内容。
普通文件通过 `O_NOFOLLOW` descriptor 读取,打开前后的 device、inode、mode、link count、UID、size、mtime 和 ctime 必须一致。
目录遍历前后也必须保持同一稳定身份。底层文件系统错误统一映射为固定错误码
`LOCAL_DATA_DIRECTORY_ADOPTION_CONFIGURATION_INVALID`,CLI 不返回原始路径、任意文件名或内容。
结果只包含固定类别名、计数、逻辑/分配字节、宽读权限计数、unsafe 计数、SQLite 主库/Keyv/sidecar 识别计数、内容摘要、未知条目
数量/摘要和完整 `planDigest`。宽读权限是审核信号;只有可写权限或身份/类型问题自动成为 unsafe。
### 4. Edge 与 Standalone 分离预算
| Profile | 最大条目 | 最大哈希字节 | 单文件上限 | 最大深度 |
| --- | ---: | ---: | ---: | ---: |
| Edge | 8,192 | 512 MiB | 64 MiB | 32 |
| Standalone | 65,536 | 4 GiB | 512 MiB | 64 |
目录通过增量 `opendir` 枚举,并在保存超过剩余预算的名称前失败,不先用一次性 `readdir` 将任意数量的目录项装入内存。文件使用
64 KiB 固定缓冲区流式哈希并在关闭前清零。`root_only` 类别不递归、不读取或哈希其子项,因此依赖缓存和历史日志规模不会进入
盘点内存或 I/O 成本。
### 5. 本阶段只发布计划,不执行迁移
该 operation 只读源目录并将结果写到 stdout。它不创建目录副本、归档、manifest、recovery 或 activation,不修改 2.x 数据,
也不把目录计划自动绑定到 ADR-0476 的 SQLite plan/activation。后续 stage 必须重新验证稳定身份并显式绑定两个计划摘要;不能把本次
inspect 输出直接当作复制授权。
## 被拒绝的替代方案
### 直接 tar 完整 data directory
拒绝。它混合不同恢复语义、可能跟随或保存不安全链接、复制跨架构缓存,并让低配设备承担不可预测的空间和内存成本。
### 为目录接管再拆一个 workspace package
拒绝。该能力只有一个短生命周期产品 owner,没有独立部署、依赖或版本生命周期;放入已有 Local Owner lifecycle 垂直目录更符合
当前包边界规则,也避免恢复“一个文件一个包”的碎片化。
### 只统计文件大小,不读取内容摘要
拒绝。大小和时间不能把后续 stage 绑定到已审核内容;确定性流式摘要提供最小的漂移证明,同时不输出文件内容。
### 在 inspect 时复制或转换文件
拒绝。盘点和 mutation 混合会让未知/不安全条目在 operator 审核前产生目标副本,也无法建立清晰的 plan-digest fence。
## 影响
### 正面
- 完整 2.x data directory 首次获得固定、可审核的资产处置模型;
- Edge 和 Standalone 使用不同硬预算,低配设备不会继承集群节点规模假设;
- 未知资产、链接和权限漂移失败关闭,且不会泄露任意文件名或内容;
- 日志、备份、仓库和依赖缓存不会污染 3.0 默认运行目录;
- 没有增加 package 粒度、常驻资源或基础制品体积。
### 代价与限制
- 对递归类别执行全内容哈希,仍会产生与资产大小线性的磁盘读取;
- 正在写入的 2.x 目录可能因稳定身份检查失败,需要先停止 writer 后重试;
- `root_only` 只证明类别根的存在、权限和类型,不证明内部历史资产完整性;
- 当前没有 stage/verify/restore,也没有固定物理 Edge 的耗时、RSS、磁盘峰值与断电演练;
- 当前目录计划尚未与 SQLite activation、service-manager cutover 和 rollback lineage 形成统一证据链。
## 验证
- D-384 聚焦 data directory CLI`5/5`
- Local Owner`195 total / 190 pass / 5 conditional skip / 0 fail`
- backend`1,535 total / 1,533 pass / 2 conditional skip / 0 fail``pnpm build:back` 通过;
- 18-package clean build 与逐包测试单次退出 0;
- package boundary、Cluster dependency、Edge import、Service Bridge import、Cluster/Worker deployment、Console 与 Console
distribution 八项审计全部 compatible/passed
- workspace 保持 18 packages`singleSourcePackages=[]``shallowSourcePackages=[]`Local Owner 为
`118 source / 117 nested / 1 root binary entry`
- 14 档 Local artifact audit 全部 compatible,基础 Edge/Standalone、Adopted、Application+AI 与 MCP 的体积和 loaded-module
基线未变化。
本阶段不修改 PostgreSQL schema、ACL、repository、role、Pool、连接或 failover 语义,因此不重跑且不重新占有 PostgreSQL HA
证明。
## 后续
- D-385:以 plan digest 和 ADR-0476 activation digest 为双 fence,设计 no-replace stage/verify manifest
-`config`、Keyv 与 `ssh.d` 定义显式转换/私有交付协议;
- 在固定物理 Edge/NAS 上测量完整目录盘点与 staging 的耗时、RSS、I/O、磁盘峰值和断电恢复;
- 把 staged data directory 证据接入 systemd/OpenRC/Compose cutover 与 rollback lineage。