Update public source

This commit is contained in:
2026-10-06 21:08:12 +03:00
commit f231cc5b1f
102 changed files with 48249 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
from __future__ import annotations
import sqlite3
from datetime import datetime, timezone
class AttemptsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_attempt(
self,
handshake_id: int,
method_id: str,
method_version: int,
job_id: str,
completed_at: str | None = None,
) -> None:
self._conn.execute(
"""
INSERT INTO crack_attempts (
handshake_id,
method_id,
method_version,
job_id,
status,
completed_at
)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
handshake_id,
method_id,
method_version,
job_id,
"COMPLETED",
completed_at or self._now(),
),
)
def get_attempt(
self,
handshake_id: int,
method_id: str,
method_version: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
handshake_id,
method_id,
method_version,
job_id,
status,
completed_at
FROM crack_attempts
WHERE handshake_id = ?
AND method_id = ?
AND method_version = ?
""",
(
handshake_id,
method_id,
method_version,
),
).fetchone()
def has_completed(
self,
handshake_id: int,
method_id: str,
method_version: int,
) -> bool:
row = self._conn.execute(
"""
SELECT 1
FROM crack_attempts
WHERE handshake_id = ?
AND method_id = ?
AND method_version = ?
AND status = 'COMPLETED'
""",
(
handshake_id,
method_id,
method_version,
),
).fetchone()
return row is not None
def list_handshake_attempts(
self,
handshake_id: int,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
handshake_id,
method_id,
method_version,
job_id,
status,
completed_at
FROM crack_attempts
WHERE handshake_id = ?
ORDER BY method_id, method_version
""",
(handshake_id,),
).fetchall()
def list_job_attempts(
self,
job_id: str,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
handshake_id,
method_id,
method_version,
job_id,
status,
completed_at
FROM crack_attempts
WHERE job_id = ?
ORDER BY handshake_id, method_id, method_version
""",
(job_id,),
).fetchall()
def list_method_attempts(
self,
method_id: str,
method_version: int,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
handshake_id,
method_id,
method_version,
job_id,
status,
completed_at
FROM crack_attempts
WHERE method_id = ?
AND method_version = ?
ORDER BY handshake_id
""",
(
method_id,
method_version,
),
).fetchall()
+148
View File
@@ -0,0 +1,148 @@
from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from typing import Any
class AuditRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_event(
self,
event_type: str,
job_id: str | None = None,
client_id: str | None = None,
handshake_id: int | None = None,
method_id: str | None = None,
method_version: int | None = None,
details: dict[str, Any] | None = None,
created_at: str | None = None,
) -> int:
cursor = self._conn.execute(
"""
INSERT INTO crack_audit_log (
created_at,
event_type,
job_id,
client_id,
handshake_id,
method_id,
method_version,
details_json
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
created_at or self._now(),
event_type,
job_id,
client_id,
handshake_id,
method_id,
method_version,
(
json.dumps(
details,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
)
if details is not None
else None
),
),
)
return int(cursor.lastrowid)
def get_event(
self,
event_id: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
created_at,
event_type,
job_id,
client_id,
handshake_id,
method_id,
method_version,
details_json
FROM crack_audit_log
WHERE id = ?
""",
(event_id,),
).fetchone()
def list_events(
self,
event_type: str | None = None,
job_id: str | None = None,
client_id: str | None = None,
handshake_id: int | None = None,
method_id: str | None = None,
method_version: int | None = None,
) -> list[sqlite3.Row]:
query = """
SELECT
id,
created_at,
event_type,
job_id,
client_id,
handshake_id,
method_id,
method_version,
details_json
FROM crack_audit_log
WHERE 1 = 1
"""
params: list[Any] = []
if event_type is not None:
query += "\n AND event_type = ?"
params.append(event_type)
if job_id is not None:
query += "\n AND job_id = ?"
params.append(job_id)
if client_id is not None:
query += "\n AND client_id = ?"
params.append(client_id)
if handshake_id is not None:
query += "\n AND handshake_id = ?"
params.append(handshake_id)
if method_id is not None:
query += "\n AND method_id = ?"
params.append(method_id)
if method_version is not None:
query += "\n AND method_version = ?"
params.append(method_version)
query += "\n ORDER BY created_at DESC, id DESC"
return self._conn.execute(query, params).fetchall()
@staticmethod
def decode_details(row: sqlite3.Row) -> dict[str, Any] | None:
if row["details_json"] is None:
return None
return json.loads(row["details_json"])
+120
View File
@@ -0,0 +1,120 @@
from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from typing import Any
class ClientsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_client(
self,
client_id: str,
name: str,
token_hash: str | None = None,
status: str = "ACTIVE",
metadata: dict[str, Any] | None = None,
) -> None:
metadata_json = (
json.dumps(
metadata,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
)
if metadata is not None
else None
)
self._conn.execute(
"""
INSERT INTO crack_clients (
id,
name,
token_hash,
status,
created_at,
last_seen_at,
metadata_json
)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
client_id,
name,
token_hash,
status,
self._now(),
None,
metadata_json,
),
)
def get_client(self, client_id: str) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
name,
token_hash,
status,
created_at,
last_seen_at,
metadata_json
FROM crack_clients
WHERE id = ?
""",
(client_id,),
).fetchone()
def list_clients(self) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
id,
name,
token_hash,
status,
created_at,
last_seen_at,
metadata_json
FROM crack_clients
ORDER BY id
"""
).fetchall()
def update_last_seen(self, client_id: str) -> None:
self._conn.execute(
"""
UPDATE crack_clients
SET last_seen_at = ?
WHERE id = ?
""",
(
self._now(),
client_id,
),
)
def update_status(self, client_id: str, status: str) -> None:
self._conn.execute(
"""
UPDATE crack_clients
SET status = ?
WHERE id = ?
""",
(
status,
client_id,
),
)
@@ -0,0 +1,95 @@
from __future__ import annotations
import sqlite3
from datetime import datetime, timezone
class CredentialsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def get_credential(
self,
access_point_id: int,
handshake_id: int,
password: str,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
access_point_id,
handshake_id,
password,
source,
created_at,
verified
FROM credentials
WHERE access_point_id = ?
AND handshake_id = ?
AND password = ?
LIMIT 1
""",
(
access_point_id,
handshake_id,
password,
),
).fetchone()
def create_hashcat_credential(
self,
access_point_id: int,
handshake_id: int,
password: str,
) -> int:
cursor = self._conn.execute(
"""
INSERT INTO credentials (
access_point_id,
handshake_id,
password,
source,
created_at,
verified
)
VALUES (?, ?, ?, 'hashcat', ?, 0)
""",
(
access_point_id,
handshake_id,
password,
self._now(),
),
)
self._conn.execute(
"""
UPDATE access_points
SET is_cracked = 1
WHERE id = ?
""",
(access_point_id,),
)
return int(cursor.lastrowid)
def mark_access_point_cracked(
self,
access_point_id: int,
) -> None:
self._conn.execute(
"""
UPDATE access_points
SET is_cracked = 1
WHERE id = ?
""",
(access_point_id,),
)
@@ -0,0 +1,58 @@
from __future__ import annotations
import sqlite3
from typing import Any
class HandshakesRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
def list_eligible(
self,
method_id: str,
method_version: int,
limit: int,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
h.id AS handshake_id,
h.hash22000,
h.access_point_id,
h.captured_at
FROM handshakes AS h
WHERE h.hash22000 IS NOT NULL
AND TRIM(h.hash22000) <> ''
AND NOT EXISTS (
SELECT 1
FROM credentials AS c
WHERE c.handshake_id = h.id
)
AND NOT EXISTS (
SELECT 1
FROM crack_attempts AS a
WHERE a.handshake_id = h.id
AND a.method_id = ?
AND a.method_version = ?
AND a.status = 'COMPLETED'
)
AND NOT EXISTS (
SELECT 1
FROM crack_reservations AS r
WHERE r.handshake_id = h.id
AND r.method_id = ?
AND r.method_version = ?
AND r.status = 'ACTIVE'
)
ORDER BY h.id
LIMIT ?
""",
(
method_id,
method_version,
method_id,
method_version,
limit,
),
).fetchall()
+452
View File
@@ -0,0 +1,452 @@
from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from typing import Any
class JobsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_job(
self,
job_id: str,
method_id: str,
method_version: int,
status: str,
hash_file_name: str,
hash_file_sha256: str,
hash_count: int,
step_count: int,
client_id: str | None = None,
) -> str:
created_at = self._now()
queued_at = created_at if status == "QUEUED" else None
self._conn.execute(
"""
INSERT INTO crack_jobs (
id,
method_id,
method_version,
status,
created_at,
queued_at,
started_at,
completed_at,
cancelled_at,
imported_at,
client_id,
hash_file_name,
hash_file_sha256,
hash_count,
step_count,
result_sha256,
error
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
job_id,
method_id,
method_version,
status,
created_at,
queued_at,
None,
None,
None,
None,
client_id,
hash_file_name,
hash_file_sha256,
hash_count,
step_count,
None,
None,
),
)
return created_at
def get_job(self, job_id: str) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
method_id,
method_version,
status,
created_at,
queued_at,
started_at,
completed_at,
cancelled_at,
imported_at,
client_id,
hash_file_name,
hash_file_sha256,
hash_count,
step_count,
result_sha256,
error
FROM crack_jobs
WHERE id = ?
""",
(job_id,),
).fetchone()
def list_jobs(
self,
status: str | None = None,
method_id: str | None = None,
client_id: str | None = None,
) -> list[sqlite3.Row]:
query = """
SELECT
id,
method_id,
method_version,
status,
created_at,
queued_at,
started_at,
completed_at,
cancelled_at,
imported_at,
client_id,
hash_file_name,
hash_file_sha256,
hash_count,
step_count,
result_sha256,
error
FROM crack_jobs
WHERE 1 = 1
"""
params: list[Any] = []
if status is not None:
query += "\n AND status = ?"
params.append(status)
if method_id is not None:
query += "\n AND method_id = ?"
params.append(method_id)
if client_id is not None:
query += "\n AND client_id = ?"
params.append(client_id)
query += "\n ORDER BY created_at DESC, id DESC"
return self._conn.execute(query, params).fetchall()
def update_job_status(
self,
job_id: str,
status: str,
) -> None:
now = self._now()
timestamp_column = {
"QUEUED": "queued_at",
"RUNNING": "started_at",
"COMPLETED": "completed_at",
"CANCELLED": "cancelled_at",
}.get(status)
if timestamp_column is None:
self._conn.execute(
"""
UPDATE crack_jobs
SET status = ?
WHERE id = ?
""",
(
status,
job_id,
),
)
return
self._conn.execute(
f"""
UPDATE crack_jobs
SET status = ?,
{timestamp_column} = ?
WHERE id = ?
""",
(
status,
now,
job_id,
),
)
def assign_client(
self,
job_id: str,
client_id: str | None,
) -> None:
self._conn.execute(
"""
UPDATE crack_jobs
SET client_id = ?
WHERE id = ?
""",
(
client_id,
job_id,
),
)
def update_job_result(
self,
job_id: str,
result_sha256: str | None = None,
error: str | None = None,
imported_at: str | None = None,
) -> None:
self._conn.execute(
"""
UPDATE crack_jobs
SET result_sha256 = ?,
error = ?,
imported_at = ?
WHERE id = ?
""",
(
result_sha256,
error,
imported_at,
job_id,
),
)
def add_job_handshake(
self,
job_id: str,
handshake_id: int,
hash22000: str,
access_point_id: int | None = None,
) -> None:
self._conn.execute(
"""
INSERT INTO crack_job_handshakes (
job_id,
handshake_id,
hash22000,
access_point_id
)
VALUES (?, ?, ?, ?)
""",
(
job_id,
handshake_id,
hash22000,
access_point_id,
),
)
def get_job_handshake(
self,
job_id: str,
handshake_id: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
job_id,
handshake_id,
hash22000,
access_point_id
FROM crack_job_handshakes
WHERE job_id = ?
AND handshake_id = ?
""",
(
job_id,
handshake_id,
),
).fetchone()
def list_job_handshakes(
self,
job_id: str,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
job_id,
handshake_id,
hash22000,
access_point_id
FROM crack_job_handshakes
WHERE job_id = ?
ORDER BY handshake_id
""",
(job_id,),
).fetchall()
def create_job_step(
self,
job_id: str,
step_no: int,
step_id: str,
status: str,
session_name: str,
definition: dict[str, Any],
) -> None:
self._conn.execute(
"""
INSERT INTO crack_job_steps (
job_id,
step_no,
step_id,
status,
session_name,
definition_json,
started_at,
completed_at,
exit_code,
restore_seen,
error
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
job_id,
step_no,
step_id,
status,
session_name,
json.dumps(
definition,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
),
None,
None,
None,
0,
None,
),
)
def get_job_step(
self,
job_id: str,
step_no: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
job_id,
step_no,
step_id,
status,
session_name,
definition_json,
started_at,
completed_at,
exit_code,
restore_seen,
error
FROM crack_job_steps
WHERE job_id = ?
AND step_no = ?
""",
(
job_id,
step_no,
),
).fetchone()
def list_job_steps(
self,
job_id: str,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
job_id,
step_no,
step_id,
status,
session_name,
definition_json,
started_at,
completed_at,
exit_code,
restore_seen,
error
FROM crack_job_steps
WHERE job_id = ?
ORDER BY step_no
""",
(job_id,),
).fetchall()
def update_job_step(
self,
job_id: str,
step_no: int,
status: str | None = None,
started_at: str | None = None,
completed_at: str | None = None,
exit_code: int | None = None,
restore_seen: bool | None = None,
error: str | None = None,
) -> None:
fields: list[str] = []
params: list[Any] = []
if status is not None:
fields.append("status = ?")
params.append(status)
if started_at is not None:
fields.append("started_at = ?")
params.append(started_at)
if completed_at is not None:
fields.append("completed_at = ?")
params.append(completed_at)
if exit_code is not None:
fields.append("exit_code = ?")
params.append(exit_code)
if restore_seen is not None:
fields.append("restore_seen = ?")
params.append(int(restore_seen))
if error is not None:
fields.append("error = ?")
params.append(error)
if not fields:
return
params.extend((job_id, step_no))
self._conn.execute(
f"""
UPDATE crack_job_steps
SET {", ".join(fields)}
WHERE job_id = ?
AND step_no = ?
""",
params,
)
+149
View File
@@ -0,0 +1,149 @@
from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from typing import Any
class MethodsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_method(
self,
method_id: str,
name: str,
description: str | None = None,
) -> None:
self._conn.execute(
"""
INSERT INTO crack_methods (
id,
name,
description,
created_at
)
VALUES (?, ?, ?, ?)
""",
(
method_id,
name,
description,
self._now(),
),
)
def get_method(self, method_id: str) -> sqlite3.Row | None:
row = self._conn.execute(
"""
SELECT
id,
name,
description,
created_at
FROM crack_methods
WHERE id = ?
""",
(method_id,),
).fetchone()
return row
def list_methods(self) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
id,
name,
description,
created_at
FROM crack_methods
ORDER BY id
"""
).fetchall()
def create_method_version(
self,
method_id: str,
version: int,
definition: dict[str, Any],
) -> None:
self._conn.execute(
"""
INSERT INTO crack_method_versions (
method_id,
version,
definition_json,
created_at
)
VALUES (?, ?, ?, ?)
""",
(
method_id,
version,
json.dumps(
definition,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
),
self._now(),
),
)
def get_method_version(
self,
method_id: str,
version: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
method_id,
version,
definition_json,
created_at
FROM crack_method_versions
WHERE method_id = ?
AND version = ?
""",
(method_id, version),
).fetchone()
def list_method_versions(
self,
method_id: str,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
method_id,
version,
definition_json,
created_at
FROM crack_method_versions
WHERE method_id = ?
ORDER BY version
""",
(method_id,),
).fetchall()
def get_next_method_version(self, method_id: str) -> int:
row = self._conn.execute(
"""
SELECT COALESCE(MAX(version), 0) + 1
FROM crack_method_versions
WHERE method_id = ?
""",
(method_id,),
).fetchone()
return int(row[0])
@@ -0,0 +1,209 @@
from __future__ import annotations
import sqlite3
from datetime import datetime, timezone
class ReservationsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_reservation(
self,
reservation_id: str,
handshake_id: int,
method_id: str,
method_version: int,
job_id: str,
client_id: str | None = None,
expires_at: str | None = None,
) -> None:
self._conn.execute(
"""
INSERT INTO crack_reservations (
id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
status,
created_at,
expires_at,
released_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
reservation_id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
"ACTIVE",
self._now(),
expires_at,
None,
),
)
def get_reservation(
self,
reservation_id: str,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
status,
created_at,
expires_at,
released_at
FROM crack_reservations
WHERE id = ?
""",
(reservation_id,),
).fetchone()
def get_active_reservation(
self,
handshake_id: int,
method_id: str,
method_version: int,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
status,
created_at,
expires_at,
released_at
FROM crack_reservations
WHERE handshake_id = ?
AND method_id = ?
AND method_version = ?
AND status = 'ACTIVE'
""",
(
handshake_id,
method_id,
method_version,
),
).fetchone()
def list_job_reservations(
self,
job_id: str,
) -> list[sqlite3.Row]:
return self._conn.execute(
"""
SELECT
id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
status,
created_at,
expires_at,
released_at
FROM crack_reservations
WHERE job_id = ?
ORDER BY created_at, id
""",
(job_id,),
).fetchall()
def list_client_reservations(
self,
client_id: str,
status: str | None = None,
) -> list[sqlite3.Row]:
query = """
SELECT
id,
handshake_id,
method_id,
method_version,
job_id,
client_id,
status,
created_at,
expires_at,
released_at
FROM crack_reservations
WHERE client_id = ?
"""
params: list[object] = [client_id]
if status is not None:
query += "\n AND status = ?"
params.append(status)
query += "\n ORDER BY created_at, id"
return self._conn.execute(query, params).fetchall()
def update_status(
self,
reservation_id: str,
status: str,
released_at: str | None = None,
) -> None:
if released_at is None and status in {
"RELEASED",
"EXPIRED",
"CANCELLED",
}:
released_at = self._now()
self._conn.execute(
"""
UPDATE crack_reservations
SET status = ?,
released_at = ?
WHERE id = ?
""",
(
status,
released_at,
reservation_id,
),
)
def update_expires_at(
self,
reservation_id: str,
expires_at: str | None,
) -> None:
self._conn.execute(
"""
UPDATE crack_reservations
SET expires_at = ?
WHERE id = ?
""",
(
expires_at,
reservation_id,
),
)
+92
View File
@@ -0,0 +1,92 @@
from __future__ import annotations
import json
import sqlite3
from datetime import datetime, timezone
from typing import Any
class ResultsRepository:
def __init__(self, conn: sqlite3.Connection) -> None:
self._conn = conn
@staticmethod
def _now() -> str:
return datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace(
"+00:00",
"Z",
)
def create_result(
self,
job_id: str,
status: str,
result: dict[str, Any],
imported_at: str | None = None,
) -> None:
self._conn.execute(
"""
INSERT INTO crack_results (
job_id,
status,
result_json,
imported_at
)
VALUES (?, ?, ?, ?)
""",
(
job_id,
status,
json.dumps(
result,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
),
imported_at or self._now(),
),
)
def get_result(
self,
job_id: str,
) -> sqlite3.Row | None:
return self._conn.execute(
"""
SELECT
job_id,
status,
result_json,
imported_at
FROM crack_results
WHERE job_id = ?
""",
(job_id,),
).fetchone()
def list_results(
self,
status: str | None = None,
) -> list[sqlite3.Row]:
query = """
SELECT
job_id,
status,
result_json,
imported_at
FROM crack_results
WHERE 1 = 1
"""
params: list[Any] = []
if status is not None:
query += "\n AND status = ?"
params.append(status)
query += "\n ORDER BY imported_at DESC, job_id DESC"
return self._conn.execute(query, params).fetchall()
@staticmethod
def decode_result(row: sqlite3.Row) -> dict[str, Any]:
return json.loads(row["result_json"])