Compare commits

..

6 Commits

Author SHA1 Message Date
rUv 153bc0595b Merge pull request #1043 from ruvnet/docs/adr-gap-remediation-1
docs(adr): Gap Register remediation — write phantom ADR-132/165, fix ADR-134 collision, correct statuses
2026-06-12 23:11:10 -04:00
ruv 8fd4ee917d docs(adr): mark ADR-164 Gap Register items resolved (G3, G5) + correct G2
Records the remediation done in this branch:
- G3 (homecore-recorder/migrate phantom ADRs) → RESOLVED: ADR-132 + ADR-165 written.
- G5 (10 streaming-engine Proposed-while-built) → RESOLVED: 136-145 flipped to
  "Accepted — partial", with the honest caveat that the notes describe building
  blocks built+tested, not live-path integration.
- G2 (missing Status headers) → corrected: ADR-134-CIR was mislabeled as missing
  (it has a Status row); the 2 genuine misses (147-benchmark-proof, 052-ddd) are
  both inside owner-gated duplicate-number collisions, so left untouched. Early
  ADRs using "| Status |" vs "| **Status** |" are different-format-but-present.
  Net: 0 status headers added.
- Updated Coverage-Gaps bullets for recorder/migrate.

Renumbering/dedup of the 6 collisions left owner-gated, as instructed.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-12 23:01:10 -04:00
ruv 5c5112db0e docs(adr): correct streaming-engine statuses 136-145 Proposed→Accepted — ADR-164 G5
All 10 streaming-engine ADRs (136-145) carried Status: Proposed while each has a
concrete commit-pinned "Built -- tested building block" Implementation-Status note
(136: 11f89727f; 137: 4fa3847ac; 138: fc7674bde; 139: 521a012d8; 140: 169a355bd;
141: 7d88eb84c; 142: 1f8e180d6; 143: 2d4f3dea5; 144: b10bc2e9a; 145: 0f336b7d3),
each with a test count.

Flipped each to "Accepted — partial (built + tested building block; integration
glue pending — see Implementation Status, commit <hash>)". Honest "partial", not
full Accepted: the notes themselves state the blocks are tested+compiling but
"mostly not yet on the live 20 Hz path". 143 (v2 dataset-gated) and 144 (no UWB
radio in fleet) carry their specific residual gates inline.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-12 23:00:54 -04:00
ruv e3696da8d8 docs(adr): write ADR-165 (HOMECORE-MIGRATE), repoint migrate 134→165 — ADR-164 G3
homecore-migrate cited "ADR-134 (HOMECORE-MIGRATE)", but on-disk ADR-134 is
"First-Class CIR Support" — a different decision. The migrate crate was governed
by a phantom identity (ADR-164 Gap G3).

- New ADR-165-homecore-migrate-from-home-assistant.md (next free number),
  reverse-documented from the shipped P1 scaffold: HA .storage reader, versioned
  format gate (unknown minor_version = hard error), per-artifact parsers, inspect
  CLI, structured errors. Status: Accepted — P1 scaffold (full conversion P2).
  Trust-boundary rationale for the untrusted .storage import is the centerpiece.
- Repointed every ADR-134 governing reference in v2/crates/homecore-migrate/
  (Cargo.toml, README.md, src/lib.rs, src/config_entries.rs,
  src/storage_format/mod.rs) → ADR-165. Left the ADR-132 (recorder-feature)
  refs intact. Explanatory renumber notes retained.
- On-disk ADR-134 (CIR) untouched. ADR-126 series-map registry row owner-gated.

Docs/comments only — cargo build -p homecore-migrate --no-default-features
still compiles.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-12 23:00:33 -04:00
ruv 9457d441b2 docs(adr): write missing ADR-132 (HOMECORE-RECORDER) — resolves ADR-164 G3
homecore-recorder cites "ADR-132" in Cargo.toml/README/lib.rs/schema.rs/
semantic.rs, but no ADR-132 file existed — the durable-state backbone was
ungoverned (ADR-164 Gap G3 / Coverage-Gaps Lens A).

