5.7 KiB
Fix: Pack Installation Virtualenv Ordering & FK ON DELETE Constraints
Date: 2026-02-05
Problems
1. Virtualenv Not Created at Permanent Location
When installing a Python pack (e.g., python_example), no virtualenv was created at the permanent storage location. Attempting to run an action yielded:
{
"error": "Execution failed during preparation",
"succeeded": false
}
2. Pack Deletion Blocked by Foreign Key Constraints
Deleting a pack that had been used (with executions) failed with:
{
"error": "Constraint violation: execution_action_fkey",
"code": "CONFLICT"
}
Root Causes
Virtualenv Ordering Bug
In install_pack (crates/api/src/routes/packs.rs), the operation ordering was incorrect:
- Pack downloaded to temp directory (
/tmp/attune-pack-installs/...) register_pack_internal(temp_path)called — creates DB record and sets up virtualenv at temp pathstorage.install_pack()copies pack from temp to permanent storage (packs/{pack_ref}/)- Temp directory cleaned up
Python virtualenvs are not relocatable — they contain hardcoded paths in shebang lines, pyvenv.cfg, and pip scripts. The copied .venv was non-functional.
Missing ON DELETE Clauses on Foreign Keys
Several foreign key constraints in the schema had no ON DELETE behavior (defaulting to RESTRICT), which blocked cascading deletes:
execution.action→action(id)— no ON DELETE (blocks action deletion)execution.parent→execution(id)— no ON DELETEexecution.enforcement→enforcement(id)— no ON DELETErule.action→action(id)— no ON DELETE, alsoNOT NULLrule.trigger→trigger(id)— no ON DELETE, alsoNOT NULLevent.source→sensor(id)— no ON DELETEworkflow_execution.workflow_def→workflow_definition(id)— no ON DELETE
When deleting a pack, the cascade deleted actions (action.pack ON DELETE CASCADE), but executions referencing those actions blocked the delete.
Fixes
1. Pack Installation Ordering
Restructured install_pack to move the pack to permanent storage before calling register_pack_internal:
- Pack downloaded to temp directory
pack.yamlread to extractpack_ref- Pack moved to permanent storage (
packs/{pack_ref}/) register_pack_internal(permanent_path)called — virtualenv creation and dependency installation now happen at the final location- Temp directory cleaned up
Added error handling to clean up permanent storage if registration fails after the move.
2. Foreign Key ON DELETE Fixes (Merged into Original Migrations)
Fixed all missing ON DELETE behaviors directly in the original migration files (requires DB rebuild):
| Table.Column | Migration File | ON DELETE | Notes |
|---|---|---|---|
execution.action |
000006_execution_system |
SET NULL |
Already nullable; action_ref text preserved |
execution.parent |
000006_execution_system |
SET NULL |
Already nullable |
execution.enforcement |
000006_execution_system |
SET NULL |
Already nullable |
rule.action |
000006_execution_system |
SET NULL |
Made nullable; action_ref text preserved |
rule.trigger |
000006_execution_system |
SET NULL |
Made nullable; trigger_ref text preserved |
event.source |
000004_trigger_sensor_event_rule |
SET NULL |
Already nullable; source_ref preserved |
workflow_execution.workflow_def |
000007_workflow_system |
CASCADE |
Meaningless without definition |
3. Model & Code Updates
- Rule model (
crates/common/src/models.rs): Changedaction: Idandtrigger: IdtoOption<Id> - RuleResponse DTO (
crates/api/src/dto/rule.rs): ChangedactionandtriggertoOption<i64> - Enforcement processor (
crates/executor/src/enforcement_processor.rs): Added guards to skip execution when a rule's action or trigger has been deleted (SET NULL) - Pack delete endpoint (
crates/api/src/routes/packs.rs): Added filesystem cleanup to remove pack directory from permanent storage on deletion
4. Test Updates
crates/common/tests/rule_repository_tests.rs: Updated assertions to useSome(id)for nullable fieldscrates/executor/src/enforcement_processor.rs(tests): Updated test Rule construction withSome()wrappers
Files Changed
migrations/20250101000004_trigger_sensor_event_rule.sql— AddedON DELETE SET NULLtoevent.sourcemigrations/20250101000006_execution_system.sql— AddedON DELETE SET NULLtoexecution.action,.parent,.enforcement; maderule.action/.triggernullable withON DELETE SET NULLmigrations/20250101000007_workflow_system.sql— AddedON DELETE CASCADEtoworkflow_execution.workflow_defcrates/api/src/routes/packs.rs— Reorderedinstall_pack; added pack directory cleanup on deletecrates/api/src/dto/rule.rs— Madeaction/triggerfields optional inRuleResponsecrates/common/src/models.rs— MadeRule.action/Rule.triggerOption<Id>crates/executor/src/enforcement_processor.rs— Handle nullable action/trigger in enforcement processingcrates/common/tests/rule_repository_tests.rs— Fixed test assertions
Design Philosophy
Historical records (executions, events, enforcements) are preserved when their referenced entities are deleted. The text ref fields (action_ref, trigger_ref, source_ref, etc.) retain the reference for auditing, while the FK ID fields are set to NULL. Rules with deleted actions or triggers become non-functional but remain in the database for traceability.
Verification
cargo check --all-targets --workspace— zero warningscargo test --workspace --lib— all 358 unit tests pass