3.0 KiB
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— LikeCreateRuleInputbut withOption<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 settingis_adhoc = true.relink_action_by_ref()/relink_trigger_by_ref()— Updates rules with NULL action/trigger FKs, matching by the text_reffield 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
_refbut 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_testdatabase configured)