mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-23 20:15:19 +08:00
141 lines
15 KiB
Markdown
141 lines
15 KiB
Markdown
# ADR-0491:有界 Secret/Config Reconciliation 与任务环境绑定
|
||
|
||
- 状态:Proposed(D-397 已实现 Legacy Env inspection、私有有界 row plan、durable plan publication 与独立 signed decision;原子 application 尚未完成)
|
||
- 日期:2026-08-23
|
||
- 决策:D-397
|
||
- 关联:ADR-0073、ADR-0074、ADR-0092、ADR-0094、ADR-0480、ADR-0482、ADR-0483、ADR-0484、ADR-0485、ADR-0486、ADR-0487、ADR-0488、ADR-0490
|
||
|
||
## 背景
|
||
|
||
QingLong 2.x 的 `Envs` 不是简单的 `name → value` 字典。运行时只使用 `status=normal` 的行,先按 `isPinned DESC、position DESC、createdAt ASC` 排序,再把同名行的值用 `&` 连接后注入所有 Legacy Task。停用行仍属于用户数据,但不应被激活。直接把每一行转换成一个 3.0 Secret 会改变同名变量语义;只把数据加密保存而不更新 TaskDefinition,又会让迁移在形式上完成、实际任务却失去环境变量。
|
||
|
||
3.0 已具有项目内版本化 SecretRef、TaskDefinition `command@v1` 的 secret environment binding、本地按引用解密注入和 Cluster remote Secret delivery。D-397 应复用这些边界,不能恢复 2.x 的全局明文 `export` 文件,也不能让 Secret 值进入 plan、review、receipt、日志、命令文件或 stdout。
|
||
|
||
此外,D-385~D-388 已处理数据目录中的 `config.sh`、Keyv 与 SSH material,但没有处理 Legacy SQLite 的 `Envs`。二者是不同 source lineage,不能因为目标端已经存在 Data Directory Adoption Secret 就把数据库 Env 判定为已迁移。当前 2.x 生产代码也没有稳定的 SQLite `Configs` 模型;捕获中出现的历史 `Configs` 表必须按未知 schema 失败关闭,不能套用 `Envs` 规则。
|
||
|
||
## 决策
|
||
|
||
### 1. 固定执行顺序与领域授权
|
||
|
||
Local reconciliation 顺序固定为:
|
||
|
||
```text
|
||
application plan
|
||
→ Automation plan / review / apply(若需要)
|
||
→ Secret/Config plan / review / apply(若需要)
|
||
→ Run History preservation(若需要,绑定最新 head)
|
||
→ cross-domain completion
|
||
```
|
||
|
||
Secret/Config 不消费 Automation decision 作为自身授权。D-391 把 `secret_and_config` facts 标为 `blocked`,其 review action 只能是 `manual_external|defer`;D-397 专用 adapter 只接收每条 fact 都精确选择 `manual_external` 的决策流,把它重新绑定到同一 sealed bundle、D-392 application plan 与当前 target snapshot,任何 `defer` 都继续失败关闭。D-397 使用独立的逐候选 signed decision:只有 `plan.outcome=ready`、候选非空且没有 manual/conflict 才能 prepare;后续 application 必须消费该终态授权。强认证 User、Project Policy、Secret custody 与 Task mutation authority 都要在写事务前及事务内重新验证。
|
||
|
||
存在 active Env 时,Automation 必须已经完成,且至少一个经 `QingLong3LegacyAdoptions` 证明的 Legacy Task 可绑定;否则不得用“Secret 已保存”冒充行为迁移。只有停用 Env 的场景可以在 Automation `no_effect` 后做纯保全。
|
||
|
||
### 2. Legacy Env 的确定性语义
|
||
|
||
`Envs` inspection 要求 `id/name/value`,并兼容缺省的 `status/isPinned/position/createdAt`。合法环境变量名必须符合 shell 与 3.0 `command@v1` 的共同子集,且不得使用 `QL3_` 保留前缀。
|
||
|
||
active 行按以下顺序处理:
|
||
|
||
```text
|
||
COALESCE(isPinned, 0) DESC,
|
||
position DESC,
|
||
createdAt ASC,
|
||
id ASC
|
||
```
|
||
|
||
同名行按该顺序用单个 `&` 连接,形成一个 effective value;它只产生一个 active Secret 和一个 Task environment binding。停用行不参与连接,每行形成一个 disabled-preservation candidate,后续可由 Owner 显式恢复或重建,但不会绑定到任务。
|
||
|
||
诊断只保存 row ordinal、source digest、disposition 与 reason。inventory 只保存计数、状态与 digest。变量名、值、remarks、labels、原始排序字段和 row body 均不得进入公开 evidence。candidate 只能通过 Local Admin 的精确私有子路径流式交给短生命周期 planner/publisher。
|
||
|
||
以下情况必须 `manual_required`:非法/保留名称、非文本或含 NUL 的值、异常 status/ordering metadata、同组 effective value 超过 16 KiB、全部 active effective values 超过 64 KiB、active binding 超过 256、停用保全数量超预算、必要列缺失或 source 漂移。不能丢弃异常行后对剩余行做“部分成功”绑定。
|
||
|
||
### 3. Edge 与 Standalone 预算
|
||
|
||
当前 inspection 与 row-plan 切片固定:
|
||
|
||
| 预算 | Edge | Standalone |
|
||
| --- | ---: | ---: |
|
||
| Legacy Env 行数 | 10,000 | 100,000 |
|
||
| disabled preservation candidates | 128 | 512 |
|
||
| active bindings | 256 | 256 |
|
||
| 单个 effective value | 16 KiB | 16 KiB |
|
||
| 全部 active effective values | 64 KiB | 64 KiB |
|
||
|
||
实现逐行读取,不把整张 `Envs` 或全部停用值加载到内存;active value 的在途内存由 64 KiB 合同封顶,停用值通过第二次有界扫描逐个交付。它位于既有 `@qinglong/local-admin/src/legacy-adoption/secret-and-config/`,不新增 workspace package、production dependency、daemon、timer、watcher、listener、socket、数据库连接池或 `src` 根平铺文件。
|
||
|
||
Local Owner 使用私有 NDJSON row plan 记录 header、逐行 content-free disposition、逐 candidate 目标冲突投影与 footer。Edge/Standalone plan 文件分别限制为 8 MiB/32 MiB,单行不超过 64 KiB;超过预算立即失败关闭。公开 plan/receipt 不保存原 Env name/value、目标 ciphertext、key ID 或原始 row body。active 与 disabled candidate 分别使用 `legacy-db-env-*` 和 `legacy-db-env-disabled-*` 命名空间;目标已经存在时只记录 envelope 元数据的组合摘要并进入 `review_skip_conflict`,不得读取明文、覆盖或自动改名。plan 绑定 application、D-391 review authorization、sealed bundle、target projection、Automation adoption ledger 的有界 content-free 投影与 prepared head,并产生可重新计算的 row-set、candidate-set、adoption-set、plan-file 和 receipt digest。
|
||
|
||
durable publisher 固定写入 `<secretConfigRoot>/<secretConfigId>/{plan.ndjson,receipt.json,staging/}`,使用 no-replace publication、`0400/0500` 权限、文件与目录 `fsync`,并覆盖 plan、receipt、terminal seal、head CAS 四个 response-loss 窗口。只有 Automation 无需 adapter 时的 `reconciliation_application_planned`,或 Automation 已完成时的 `reconciliation_automation_applied`,可以单向推进到 `reconciliation_secret_config_planned`;verify 只读复算 plan/receipt/seal/head 绑定,不修复漂移。active Env 若没有至少一条已采纳 Legacy Task ledger 记录仍为 manual;历史 `Configs` 计入 `unadaptedLegacyConfigCount` 并保持 manual。
|
||
|
||
独立决策使用私有 NDJSON decision file,每个候选必须按 ordinal/digest 精确选择 `apply_active_binding/reviewed_active_binding`、`preserve_disabled/reviewed_disabled_preservation` 或 `skip/operator_excluded|target_conflict|security_review_required`。任何 `skip` 都把终态 outcome 降为 `manual_required`,不能被 application 当成部分成功;`no_effect` 不需要决策,manual/conflict plan 也不能通过强认证升级。签名授权使用与 D-391 review 相同的强认证 User,认证年龄最多 5 分钟、授权生命期最多 30 分钟,并以独立 HMAC domain 绑定 decision、Secret/Config plan、candidate set、application、preparation、prepared head、sealed bundle、reviewer 与时间。Edge/Standalone decision/authorization 文件分别限制为 1 MiB/4 MiB,沿用 owner-only `0700/0600`、sealed `0500/0400`、no-replace 与 `fsync`。lineage 单向推进 `reconciliation_secret_config_planned → reconciliation_secret_config_decision_prepared → reconciliation_secret_config_reviewed`;prepare/commit 的全部 publication response-loss 窗口都精确重放且不重复认证,terminal verify 只读复算 sealed decision、authorization、receipt 与当前 reviewed head。
|
||
|
||
### 4. 原子 application 必须同时完成 custody 与行为绑定
|
||
|
||
后续 D-397 application 必须在一个 `BEGIN IMMEDIATE` 事务内完成:
|
||
|
||
1. 复验 Project/RoleBinding fence、signed decision、sealed source、target snapshot 与当前 instance head;
|
||
2. 为每个 active effective Env 写入加密 Local Secret envelope、content-free `secret.create` audit 与 adoption item;
|
||
3. 为每个 disabled candidate 写入加密但未绑定的 Secret,并记录 disabled preservation disposition;
|
||
4. 为每个经 Automation adoption ledger 证明的 Legacy Task 追加 TaskDefinition revision,把 active Env 绑定为 `kind=secret` 的 environment;
|
||
5. 为指向旧 Task revision 的 Legacy Trigger 追加 Trigger revision,并更新 local schedule/dispatch revision;
|
||
6. 写入不可变 Secret/Config reconciliation receipt ledger 后一次提交。
|
||
|
||
任一 Secret、Task、Trigger、dispatch、audit、ledger 或 fence 冲突都回滚整个事务。禁止先提交 Secret 再逐任务修补,也禁止在现有 Task revision 上原地改 JSON。目标已有同名/同源 Secret、非 Legacy Task、Plugin-owned Task 或用户在 stopped window 中产生的 revision 都按冲突处理,不自动覆盖或重命名。
|
||
|
||
由于该 adapter 执行 DML,它需要独立的 prepared/applied/rolled-back lineage 与写前 target backup;无 DML 的 Run History preservation 继续只绑定最新 head。跨领域 completion 后才可回收 Automation 与 Secret/Config 两份 rollback material。空间不足必须在 prepare 前失败,不得在低配设备上边写边赌。
|
||
|
||
### 5. preserve、destroy 与 completion
|
||
|
||
D-397 application 的成功终态只声明:
|
||
|
||
- active Env 已加密并绑定到全部已采纳 Legacy Task;
|
||
- disabled Env 已加密保全且保持未激活;
|
||
- sealed source database 与 rollback backup 仍然存在;
|
||
- `physicalErasureGuaranteed=false`。
|
||
|
||
它不删除 Legacy SQLite、capture bundle、Data Directory Adoption Secret 或外部备份。明文销毁必须是目标 restart/readiness、观察窗口和 rollback 保留策略之后的独立强认证 ceremony,并明确列出可删除对象;闪存、CoW 文件系统与备份介质不能声称物理擦除保证。
|
||
|
||
Completion 需要演进为兼容旧 receipt 的下一 schema:`secret_and_config + adapter_required` 必须携带 exact Secret/Config application evidence;active Env 的 custody-only receipt 不合法。`adapterCount` 由实际 Automation、Secret/Config、Run History 三类证明推导,调用方不能自报。任何未知 `Configs`、manual Env、未绑定 Task、stale run-history proof、rolled-back state 或 target drift 都继续拒绝 `reconciliation_completed`。
|
||
|
||
### 6. `Configs` 与 D-385~D-388 的边界
|
||
|
||
- 数据目录 `config.sh`、Keyv、SSH 继续由 ADR-0477~ADR-0481 的 lineage 管理,默认 activation 仍为 disabled;
|
||
- SQLite `Envs` 由本 ADR 管理,active effective value 必须绑定 Task 才能终态;
|
||
- SQLite `Configs` 在没有版本化 schema transformer 前只能保留在 sealed bundle 并进入 manual,不自动解析字段或猜测文件路径;
|
||
- 两条 lineage 可以在 target Secret store 共存,但发布器必须检查 Secret name/source digest 冲突,不能重复接管同一 material。
|
||
|
||
### 7. Cluster 是独立实现
|
||
|
||
Cluster migration 不复用 Local SQLite handle、POSIX keyring、instance head 或本机 backup。它需要独立的 PostgreSQL SERIALIZABLE ledger、外部 KMS/Secret provider custody、exact Project/Task revision fence、Trigger revision 与 remote delivery authority,以及 HA promotion 后仍可验证的 receipt。
|
||
|
||
Cluster 不得把 Legacy Env 明文写入 PostgreSQL、ConfigMap、Job command、Pod environment 或迁移日志。一次性 migration Job 应使用精确投影、短期身份、零 watcher/controller,并在事务提交前后绑定 PostgreSQL timeline/role readiness。Edge 不安装或加载这些 Cluster 依赖;Cluster 也不能把本机 10,000 行预算冒充集群容量证明。
|
||
|
||
## 被拒绝的替代方案
|
||
|
||
### 恢复全局 `export` 文件
|
||
|
||
拒绝。它重新引入明文落盘、所有 Task 隐式继承、无法审计的动态作用域和本地/Cluster 行为分叉。
|
||
|
||
### 每个 Legacy Env 行直接创建一个 active Secret
|
||
|
||
拒绝。同名行在 2.x 中按顺序用 `&` 合并;逐行绑定既无法表达该语义,也会产生重复 environment name。
|
||
|
||
### 只做 Secret custody,不更新任务
|
||
|
||
拒绝。数据存在不等于任务行为已迁移;active Env 未绑定时 completion 必须失败。
|
||
|
||
### 在 Automation apply 中顺便导入 Env
|
||
|
||
拒绝。Automation decision 没有 Secret custody authority,也没有逐 Env 决策;把两个领域塞入同一签名会扩大权限并破坏独立 rollback/evidence。
|
||
|
||
### 自动猜测 `Configs` 表结构
|
||
|
||
拒绝。历史版本和第三方 fork 可能有同名异义表;字段猜测会把未知数据变成错误配置或 Secret。
|
||
|
||
## 当前验证与后续门禁
|
||
|
||
D-397 当前四切片已经实现并测试:absent、unsupported、Edge over-budget、2.x 顺序、同名连接、disabled preservation、保留前缀、异常状态、effective overflow、candidate digest、content-free diagnostics、私有有界 row plan、目标 Secret 冲突、Automation adoption projection、no-effect/manual outcome、durable no-replace publication、terminal seal、head CAS、逐候选独立 signed decision、同一强认证 reviewer、decision/authorization byte bound、`skip → manual_required`、prepare/commit response-loss exact replay、只读 terminal verify 与 plan/receipt/authorization/head drift。调用方 visitor 的预算异常保持原始类型,不再被误报为 SQLite 读取失败。Local Admin 完整测试为 96/96;Local Owner 完整测试为 295/288/7/0;后端完整门为 1564/1562/2/0,18-package clean build/test 为 2942/2920/22/0。package boundary、Cluster dependency 62/62、Edge import 与十四档 Local artifact audit 全部 compatible;基础 Edge/Standalone 仍为 2,611,978 / 2,612,056 bytes、319 files、58 loaded modules,Owner-only authority 没有进入低资源常驻制品。workspace 保持 18 packages、`singleSourcePackages=[]`、`shallowSourcePackages=[]`。Local Admin 保持 48/47,Local Owner 随六个职责明确的 decision 嵌套文件增至 184/183;根目录仍只有一个 50 行 binary entry,没有新增平铺源文件。依赖隔离门只允许 exact Secret/Config row planner 导入 inspection subpath,并只允许 decision authorization/coordinator 导入各自最小密钥、principal 与强认证只读 authority;邻接文件继续被拒绝。
|
||
|
||
转为 Accepted 前仍必须完成:原子 Secret/Task/Trigger/dispatch publisher、prepared/apply/rollback response-loss、completion schema 演进、18-package/boundary/artifact gates、真实 Edge 空间预算、PostgreSQL HA 与 Cluster Secret provider live gate。
|