RuView homecore-migrate 迁移审查实战:把 Home Assistant 当作不可信输入,用不可覆盖写入守住数据边界
【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView
本文围绕 RuView 仓库中homecore元测试工具(metaharness)打包的迁移审查技能文件 SKILL.md 展开。该技能把"审查 Home Assistant 迁移"定义为一条六步规则:先检查后写入、拒绝不受支持的存储 schema 版本、保留前向兼容的未知配置字段、使用显式目标路径与原子不可覆盖(no-clobber)写入、绝不在错误/日志/转录中泄露密钥值、如实标注自动化与集成转换的未完成行为。掌握这六条规则及其在v2/crates/homecore-migrateRust crate 中的落地方式后,你可以对任何"读取外部文件系统 → 转换 → 落盘"的迁移工具做安全审查,也能直接运行仓库现成的 inspect/import 命令完成一次零风险的 HA 配置迁移演练。
技能文档:一条 6 项的迁移审查清单
技能文件 SKILL.md 本体很短,但它是一份面向 Agent 的审查契约,frontmatter 中的name: review-homecore-migration声明了其用途——"Review Home Assistant migration as untrusted versioned input and no-clobber output"(把 HA 迁移当作不可信的版本化输入、以不可覆盖方式输出审查)。正文六条规则:
- Inspect before writing(先检查,后写入);
- Reject unsupported storage schema versions(拒绝不受支持的存储 schema 版本);
- Preserve compatible unknown config-entry fields(保留兼容的未知 config-entry 字段);
- Use explicit destinations and atomic no-clobber writes(显式目标路径 + 原子不可覆盖写入);
- Never expose secret values in errors, logs, issues, or transcripts(绝不在错误、日志、issue 或转录中暴露密钥值);
- Label incomplete automation, secret-reference, and integration behavior(如实标注自动化转换、
!secret引用解析、集成执行等未完成行为)。
同目录下还有一份人类可读的姊妹文件 skills/migrate.md,内容更详细:要求在任何写入之前先走迁移 CLI 的 inspect 路径、把.storage与 YAML 视为不可信版本化输入、对不受支持的 schema 版本强制硬失败、保留未知前向兼容字段、使用显式目标与原子 no-clobber 写入、错误/日志中不得包含密钥值,并且要求"自动化转换、secret 引用解析与集成执行必须按其当前实现状态描述"。
需要澄清一个边界:homecore元测试工具本身不启动服务器、不迁移数据(见 harness/homecore/README.md:"It does not start a home server, change configuration, migrate data, or publish code by itself")。真正执行迁移的是 Rust crate v2/crates/homecore-migrate,其行为契约由 ADR-165 固化,而这份 SKILL 则是对该契约的"审查视角"浓缩——审查时逐条对照实现即可。
规则 1:先 inspect 后写入——inspect 是只读路径
技能第一条规则对应 CLI 的命令面。src/cli.rs 中Command枚举把命令分成两类:
- 只读检查类:
Inspect、InspectConfigEntries、InspectSecrets、InspectAutomations,只接收一个--storage(或--config-dir)参数,没有任何--to目标参数——从参数结构上就杜绝了写入能力; - 导入写入类:
ImportEntities、ImportDevices、ImportConfigEntries,每个都要求显式的--storage(HA 侧.storage/目录)与--to(HOMECORE 目标存储目录)双参数,外加一个默认关闭的--force开关。
ADR-165 把inspect的价值表述为"users a no-risk dry run before any write":它会报告 entity/device/config 计数、以及脱敏后的secret 名称与自动化清单,并标记不受支持的 schema 版本。因此审查迁移工具时的第一个问题就是:是否存在不依赖任何写参数、可独立运行的检查入口?在本仓库中答案是肯定的,且检查命令的参数结构(只有源路径)保证了它不可能产生副作用。
规则 2:schema 版本门——未知版本必须硬失败
HA 的.storage/*.json文件共享同一外层信封结构,这一点在 src/storage.rs 的模块注释中写得很清楚:
{ "version": 1, "minor_version": 3, "key": "core.entity_registry", "data": { ... } }HaStorageEnvelope结构体反序列化这个外层包装,其中minor_version使用#[serde(default)]兼容旧文件缺省字段;data字段保持为serde_json::Value,把具体解析交给版本化解析器。
真正的安全门在 src/storage_format/v13.rs:
MAJOR_VERSION = 1,SUPPORTED_MINOR_VERSIONS = &[1..=13](对应 HA 2025.1 时代的 entity/device registry);require_supported(file, version, minor_version)要求每个解析器在访问任何字段之前先调用它;- 不在支持集合内的
(version, minor_version)组合直接返回MigrateError::UnsupportedSchemaVersion,错误文本中带上文件名与两个版本号,提示"Upgrade homecore-migrate or downgrade HA to a supported release"。
ADR-165 §2.1 把这条规则称为"load-bearing safety rule":未知的minor_version是硬错误,而不是静默的 best-effort 解析——"Better to refuse than to corrupt"(拒绝比损坏好)。配套测试rejects_unknown_minor_version验证minor_version=99与version=2均被拒绝,require_supported_err_carries_file_name验证错误信息携带文件名与版本号,便于排障。审查清单中这一条的落点就是:确认存在"未知版本 → 硬失败"的门控,且该门控在字段访问之前执行。
规则 3:保留未知字段——无损转换与类型化警告
对core.config_entries的转换策略是"原文逐字保留 + 类型化警告"。src/config_entries.rs 的输出结构ConvertedConfigEntries中带有warnings: Vec<MigrationWarning>字段;转换逻辑把源码中多出来的字段(source_extra)、不支持的 domain(MigrationWarning::UnsupportedDomain)与不可迁移的字段(MigrationWarning::UnsupportedField)全部收集进 warnings,而不是丢弃。测试unknown_domain_and_fields_are_lossless_with_typed_warnings断言未知 domain 与未知字段都会出现在 warnings 中——即信息无损,但语义诚实。
crate README 对此的表述是:config entries 是"storage-compatible, not runtime-compatible"——导入一个条目不会安装或执行对应的 HA Python 集成,必须由一个 HOMECORE 插件显式认领该 domain 并消费被保留的原始 payload。这正是技能第 6 条(如实标注集成行为)在数据层的体现:保留原文 payload 是为了未来的插件消费,而不是假装集成已经可用。
规则 4:显式目标 + 原子 no-clobber 写入
技能第四条是整套规则中工程含量最高的一条,实现位于 src/storage.rs 的write_json_atomic/write_json_atomic_noclobber:
- 同目录临时文件:目标文件所在目录内创建
.目标名.PID.序号.tmp(PID + 全局原子序号避免并发冲突),create_new(true)保证临时文件本身也是"不覆盖创建"; - 写入 + 落盘:
write_all后执行sync_all,确保字节持久化后再发布; - 硬链接发布而非 rename:关键差异在于
fs::hard_link(&temp, target)—— 如果目标已存在,hard_link以AlreadyExists失败,而 POSIX 的 rename 会替换目标。代码注释解释了选型动机:"hard_linkfails withAlreadyExistsif another process won the destination race, unlike a POSIX rename which would replace the destination after a check-then-rename sequence",即避免了 check-then-rename 的竞态窗口; - 错误归一化:捕获
AlreadyExists后统一改写为"destination exists; refusing to overwrite",失败路径会清理残留临时文件。
逃生通道是 CLI 层的--force(见 src/cli.rs):force=true时改为先删除目标再走同一条 hard_link 发布路径,用于"修好一条坏源行之后重跑导入"的场景。代码注释诚实标注了该路径的代价:remove 与 hard_link 之间的崩溃窗口会留下"无目标文件"而非旧文件——这是显式选择覆盖时接受的折中,默认 no-clobber 路径的原子性不受影响。
三个导入命令的目标文件映射固定如下(来自 v2/crates/homecore-migrate/README.md):
源(HA.storage/) | 目标(HOMECORE) | 格式 |
|---|---|---|
core.entity_registry | core.entity_registry | HA 兼容 v1/minor 13 信封 |
core.device_registry | core.device_registry | HA 兼容 v1/minor 13 信封 |
core.config_entries | homecore.config_entries | HOMECORE v1/minor 0 信封 |
典型用法:
homecore-migrate import-entities \ --storage ~/.homeassistant/.storage \ --to ~/.homecore/storage homecore-migrate import-devices \ --storage ~/.homeassistant/.storage \ --to ~/.homecore/storage homecore-migrate import-config-entries \ --storage ~/.homeassistant/.storage \ --to ~/.homecore/storage成功的导入输出一行机器可读 JSON,例如:
{"kind":"device_registry","imported":8,"warning_count":0,"warnings":[],"destination":"/home/user/.homecore/storage/core.device_registry"}审查要点:写入目标必须是显式参数(不接受隐式默认路径)、发布动作必须是原子的、且默认拒绝覆盖。本 crate 的 ADR-165 §2.4 安全评审还确认了"destination writes are explicit--topaths and no-clobber; paths are user-supplied dirs joined with fixed filenames(用户目录 + 固定文件名,无../绝对路径穿越)"。
规则 5:密钥永不入日志——脱敏错误变体
这是整个 crate 中最"反直觉"的一处防御,值得完整展开。HA 的secrets.yaml是一个扁平 key→value 映射,src/secrets.rs 的read_secrets把它解析成HashMap<String, String>(值一律按字符串处理以避免类型不匹配)。
风险在于解析失败本身:serde_yaml对类型标签强转错误(例如port: !!int <值>)会生成形如invalid value: string "<the-secret-value>"的消息——把出错的标量原样内嵌进错误文本。如果照常规把它包进通用的MigrateError::YamlParse { source }变体,这条消息就会经由InspectSecretsCLI 路径打印到 stderr,直接泄密。
因此 src/lib.rs 中专门设计了MigrateError::SecretsParse { path, line, column }变体:它故意不内嵌底层serde_yaml::Error,只保留文件路径与serde_yaml::Error::location()给出的粗略行列号,渲染文本为secrets.yaml parse error in {path} (line {line}, column {column}): malformed YAML (value content redacted)。read_secrets中的注释明确写着 "SECURITY: do NOT useMigrateError::YamlParsehere"。
这一规则由回归测试钉死(secrets::tests::malformed_secrets_error_never_contains_secret_value):构造一个含api_port: !!int s3cr3t_TOKEN_VALUE的畸形文件,断言渲染后的错误字符串以及完整#[source]链(因为 anyhow/CLI 会打印 source 链)都不包含密钥值,且错误确实是SecretsParse变体(fail-closed)。另有测试确认错误文本包含line与redacted字样——脱敏的同时仍保留定位能力。审查迁移工具时,凡是会读取密钥/凭据文件的路径,都应追问:解析错误的 source 链上是否可能携带原始值?
规则 6:诚实标注未完成能力
技能最后一条要求"Label incomplete automation, secret-reference, and integration behavior"——迁移工具的能力边界必须被显式标注,而不是让使用者自己发现。仓库中这一条有三层证据:
- CLI 层:
InspectAutomations的帮助文本直接写着 "Count and list automations from automations.yaml (conversion is P2)"; - crate 层:v2/crates/homecore-migrate/README.md 的 "Remaining limitations" 一节逐条列出:自动化转换未实现(只 inspect
automations.yaml)、其他 YAML 文件中的!secret引用解析未实现、已删除实体/设备的 tombstone 不导入、minor version 13 之后的设备字段需要显式更新解析器且未知版本 fail-closed、不提供并行的 HA recorder 数据库导出器; - ADR 层:ADR-165 §2.5 "Deferred to P2+ (NOT built — honestly labelled)" 声明导入的 config entries 不会安装/执行 Python HA 集成,
automations.yaml→homecore-automation转换与!secret解析属于 P2,并提醒 README 中的性能数字(envelope 解析 < 5 ms、1000 实体加载 < 50 ms)是估计值、待基准验证。
这一条对应homecore元测试工具的 "Capability honesty" 原则:目录中区分 implemented / feature-gated / provider-required / integration-dependent 四种状态,审查时同样应要求迁移工具对每条未实现路径给出明确的状态标注。
验证方式
对 crate 本身的验证命令定义在 v2/crates/homecore-migrate/README.md 的 Validation 一节:
cargo test -p homecore-migrate cargo clippy -p homecore-migrate --all-targets -- -D warningsADR-165 §2.6 给出的测试覆盖面是:registry 往返、未知版本拒绝、config 字段/domain 无损保留、畸形输入、以及"crash-safe/no-overwrite destination behaviour"。在仓库中运行上述命令即可复现这些验证;inspect系列命令则可以在任何真实 HA 配置目录上做一次零副作用演练。
小结:六条规则如何构成一份可执行的审查清单
回到 SKILL.md 的六条规则,每一条都能在v2/crates/homecore-migrate中找到可验证的落点:先 inspect 后写对应只读参数结构(src/cli.rs);schema 硬失败对应require_supported门控(src/storage_format/v13.rs);未知字段保留对应MigrationWarning无损收集(src/config_entries.rs);原子 no-clobber 写入对应 temp+sync+hard_link 发布链(src/storage.rs);密钥脱敏对应SecretsParse专用变体及其 source 链回归测试(src/lib.rs、src/secrets.rs);诚实标注对应 README limitations 与 ADR 的 P2 清单。这套"契约(ADR)→ 实现(crate)→ 审查视角(SKILL)"三层对齐的结构,是 RuView 对数据完整性敏感型导入工具给出的完整参考实现。
【免费下载链接】RuViewπ RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.项目地址: https://gitcode.com/GitHub_Trending/wi/RuView
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考