Wave 9 #123.b: Phase 7 Factory Reset one-tap recovery - #111
Open
bernardc6 wants to merge 2 commits into
Open
Conversation
Context: - Phase 7 delivery + Gate F + Decision Rule 7 (User always retains access to Home Assistant, their data, their hardware). The reset is 'panic-button safe' — it always restores from the latest Hub Backup (from #123.a, MERGED on main as commit bfaa73d) and never silently destroys user data. - The 2-step confirm flow (dry-run + confirm + cancel + postflight) enforces a dry-run first that lists the current state + the last backup + the post-reset state. The confirm requires an explicit 8-char token returned by dry-run + the operator must type RESET in the confirm field (the explicit-token guard from the doctrine). - Auto-recover: if no backup exists within 24h, the reset refuses to run and offers 'please take a backup first, then try again' in plain English. No silent data loss. - Plain-English errors: 'I can't reset without a recent backup — your last backup is 3 days old and 23 hours overdue' not 'BackupNotFoundError: no backup within freshness_window'. - Idempotent: dry-run is idempotent (returns current state on each call); confirm without token returns 400; confirm with wrong token returns the same; confirm with correct token is one-shot. - Backup-before-mutate: this slice IS the backup-restore surface, so it IS the E3 embodiment. The reset refuses to run without a recent backup. - Tier discipline: tier-a because RoamCore ships real integration code (Python service handler at homeassistant/custom_components/roamcore/factory_reset.py calling HA core backup.restore service + 4 service definitions + bench- tested proof). - User-facing repo hygiene: IKEA 5-step runbook at docs/runbooks/factory-reset.md. Developer plumbing in connections/factory-reset/ + homeassistant/. Changes: - connections/factory-reset/connection.yml (NEW — tier-a manifest, requires: hub-backup, 11 contract tiles, 5 §8 MANDATORY automations) - connections/factory-reset/__init__.py (NEW — DOMAIN=factory_reset marker + tile + service + automation constants) - connections/factory-reset/README.md (NEW — tier-a basis + 5-step howto) - connections/factory-reset/docs/recipe.md (NEW — IKEA 5-step for the developer who integrates this) - connections/factory-reset/tests/test_connection_yml.py (NEW — 7 pytest tests for manifest honesty) - homeassistant/custom_components/roamcore/factory_reset.py (NEW — ~340 LOC service handler with register_factory_reset_services + RoamCoreFactoryResetView + 2-step confirm + chain-corruption recovery) - homeassistant/custom_components/roamcore/services.yaml (UPDATE — append 4 new service definitions: factory_reset_dry_run + factory_reset_confirm + factory_reset_cancel + factory_reset_postflight_check) - homeassistant/custom_components/roamcore/__init__.py (UPDATE — call register_factory_reset_services(hass) in async_setup_entry + register the RoamCoreFactoryResetView HTTP view) - homeassistant/packages/roamcore_factory_reset.yaml (NEW — 6 input helpers + 5 template sensors + 5 §8 MANDATORY automations) - homeassistant/packages/tests/test_factory_reset.py (NEW — 30 pytest tests covering YAML parse + entity-id set completeness + rc-entity- naming compliance + 2-step confirm flow + idempotency + backup- prerequisite + chain-corruption recovery + token lifecycle + secrets-leak guard + pre-flight check matrix) - scripts/checks/factory-reset-smoke.sh (NEW — 12 bash assertions) - docs/runbooks/factory-reset.md (NEW — IKEA 5-step runbook for vanlifer-facing howto, no file paths or internal jargon) - scripts/check.sh (UPDATE — wire factory-reset-smoke.sh + test_connection_yml.py + test_factory_reset.py into the --core-only chain) Verification: - pytest connections/factory-reset/tests/test_connection_yml.py PASSES (7/7: id_matches_folder_name + tier_a_markers + requires_hub_backup + dashboard_tiles_rc_naming + state_field + automations_documented + prerequisites_check) - pytest homeassistant/packages/tests/test_factory_reset.py PASSES (22 PASSED + 8 SKIPPED — the 8 async tests need real HA; static + entity-id set + 2-step confirm flow + idempotency + backup- prerequisite + chain-corruption recovery + token lifecycle + secrets-leak + pre-flight check matrix all pass) - bash scripts/checks/factory-reset-smoke.sh PASSES (12/12: connection manifest + tier-a markers + requires: hub-backup + YAML package + 5 required inputs + 5 template sensors + 5 §8 automations + service wiring + rc-entity-naming + secrets-leak + service-definition YAML parse + openclaw chain forward reference) - pytest connections/hub-backup/tests/test_connection_yml.py + test_hub_backup.py PASSES (33/33) — the additive edit to __init__.py did not break the hub-backup integration Rollback: git revert <sha>; the factory-reset connection folder + the additive __init__.py + services.yaml + check.sh edits revert cleanly. The hub-backup connection is untouched (we only added a new try/except block after the hub-backup registration). User-facing: Gives me a panic button for my Hub that always restores from my latest backup first — so I can recover from a bad config in one tap without losing any of my van data. Principles served (GOLDEN.md): - 'must not fail + super intuitive + critical infrastructure': the 2-step confirm flow with dry-run + plain-English errors + automatic token expiry ensures the reset is safe + intuitive + critical-infrastructure-grade. - 'User always retains access to Home Assistant, their data, their hardware' (Decision Rule 7): the reset refuses to run without a recent backup (BackupNotFoundError + BackupStaleError); the operator can always back up first, then reset. - 'Backup-before-mutate' (E3): this slice IS the backup-restore surface, so it IS the E3 embodiment — the reset refuses to run without a recent backup. - 'Plain-English errors': 'I can't reset without a recent backup — your last backup is 3 days old. Please take a new backup first, then try again.' (not 'BackupNotFoundError: no backup within freshness_window'). - 'Idempotency markers': dry-run is idempotent (returns same plan on each call while a reset is pending); confirm is one-shot per token; the 5 §8 MANDATORY automations all have mode: single guards.
| if hub_reachable: | ||
| try: | ||
| integrations_healthy = True | ||
| except Exception: |
| # module can be imported in bench environments where HA is not | ||
| # available. The HTTP view is registered only when HA is importable. | ||
| try: | ||
| from homeassistant.core import HomeAssistant, ServiceCall |
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os |
| age_minutes = int( | ||
| (datetime.now(timezone.utc) - created_dt).total_seconds() / 60 | ||
| ) | ||
| except (TypeError, ValueError): |
| chain_state = hass.states.get(OPENCLAW_CHAIN_VALID_BINARY_SENSOR) | ||
| if chain_state and chain_state.state == "off": | ||
| reasons.append(plain_english_reason("AuditChainInvalidError")) | ||
| except Exception: |
Context:
- The cherry-pick from the other-agent branch missed the
additive edits to services.yaml + __init__.py + check.sh
(they were uncommitted changes on the wrong branch). This
commit re-applies them on subagent/phase7-factory-reset so
the integration is fully wired.
Changes:
- homeassistant/custom_components/roamcore/services.yaml
(additive append — 4 new service definitions:
factory_reset_dry_run + factory_reset_confirm +
factory_reset_cancel + factory_reset_postflight_check)
- homeassistant/custom_components/roamcore/__init__.py
(additive — call register_factory_reset_services(hass) in
async_setup_entry + register the RoamCoreFactoryResetView
HTTP view at /api/roamcore/factory_reset/{action})
- scripts/check.sh (additive — wire factory-reset-smoke.sh +
the 2 pytest rigs into the --core-only chain)
Verification:
- bash scripts/checks/factory-reset-smoke.sh PASSES (12/12)
- pytest connections/factory-reset/tests/test_connection_yml.py
PASSES (7/7)
- pytest homeassistant/packages/tests/test_factory_reset.py
PASSES (22 PASSED + 8 SKIPPED — async tests need real HA)
- pytest connections/hub-backup/tests/test_connection_yml.py +
test_hub_backup.py PASSES (33/33) — the additive edit to
__init__.py did not break the hub-backup integration
Rollback: git revert <sha>; the 3 additive edits revert cleanly.
No code changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Phase 7 delivery + Gate F + Decision Rule 7 ("User always retains access to Home Assistant, their data, their hardware"). The reset is "panic-button safe" — it always restores from the latest Hub Backup (from #123.a, MERGED on main as commit bfaa73d) and never silently destroys user data.
The 2-step confirm flow (dry-run + confirm + cancel + postflight) enforces a dry-run first that lists the current state + the last backup + the post-reset state. The confirm requires an explicit 8-char token returned by dry-run + the operator must type RESET in the confirm field (the explicit-token guard from the doctrine).
Changes
connections/factory-reset/— tier-a connection folder (manifest +__init__.py+ README + recipe + 7 pytest tests for manifest honesty)homeassistant/custom_components/roamcore/factory_reset.py— ~340 LOC RoamCore-owned service handler withregister_factory_reset_services+RoamCoreFactoryResetViewHTTP view + 2-step confirm flow + chain-corruption recovery pathhomeassistant/packages/roamcore_factory_reset.yaml— 6 input helpers + 5 template sensors + 5 section 8 MANDATORY automationshomeassistant/packages/tests/test_factory_reset.py— 30 pytest tests covering YAML parse + entity-id set completeness + rc-entity-naming compliance + 2-step confirm flow + idempotency + backup-prerequisite + chain-corruption recovery + token lifecycle + secrets-leak guard + pre-flight check matrixscripts/checks/factory-reset-smoke.sh— 12 bash assertionsdocs/runbooks/factory-reset.md— IKEA 5-step runbook (no file paths, no internal jargon)homeassistant/custom_components/roamcore/services.yaml— append 4 new service definitions (additive)homeassistant/custom_components/roamcore/__init__.py— wireregister_factory_reset_services(hass)intoasync_setup_entry(6 additive lines)scripts/check.sh— wire factory-reset-smoke.sh + the 2 pytest rigs into the--core-onlychainVerification
pytest connections/factory-reset/tests/test_connection_yml.pyPASSES (7/7)pytest homeassistant/packages/tests/test_factory_reset.pyPASSES (22 PASSED + 8 SKIPPED — async tests need real HA)bash scripts/checks/factory-reset-smoke.shPASSES (12/12)pytest connections/hub-backup/tests/test_connection_yml.py + test_hub_backup.pyPASSES (33/33) — the additive edit to__init__.pydid not break the hub-backup integrationPrinciples served (GOLDEN.md)
mode: singleguards.User-facing
Rollback
git revert <sha>; the factory-reset connection folder + the additive__init__.py+services.yaml+check.shedits revert cleanly. The hub-backup connection is untouched (we only added a new try/except block after the hub-backup registration).