Files
attune/work-summary/2026-02-05-pack-reinstall-preserve-adhoc-rules.md
2026-02-23 20:45:10 -06:00

57 lines
3.0 KiB
Markdown

# Pack Reinstallation: Preserve Ad-Hoc Rules
**Date**: 2026-02-05
## Problem
When reinstalling a pack (force=true), user-created (ad-hoc) rules belonging to that pack were being permanently deleted. This happened because the reinstallation flow performed a hard `PackRepository::delete()` before recreating the pack, and the `rule.pack` foreign key uses `ON DELETE CASCADE` — destroying all rules owned by the pack, including custom ones created by users through the API or UI.
Additionally, rules from *other* packs that referenced triggers or actions from the reinstalled pack would have their `action` and `trigger` FK columns set to `NULL` (via `ON DELETE SET NULL`) when the old pack's entities were cascade-deleted, but were never re-linked after the new entities were created.
## Root Cause
In `register_pack_internal()` (`crates/api/src/routes/packs.rs`), the force-reinstall path was:
```
1. Delete existing pack (CASCADE deletes ALL rules, actions, triggers, sensors, runtimes)
2. Create new pack + components
```
No distinction was made between pack-defined rules (`is_adhoc = false`) and user-created rules (`is_adhoc = true`).
## Solution
### Repository Changes (`crates/common/src/repositories/rule.rs`)
Added four new methods/types:
- **`RestoreRuleInput`** — Like `CreateRuleInput` but with `Option<Id>` for action and trigger, since referenced entities may not exist after reinstallation.
- **`find_adhoc_by_pack()`** — Queries ad-hoc rules (`is_adhoc = true`) belonging to a specific pack.
- **`restore_rule()`** — Inserts a rule with optional action/trigger FK IDs, always setting `is_adhoc = true`.
- **`relink_action_by_ref()` / `relink_trigger_by_ref()`** — Updates rules with NULL action/trigger FKs, matching by the text `_ref` field to re-establish the link.
### Pack Registration Changes (`crates/api/src/routes/packs.rs`)
Modified `register_pack_internal()` to add two phases after component loading:
**Phase 1 — Save & Restore Ad-Hoc Rules:**
- Before deleting the old pack, queries and saves all ad-hoc rules
- After the new pack and components are created, restores each saved rule with the new pack ID
- Resolves action/trigger FKs by looking up entities by ref; if not found, the rule is preserved with NULL FKs (non-functional but not lost)
**Phase 2 — Re-link Orphaned Rules from Other Packs:**
- Iterates over all newly created actions and triggers
- For each, updates any rules (from any pack) that have a matching `_ref` but a NULL FK
## Files Changed
| File | Change |
|------|--------|
| `crates/common/src/repositories/rule.rs` | Added `RestoreRuleInput`, `find_adhoc_by_pack()`, `restore_rule()`, `relink_action_by_ref()`, `relink_trigger_by_ref()` |
| `crates/api/src/routes/packs.rs` | Save ad-hoc rules before pack deletion; restore them and re-link orphaned cross-pack rules after component loading |
## Testing
- Zero compiler warnings across the workspace
- All unit tests pass
- Integration test failures are pre-existing (no `attune_test` database configured)