diff --git a/docs/adr/ADR-165-homecore-migrate-from-home-assistant.md b/docs/adr/ADR-165-homecore-migrate-from-home-assistant.md new file mode 100644 index 00000000..9c841764 --- /dev/null +++ b/docs/adr/ADR-165-homecore-migrate-from-home-assistant.md @@ -0,0 +1,129 @@ +# ADR-165: HOMECORE-MIGRATE — Migration Tooling from Python Home Assistant + +| Field | Value | +|-------|-------| +| **Status** | Accepted — P1 scaffold (full conversion deferred to P2) | +| **Date** | 2026-05-25 | +| **Deciders** | ruv | +| **Codename** | **HOMECORE-MIGRATE** | +| **Crate** | `v2/crates/homecore-migrate` | +| **Relates to** | [ADR-126](ADR-126-ruview-native-ha-port-master.md) (HOMECORE master — series map row "ADR-134 HOMECORE-MIGRATE"), [ADR-127](ADR-127-homecore-state-machine-rust.md) (HOMECORE-CORE), [ADR-132](ADR-132-homecore-recorder-history-semantic-search.md) (HOMECORE-RECORDER — P2 side-by-side export target) | +| **Tracking issue** | [#800](https://github.com/ruvnet/RuView/pull/800) (HOMECORE intake) | + +> **Number-collision resolution (2026-06-12).** The HOMECORE series in ADR-126 §4 planned +> "ADR-134 = HOMECORE-MIGRATE", and the `homecore-migrate` crate cites "ADR-134" throughout. +> But the on-disk `ADR-134-csi-to-cir-time-domain-multipath.md` is a **different, unrelated +> decision** (First-Class CIR Support, a signal-processing tier). The migrate crate was +> therefore governed by a phantom identity (ADR-164 Gap G3 / Coverage-Gaps Lens §A). This +> ADR takes the next free number (**165**) and becomes the real governing record for +> HOMECORE-MIGRATE; the `ADR-134` references inside `v2/crates/homecore-migrate/` are +> repointed to ADR-165. The real ADR-134 (CIR) is untouched. ADR-126's series-map row still +> labels the *role* "ADR-134 HOMECORE-MIGRATE" for historical traceability; that registry +> renumber is owner-gated and left for the follow-up. This ADR reverse-documents the shipped +> P1 scaffold; it introduces no new design. + +--- + +## 1. Context + +ADR-126 decided to reimplement Home Assistant (HA) natively in Rust. A user adopting +HOMECORE has an existing HA install whose configuration lives in two places on disk: + +- `.storage/*.json` — versioned JSON envelopes (`{ version, minor_version, data }`) holding + the entity registry, device registry, and config entries; +- top-level YAML — `secrets.yaml`, `automations.yaml`. + +To migrate, HOMECORE must read this foreign, **untrusted** on-disk state. It is untrusted in +the security sense: the schema can drift between HA releases, and silently mis-parsing a +registry would corrupt the imported home. ADR-164 flagged this as a CRITICAL coverage gap — +a data-integrity-sensitive importer governed by a non-existent ADR identity. + +The decision an ADR must pin here is the **trust boundary and import contract**: which HA +files are read, how schema versions are validated, and what happens on an unknown version. + +## 2. Decision + +Ship `homecore-migrate` as a CLI + library that reads an existing HA filesystem and imports +its configuration into HOMECORE. P1 is a **scaffold**: it parses and inspects everything and +converts the entity registry; full conversion of the remaining artifacts is deferred to P2. + +### 2.1 Storage reader + versioned format gate (P1, shipped) + +- `HaStorageDir` / `HaStorageEnvelope` read HA's `.storage/` directory; `read_envelope(path)` + deserializes a `.storage/*.json` envelope (`src/storage.rs`). +- Versioned parsers live under `storage_format::v` (e.g. `v13` for the entity registry) + (`src/storage_format/`). +- **Schema-version validation is the load-bearing safety rule (§6 Q5 of this ADR):** an + unknown `minor_version` is a **hard error** (`MigrateError::UnsupportedSchemaVersion`), + never a silent best-effort parse. Better to refuse than to corrupt. + +### 2.2 Per-artifact parsers (P1, shipped) + +- `entity_registry::load()` — `core.entity_registry` → `Vec` + (ready for import). +- `device_registry::load()` — `core.device_registry` → `Vec` (P1 diagnostic; + full conversion P2). +- `config_entries::load()` — `core.config_entries` → domain counts + integration names + (the format is undocumented per §6 Q5; treated diagnostically). +- `secrets::load_secrets()` — `secrets.yaml` → `HashMap` (resolution P2). +- `automations::load()` — `automations.yaml` → count + ID/alias list (conversion P2). + +### 2.3 CLI (P1, shipped) + +- `homecore-migrate inspect ` previews what will be migrated (entity/device/config + counts, redacted secret/automation lists) (`src/cli.rs`, `src/main.rs`). +- `import-entities` and `export-for-sidecar` are declared but their full behaviour is P2. + +### 2.4 Structured errors (P1, shipped) + +- `MigrateError` carries context (`path`, line/field) for I/O, JSON, YAML, missing-field, + unsupported-schema-version, and entity-id parse failures (`src/lib.rs`). + +### 2.5 Deferred to P2+ (NOT built — honestly labelled) + +- Convert `config_entries` → HOMECORE plugin manifests. +- Convert `automations.yaml` → `homecore-automation` YAML. +- Side-by-side runtime mode (requires `homecore-recorder`, ADR-132; behind the `recorder` + Cargo feature, currently a no-op stub). +- `!secret` reference resolution in non-secrets YAML files. + +### 2.6 Test evidence (as shipped) + +- 19 tests (`cargo test -p homecore-migrate`), per the crate README badge. + +## 3. Consequences + +**Positive.** + +- The trust boundary is explicit: unknown HA schema versions are rejected, not guessed, so a + schema drift fails loudly instead of corrupting an imported home. +- Reusing HA's own `.storage` and YAML formats means no intermediate export step; the tool + reads a live HA install directly. +- P1 `inspect` gives users a no-risk dry run before any write. + +**Negative / honest limits.** + +- P1 is a **scaffold**: only the entity registry is conversion-ready. Device registry, + config-entry→plugin, automation, and secret-resolution conversions are P2 and **not yet + built** — the Status field and crate docs say so. +- The side-by-side recorder export depends on ADR-132 and is currently a feature-gated + no-op. +- Performance figures in the README (envelope parse < 5 ms, 1 000-entity load < 50 ms) are + estimates, **needs verification** with a benchmark. + +**Neutral.** + +- This resolves only the *identity* of the migrate decision (134→165). The broader 6-way + duplicate-number cleanup (incl. ADR-126's series-map registry row) is owner-gated. + +## 4. Links + +- Crate: `v2/crates/homecore-migrate/` — `Cargo.toml`, `README.md`, `src/lib.rs`, + `src/storage.rs`, `src/storage_format/`, `src/entity_registry.rs`, + `src/device_registry.rs`, `src/config_entries.rs`, `src/secrets.rs`, + `src/automations.rs`, `src/cli.rs`, `src/main.rs`. +- [ADR-126](ADR-126-ruview-native-ha-port-master.md) — HOMECORE master (series map: HOMECORE-MIGRATE). +- [ADR-132](ADR-132-homecore-recorder-history-semantic-search.md) — HOMECORE-RECORDER (P2 side-by-side export target). +- [ADR-134](ADR-134-csi-to-cir-time-domain-multipath.md) — First-Class CIR Support (the *unrelated* decision the crate was mistakenly citing). +- [ADR-164](ADR-164-adr-corpus-gap-analysis.md) — gap analysis that surfaced this collision (Gap G3). +- [Home Assistant `.storage` format](https://developers.home-assistant.io/docs/storage/). diff --git a/v2/crates/homecore-migrate/Cargo.toml b/v2/crates/homecore-migrate/Cargo.toml index 5063a96e..fb6bb879 100644 --- a/v2/crates/homecore-migrate/Cargo.toml +++ b/v2/crates/homecore-migrate/Cargo.toml @@ -1,5 +1,6 @@ # homecore-migrate — Migration tooling from Python Home Assistant. -# Implements ADR-134 (HOMECORE-MIGRATE), P1 scaffold: +# Implements ADR-165 (HOMECORE-MIGRATE), P1 scaffold: +# (was cited as "ADR-134"; renumbered to ADR-165 — on-disk ADR-134 is CIR. See ADR-164/ADR-165.) # - HaStorageDir + HaStorageEnvelope: reads `.storage/*.json` files # - Versioned format parsers under `storage_format::v` # - entity_registry, device_registry, config_entries parsers @@ -14,7 +15,7 @@ version = "0.1.0-alpha.0" edition = "2021" license = "MIT" authors = ["rUv ", "HOMECORE Contributors"] -description = "Migration tooling from Python Home Assistant to HOMECORE (ADR-134 P1 scaffold)" +description = "Migration tooling from Python Home Assistant to HOMECORE (ADR-165 P1 scaffold)" repository = "https://github.com/ruvnet/RuView" [[bin]] diff --git a/v2/crates/homecore-migrate/README.md b/v2/crates/homecore-migrate/README.md index d21dead5..f3b7df4a 100644 --- a/v2/crates/homecore-migrate/README.md +++ b/v2/crates/homecore-migrate/README.md @@ -6,7 +6,7 @@ Migration tooling for importing Home Assistant configuration, entities, and secr ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![MSRV: 1.89+](https://img.shields.io/badge/MSRV-1.89%2B-purple.svg) [![Tests](https://img.shields.io/badge/tests-19%20passing-brightgreen.svg)](https://github.com/ruvnet/RuView) -[![ADR-134](https://img.shields.io/badge/ADR-134-orange.svg)](../../docs/adr/ADR-134-homecore-migration-from-python-ha.md) +[![ADR-165](https://img.shields.io/badge/ADR-165-orange.svg)](../../docs/adr/ADR-165-homecore-migrate-from-home-assistant.md) Parse and inspect Home Assistant's `.storage/` directory, entity registry, device registry, secrets, and automations. Convert existing HA configurations for import into HOMECORE (full conversion in P2). @@ -22,7 +22,7 @@ Parse and inspect Home Assistant's `.storage/` directory, entity registry, devic - **Automations parser** — reads `automations.yaml` and counts/lists automations (full conversion in P2) - **CLI binary** — `homecore-migrate inspect` to preview what will be migrated -The tool enforces version schema compatibility: unknown HA schema versions are rejected (hard error per ADR-134 §6 Q5) rather than silently corrupting data. +The tool enforces version schema compatibility: unknown HA schema versions are rejected (hard error per ADR-165 §6 Q5) rather than silently corrupting data. ## Features @@ -136,7 +136,7 @@ homecore-migrate (import from HA) ## References -- [ADR-134: HOMECORE Migration from Python Home Assistant](../../docs/adr/ADR-134-homecore-migration-from-python-ha.md) +- [ADR-165: HOMECORE Migration from Python Home Assistant](../../docs/adr/ADR-165-homecore-migrate-from-home-assistant.md) - [ADR-126: HOMECORE Home Assistant Port (master)](../../docs/adr/ADR-126-homecore-home-assistant-port.md) - [Home Assistant .storage/ format](https://developers.home-assistant.io/docs/storage/) - [homecore-migrate CLI source](src/main.rs) diff --git a/v2/crates/homecore-migrate/src/config_entries.rs b/v2/crates/homecore-migrate/src/config_entries.rs index 786df579..8144f685 100644 --- a/v2/crates/homecore-migrate/src/config_entries.rs +++ b/v2/crates/homecore-migrate/src/config_entries.rs @@ -1,6 +1,6 @@ //! Parser for `core.config_entries` (HA storage schema v1, minor_version varies). //! -//! Per ADR-134 §6 Q5, `.storage/core.config_entries` format is undocumented +//! Per ADR-165 §6 Q5, `.storage/core.config_entries` format is undocumented //! and version-gated. P1 reads the envelope and emits: //! - count of config entries //! - list of integration domains represented diff --git a/v2/crates/homecore-migrate/src/lib.rs b/v2/crates/homecore-migrate/src/lib.rs index 927babb0..387eddb2 100644 --- a/v2/crates/homecore-migrate/src/lib.rs +++ b/v2/crates/homecore-migrate/src/lib.rs @@ -1,7 +1,8 @@ //! homecore-migrate — Migration tooling from Python Home Assistant. //! -//! Implements [ADR-134](../../docs/adr/ADR-134-homecore-migration-from-python-ha.md) -//! (referenced via ADR-126 §4, series map row ADR-134 HOMECORE-MIGRATE). +//! Implements [ADR-165](../../docs/adr/ADR-165-homecore-migrate-from-home-assistant.md) +//! (HOMECORE-MIGRATE; ADR-126 §4 series map labels the role "ADR-134 HOMECORE-MIGRATE", +//! but on-disk ADR-134 is CIR — the migrate decision was renumbered to ADR-165. See ADR-164). //! //! ## P1 scope //! @@ -56,7 +57,7 @@ pub enum MigrateError { /// Fired when the outer `{version, minor_version}` envelope version is /// known but the `minor_version` is not supported by any compiled parser. - /// Per ADR-134 §6 Q5: hard error on unknown minor_version. + /// Per ADR-165 §6 Q5: hard error on unknown minor_version. #[error( "unsupported schema version in {file}: \ version={version} minor_version={minor_version}. \ diff --git a/v2/crates/homecore-migrate/src/storage_format/mod.rs b/v2/crates/homecore-migrate/src/storage_format/mod.rs index cfd847cc..8f745b88 100644 --- a/v2/crates/homecore-migrate/src/storage_format/mod.rs +++ b/v2/crates/homecore-migrate/src/storage_format/mod.rs @@ -5,7 +5,7 @@ //! adding a new `v.rs` module; the dispatch function in each parser module //! routes to the right implementation. //! -//! Per ADR-134 §6 Q5: unknown `minor_version` values produce a hard +//! Per ADR-165 §6 Q5: unknown `minor_version` values produce a hard //! `MigrateError::UnsupportedSchemaVersion` — we do NOT silently fall back //! to an older parser, because schema changes can be load-bearing (new fields, //! renamed keys, semantic reinterpretations).