fix(daemon): measure attention window from failure
Closes the gap #239 describes: §5.7's 30-day window was filtered on grabbed_at, so a torrent stalling past the window before hard-failing at import never surfaced in the needs-a-decision queue. grabs gains failed_at (migration 0030, backfilled from grabbed_at for existing failed rows), the import tick stamps it on hard fail, and every window query in the daemon notifier and the attention endpoint reads it. §5.7 now states the anchor explicitly. §6.2's pack backoff stays on grabbed_at deliberately; noted on the issue. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -621,7 +621,10 @@ impl ImportAction {
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"UPDATE grabs SET state = 'failed' WHERE id = ?",
|
||||
"UPDATE grabs
|
||||
SET state = 'failed',
|
||||
failed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE id = ?",
|
||||
pending.grab_id
|
||||
)
|
||||
.execute(database.pool())
|
||||
@@ -759,7 +762,10 @@ impl ImportAction {
|
||||
)
|
||||
.await?;
|
||||
sqlx::query!(
|
||||
"UPDATE grabs SET state = 'failed' WHERE id = ?",
|
||||
"UPDATE grabs
|
||||
SET state = 'failed',
|
||||
failed_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
|
||||
WHERE id = ?",
|
||||
pending.grab_id
|
||||
)
|
||||
.execute(database.pool())
|
||||
@@ -1564,11 +1570,16 @@ mod tests {
|
||||
assert_eq!(normalised, arr_parse::normalise(RELEASE_NAME));
|
||||
assert_eq!(reason, "dolby_vision_profile");
|
||||
|
||||
let grab_state: String = sqlx::query_scalar("SELECT state FROM grabs")
|
||||
.fetch_one(h.database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
let (grab_state, failed_at): (String, Option<String>) =
|
||||
sqlx::query_as("SELECT state, failed_at FROM grabs")
|
||||
.fetch_one(h.database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(grab_state, "failed");
|
||||
assert!(
|
||||
failed_at.is_some(),
|
||||
"§5.7's window runs from the failure, so the failure is stamped"
|
||||
);
|
||||
let movie_state: String = sqlx::query_scalar("SELECT state FROM movies WHERE id = 1")
|
||||
.fetch_one(h.database.pool())
|
||||
.await
|
||||
@@ -2202,11 +2213,16 @@ mod tests {
|
||||
)],
|
||||
"only the release is blacklisted, never the season"
|
||||
);
|
||||
let grab_state: String = sqlx::query_scalar("SELECT state FROM grabs")
|
||||
.fetch_one(h.database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
let (grab_state, failed_at): (String, Option<String>) =
|
||||
sqlx::query_as("SELECT state, failed_at FROM grabs")
|
||||
.fetch_one(h.database.pool())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(grab_state, "failed");
|
||||
assert!(
|
||||
failed_at.is_some(),
|
||||
"§5.7's window runs from the failure, so the failure is stamped"
|
||||
);
|
||||
let states: Vec<(String, bool)> =
|
||||
sqlx::query_as("SELECT state, wanted FROM episodes ORDER BY number")
|
||||
.fetch_all(h.database.pool())
|
||||
|
||||
Reference in New Issue
Block a user