Merge branch 'feature/cw-7513' into feature/cw-7513-specs

This commit is contained in:
Tanmay Deep Sharma
2026-07-15 13:26:50 +05:30
2 changed files with 10 additions and 5 deletions
+2 -1
View File
@@ -133,7 +133,8 @@ class AutomationRule < ApplicationRecord
end
def discard_stale_pending_executions
pending_executions.pending.delete_all
# Includes stale processing rows: the sweep would otherwise reclaim and fire them.
pending_executions.armed.delete_all
end
def validate_single_condition(condition)
@@ -42,6 +42,9 @@ class AutomationRulePendingExecution < ApplicationRecord
pending.where(due_at: ..Time.current).or(processing.where(updated_at: ...STALE_PROCESSING_TIMEOUT.ago))
}
# Non-terminal rows still bound to fire (a stale processing row is reclaimed by the sweep).
scope :armed, -> { where(status: [statuses[:pending], statuses[:processing]]) }
def self.schedule(rule:, conversation:, message: nil)
key = arm_episode_key_for(conversation, message)
create!(
@@ -63,7 +66,7 @@ class AutomationRulePendingExecution < ApplicationRecord
def self.arm_episode_key_for(conversation, message)
return episode_key_for(conversation, message) unless message&.incoming? && conversation.waiting_since.blank?
"awaiting_agent:#{message.created_at.to_i}"
"awaiting_agent:#{message.created_at.to_f}"
end
# Episode keys identify one qualifying stretch of conversation state; when the recomputed
@@ -73,9 +76,10 @@ class AutomationRulePendingExecution < ApplicationRecord
# Sub-second precision so a resolve→reopen inside one second still ends the episode.
"status:#{(conversation.status_changed_at.presence || conversation.created_at).to_f}"
elsif message.incoming?
# waiting_since is cleared on agent/bot reply, so a reply invalidates this episode.
# Strict here: at fire time a nil waiting_since means the agent replied (episode ended).
"awaiting_agent:#{conversation.waiting_since.to_i}"
# waiting_since is cleared on agent/bot reply, so a reply invalidates this episode. Strict
# here: at fire time a nil waiting_since means the agent replied (episode ended). Sub-second
# precision so a reply→re-wait inside one second is still a distinct episode.
"awaiting_agent:#{conversation.waiting_since.to_f}"
else
# A new customer message changes the max incoming id, invalidating this episode.
"reply_chase:#{conversation.messages.incoming.maximum(:id) || 0}"