4.2 KiB
Timer Sensor Discriminator Fix
Date: 2026-02-04
Status: ✅ Fixed
Type: Bug Fix
Problem
The timer sensor was failing to start timers with the error:
Failed to start timer for rule 1: Failed to parse trigger_params as TimerConfig
Root Cause
The timer sensor's TimerConfig enum is defined as a tagged union using serde's #[serde(tag = "type")]:
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TimerConfig {
Interval { interval: u64, unit: TimeUnit },
Cron { expression: String },
DateTime { fire_at: DateTime<Utc> },
}
This requires JSON with a type discriminator field:
{
"type": "interval",
"interval": 1,
"unit": "seconds"
}
However, the trigger_params stored in the rule table was missing the type field:
{
"interval": 1,
"unit": "seconds"
}
Without the discriminator, serde couldn't deserialize the enum variant, causing the parse error.
Investigation Steps
- Observed error in sensor logs
- Checked database for
trigger_paramscontent - Found parameters were correct but missing
typefield - Examined
TimerConfigenum definition incrates/sensor-timer/src/types.rs - Identified the tagged union requirement
Solution
Immediate Fix: Manually added the type field to existing rule:
UPDATE rule
SET trigger_params = jsonb_set(trigger_params, '{type}', '"interval"')
WHERE trigger_ref = 'core.intervaltimer';
Result: Timer sensor now working correctly, firing events every second.
Not Related to JSON Schema Migration
This issue was NOT caused by the JSON Schema format migration. This is a separate data format issue:
- Schema format: How parameter definitions are structured (inline vs standard JSON Schema)
- Data format: How parameter values are stored (with or without type discriminator)
The JSON Schema migration only affected the parameter schema definitions in YAML files and the database. It did not affect the parameter values stored in rules.
Proper Long-Term Fix
The issue should be fixed at the source - when rules are created. The system should automatically add the appropriate type discriminator to trigger_params based on the trigger_ref:
| Trigger Ref | Type Value |
|---|---|
core.intervaltimer |
"interval" |
core.crontimer |
"cron" |
core.datetimetimer |
"datetime" |
Implementation Options
- Database Trigger: Add a PostgreSQL trigger to automatically inject the
typefield - API Layer: Add the
typefield in the API when creating/updating rules - Repository Layer: Add the
typefield in the rule repository's create/update methods - Timer Sensor: Make the timer sensor more flexible to infer type from trigger_ref
Recommended: Option 3 (Repository Layer) - keep the logic in Rust code where it's testable and maintainable.
Files Involved
crates/sensor-timer/src/types.rs- DefinesTimerConfigenum with tagged unioncrates/sensor-timer/src/timer_manager.rs- DeserializesTimerConfigfrom rule params- Database:
ruletable,trigger_paramscolumn
Verification
After applying the fix:
# Check rule parameters
docker compose exec -T postgres psql -U attune -d attune \
-c "SELECT id, ref, jsonb_pretty(trigger_params) FROM rule;"
# Verify timer is working
docker compose logs sensor | grep "Timer fired"
Expected output:
- Rule parameters include
"type": "interval" - Sensor logs show "Timer fired for rule 1, created event X" every second
- Events are being created in the database
Action Items
- Implement automatic
typefield injection in rule repository - Add validation to ensure timer trigger params include required
typefield - Update rule creation tests to verify
typefield is present - Consider making timer sensor more lenient (infer type from trigger_ref)
- Document the required format for timer trigger parameters
Conclusion
The timer sensor is now operational. The issue was a missing discriminator field in the stored parameter data, not a problem with the JSON Schema migration. The system needs a proper fix to automatically add the type field when creating rules with timer triggers.