feat(daemon): blacklist hard fails, waive soft fails
ci / web (pull_request) Successful in 41s
e2e / e2e (pull_request) Successful in 1m25s
ci / rust (pull_request) Successful in 3m5s

The blacklist moves to arr-db so every trigger that classifies a release
shares one check — targeted search today, RSS matching when it lands.
Applying it during classification rather than at selection also makes the
stored row say `blacklisted`, which is what keeps §9.3's manual view from
offering a hard-failed release as a clean match.

Both §6.3 keys now do work: the normalised name catches a re-upload, and
the infohash catches the same torrent re-listed under a new name — the
latter only after Transmission has fetched a `.torrent`, so that grab is
undone rather than recorded.

Soft-failed imports already stored a waiver; `GET /api/movies/{id}/files`
is what carries it out.

Closes #24

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Miguel Palhas
2026-08-22 23:36:02 +01:00
co-authored by Claude Opus 5
parent 0343783a57
commit f36e86ec66
13 changed files with 641 additions and 43 deletions
@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: i64\", path AS \"path!: String\", size AS \"size!: i64\", probed AS \"probed?: serde_json::Value\", json_extract(waiver, '$.rule') AS \"waiver?: String\" FROM media_files WHERE owner_kind = 'movie' AND owner_id = ? ORDER BY path",
"describe": {
"columns": [
{
"name": "id!: i64",
"ordinal": 0,
"type_info": "Integer"
},
{
"name": "path!: String",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "size!: i64",
"ordinal": 2,
"type_info": "Integer"
},
{
"name": "probed?: serde_json::Value",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "waiver?: String",
"ordinal": 4,
"type_info": "Null"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
true,
null
]
},
"hash": "0696aa0067ba82e31b5e9e46793080cd50793c8bb0bfee16ef932dcee2b85bf4"
}
@@ -1,20 +1,26 @@
{
"db_name": "SQLite",
"query": "SELECT normalised_name AS \"normalised_name!: String\" FROM blacklist",
"query": "SELECT normalised_name AS \"normalised_name!: String\", infohash FROM blacklist",
"describe": {
"columns": [
{
"name": "normalised_name!: String",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "infohash",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false
false,
true
]
},
"hash": "48c46aa1908add3d6abff142dbb22c2bce92c6483564c159731e64fdd5f7f8bf"
"hash": "071c14544e225001d31c5da60c90d3144fc5a071893c74b2c1eea51bfe00ac97"
}
@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT count(*) AS \"count!: i64\" FROM blacklist\n WHERE normalised_name = ? AND infohash IS NULL",
"describe": {
"columns": [
{
"name": "count!: i64",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "7505ce51ba10ce886c5b6888e8d27aecea4ee2f5412894dfe99057e8e88c7db3"
}
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "INSERT INTO blacklist (infohash, normalised_name, reason)\n VALUES (?, ?, ?)\n ON CONFLICT (infohash) DO NOTHING",
"query": "INSERT INTO blacklist (infohash, normalised_name, reason)\n VALUES (?, ?, ?)\n ON CONFLICT (infohash) DO NOTHING",
"describe": {
"columns": [],
"parameters": {
@@ -8,5 +8,5 @@
},
"nullable": []
},
"hash": "b4b63449fdfb0bf5fb7095577d2e4759784a92ad87cb151150689fc355473328"
"hash": "7d56b343c398ed8dee3a1c1f7cc8464b2529c83ec04453cc604ab064e0456194"
}