RuView homecore-migrate:把 Home Assistant 迁移评审的六条纪律落到 Rust 源码里
【免费下载链接】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 迁移评审技能 展开:它规定了“评审一次 Home Assistant 迁移”时必须遵守的六条纪律——先 inspect 后写入、把.storage与 YAML 视为不可信的带版本输入、未知 schema 版本硬性失败、保留未知的前向兼容字段、显式目标路径加原子 no-clobber 写入、任何输出中绝不出现密钥明文。读完本文,你可以理解 RuView 的homecore-migrate工具(v2/crates/homecore-migrate/)是如何逐条落实这些纪律的,并掌握一条从inspect到import-*的安全迁移操作路径,以及每条纪律背后的 Rust 实现证据。
迁移评审技能:六条纪律的原文与定位
harness/homecore/skills/migrate.md 是 homecore 元框架(metaharness)下的一个“技能”文件——供本地 Claude Code / Codex 等 Agent 宿主在探索仓库时遵循的评审清单。全文只有 6 行要点,但每一行都对应homecore-migrate工具中一段可验证的实现:
- 任何写入之前,先运行迁移 CLI 的 inspect 路径;
- 把
.storage与 YAML 当作不可信的带版本输入对待; - 对不支持的 schema 版本要求硬性失败;
- 保留未知的前向兼容 config-entry 字段;
- 使用显式目标路径和原子 no-clobber 写入;
- 错误、日志、issue 或转写文本中绝不允许出现密钥值。
文档最后还有一条元规则:自动化转换、密钥引用解析和集成执行,必须按“当前实现的真实状态”来描述。这条“能力诚实”原则与 homecore 元框架 README 中的 Capability honesty 一节呼应:目录区分 implemented、feature-gated、provider-required 与 integration-dependent 行为,包装好的引用只是导航证据,源码、测试与已接受的 ADR 才是权威。
该技能所在的homecore元框架本身是只读的:它映射能力到源码与验证命令、暴露一个有界 MCP 服务器、可委派仓库探索给本地 Agent CLI,但“不会自行启动 home server、修改配置、迁移数据或发布代码”(见 harness/homecore/README.md)。因此skills/migrate.md的正确读法是:它定义的是迁移实现应满足的评审契约,而契约的执行体是homecore-migratecrate。
实现主体:homecore-migrate crate 的结构
迁移决策的权威记录是 ADR-165: HOMECORE-MIGRATE。背景是 ADR-126 决定在 Rust 中原生重实现 Home Assistant;用户存量 HA 安装的配置落在磁盘两处:
.storage/*.json——版本化 JSON 信封({ version, minor_version, data }),保存实体注册表、设备注册表与 config entries;- 顶层 YAML——
secrets.yaml、automations.yaml。
ADR-165 指出,这份外来状态在安全意义上是“不可信”的:schema 会随 HA 版本漂移,静默误解析会污染整个导入后的家庭数据,因此必须钉死信任边界与导入契约。实现落在 v2/crates/homecore-migrate,按 crate 文档 的模块划分:
| 模块 | 职责 |
|---|---|
storage | HaStorageDir/HaStorageEnvelope、read_envelope(path)、原子写入 |
storage_format | 版本化格式解析器(当前为v13);未知minor_version→ 硬错误 |
entity_registry | core.entity_registry→Vec<homecore::EntityEntry> |
device_registry | 支持的 HA v13 设备字段 →homecore::DeviceEntry |
config_entries | 无损、版本化的 HOMECORE 表示 + 类型化警告 |
secrets | secrets.yaml→HashMap<String, String>(带脱敏错误) |
automations | automations.yaml计数 + ID 列表(转换为 P2) |
cli/main | clap 子命令与 JSON 摘要输出 |
验证方式是 crate 自带的:cargo test -p homecore-migrate与cargo clippy -p homecore-migrate --all-targets -- -D warnings(见 v2/crates/homecore-migrate/README.md 的 Validation 一节)。
纪律一:先 inspect,后写入
技能第一条要求“任何写入之前,先运行 inspect 路径”。CLI 侧这对应一组完全只读的子命令。src/cli.rs 中Command枚举定义了七个子命令:inspect、import-entities、import-devices、inspect-config-entries、import-config-entries、inspect-secrets、inspect-automations;inspect 系列只接受一个--storage(或--config-dir)参数,根本没有目标写入参数——写入只能来自显式的import-*命令。
src/main.rs 中Inspect分支的行为是“探测 + 报告”:逐个检查core.entity_registry、core.device_registry、core.config_entries是否存在,存在则读取并打印计数(实体数、设备数、config-entry 数与 domain 列表);任何一项解析失败也只是打印ERROR — {e}而不是中断整个预览。inspect-secrets打印密钥名称但值一律渲染为<redacted>;inspect-automations打印自动化数量与id/alias列表。
实操流程因此是两段式的:
# 1) 无风险预览:只读,不产生任何目标文件 homecore-migrate inspect --storage ~/.homeassistant/.storage homecore-migrate inspect-secrets --config-dir ~/.homeassistant homecore-migrate inspect-automations --config-dir ~/.homeassistant # 2) 确认无误后再写入(显式目标目录) homecore-migrate import-entities \ --storage ~/.homeassistant/.storage \ --to ~/.homecore/storageADR-165 把inspect明确列为正面后果之一:“inspect给用户一个写入前的无风险 dry run。”
纪律二:.storage与 YAML 是不可信的带版本输入
“不可信”在这里不是指恶意输入,而是指格式可能随 HA 版本漂移。信封层结构在 src/storage.rs 中定义:
pub struct HaStorageEnvelope { pub version: u32, /// Introduced in HA 2022.x for backwards-compatible schema additions. #[serde(default)] pub minor_version: u32, pub key: String, /// Inner payload. Parsed by versioned format-specific code. pub data: serde_json::Value, }两个值得注意的设计:
- 每个
.storage/*.json共享同一外层信封,read_envelope(path)只负责解外层包装;data保持为未类型化的serde_json::Value,交给storage_format::v<N>下的版本化解析器(当前实现为 src/storage_format/v13.rs)进一步反序列化。注释直接标注了来源:HA 的homeassistant/helpers/storage.py的Store._write_data。 minor_version带#[serde(default)]:旧版 HA 的信封没有这个字段时默认为 0,而 unit 测试envelope_missing_minor_version_defaults_to_zero锁定了这一行为(src/storage.rs)。
对“不可信”的防御还体现在错误处理上:整个 crate 使用统一的结构化错误类型MigrateError(src/lib.rs),覆盖 I/O、JSON 解析、YAML 解析、密钥解析、不支持的 schema 版本、意外存储键、缺失字段与 entity_id 解析失败。ADR-165 记录的安全审查结论是:畸形/带类型标签/截断的.storageJSON 与 YAML只能报错、绝不 panic(生产代码中所有unwrap/expect均为测试专用)。
纪律三:未知 schema 版本必须硬失败
这是 ADR-165 标注的“承重安全规则”(the load-bearing safety rule):未知minor_version是硬错误,而不是静默的 best-effort 解析。错误变体在 src/lib.rs 中:
#[error( "unsupported schema version in {file}: \ version={version} minor_version={minor_version}. \ Upgrade homecore-migrate or downgrade HA to a supported release." )] UnsupportedSchemaVersion { file: String, version: u32, minor_version: u32 }错误信息本身也给出了两条明确的出路:升级homecore-migrate以支持新版本,或降级 HA 到受支持版本——即“宁可拒绝,不可损坏”(fail-closed)。v2/crates/homecore-migrate/README.md 的 Remaining limitations 一节同样强调:比 HA registry minor version 13 更新的新字段需要显式更新解析器,未知版本一律 fail closed。这与 ADR-165 的正面后果一致:schema 漂移会大声失败,而不是悄悄污染导入后的家庭数据。
纪律四:无损保留未知的前向兼容字段
对core.config_entries,技能要求“保留未知的前向兼容 config-entry 字段”。实现上的契约是(见 v2/crates/homecore-migrate/README.md 与 src/config_entries.rs):
convert_config_entries()输出一个版本化的homecore.config_entries存储信封(HOMECORE v1/minor 0);- 原始行逐字保留(each original row is retained verbatim),因此 HA 侧未来新增的字段不会在导入时丢失;
- 不支持的 domain 与不可移植的字段不丢弃,而是产生类型化警告(typed warnings,
code字段做 snake_case 标签),随结果一并上报。
目标端文件映射关系:
源(HA.storage) | 目标(HOMECORE storage) | 格式 |
|---|---|---|
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 信封 |
设备注册表的转换覆盖支持的 v13 字段:identifiers、connections、versions、serial number、labels、topology 与 config-entry 链接。
需要强调的是边界:README 明确写着 config entries 是storage-compatible, not runtime-compatible——导入条目只是把数据持久化到 HOMECORE,并不会安装或执行原来的 HA Python 集成;必须由一个 HOMECORE 插件显式认领该 domain 并消费保留下来的源载荷。这正是技能最后一条元规则(按当前实现状态描述自动化转换、密钥引用解析与集成执行)的落点:这三件事在当前版本中均未实现,工具只做 inspect。
纪律五:显式目标与原子 no-clobber 写入
技能第五条对应两个实现面:显式目标(所有 import 命令必须带--to参数)与原子写入(永不隐式覆盖已存在文件)。
写入核心是 src/storage.rs 的write_json_atomic_noclobber/write_json_atomic。流程是:
- 序列化为 pretty JSON,在目标同目录创建
.target.<pid>.<seq>.tmp临时文件(create_new(true)保证不撞名); - 写入字节后
sync_all()落盘; - 通过
fs::hard_link(temp, target)原子发布:硬链接创建若目标已存在会返回AlreadyExists,这与 POSIXrename的“先检查后重命名仍可能覆盖”语义不同——no-clobber 是文件系统原子保证的,不是应用层检查; - 成功后删除临时文件;失败则清理临时文件,并把
AlreadyExists统一渲染为destination exists; refusing to overwrite。
这里有一个微妙的竞态论证:源码注释(src/storage.rs)指出,如果两个进程同时导入同一目标,硬链接的AlreadyExists能让失败方明确输掉竞态,而 rename 语义下后到者会静默替换先到者的结果。
覆盖是显式选择的逃生舱:import-entities/import-devices/import-config-entries都支持--force(见 src/cli.rs 的参数注释:用于修复坏行后重跑或 HA 侧变化后重新导入)。--force路径改为“先移除旧文件、再走同一个 hard_link 发布步骤”,源码注释(src/storage.rs)诚实记录了这一权衡:它短暂放大了崩溃窗口(remove 与 hard_link 之间崩溃会丢失旧文件而非保留旧文件),但这是显式请求覆盖的可选代价,默认的 no-clobber 原子性不受影响;同时选择 remove+hard_link 而非 rename-over-existing,是为了规避 Windows 上ERROR_ACCESS_DENIED的共享冲突抖动。
每次成功导入输出单行机器可读的 JSON 摘要(src/main.rs 定义ImportSummary):
{"kind":"device_registry","imported":8,"warning_count":0,"warnings":[],"destination":"/home/user/.homecore/storage/core.device_registry"}warnings数组在import-config-entries中会携带纪律四所述的类型化警告,脚本可以直接解析判断导入质量。
纪律六:密钥值绝不进入任何输出
这是六条纪律中唯一涉及安全泄露面的一条,也是 ADR-165 在 2026-06 安全审查中专门补强的一项。问题出在serde_yaml的错误消息:对类型标签强转错误(例如port: !!int <value>),它会逐字引用出错标量(invalid value: string "<the-secret-value>"),而这条消息会经InspectSecretsCLI 路径传播到 stderr——绕过了 CLI 有意为之的<redacted>设计,把密钥值泄漏进日志。
修复在 src/secrets.rs:read_secrets不再让secrets.yaml解析失败落入通用的MigrateError::YamlParse { source }变体,而是映射到专门的脱敏变体(src/lib.rs):
#[error( "secrets.yaml parse error in {path} (line {line}, column {column}): \ malformed YAML (value content redacted)" )] SecretsParse { path: String, line: usize, column: usize }该变体刻意不嵌入底层serde_yaml::Error,只携带文件路径与一个粗糙的行列位置(来自serde_yaml::Error::location()),让用户能定位问题而不打印值。这条规则由测试malformed_secrets_error_never_contains_secret_value锁定(src/secrets.rs 起):断言渲染后的错误以及其完整的#[source]链都不包含密钥值——即连 Rust 错误链上任何上游 message 也被检查,而不只是最外层字符串。
技能第 6 条的覆盖范围比错误消息更广:错误(errors)、日志(logs)、issue、转写文本(transcripts)。与之配套的其他“零密钥输出”设计包括:inspect-secrets的<redacted>渲染(src/main.rs 中每个键打印为{key} = <redacted>),以及inspect对 secret/automation 列表的脱敏输出(ADR-165 §2.3 称之为 “redacted secret/automation lists”)。
诚实的边界:当前实现状态
按技能文档的元规则,以下状态必须如实描述(依据 v2/crates/homecore-migrate/README.md 的 Remaining limitations 与 ADR-165 §2.5):
- 自动化转换未实现:只 inspect
automations.yaml(计数 + ID/alias 列表),不生成homecore-automationYAML; !secret引用解析未实现:除secrets.yaml本身外,其他 YAML 文件中的密钥引用不会解析;- 墓碑(tombstone)不导入:已删除的实体/设备不留痕;
- 无并行 recorder 导出:side-by-side 运行时模式依赖
homecore-recorder(ADR-132),当前是 feature-gated 的 no-op 桩; - 性能数字是估计值:ADR-165 明确标注 README 中的性能图(信封解析 < 5 ms、1000 实体加载 < 50 ms)为估计值,尚待基准验证,不应作为事实引用。
另外,ADR-165 还澄清了一个仓库历史细节:homecore-migratecrate 早期引用的是“幻影身份” ADR-134(与磁盘上真实的 ADR-134《First-Class CIR Support》撞号),2026-06-12 的编号冲突决议后重指到 ADR-165;crate 内残留的 “ADR-134” 字样应读作 ADR-165。
验证与延伸阅读
- 运行迁移相关测试与 lint:
cargo test -p homecore-migrate、cargo clippy -p homecore-migrate --all-targets -- -D warnings(v2/crates/homecore-migrate/README.md)。 - 迁移契约的权威 ADR:docs/adr/ADR-165-homecore-migrate-from-home-assistant.md,其中 §2.4 记录了安全审查的逐项结论(源永不被修改、目标写入均为显式
--to且 no-clobber、路径为用户提供的目录拼接固定文件名、未知 schema 版本 fail-closed、无 SQL/shell 注入面)。 - 评审纪律的上游载体:harness/homecore/skills/migrate.md 与 harness/homecore/README.md(homecore 元框架的只读边界与能力诚实原则)。
- 决策链:ADR-126(HOMECORE 主决策)→ ADR-132(recorder,P2 导出目标)→ ADR-165(本文的迁移契约)→ ADR-285(homecore 元框架,见 docs/adr/ADR-285-homecore-wasm-first-metaharness.md)。
【免费下载链接】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),仅供参考