chore: curatare agresiva comentarii — scoatere referinte US/PRD din cod si template-uri
Eliminat zgomotul de trasabilitate (US-xxx, PRD x.x, Rn, OV-x, Tn, decizii/naratiune istorica) din 41 fisiere app/ + template-uri. Pastrate comentariile care documenteaza invarianti si logica ne-evidenta (idempotenta/hash, reconciliere anti-duplicat, RAR 500 esec definitiv, creds per cont, WAF User-Agent, 422 fara echo de parola, scope NULL->1), curatate doar de tokeni. Verificare: pentru cele 27 module .py curatate, structura de cod (tokeni non-comentariu/ non-string) e IDENTICA fata de HEAD -> doar comentarii/docstring-uri schimbate. Singura schimbare de cod e in tests/test_web_responsive.py (scos 3 assert pe markeri US-006/007/008, inlocuite de asertiunile structurale alaturate). 0 tokeni US/PRD reziduali in app/. Regresie: 896 passed, 1 deselected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,23 @@
|
||||
"""Worker RAR — proces propriu (NU task asyncio in uvicorn; plan.md sect. 4).
|
||||
"""Worker RAR — proces propriu (NU task asyncio in uvicorn).
|
||||
|
||||
Bucla: heartbeat -> recupereaza orfane -> claim atomic -> login -> postPrezentare -> update.
|
||||
Ruleaza ca proces separat sub `restart: always` (docker compose).
|
||||
|
||||
T2 implementat:
|
||||
- claim atomic anti-race (BEGIN IMMEDIATE), respecta next_attempt_at (backoff).
|
||||
- reconciliere anti-duplicat pe raspuns pierdut: pe eroare tranzitorie/timeout SAU pe
|
||||
rand 'sending' orfan (worker mort mid-POST), interogheaza finalizate si match pe
|
||||
vin+dataPrestatie+odometruFinal; daca exista -> 'sent' (NU re-trimite).
|
||||
- retry/backoff exponential pe erori tranzitorii; peste worker_max_retries -> 'error' (banner).
|
||||
- retry/backoff exponential pe erori tranzitorii; peste worker_max_retries -> 'error'.
|
||||
- lease/timeout pe randuri 'sending' orfane.
|
||||
- re-login la token expirat (401 mid-sesiune) — JWT 30h, retry NU plafonat la 30h.
|
||||
|
||||
Creds per-cerere (plan sect. 5): fiecare submission poarta creds RAR CRIPTATE
|
||||
(rar_creds_enc). Worker-ul face login per CONT cu acele creds, cache-uieste JWT
|
||||
(30h) in memorie si STERGE creds-urile contului dupa primul login reusit. Token-ul
|
||||
in memorie acopera restul trimiterilor; la restart token-ul se pierde si contul
|
||||
re-loghează la urmatorul submission care aduce creds proaspete (degradare acceptata).
|
||||
Creds per-cerere: fiecare submission poarta creds RAR CRIPTATE (rar_creds_enc).
|
||||
Worker-ul face login per CONT cu acele creds, cache-uieste JWT (30h) in memorie si
|
||||
STERGE creds-urile contului dupa primul login reusit. Token-ul in memorie acopera
|
||||
restul trimiterilor; la restart token-ul se pierde si contul re-logheaza la urmatorul
|
||||
submission care aduce creds proaspete (degradare acceptata).
|
||||
Dev: `worker_use_test_creds` foloseste creds <test> cand submission-ul nu are enc.
|
||||
|
||||
Ce NU e inca: criptare PII payload at-rest (P2), b64Image mare pe disc (P2).
|
||||
|
||||
Pornire: python -m app.worker
|
||||
"""
|
||||
|
||||
@@ -61,8 +58,8 @@ def _iso(dt: datetime) -> str:
|
||||
|
||||
|
||||
def _wlog(conn, tip: str, mesaj: str, *, nivel: str = "INFO", account_id=None, cod=None, context=None) -> None:
|
||||
"""Migrare print -> jurnal structurat (US-005): emite evenimentul (sursa=worker, dublu
|
||||
canal DB+fisier) SI pastreaza linia in stdout (operatorul tailuieste .run/worker.log)."""
|
||||
"""Emite evenimentul (sursa=worker, dublu canal DB+fisier) SI pastreaza linia in
|
||||
stdout (operatorul tailuieste .run/worker.log)."""
|
||||
print(f"[worker] {mesaj}", flush=True)
|
||||
log_event(tip, nivel=nivel, account_id=account_id, cod=cod, mesaj=mesaj, context=context,
|
||||
conn=conn, sursa="worker")
|
||||
@@ -84,17 +81,17 @@ def _is_transient(exc: Exception) -> bool:
|
||||
|
||||
# --- Operatii pe submissions ---
|
||||
|
||||
# Stari blocate ne-sent care primesc retentie proprie (US-013). Mai scurta decat
|
||||
# cele 90z ale `sent`: un blocat n-are valoare de audit ca o trimitere reusita.
|
||||
# Stari blocate ne-sent care primesc retentie proprie. Mai scurta decat cele 90z
|
||||
# ale `sent`: un blocat n-are valoare de audit ca o trimitere reusita.
|
||||
_BLOCKED_STATES = ("error", "needs_data", "needs_mapping")
|
||||
|
||||
|
||||
def mark(conn, submission_id: int, status: str, *, rar_status_code=None, rar_error=None, id_prezentare=None) -> None:
|
||||
if status == "sent":
|
||||
# T16: purge_after = sent + 90 zile (GDPR/L.142 retentie maxima).
|
||||
# purge_after = sent + 90 zile (GDPR/L.142 retentie maxima).
|
||||
purge_expr = "datetime('now', '+90 days')"
|
||||
elif status in _BLOCKED_STATES:
|
||||
# US-013: randurile blocate primesc si ele purge_after (altfel raman permanent).
|
||||
# Randurile blocate primesc si ele purge_after (altfel raman permanent).
|
||||
days = int(get_settings().blocked_retention_days)
|
||||
purge_expr = f"datetime('now', '+{days} days')"
|
||||
else:
|
||||
@@ -114,15 +111,15 @@ def mark(conn, submission_id: int, status: str, *, rar_status_code=None, rar_err
|
||||
)
|
||||
|
||||
|
||||
# T16: purge interval in secunde (odata pe ora, nu prea agresiv)
|
||||
# Purge interval in secunde (odata pe ora, nu prea agresiv)
|
||||
_PURGE_INTERVAL_S = 3600
|
||||
|
||||
|
||||
def purge_expired(conn) -> dict[str, int]:
|
||||
"""Sterge randurile expirate (purge_after < now).
|
||||
|
||||
T16/OV-5 + US-013/US-008: submissions `sent` SI blocate (error/needs_data/needs_mapping)
|
||||
expirate; import_batches expirate (import_rows via CASCADE); app_events expirate (jurnal).
|
||||
Submissions `sent` SI blocate (error/needs_data/needs_mapping) expirate;
|
||||
import_batches expirate (import_rows via CASCADE); app_events expirate (jurnal).
|
||||
EXCLUDE explicit `queued`/`sending` (randuri active — nu se purjeaza niciodata, chiar
|
||||
daca ar avea un purge_after rezidual; reactivarea il curata oricum).
|
||||
Intoarce {submissions_purged, batches_purged, events_purged}.
|
||||
@@ -174,7 +171,7 @@ def claim_one(conn) -> dict | None:
|
||||
"FROM submissions s LEFT JOIN accounts a ON a.id = s.account_id "
|
||||
"WHERE s.status='queued' "
|
||||
"AND (s.next_attempt_at IS NULL OR s.next_attempt_at <= ?) "
|
||||
# Gate pe stare de cont (5.5): doar 'active' trimite. Derivam defensiv din `active`
|
||||
# Gate pe stare de cont: doar 'active' trimite. Derivam defensiv din `active`
|
||||
# cand `status` lipseste (DB veche pre-migrare), pastrand active=1 <=> 'active'.
|
||||
"AND COALESCE(a.status, CASE WHEN COALESCE(a.active,1)=1 THEN 'active' ELSE 'pending' END) = 'active' "
|
||||
"ORDER BY s.id LIMIT 1",
|
||||
@@ -253,7 +250,7 @@ def process_one(conn, settings: Settings, rar: RarClient, token: str, claimed: d
|
||||
# RAR a raspuns DEFINITIV cu o eroare de procesare (ex. ORA-12899). NU e o
|
||||
# pierdere de raspuns ambigua -> NU reconcilia (recordul, daca exista la RAR,
|
||||
# e PARTIAL/rupt si nu trebuie marcat fals 'sent') si NU reincerca (acelasi
|
||||
# input va esua iar). Marcam 'error' cu mesajul real RAR. (Confirmat live 2026-06-23.)
|
||||
# input va esua iar). Marcam 'error' cu mesajul real RAR.
|
||||
detail = json.dumps(errors.eroare("RAR_EROARE_SERVER", cauza=exc.rar_message), ensure_ascii=False)
|
||||
mark(conn, sid, "error", rar_status_code=500, rar_error=detail)
|
||||
_wlog(conn, "submission_error", f"submission {sid} -> error (RAR 500): {exc.rar_message}",
|
||||
@@ -363,7 +360,7 @@ class AccountSessions:
|
||||
token = rar.login(creds["email"], creds["password"])
|
||||
except RarAuthError as exc:
|
||||
rar.close()
|
||||
# US-005: login esuat (401) — FARA email/parola (doar codul HTTP + contul).
|
||||
# Login esuat (401) — FARA email/parola (doar codul HTTP + contul).
|
||||
log_event("rar_login", nivel="WARNING", account_id=account_id,
|
||||
cod="RAR_CREDS_INVALIDE",
|
||||
mesaj=f"login RAR esuat (cont {account_id}): {exc.status_code or 401}",
|
||||
@@ -375,11 +372,11 @@ class AccountSessions:
|
||||
raise
|
||||
self._sessions[account_id] = (rar, token)
|
||||
write_heartbeat(conn, rar_login_ok=True, detail=f"login RAR ok (cont {account_id})")
|
||||
# US-005: login reusit (fara email/parola in clar — context curat).
|
||||
# Login reusit (fara email/parola in clar — context curat).
|
||||
log_event("rar_login", account_id=account_id, mesaj=f"login RAR ok (cont {account_id})",
|
||||
context={"rezultat": "ok", "http": 200}, conn=conn, sursa="worker")
|
||||
# Creds efemere pe submissions nu mai sunt necesare: JWT acopera retry-urile -> sterge.
|
||||
# GATE PURJARE (T1/Voce#5): sterge DOAR submissions.rar_creds_enc, NU accounts.rar_creds_enc.
|
||||
# GATE PURJARE: sterge DOAR submissions.rar_creds_enc, NU accounts.rar_creds_enc.
|
||||
# Canal web: fallback exista in accounts -> purjarea e inofensiva (re-login dupa restart).
|
||||
# Canal API pur: purjarea e identica cu Treapta 1 (neatinsa).
|
||||
conn.execute(
|
||||
@@ -418,7 +415,7 @@ def _creds_for(claimed: dict, settings: Settings) -> dict | None:
|
||||
|
||||
|
||||
def _creds_from_account(conn, account_id: int) -> dict | None:
|
||||
"""Fallback T1/D4: crede RAR durabile per-cont din accounts.rar_creds_enc.
|
||||
"""Fallback: crede RAR durabile per-cont din accounts.rar_creds_enc.
|
||||
|
||||
Canal web nu are re-pusher. Cand submission n-are creds (sterse dupa primul login
|
||||
sau upload web fara creds), worker-ul re-citeste din cont si poate re-login oricand.
|
||||
@@ -436,7 +433,7 @@ def run() -> int:
|
||||
signal.signal(signal.SIGINT, _stop)
|
||||
|
||||
settings = get_settings()
|
||||
set_source("worker") # US-005: evenimentele worker-ului au sursa=worker (fisier app-worker.log)
|
||||
set_source("worker") # evenimentele worker-ului au sursa=worker (fisier app-worker.log)
|
||||
init_db()
|
||||
conn = get_connection()
|
||||
print(f"[worker] pornit (send_enabled={settings.worker_send_enabled}, env={settings.rar_env})", flush=True)
|
||||
@@ -448,7 +445,7 @@ def run() -> int:
|
||||
try:
|
||||
write_heartbeat(conn, detail=f"poll (queue={_queue_depth(conn)})")
|
||||
|
||||
# T16: purjare periodica (odata pe ora) — NU mai frecvent.
|
||||
# Purjare periodica (odata pe ora) — NU mai frecvent.
|
||||
now_ts = time.time()
|
||||
if now_ts - _last_purge_time >= _PURGE_INTERVAL_S:
|
||||
stats = purge_expired(conn)
|
||||
@@ -474,20 +471,20 @@ def run() -> int:
|
||||
|
||||
sid = claimed["id"]
|
||||
account_id = claimed["account_id"]
|
||||
# T1/US-012: randul poarta creds proaspete (rar_creds_enc != NULL) — fie prima
|
||||
# trimitere a contului, fie o REACTIVARE dupa creds gresite. Invalidam sesiunea
|
||||
# RAR cache-uita ca un JWT vechi (30h) din parola GRESITA sa nu trimita cu ea,
|
||||
# Randul poarta creds proaspete (rar_creds_enc != NULL) — fie prima trimitere
|
||||
# a contului, fie o REACTIVARE dupa creds gresite. Invalidam sesiunea RAR
|
||||
# cache-uita ca un JWT vechi (30h) din parola GRESITA sa nu trimita cu ea,
|
||||
# ignorand corectia. Re-login imediat cu creds-urile noi.
|
||||
if claimed.get("creds_enc"):
|
||||
sessions.invalidate(account_id)
|
||||
# T1/D4: incearca creds din submission (canal API efemer), cu fallback la
|
||||
# Incearca creds din submission (canal API efemer), cu fallback la
|
||||
# accounts.rar_creds_enc (canal web durabil). Canal web n-are re-pusher.
|
||||
creds = _creds_for(claimed, settings) or _creds_from_account(conn, account_id)
|
||||
|
||||
try:
|
||||
token = sessions.get_token(conn, account_id, creds)
|
||||
except RarAuthError as exc:
|
||||
# Creds gresite (login 401): NU se face retry (plan, failure registry).
|
||||
# Creds gresite (login 401): NU se face retry.
|
||||
mark(conn, sid, "error", rar_status_code=401,
|
||||
rar_error=json.dumps(errors.eroare("RAR_CREDS_INVALIDE", cauza="credentiale RAR invalide"), ensure_ascii=False))
|
||||
# rar_login esuat e deja logat in get_token; aici doar tranzitia submission-ului.
|
||||
|
||||
Reference in New Issue
Block a user