Reverse-documented from the shipped, tested crate (not invented): SQLite
HA-compatible recorder schema v48 (P1, 14 tests), ruvector HNSW semantic
index (P2, feature-gated, 20 tests), hash-embedding honesty note, P3 real
embeddings planned. Status: Accepted (shipped). Filename matches the link
the crate README already pointed at. Documented retroactively; honest about
hash-embedding limits and unbenchmarked latency targets.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-12 23:00:15 -04:00
rUv 626b4b2e97 Merge pull request #1042 from ruvnet/docs/adr-164-gap-analysis
docs(adr): ADR-164 — ADR corpus gap analysis & remediation backlog (162 ADRs)
2026-06-12 22:47:21 -04:00
18 changed files with 286 additions and 25 deletions
@@ -0,0 +1,130 @@
# ADR-132: HOMECORE-RECORDER — State History + Semantic Search
| Field | Value |
|-------|-------|
| **Status** | Accepted |
| **Date** | 2026-05-25 |
| **Deciders** | ruv |
| **Codename** | **HOMECORE-RECORDER** |
| **Crate** | `v2/crates/homecore-recorder` |
| **Relates to** | [ADR-126](ADR-126-ruview-native-ha-port-master.md) (HOMECORE master — series map row ADR-132), [ADR-127](ADR-127-homecore-state-machine-rust.md) (HOMECORE-CORE state machine), [ADR-124](ADR-124-rvagent-mcp-ruvector-npm-integration.md) (ruvector/SENSE-BRIDGE), [ADR-130](ADR-130-homecore-rest-websocket-api.md) (HOMECORE-API query surface, downstream) |
| **Tracking issue** | [#800](https://github.com/ruvnet/RuView/pull/800) (HOMECORE intake) |
> **Documented retroactively (2026-06-12).** The `homecore-recorder` crate shipped under
> the ADR-126 series map (which planned an "ADR-132 HOMECORE-RECORDER") but the standalone
> ADR file was never written; the crate's `Cargo.toml`, `README.md`, `lib.rs`, `schema.rs`,
> and `semantic.rs` all cite "ADR-132". This ADR reverse-documents the decision that the
> shipped, tested code already embodies (ADR-164 Gap G3 / Coverage-Gaps Lens §A). It does
> **not** introduce new design; it records what is built. Date reflects the crate's intake
> era (first commit `e96ebaea8`, 2026-05-25); real-impl pass landed in `7c8071145`
> (2026-06-11).
---
## 1. Context
ADR-126 (the HOMECORE master) decided to reimplement Home Assistant (HA) natively in Rust.
HA persists every state change to a SQLite *recorder* database; downstream features
(history graphs, the logbook, long-term statistics, automation conditions that reference
past state) all read that store. HOMECORE therefore needs a durable state-history backbone.
Two forces shape the decision:
1. **Migration / coexistence.** Users adopting HOMECORE will have an existing HA
`recorder` database. Reusing HA's on-disk schema (rather than inventing a new one) lets
HOMECORE read an existing HA `home-assistant_v2.db` directly and lets HA-aware tooling
read HOMECORE's store. This is the same trust boundary that `homecore-migrate`
(ADR-165) handles for `.storage/*.json`.
2. **Semantic queries.** HA history is queried with SQL `BETWEEN`/`WHERE` clauses. The
HOMECORE platform already carries ruvector (ADR-124) for vector search, so the recorder
can additionally embed state changes and answer natural-language queries
("which kitchen devices were warm at 3 PM?") via k-NN — a capability HA does not have.
The recorder is the **durable-state surface**: if it is wrong, history, logbook, and
historical-condition automations are all wrong. ADR-164 flagged it as a CRITICAL coverage
gap precisely because such a load-bearing crate had no governing ADR.
## 2. Decision
Ship `homecore-recorder` as a SQLite state-history recorder with an HA-compatible schema
and an optional ruvector-backed semantic index, in three phases. P1 and P2 are built and
tested; P3 is planned.
### 2.1 Storage — SQLite with the HA recorder schema (P1, shipped)
- Persist via `sqlx` with the SQLite backend only (no Postgres, no TLS feature set).
- Mirror HA recorder **schema v48** so the store is bidirectionally readable
(`src/schema.rs`):
- `state_attributes` — shared attribute JSON blobs, deduped by an FNV-1a 64-bit hash
stored as a signed `i64` (matches HA's dedup key);
- `states` — one row per state write (`entity_id`, `state`, `attributes_id` FK,
`last_changed_ts`/`last_updated_ts` as REAL Unix seconds, `context_id` UUID);
- `events` — domain events (`event_type`, `event_data` JSON, `time_fired_ts`);
- `recorder_runs` — boot/shutdown bookends for history-gap detection.
- All DDL uses `CREATE TABLE IF NOT EXISTS`, so schema application is idempotent and safe
on every startup.
- Default persistence path `.homecore/home.db` (configurable).
### 2.2 Capture — listener on the HOMECORE event bus (P1, shipped)
- `RecorderListener` subscribes to the HOMECORE event bus (ADR-127) and captures
`StateChanged` events, writing snapshots through `Recorder` (`src/listener.rs`,
`src/db.rs`).
- A `DedupEngine` (`src/dedup.rs`) skips redundant writes when the state hash is unchanged,
matching HA's stateful-listener behaviour.
### 2.3 Semantic search — ruvector HNSW (P2, shipped, feature-gated)
- Behind the `ruvector` Cargo feature, the `Recorder` additionally calls a `SemanticIndex`
implementation (`src/semantic.rs`) that embeds state attributes and stores vectors in a
`ruvector-core` HNSW index for k-NN search.
- P2 embeddings are **hash-based** (sha2) — a deliberate, honest placeholder. They give a
working HNSW surface without claiming sentence-level semantic quality.
- When the feature is off, `NullSemanticIndex` satisfies the `SemanticIndex` trait bound
with no allocation, so the structural recorder ships independently of ruvector.
### 2.4 Real sentence embeddings (P3, planned — not yet built)
- Replace the hash embeddings with ruvector-attention sentence embeddings (dim → 384). Not
implemented; tracked as a follow-up. The README and `Cargo.toml` label this P3 explicitly.
### 2.5 Test evidence (as shipped)
- P1: 14 tests (`cargo test -p homecore-recorder --no-default-features`).
- P2: 20 tests (`cargo test -p homecore-recorder --features ruvector`).
## 3. Consequences
**Positive.**
- HA-schema compatibility makes migration (ADR-165) and coexistence cheap: HOMECORE can
read an existing HA `recorder.db`, and any SQLite tool can read HOMECORE's history.
- The semantic index is **additive** and feature-gated: the durable structural recorder has
no hard dependency on ruvector, so the storage backbone ships first.
- Standard SQLite means no proprietary export format; history is directly queryable.
**Negative / honest limits.**
- P2 semantic search uses **hash embeddings**, not real sentence embeddings — query quality
is limited until P3. This is disclosed in the crate docs and here; it must not be cited as
semantic-quality-validated.
- No per-crate benchmarks exist yet; the latency figures in the README
(state-write p50 < 2 ms, semantic search < 10 ms on 1 M records) are design targets /
estimates, **needs verification** with a criterion baseline.
- Pinning to HA schema v48 couples HOMECORE to a specific HA recorder schema generation;
future HA schema bumps require an explicit migration step.
**Neutral.**
- This ADR governs the recorder crate only. The query/REST surface over recorder data is
HOMECORE-API (ADR-130, P3); automation conditions on historical state are
HOMECORE-automation (ADR-129, P3).
## 4. Links
- Crate: `v2/crates/homecore-recorder/``Cargo.toml`, `README.md`, `src/lib.rs`,
`src/db.rs`, `src/schema.rs`, `src/dedup.rs`, `src/listener.rs`, `src/semantic.rs`.
- [ADR-126](ADR-126-ruview-native-ha-port-master.md) — HOMECORE master (series map: ADR-132 = HOMECORE-RECORDER).
- [ADR-165](ADR-165-homecore-migrate-from-home-assistant.md) — HOMECORE-MIGRATE (reads HA `.storage`; P2 exports a side-by-side recorder DB).
- [ADR-164](ADR-164-adr-corpus-gap-analysis.md) — gap analysis that surfaced this missing ADR (Gap G3).
- [Home Assistant Recorder integration](https://www.home-assistant.io/integrations/recorder/).
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see §8 Implementation Status, commit `11f89727f`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-core` (`types.rs`: `CsiFrame`/`CsiMetadata`); `wifi-densepose-signal/src/ruvsense/mod.rs` (`RuvSensePipeline`, six-stage flow); `v2/Cargo.toml` (workspace topology) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `4fa3847ac`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-signal` (`ruvsense/multistatic.rs``fuse`, `attention_weighted_fusion`); `wifi-densepose-ruvector` (`viewpoint/fusion.rs``MultistaticArray`); `wifi-densepose-bfld` (`event.rs`) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `fc7674bde`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-signal` (`ruvsense/multiband.rs`, `ruvsense/multistatic.rs`); `wifi-densepose-ruvector` (`viewpoint/geometry.rs`, `viewpoint/coherence.rs`, `viewpoint/attention.rs`, `viewpoint/fusion.rs`) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `521a012d8`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | New module/crate `wifi-densepose-worldgraph` alongside `v2/crates/wifi-densepose-geo` and `v2/crates/homecore`; petgraph bridge pattern from `v2/crates/ruv-neural/ruv-neural-graph/src/petgraph_bridge.rs`; integrates `homecore/src/registry.rs` `area_id` and `wifi-densepose-mat/src/domain/scan_zone.rs` |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `169a355bd`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-sensing-server/src/semantic/` (`bus.rs`, `common.rs`); `homecore/src/state.rs` + `event.rs`; `homecore-assist` |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `7d88eb84c`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-bfld` (new module `mode.rs` + `attestation.rs`; extends `lib.rs` `PrivacyClass`, `sink.rs`, `privacy_gate.rs`, `identity_risk.rs`, `emitter.rs`, `ha_discovery.rs`) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `1f8e180d6`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-signal` (`ruvsense/longitudinal.rs`, `ruvsense/attractor_drift.rs`, `ruvsense/calibration.rs`, `ruvsense/field_model.rs`, `ruvsense/tomography.rs`); `wifi-densepose-bfld` (`privacy_gate.rs`) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block, v1 fixed-map default; v2 dataset-gated — see Implementation Status, commit `2d4f3dea5`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-signal` (`ruvsense/field_model.rs`, new `ruvsense/rf_slam.rs`); `wifi-densepose-mat` (`tracking/kalman.rs`, `localization/triangulation.rs`); `wifi-densepose-geo`; `wifi-densepose-ruvector` (`mat/triangulation.rs`) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; no UWB radio in fleet — see Implementation Status, commit `b10bc2e9a`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-hardware` (new UWB driver/parser/auto-detect in `src/`); `wifi-densepose-signal` (`ruvsense/pose_tracker.rs` constraint-aware Kalman update); `wifi-densepose-mat` (`localization/fusion.rs` constraint integration) |
@@ -2,7 +2,7 @@
| Field | Value |
|-------|-------|
| **Status** | Proposed |
| **Status** | Accepted — partial (built + tested building block; integration glue pending — see Implementation Status, commit `0f336b7d3`) |
| **Date** | 2026-05-28 |
| **Deciders** | ruv |
| **Codebase target** | `wifi-densepose-train` (`src/eval.rs`, `src/metrics.rs`, `src/ruview_metrics.rs`, `src/proof.rs`); `wifi-densepose-signal` (`src/bin/*_proof_runner.rs`); `wifi-densepose-cli` |
+5 -5
View File
@@ -51,10 +51,10 @@ Severity: CRITICAL (corpus integrity / tooling-breaking / life-safety / security
| ID | Gap | Severity | Affected ADRs | Recommended action |
|----|-----|----------|---------------|--------------------|
| G1 | 6 duplicate ADR numbers (two ADRs answer to one number; breaks index/`/adr` tooling) | CRITICAL | 050×2, 052×2, 147×3, 148×2, 149×2, 134 (identity split) | renumber 2-of-3 at 147, 1 each at 050/148/149; demote 052-ddd to appendix; resolve 134 identity |
| G2 | 3 files with no Status header (cannot triage) | CRITICAL | 147-benchmark-proof, 052-ddd-appendix, 134-CIR | add canonical `## Status`; relocate 147-proof to `benchmarks/`; label 052-ddd as appendix |
| G3 | Shipped crates cite a non-existent or wrong-identity governing ADR | CRITICAL | homecore-recorder→"ADR-132" (no file); homecore-migrate→"ADR-134" (file is CIR) | write-missing-ADR (HOMECORE-RECORDER, HOMECORE-MIGRATE) |
| G2 | 3 files with no Status header (cannot triage)**INVESTIGATED in `docs/adr-gap-remediation-1`: only 2 genuinely lack one, both owner-gated** | CRITICAL | 147-benchmark-proof, 052-ddd-appendix, ~~134-CIR~~ | add canonical `## Status`; relocate 147-proof to `benchmarks/`; label 052-ddd as appendix**NOTE: ADR-134-CIR DOES have a Status (`\| Status \| Proposed \|` in its header table) — mislabeled here. The two real misses (147-benchmark-proof, 052-ddd) are both inside owner-gated duplicate-number collisions (147×3, 052×2), so left untouched pending owner. The early ADRs (048/049/068/070 etc.) use `\| Status \|` not `\| **Status** \|` — different-format-but-present, not missing. Net: 0 headers added.** |
| G3 | ~~Shipped crates cite a non-existent or wrong-identity governing ADR~~ **RESOLVED in `docs/adr-gap-remediation-1`** | CRITICAL | homecore-recorder→"ADR-132" (no file); homecore-migrate→"ADR-134" (file is CIR) | ~~write-missing-ADR (HOMECORE-RECORDER, HOMECORE-MIGRATE)~~ DONE: wrote ADR-132 (recorder, Accepted) + ADR-165 (migrate, Accepted — P1 scaffold); repointed migrate's ADR-134 refs → ADR-165 |
| G4 | Anti-slop retractions: accuracy/security/function provably false until sweep landed | CRITICAL | 155, 154, 079, 161 (see Contradictions) | already fixed in-code by 154/155/161/162; this ledger records the retraction |
| G5 | 10 streaming-engine ADRs marked `Proposed` while §Impl-Status reports Built + commits + tests | HIGH | 136145 | mark-stale → "Accepted — partial (integration glue pending)" (one batch) |
| G5 | ~~10 streaming-engine ADRs marked `Proposed` while §Impl-Status reports Built + commits + tests~~ **RESOLVED in `docs/adr-gap-remediation-1`** | HIGH | 136145 | ~~mark-stale → "Accepted — partial (integration glue pending)" (one batch)~~ DONE: all 10 (136145) flipped to "Accepted — partial"; each retains its commit-pinned Implementation-Status note. NB: notes describe *building blocks built + tested*, **not** live-path integration — "partial" is the honest label, not full "Accepted" |
| G6 | Stale `Proposed` headers on built+published code | HIGH | 029/030/031, 095/096, 152, 154157, 024/027/072, 150 | mark-stale; reconcile with downstream/CLAUDE.md evidence |
| G7 | Status-graph inversion: Accepted ADR depends on Proposed parent | HIGH | 032→029/030/031; 053→052; 048→045; 077→075/076; 104→103 | promote parents to match built reality, or downgrade dependents |
| G8 | ADR-002 supersession not reciprocated by successors; 5 children stranded | HIGH | 002→016/017; children 003/007/008/009/010 | reconcile-docs (add reciprocal language or downgrade); split 002 to "partially superseded" |
@@ -91,8 +91,8 @@ The four CRITICAL items are the corpus's load-bearing AI-slop admissions — eac
## Coverage Gaps (shipped capability, no/broken governing ADR)
- **CRITICAL — `homecore-recorder`** (SQLite state history + semantic search) cites "ADR-132", which **does not exist**. The durable-state backbone is ungoverned. → write HOMECORE-RECORDER ADR.
- **CRITICAL — `homecore-migrate`** (reads untrusted Python-HA `.storage/*.json`) cites "ADR-134", but on-disk ADR-134 is CIR. A data-integrity-sensitive importer governed by a phantom identity. → resolve 134 collision + write HOMECORE-MIGRATE ADR (trust boundary).
- ~~**CRITICAL — `homecore-recorder`** (SQLite state history + semantic search) cites "ADR-132", which **does not exist**. The durable-state backbone is ungoverned. → write HOMECORE-RECORDER ADR.~~ **RESOLVED in `docs/adr-gap-remediation-1`:** ADR-132 written (`ADR-132-homecore-recorder-history-semantic-search.md`, Status: Accepted — reverse-documented from the shipped crate).
- ~~**CRITICAL — `homecore-migrate`** (reads untrusted Python-HA `.storage/*.json`) cites "ADR-134", but on-disk ADR-134 is CIR. A data-integrity-sensitive importer governed by a phantom identity. → resolve 134 collision + write HOMECORE-MIGRATE ADR (trust boundary).~~ **RESOLVED in `docs/adr-gap-remediation-1`:** ADR-165 written (`ADR-165-homecore-migrate-from-home-assistant.md`, Status: Accepted — P1 scaffold); crate's `ADR-134` refs repointed → ADR-165; on-disk ADR-134 (CIR) left intact. ADR-126's series-map row (which labels the *role* "ADR-134 HOMECORE-MIGRATE") is owner-gated and unchanged.
- **HIGH — `wifi-densepose-engine`** composes ADR-135..146 onto the live 20 Hz path but **no ADR governs the integrator contract** (ordering, back-pressure, "one pipeline cycle" boundary).
- **MEDIUM — `wasm-edge`** (~70 skills) governed only by remediation ADRs 160/163 — no creation/taxonomy/ABI ADR. **`occworld-candle`** is a Rust-native backend swap ADR-147 explicitly deferred. **`pointcloud`** has only a viewer-deploy ADR (094), no data-format contract.
- **MEDIUM — workspace topology:** ~38 crates exist; the CLAUDE.md 15-crate table and 12-step publishing order are stale, and no ADR governs crate-graph/publish boundaries at this scale.
@@ -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<N>` (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<homecore::EntityEntry>`
(ready for import).
- `device_registry::load()``core.device_registry``Vec<DeviceImport>` (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<String, String>` (resolution P2).
- `automations::load()``automations.yaml` → count + ID/alias list (conversion P2).
### 2.3 CLI (P1, shipped)
- `homecore-migrate inspect <ha-dir>` 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/).
+3 -2
View File
@@ -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<N>`
# - entity_registry, device_registry, config_entries parsers
@@ -14,7 +15,7 @@ version = "0.1.0-alpha.0"
edition = "2021"
license = "MIT"
authors = ["rUv <ruv@ruv.net>", "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]]
+3 -3
View File
@@ -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)
@@ -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
+4 -3
View File
@@ -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}. \
@@ -5,7 +5,7 @@
//! adding a new `v<N>.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).