mirror of
https://github.com/ruvnet/RuView
synced 2026-08-04 19:31:42 +00:00
deploy: 42d56fc1a5
This commit is contained in:
@@ -82,6 +82,11 @@ The entity registry is a `RwLock<HashMap<EntityId, EntityEntry>>` backed by an a
|
|||||||
|
|
||||||
`DeviceRegistry` mirrors HA's `core.device_registry` schema (version 13). Devices are identified by a set of `(id_type, id_value)` tuples (the `identifiers` field), which matches HA's pattern of accepting multiple identifier types per device (MAC address, serial number, integration-specific ID).
|
`DeviceRegistry` mirrors HA's `core.device_registry` schema (version 13). Devices are identified by a set of `(id_type, id_value)` tuples (the `identifiers` field), which matches HA's pattern of accepting multiple identifier types per device (MAC address, serial number, integration-specific ID).
|
||||||
|
|
||||||
|
`DeviceEntry` and the in-memory `DeviceRegistry` are implemented. On server
|
||||||
|
startup, entity and device registry files are restored in deterministic key
|
||||||
|
order with a configurable hard row bound; malformed individual entries are
|
||||||
|
isolated and reported.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. HA-side reference table
|
## 3. HA-side reference table
|
||||||
|
|||||||
@@ -148,6 +148,12 @@ correctness, fail-closed write integrity, semantic-store NaN poisoning, and PII
|
|||||||
- **Memory-DoS — `get_state_history` was unbounded.** No `LIMIT`, so a wide time window over a
|
- **Memory-DoS — `get_state_history` was unbounded.** No `LIMIT`, so a wide time window over a
|
||||||
high-frequency entity loaded an unbounded row set into memory. Now capped at
|
high-frequency entity loaded an unbounded row set into memory. Now capped at
|
||||||
`MAX_HISTORY_ROWS` (1,000,000); sibling search paths were already `k`-bounded.
|
`MAX_HISTORY_ROWS` (1,000,000); sibling search paths were already `k`-bounded.
|
||||||
|
- **Startup state restoration.** `latest_states(limit)` selects one newest row
|
||||||
|
per entity with `(last_updated_ts, state_id)` tie-breaking, orders results by
|
||||||
|
entity ID, and caps requests at 100,000. Malformed rows are skipped with
|
||||||
|
typed warnings. `restore_latest` preserves recorded timestamps and installs
|
||||||
|
snapshots with a `homecore.restore` context before the recorder listener and
|
||||||
|
automation engine start.
|
||||||
- **Disk-DoS / documented-but-missing `purge`.** The README advertised `Recorder::purge`, but
|
- **Disk-DoS / documented-but-missing `purge`.** The README advertised `Recorder::purge`, but
|
||||||
no retention path existed → unbounded disk growth. Added a **transactional** `purge(older_than)`
|
no retention path existed → unbounded disk growth. Added a **transactional** `purge(older_than)`
|
||||||
with an **exclusive** cutoff (idempotent, no off-by-one) that deletes old `states`/`events` and
|
with an **exclusive** cutoff (idempotent, no off-by-one) that deletes old `states`/`events` and
|
||||||
|
|||||||
@@ -224,8 +224,9 @@ touched:
|
|||||||
SHA-256-checks the module, Ed25519-verifies the signature against
|
SHA-256-checks the module, Ed25519-verifies the signature against
|
||||||
`publisher_key`, and enforces a `PluginPolicy` trust allowlist
|
`publisher_key`, and enforces a `PluginPolicy` trust allowlist
|
||||||
(secure-default rejects unsigned/untrusted/tampered modules).
|
(secure-default rejects unsigned/untrusted/tampered modules).
|
||||||
- **HAP real pairing (P2)** — SRP/HKDF pairing + encrypted sessions; current
|
- **HAP real pairing (P2)** — **DONE (2026-07-27 addendum below).** SRP/HKDF
|
||||||
bridge is an accessory-mapping surface. **ACCEPTED-FUTURE (honestly stubbed).**
|
Pair-Setup, transcript-authenticated Pair-Verify, encrypted sessions, and
|
||||||
|
administrator-only pairing management now land as one fail-closed boundary.
|
||||||
- **`RunMode::Queued`/`Restart`/`max` ordering** — ~~`Single`/`Parallel` are
|
- **`RunMode::Queued`/`Restart`/`max` ordering** — ~~`Single`/`Parallel` are
|
||||||
honored; bounded queueing, restart-kill, and `max` concurrency are not yet
|
honored; bounded queueing, restart-kill, and `max` concurrency are not yet
|
||||||
wired (every non-Single mode is parallel).~~ **DONE — ADR-162 §A5.** Restart
|
wired (every non-Single mode is parallel).~~ **DONE — ADR-162 §A5.** Restart
|
||||||
@@ -336,3 +337,35 @@ is still delivered (old code: 5s-timeout panic).
|
|||||||
+1 api-root accept-guard, +1 WS lag-survival), 0 failed. Workspace green.
|
+1 api-root accept-guard, +1 WS lag-survival), 0 failed. Workspace green.
|
||||||
Python deterministic proof unchanged (homecore-api is off the signal proof
|
Python deterministic proof unchanged (homecore-api is off the signal proof
|
||||||
path).
|
path).
|
||||||
|
|
||||||
|
## Addendum — HAP cryptographic boundary completed (2026-07-27)
|
||||||
|
|
||||||
|
The P2 HAP deferral recorded above is closed as a single security boundary in
|
||||||
|
`homecore-hap`; it was not replaced with a success-shaped partial protocol.
|
||||||
|
|
||||||
|
- Pair-Setup M1-M6 uses RustCrypto SRP-6a with the RFC 5054 3072-bit group,
|
||||||
|
SHA-512 and HAP proof compatibility, followed by the specified
|
||||||
|
HKDF-SHA512, ChaCha20-Poly1305, and Ed25519 transcript construction.
|
||||||
|
- Pair-Verify M1-M4 uses ephemeral X25519, strict Ed25519 transcript
|
||||||
|
verification, and separately derived directional control keys.
|
||||||
|
- The TCP server changes to authenticated HAP record framing only after the
|
||||||
|
plaintext M4 response is written. Record lengths are authenticated, plaintext
|
||||||
|
is capped at 1024 bytes, counters are independent and monotonic, and any
|
||||||
|
authentication/replay/framing failure closes without an oracle response.
|
||||||
|
- Accessory identity, signing seed, SRP verifier, and controller pairings share
|
||||||
|
one versioned, bounded, permission-checked, atomically replaced store. The raw
|
||||||
|
setup code is disclosed only on first provisioning and is not persisted.
|
||||||
|
- Protected endpoints require an encrypted Pair-Verify session. Pairing
|
||||||
|
management rechecks current persisted administrator authority, handles the
|
||||||
|
last-admin invariant, updates mDNS paired state, and revokes live sessions.
|
||||||
|
|
||||||
|
Evidence includes a deterministic HAP SRP vector, complete in-process
|
||||||
|
Pair-Setup and Pair-Verify ceremonies, malformed/proof/transcript tests, record
|
||||||
|
tamper/replay/oversize tests, persistence lifecycle tests, and a real TCP test
|
||||||
|
that verifies Pair-Verify, accesses `/accessories` over encrypted records, then
|
||||||
|
proves replay closes the connection.
|
||||||
|
|
||||||
|
This closes the cryptographic implementation item, not the entire Apple Home
|
||||||
|
product surface. Current-Apple/MFi interoperability has not been certified;
|
||||||
|
transient/split Pair-Setup, writable/timed characteristics, resource endpoints,
|
||||||
|
and persisted AID/IID allocation remain explicitly unsupported.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------|-------|
|
|-------|-------|
|
||||||
| **Status** | Accepted — P1 scaffold (full conversion deferred to P2) |
|
| **Status** | Accepted — registry/config persistence implemented |
|
||||||
| **Date** | 2026-05-25 |
|
| **Date** | 2026-05-25 |
|
||||||
| **Deciders** | ruv |
|
| **Deciders** | ruv |
|
||||||
| **Codename** | **HOMECORE-MIGRATE** |
|
| **Codename** | **HOMECORE-MIGRATE** |
|
||||||
@@ -44,8 +44,8 @@ files are read, how schema versions are validated, and what happens on an unknow
|
|||||||
## 2. Decision
|
## 2. Decision
|
||||||
|
|
||||||
Ship `homecore-migrate` as a CLI + library that reads an existing HA filesystem and imports
|
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
|
its configuration into HOMECORE. Registry and config-entry conversion are durable; automation
|
||||||
converts the entity registry; full conversion of the remaining artifacts is deferred to P2.
|
conversion and secret-reference resolution remain deferred.
|
||||||
|
|
||||||
### 2.1 Storage reader + versioned format gate (P1, shipped)
|
### 2.1 Storage reader + versioned format gate (P1, shipped)
|
||||||
|
|
||||||
@@ -57,22 +57,25 @@ converts the entity registry; full conversion of the remaining artifacts is defe
|
|||||||
unknown `minor_version` is a **hard error** (`MigrateError::UnsupportedSchemaVersion`),
|
unknown `minor_version` is a **hard error** (`MigrateError::UnsupportedSchemaVersion`),
|
||||||
never a silent best-effort parse. Better to refuse than to corrupt.
|
never a silent best-effort parse. Better to refuse than to corrupt.
|
||||||
|
|
||||||
### 2.2 Per-artifact parsers (P1, shipped)
|
### 2.2 Per-artifact conversion (shipped)
|
||||||
|
|
||||||
- `entity_registry::load()` — `core.entity_registry` → `Vec<homecore::EntityEntry>`
|
- `entity_registry::load()` — `core.entity_registry` → `Vec<homecore::EntityEntry>`
|
||||||
(ready for import).
|
(ready for import).
|
||||||
- `device_registry::load()` — `core.device_registry` → `Vec<DeviceImport>` (P1 diagnostic;
|
- `device_registry::read_device_registry()` converts the supported v13 device fields into
|
||||||
full conversion P2).
|
`homecore::DeviceEntry`; `write_device_registry()` emits an HA-compatible v13 envelope.
|
||||||
- `config_entries::load()` — `core.config_entries` → domain counts + integration names
|
- `config_entries::convert_config_entries()` emits versioned `homecore.config_entries`
|
||||||
(the format is undocumented per §6 Q5; treated diagnostically).
|
storage. Original rows are retained verbatim, while unsupported domains and fields produce
|
||||||
|
typed warnings instead of being discarded.
|
||||||
- `secrets::load_secrets()` — `secrets.yaml` → `HashMap<String, String>` (resolution P2).
|
- `secrets::load_secrets()` — `secrets.yaml` → `HashMap<String, String>` (resolution P2).
|
||||||
- `automations::load()` — `automations.yaml` → count + ID/alias list (conversion P2).
|
- `automations::load()` — `automations.yaml` → count + ID/alias list (conversion P2).
|
||||||
|
|
||||||
### 2.3 CLI (P1, shipped)
|
### 2.3 CLI
|
||||||
|
|
||||||
- `homecore-migrate inspect <ha-dir>` previews what will be migrated (entity/device/config
|
- `homecore-migrate inspect <ha-dir>` previews what will be migrated (entity/device/config
|
||||||
counts, redacted secret/automation lists) (`src/cli.rs`, `src/main.rs`).
|
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.
|
- `import-entities`, `import-devices`, and `import-config-entries` write destination files and
|
||||||
|
emit one-line JSON summaries. Writes use synced same-directory temporary files and atomic
|
||||||
|
no-clobber publication; an existing destination is never implicitly replaced.
|
||||||
|
|
||||||
### 2.4 Structured errors (P1, shipped)
|
### 2.4 Structured errors (P1, shipped)
|
||||||
|
|
||||||
@@ -88,27 +91,25 @@ converts the entity registry; full conversion of the remaining artifacts is defe
|
|||||||
file path and a coarse location (`serde_yaml::Error::location()`), never the scalar content.
|
file path and a coarse location (`serde_yaml::Error::location()`), never the scalar content.
|
||||||
Pinned by `secrets::tests::malformed_secrets_error_never_contains_secret_value` (asserts the
|
Pinned by `secrets::tests::malformed_secrets_error_never_contains_secret_value` (asserts the
|
||||||
rendered error **and its full `#[source]` chain** never contain the secret value).
|
rendered error **and its full `#[source]` chain** never contain the secret value).
|
||||||
**Review dimensions confirmed clean with evidence:** source is never mutated (no
|
**Review dimensions confirmed clean with evidence:** source is never mutated; destination
|
||||||
`fs::write`/`remove`/`create` anywhere — P1 reads source, writes nothing); paths are
|
writes are explicit `--to` paths and no-clobber; paths are
|
||||||
user-supplied dirs joined with fixed filenames (no `..`/absolute traversal beyond the
|
user-supplied dirs joined with fixed filenames (no `..`/absolute traversal beyond the
|
||||||
user's own privileges); malformed/typed/truncated `.storage` JSON and YAML **error, never
|
user's own privileges); malformed/typed/truncated `.storage` JSON and YAML **error, never
|
||||||
panic** (every production `unwrap`/`expect` is test-only); unknown schema `minor_version`
|
panic** (every production `unwrap`/`expect` is test-only); unknown schema `minor_version`
|
||||||
hard-errors fail-closed; no SQL/shell/path injection surface (the tool emits diagnostics
|
hard-errors fail-closed; no SQL/shell injection surface.
|
||||||
only, persists nothing in P1).
|
|
||||||
|
|
||||||
### 2.5 Deferred to P2+ (NOT built — honestly labelled)
|
### 2.5 Deferred to P2+ (NOT built — honestly labelled)
|
||||||
|
|
||||||
- Convert `config_entries` → HOMECORE plugin manifests.
|
- Execute imported config entries (a matching HOMECORE plugin must claim the preserved domain).
|
||||||
- Convert `automations.yaml` → `homecore-automation` YAML.
|
- Convert `automations.yaml` → `homecore-automation` YAML.
|
||||||
- Side-by-side runtime mode (requires `homecore-recorder`, ADR-132; behind the `recorder`
|
- Side-by-side runtime mode (requires `homecore-recorder`, ADR-132; behind the `recorder`
|
||||||
Cargo feature, currently a no-op stub).
|
Cargo feature, currently a no-op stub).
|
||||||
- `!secret` reference resolution in non-secrets YAML files.
|
- `!secret` reference resolution in non-secrets YAML files.
|
||||||
|
|
||||||
### 2.6 Test evidence (as shipped)
|
### 2.6 Test evidence
|
||||||
|
|
||||||
- 21 tests (`cargo test -p homecore-migrate`) — 19 as originally shipped plus 2 added by the
|
- Targeted tests cover registry round trips, unknown versions, lossless unsupported config
|
||||||
2026-06 security review (`secrets::tests::malformed_secrets_error_never_contains_secret_value`,
|
fields/domains, malformed input, and crash-safe/no-overwrite destination behaviour.
|
||||||
`malformed_secrets_error_reports_location`).
|
|
||||||
|
|
||||||
## 3. Consequences
|
## 3. Consequences
|
||||||
|
|
||||||
@@ -118,13 +119,12 @@ converts the entity registry; full conversion of the remaining artifacts is defe
|
|||||||
schema drift fails loudly instead of corrupting an imported home.
|
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
|
- Reusing HA's own `.storage` and YAML formats means no intermediate export step; the tool
|
||||||
reads a live HA install directly.
|
reads a live HA install directly.
|
||||||
- P1 `inspect` gives users a no-risk dry run before any write.
|
- `inspect` gives users a no-risk dry run before any write.
|
||||||
|
|
||||||
**Negative / honest limits.**
|
**Negative / honest limits.**
|
||||||
|
|
||||||
- P1 is a **scaffold**: only the entity registry is conversion-ready. Device registry,
|
- Imported config entries are durable but do not install or execute Python HA integrations.
|
||||||
config-entry→plugin, automation, and secret-resolution conversions are P2 and **not yet
|
- Automation conversion and secret-reference resolution are not built.
|
||||||
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
|
- The side-by-side recorder export depends on ADR-132 and is currently a feature-gated
|
||||||
no-op.
|
no-op.
|
||||||
- Performance figures in the README (envelope parse < 5 ms, 1 000-entity load < 50 ms) are
|
- Performance figures in the README (envelope parse < 5 ms, 1 000-entity load < 50 ms) are
|
||||||
|
|||||||
Reference in New Issue
Block a user