Implementeaza PRD 5.6 complet (14 stories, TDD). Doua axe:
Lifecycle trimiteri blocate (Val A):
- submissions_admin.py: sterge/repune scoped (404 cross-account inaintea lui 409 stare)
- reactivare dedup peste `error` cu CAS (WHERE id=? AND status='error'), creds noi in
submissions + accounts.rar_creds_enc; worker invalideaza sesiunea RAR la creds proaspete
(JWT 30h vechi nu mai trimite cu parola gresita); camp aditiv `reactivated:true`
- retentie randuri blocate 30z; purge_expired exclude queued/sending; purge_after curatat
la reactivare/requeue
- API DELETE /v1/prezentari/{id} + /repune (200+JSON); UI butoane + bulk + banner actionabil
Observabilitate:
- app/observ.py log_event: dublu canal app_events (DB) + RotatingFileHandler per-proces,
redactare creds/PII la scriere (redact_pii/vin_partial)
- request_id middleware + X-Request-ID pe toate raspunsurile
- handler global excepții -> 500 envelope 6-chei + request_id (traceback doar in jurnal)
- audit cerere API (api_prezentari/api_auth_esuat) + audit worker (rar_login/tranzitii)
- tab "Jurnal" filtrabil scoped (non-admin doar contul sau); retentie jurnal 90z
- rar_error expus in GET /v1/prezentari/{id} (recovery observabil)
pytest -q: 741 passed, 0 failed. Docs: PRD raport VERIFY, contract endpointuri noi, ROADMAP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39 KiB
PRD 5.6 — Observabilitate, jurnal aplicatie & lifecycle trimiteri blocate
Stare: aprobat + review /autoplan complet (4 decizii de gust rezolvate 2026-06-23; vezi Anexa /autoplan)
Proces complet:
docs/ROADMAP.md§5. Contract RAR (sursa de adevar):docs/api-rar-contract.md. Catalog erori (sursa de adevar coduri):app/errors.py(PRD 5.4). Redactare creds:app/security.py.
0. Context — de ce acum
Un client ROAAUTO (Visual FoxPro, MSXML2.ServerXMLHTTP) a primit "Internal Server
Error" la POST /v1/prezentari si nu a existat niciun mod de a vedea ce s-a intamplat
fara a citi traceback-ul brut din access-log-ul uvicorn (.run/api.log).
Cauza concreta a fost o cheie Fernet invalida in .env (AUTOPASS_CREDS_KEY),
care arunca ValueError abia la primul encrypt_creds — un 500 brut, fara mesaj
util pentru client si fara inregistrare la nivel de aplicatie. Cauza a fost reparata
ca hotfix (cheie valida in .env + validare fail-fast la startup —
crypto.validate_creds_key, apelata in main.lifespan; confirmat live HTTP 200).
Acest incident a expus trei goluri structurale, care sunt obiectul acestui PRD:
- Excepțiile interne devin 500 brut, fara traducere in contractul de erori pe 3 niveluri (PRD 5.4) si fara log cu context (request_id, ruta, cont).
- Nu exista jurnal de aplicatie la nivel de eveniment — doar access-log uvicorn
(linii HTTP) +
print(...)ad-hoc in worker. Nu se poate raspunde la "ce s-a intamplat cu cererea X / contul Y" fara grep prin traceback-uri. - Nu exista monitorizare a fluxului de afaceri dincolo de
/healthz+/metrics: incercari de login RAR, ciclul de viata al trimiterilor, erori din catalog.
0bis. Context — lacune de lifecycle (descoperite la testarea live)
La testul live pe RAR test au iesit la iveala doua probleme de lifecycle, confirmate in cod:
- Trimiterile blocate sunt permanente.
purge_afterse seteaza DOAR lastatus='sent'(mark()), iarpurge_expiredsterge DOAR randurisentexpirate. Randurileerror/needs_data/needs_mappingnu primesc niciodatapurge_after→ nu se purjeaza niciodata. Login 401 (creds RAR gresite) →errordirect, fara retry (by design, ca sa nu blocheze contul) → randul ramane la nesfarsit in dashboard, fara cale de stergere prin UI/API/CLI (corectia inline US-010 e doar pentruneeds_*, nuerror). - Un rand
errorblocheaza retrimiterea aceluiasi payload. Cheia de idempotenta e hash de CONTINUT (vin+nr+data+odometru+prestatii+cont) — parola NU intra in cheie. Daca un client trimite cu parola gresita (→error), apoi corecteaza parola si retrimite acelasi payload, primestededuped: truecustatus: errorsi prezentarea nu se mai trimite niciodata. Randul eronat "fura" cheia.
Reproductibil acum: submission 15 (din clientul VFP cu parola placeholder) e blocat pe
error RAR_CREDS_INVALIDE; testul reusit (submission 16 → idPrezentare 68818) a
necesitat un odometru diferit tocmai ca sa ocoleasca aceasta capcana.
1. Obiectiv
Un sistem de observabilitate coerent: orice eveniment relevant (cerere API, login RAR,
tranzitie de trimitere, eroare) este inregistrat structurat intr-un tabel app_events
(vizibil intr-un tab "Jurnal" din dashboard, filtrabil pe cont/tip/data) si intr-un
log text pentru depanare low-level. Orice excepție neasteptata produce un raspuns pe 3
niveluri (PRD 5.4) in loc de 500 brut. PII si credentialele sunt redactate peste tot.
In plus, inchidem doua lacune de lifecycle descoperite la testarea live (vezi §0bis):
trimiterile blocate (mai ales error din creds RAR gresite) sunt azi permanente, nu se
pot sterge/re-pune in coada din interfata, blocheaza retrimiterea aceluiasi payload prin
dedup si nu se purjeaza niciodata. Le facem gestionabile (sterge / re-pune in coada),
deblocam dedup-ul si le aducem sub retentie.
2. Non-Goals (anti scope-creep)
- Fara dependinte/infrastructura noi de observabilitate (Sentry, ELK, Loki, OpenTelemetry, Prometheus push). Ramanem pe SQLite + fisier + dashboard HTMX existent.
- Fara alerting (email/SMS/webhook la eroare).
notify_signupramane singura notificare; alertarea = follow-up daca apare nevoia din uz real. - Nu schimbam contractul de erori (PRD 5.4). Pe partea de observabilitate doar
observam si traducem 500-urile ramase. Partea de lifecycle (US-009+) adauga DOAR
doua tranzitii noi controlate —
error → queued(re-pune in coada) si stergere de randuri ne-sent — fara a atinge logica de trimitere a worker-ului. - Nu stergem/atingem randuri
sentprin noile actiuni de lifecycle: sunt dovada de trimitere la RAR (audit). Stergerea/re-punerea opereaza DOAR peerror/needs_data/needs_mapping;sending(in zbor) e protejat de lease-ul worker-ului. - Nu anulam nimic la RAR —
FINALIZATAramane terminal acolo (fara API de anulare). Stergem doar randul LOCAL din coada gateway-ului, nu inregistrarea de la RAR. - Nu modificam
/healthzsi/metrics(raman; jurnalul e complementar, nu inlocuitor). - Fara UI de configurare a logarii (nivel/retentie se seteaza din env, nu din web).
- Nu logam corpuri de payload integral (PII vehicul/proprietar) — doar metadate + identificatori (submission_id, cont, cod eroare). VIN/nr se logheaza doar redactat/partial.
3. Stories atomice
US-000 (hotfix 500) e DEJA LIVRAT in afara procesului normal (vezi §0): cheie Fernet valida in
.env+crypto.validate_creds_key()apelata inmain.lifespan, confirmata live (POST VFP → 200,queued). Listata aici doar pentru trasabilitate; nu se re-executa.
US-001: Handler global de excepții → eroare 3 niveluri + log
Ca integrator ROAAUTO vreau ca orice eroare interna sa-mi intoarca un raspuns structurat (nu "Internal Server Error" gol) pentru ca sa stiu daca e problema mea (date) sau a gateway-ului, si sa pot raporta cu un identificator.
- Depinde de: — (US-003 imbogateste logul; handlerul poate loga si simplu intai)
- Fisiere:
app/main.py(@app.exception_handler(Exception)),app/errors.py(cod nouEROARE_INTERNA),tests/test_error_handler.py - Test intai (RED):
tests/test_error_handler.py—test_exceptie_neasteptata_da_500_structurat,test_raspuns_contine_request_id_fara_traceback,test_creds_nu_apar_in_raspuns - Acceptance criteria:
- O excepție neprinsa pe orice ruta → HTTP 500 cu body
{cod: "EROARE_INTERNA", problema, fix, request_id}(3 niveluri, PRD 5.4). - Body-ul NU contine traceback, mesaj de excepție brut, sau credentiale (scrub).
- Traceback-ul complet +
request_id+ ruta +account_idse scriu in log (fisier) prinscrub(app/security.py), niciodata in raspuns. - Handlerele existente (LoginRequired/AdminRequired/CSRF/RequestValidationError)
raman neatinse; doar
Exceptiongeneric e nou.
- O excepție neprinsa pe orice ruta → HTTP 500 cu body
- Verificare E2E: forteaza o excepție (ex. cheie Fernet invalida pe o ruta de test) → raspuns JSON 3 niveluri cu request_id; traceback doar in log.
US-002: request_id per cerere (corelare)
Ca operator vreau un identificator unic pe fiecare cerere pentru ca sa pot lega raspunsul clientului de randul din jurnal si de traceback.
- Depinde de: —
- Fisiere:
app/web/middleware.py(sau middleware inmain.py),tests/test_request_id.py - Test intai (RED):
tests/test_request_id.py—test_raspuns_are_header_x_request_id,test_request_id_propagat_in_log - Acceptance criteria:
- Fiecare raspuns are header
X-Request-ID(generat daca clientul nu trimite unul). request_ide disponibil in handlerul de erori (US-001) si in logger (US-003) pe durata cererii (contextvar, fara a polua semnaturi).- Format opac, fara PII (ex.
secrets.token_hex(8)).
- Fiecare raspuns are header
- Verificare E2E: doua cereri → doua
X-Request-IDdistincte, regasite in jurnal.
US-003: Logger structurat central (app/observ.py)
Ca dezvoltator vreau un singur punct prin care se emit evenimente pentru ca formatul, redactarea si dublul canal (DB + fisier) sa fie consistente si imposibil de ocolit.
- Depinde de: US-002 (request_id), schema
app_events(US-004) - Fisiere:
app/observ.py(modul nou:log_event(...)),app/schema.sql(tabelaapp_events),app/db.py(helper insert + read paginat),tests/test_observ.py - Test intai (RED):
tests/test_observ.py—test_log_event_scrie_in_db_si_fisier,test_log_event_redacteaza_pii_si_creds,test_nivel_filtrat_din_env - Acceptance criteria:
log_event(tip, *, nivel, account_id=None, cod=None, mesaj=None, context=None)scrie un rand inapp_eventsSI o linie in log text (acelasi continut redactat).- Toate valorile trec prin
scrub(app/security.py) inainte de persistare — parole/token-uri/rar_credentials→***REDACTED***; VIN logat doar partial. - Nivelul minim e configurabil din env (
AUTOPASS_LOG_LEVEL, defaultINFO). - Eroarea la scrierea jurnalului NU propaga (best-effort, ca
notify_signup): o cadere a logului nu doboara cererea/worker-ul. app_events:id, ts, request_id, account_id, sursa(api|worker), tip, nivel, cod, mesaj, context_json, purge_after.
- Verificare E2E: apel
log_eventdin shell → rand in DB + linie in fisier, ambele redactate.
US-004: Audit cerere API per cont
Ca operator vreau sa vad fiecare cerere /v1/* (cine, ce, rezultat)
pentru ca sa pot diagnostica integrari (ex. clientul VFP) fara acces la server.
- Depinde de: US-003
- Fisiere: middleware/dependinta in
app/api/v1/(hook pe rutele v1),app/api/v1/router.py(evenimente la enqueue),tests/test_audit_api.py - Test intai (RED):
tests/test_audit_api.py—test_post_prezentari_logheaza_eveniment_cont,test_eveniment_contine_status_si_count_fara_pii,test_401_logat_ca_auth_esuat - Acceptance criteria:
POST /v1/prezentariemite evenimentapi_prezentaricu:account_id, nr. prezentari, distributie status rezultat (queued/needs_data/needs_mapping/deduped).- Esecurile de auth (401 cheie invalida/lipsa in prod) emit
api_auth_esuatcu IP + prefix cheie (nu cheia intreaga). - Niciun camp de payload PII integral (doar count + statusuri + coduri).
- Verificare E2E: POST ca VFP (cheie valida + invalida) → ambele apar in jurnal cu cont/rezultat.
US-005: Audit login RAR + ciclu de viata trimiteri (worker)
Ca operator vreau sa vad incercarile de login RAR si tranzitiile trimiterilor pentru ca "nu exista incercari de login vizibile" a fost o plangere directa.
- Depinde de: US-003
- Fisiere:
app/worker/__main__.py(inlocuiesteprint(...)culog_event),app/rar_client.py(eveniment login ok/esuat),tests/test_worker_observ.py - Test intai (RED):
tests/test_worker_observ.py—test_login_reusit_logat,test_login_401_logat_fara_parola,test_tranzitie_sent_si_error_logate - Acceptance criteria:
- Login RAR (reusit/esuat) → eveniment
rar_logincuaccount_id, rezultat, cod HTTP; fara email/parola in clar (scrub). - Tranzitiile
sending→sent/→needs_data/→error/ reconciliere → evenimente cusubmission_id,account_id, cod eroare din catalog. print(...)existente din worker migrate lalog_event(sursa=worker), fara a pierde mesajele in stdout (logul text ramane).
- Login RAR (reusit/esuat) → eveniment
- Verificare E2E: o trimitere live pe RAR test (
--send) →rar_loginok +sentcuidPrezentarein jurnal.
US-006: Tab "Jurnal" in dashboard (admin, filtrabil)
Ca admin vreau sa vad jurnalul in dashboard pentru ca sa diagnostichez fara SSH.
- Depinde de: US-003 (date), US-004/US-005 (continut util)
- Fisiere:
app/web/routes.py(ruta/_fragments/jurnal+ tab),app/web/admin_routes.py(gating admin daca e global),app/web/templates/_jurnal.html,tests/test_web_jurnal.py - Test intai (RED):
tests/test_web_jurnal.py—test_jurnal_doar_admin,test_filtru_pe_tip_si_data,test_non_admin_vede_doar_evenimentele_contului_sau - Acceptance criteria:
- Tab "Jurnal" cu lista paginata: ts, sursa, tip, nivel, cont, cod, mesaj (redactat).
- Filtre: tip eveniment, nivel, interval data, (admin) cont. Scoped: un cont non-admin vede DOAR evenimentele proprii (regula NULL→cont 1, ca restul UI-ului).
- Stil consistent cu tabelele PRD 5.5 (grila
.tablewrap), AA light+dark. - Fara expunere de creds/PII (rendare din campuri deja redactate la scriere).
- Verificare E2E: browser HTMX pe
/→ tab Jurnal, filtrare perar_login/api_prezentari, scoping verificat cu 2 conturi.
US-007: Redactare PII/parole in jurnal (gard de siguranta)
Ca responsabil GDPR vreau garantia ca jurnalul nu scurge date sensibile pentru ca PII vehicul/proprietar + creds RAR nu au voie in loguri (L.142/GDPR).
- Depinde de: US-003
- Fisiere:
app/security.py(extindescrub/SENSITIVE_KEYSdaca e nevoie),tests/test_jurnal_redactare.py - Test intai (RED):
tests/test_jurnal_redactare.py—test_parola_niciodata_in_app_events,test_vin_logat_partial,test_payload_integral_nu_se_logheaza - Acceptance criteria:
- Niciun rand
app_events(sau linie fisier) nu continepassword/token/ email creds in clar — verificat prin scanare la nivel de test. - VIN/nr inmatriculare se logheaza doar partial (ex. ultimele 4) sau hash scurt.
- Test "fuzz": evenimente cu chei sensibile in
context→ toate mascate.
- Niciun rand
- Verificare E2E: provoaca eroare cu creds reale → cauta parola in
app_events+ fisier → 0 hits.
US-008: Retentie / purjare jurnal (GDPR)
Ca responsabil GDPR vreau ca jurnalul sa se auto-stearga dupa o perioada
pentru ca retentia PII e limitata (acelasi mecanism ca submissions/import_batches).
- Depinde de: US-003
- Fisiere:
app/worker/__main__.py(purge_expiredextins peapp_events),app/schema.sql(purge_after),tests/test_jurnal_retentie.py - Test intai (RED):
tests/test_jurnal_retentie.py—test_app_events_primesc_purge_after,test_purjare_sterge_evenimente_expirate - Acceptance criteria:
- Fiecare rand
app_eventsprimestepurge_after = now + 90 zile(AUTOPASS_LOG_RETENTION_DAYS, default 90 — decizie §5). - Purjarea orara existenta (T16) sterge si
app_eventsexpirate. - Logul text foloseste
RotatingFileHandlerin aplicatie (rotatie pe dimensiune, N fisiere de backup) — decizie §5; nu depindem de deploy pentru rotatie.
- Fiecare rand
- Verificare E2E: insereaza eveniment cu
purge_afterin trecut → rulează purjarea → dispare.
US-009: Backend — sterge + re-pune in coada randuri ne-sent (helper)
Ca operator vreau sa pot sterge sau re-pune in coada o trimitere blocata
pentru ca un rand error (creds gresite) ramane altfel permanent si nereparabil.
- Depinde de: —
- Fisiere:
app/submissions_admin.py(modul nou:delete_submission,requeue_submission),tests/test_submissions_admin.py - Test intai (RED):
tests/test_submissions_admin.py—test_sterge_rand_error_scoped,test_nu_sterge_sent_sau_sending,test_repune_error_devine_queued_reset_retry,test_repune_re_ruleaza_classify,test_scope_cross_account_404 - Acceptance criteria:
delete_submission(conn, account_id, sid)sterge randul DOAR daca eerror/needs_data/needs_mappingSI apartine contului; altfel ridica/Intoarce refuz (sent/sending → interzis, cross-account → inexistent).requeue_submission(conn, account_id, sid)mutaerror → queued, reseteazaretry_count=0,next_attempt_at=NULL,sending_since=NULL, re-ruleazaclassifype payload (poate ajungeneeds_data/needs_mappingdaca continutul cere).- Niciuna nu atinge
sent(audit) sausending(lease worker). - Ambele emit eveniment in jurnal (US-003):
submission_sters/submission_repus.
- Verificare E2E: helper apelat din shell pe submission 15 →
queued; pe unsent→ refuz.
US-010: API v1 — sterge + re-pune in coada
Ca integrator ROAAUTO vreau endpointuri pentru a curata/relua trimiteri blocate pentru ca softul propriu sa gestioneze coada fara interventie manuala in DB.
- Depinde de: US-009
- Fisiere:
app/api/v1/router.py(DELETE /v1/prezentari/{id},POST /v1/prezentari/{id}/repune),tests/test_api_lifecycle.py - Test intai (RED):
tests/test_api_lifecycle.py—test_delete_scoped_pe_cheie,test_delete_sent_403,test_repune_error_queued,test_repune_inexistent_404 - Acceptance criteria:
DELETE /v1/prezentari/{id}→ 200 + body JSON{ok, submission_id, status_anterior}(NU 204; clienti VFP string-parse) pe randuri ne-sent ale contului cheii.- Scope evaluat INAINTEA starii (decizie /autoplan #20): cross-account / inexistent
→ 404 (acelasi mesaj, B3 — nu confirmam existenta); own-account
sent/sending→ 409 (conflict de stare). Testtest_delete_cross_account_sent_404. POST /v1/prezentari/{id}/repune→ randul devinequeued(peste helper US-009).- Scoped strict pe contul cheii API (nu se poate atinge alt cont).
- Verificare E2E: cu cheia contului 2,
POST .../15/repune→ 200; worker il re-trimite (creds corecte).
US-011: Web dashboard — butoane Sterge / Re-pune in coada
Ca operator in dashboard vreau butoane pe randurile blocate pentru ca sa le gestionez vizual, fara API/SQL.
- Depinde de: US-009
- Fisiere:
app/web/routes.py(rutePOST /trimitere/{id}/sterge,POST /trimitere/{id}/repune,POST /trimiteri/sterge-bulkcu CSRF + PRG),app/web/templates/_trimitere_detaliu.html+ lista Trimiteri (selectie),tests/test_web_lifecycle.py - Test intai (RED):
tests/test_web_lifecycle.py—test_buton_sterge_doar_pe_blocate,test_repune_din_ui_scoped_sesiune,test_csrf_enforce,test_bulk_sterge_doar_blocate_scoped - Acceptance criteria:
- Pe detaliul unui rand
error: buton "Re-pune in coada" + "Sterge" cu dialog de confirmare simpla (decizie §5). - Pe lista Trimiteri: selectie multipla + "Sterge selectate" (bulk), pe modelul panoului admin (PRD 5.5); actioneaza DOAR pe randuri blocate ale contului.
- Randuri
sent/sending: fara butoane si neselectabile pentru stergere (read-only). - Scoped pe sesiune (regula NULL→cont 1); CSRF enforce; PRG dupa actiune.
- Stil consistent cu corectia inline existenta + panoul admin bulk (PRD 5.5), AA light+dark.
- Pe detaliul unui rand
- Verificare E2E: browser HTMX → pe submission 15 "Re-pune in coada" → dispare din "error", reapare ca trimis dupa worker; "Sterge" → dispare din lista.
US-012: Dedup nu mai e blocat de un rand error
Ca integrator vreau ca o retrimitere a aceluiasi payload (dupa ce am corectat
parola) sa fie acceptata pentru ca azi un rand error cu aceeasi cheie o blocheaza tacit.
- Depinde de: — (atinge
app/api/v1/router.pyenqueue +import_router) - Fisiere:
app/api/v1/router.py(create_prezentari),app/api/v1/import_router.py(commit, daca aplica),tests/test_dedup_error.py - Test intai (RED):
tests/test_dedup_error.py—test_resubmit_peste_error_reactiveaza,test_resubmit_actualizeaza_creds_pe_reactivare,test_resubmit_peste_sent_ramane_deduped,test_resubmit_peste_queued_ramane_deduped - Acceptance criteria:
- La enqueue, daca randul existent cu aceeasi
idempotency_keyeerror: se RE-ACTIVEAZA acelasi rand (re-ruleazaclassify, actualizeazarar_creds_enccu creds-urile noi din cerere, resetretry_count/next_attempt_at,purge_after=NULL), si raspunsul poarta camp aditivreactivated: true+ starea noua (ex.queued);dedupedramane cu semantica actuala (decizie /autoplan #19, NU se repurpose-aza). - Reactivarea e un UPDATE compare-and-swap (
WHERE id=? AND status='error'); dacarowcount==0(alt POST/requeue a schimbat starea intre timp) -> raspuns dedup pe starea curenta. Worker-ul invalideaza sesiunea RAR cache-uita a contului cand randul claim-uit poartarar_creds_enc != NULL(altfel JWT vechi 30h trimite cu parola gresita — vezi T1 anexa). - Creds noi se propaga si in
accounts.rar_creds_enc(canal web durabil, decizie #17). - Pentru
sent/queued/sending: comportament neschimbat →deduped: true(nu cream dubluri, nu deranjam in-flight/trimise). needs_data/needs_mapping: ramandedupedla resubmit (decizie §5) — corectia se face exclusiv prin UI (corectia inline existenta), nu prin re-trimiterea payload-ului.- Invariantul UNIQUE(idempotency_key) ramane (re-folosim randul, nu inseram al doilea).
- La enqueue, daca randul existent cu aceeasi
- Verificare E2E: POST cu parola gresita →
error; re-POST acelasi payload cu parola corecta →queued(nudeduped); worker trimite →sent.
US-013: Retentie / purjare randuri ne-sent blocate
Ca responsabil GDPR vreau ca si trimiterile blocate sa se auto-stearga dupa o
perioada pentru ca altfel PII-ul lor ramane permanent (azi doar sent se purjeaza).
- Depinde de: —
- Fisiere:
app/worker/__main__.py(markseteazapurge_aftersi pe stari blocate;purge_expiredextins peerror/needs_*),tests/test_purge_blocate.py - Test intai (RED):
tests/test_purge_blocate.py—test_error_primeste_purge_after,test_purjare_sterge_error_expirat,test_sent_si_blocate_retentii_separate_daca_difera - Acceptance criteria:
- Randurile care intra in
error/needs_data/needs_mappingprimescpurge_after(AUTOPASS_BLOCKED_RETENTION_DAYS, default 30 zile — decizie §5, mai scurt decat 90zsent). - Purjarea orara (T16) sterge si randurile blocate expirate, nu doar
sent. - O re-activare (US-012) / re-pune in coada (US-009) reseteaza/curata
purge_after(randul redevine activ, nu mai e candidat la purjare imediat).
- Randurile care intra in
- Verificare E2E: rand
errorcupurge_afterin trecut → rulează purjarea → dispare.
US-014: "Necesita atentia ta" devine actionabil (link + identificare rand)
Ca operator vreau ca avertismentul de trimiteri blocate sa-mi spuna CARE prezentare
a esuat si sa ma duca la ea pentru ca azi arata doar un contor ("Eroare la trimitere (1)"),
fara VIN/id/link — nu pot actiona, iar banner-ul nu se stinge niciodata cat timp exista error.
- Depinde de: — (UI peste filtrul existent
/_fragments/submissions?status=); se imbina natural cu US-011 (butoane sterge/re-pune in coada) si US-013 (purjare → banner se stinge) - Fisiere:
app/web/templates/_status.html,app/web/routes.py(_render_status/ fragment status — expune si identificatorii randurilor blocate, nu doar contoare),tests/test_web_status_fragment.py - Test intai (RED):
tests/test_web_status_fragment.py—test_categorie_blocata_linkeaza_la_trimiteri_filtrate,test_status_arata_identificator_rand_blocat,test_scoped_pe_cont - Acceptance criteria:
- Fiecare categorie din "Necesita atentia ta" e link catre lista "Trimiteri"
filtrata pe acea stare (deep-link
?tab=...&status=erroretc.), scoped pe cont. - Sub fiecare categorie se afiseaza identificatorul randurilor blocate (VIN partial
- nr inmatriculare +
#id), cel putin pentru primele N, cu "...si inca M" daca sunt mai multe.
- nr inmatriculare +
- Banner-ul dispare cand nu mai exista randuri blocate (consecinta US-009/011/013: stergere / re-pune in coada / purjare → contor 0 → sectiunea nu se mai randeaza).
- Nimic nou expus fara scope (regula NULL→cont 1); PII doar partial (ca jurnalul, US-007).
- Fiecare categorie din "Necesita atentia ta" e link catre lista "Trimiteri"
filtrata pe acea stare (deep-link
- Verificare E2E: browser HTMX → "Eroare la trimitere (1)" arata
#15 WVW…0001 / B123ABCsi linkeaza in Trimiteri filtrat peerror; dupa re-pune in coada +sent, banner-ul dispare.
4. Riscuri
- Scriere DB pe calea fiecarei cereri (US-004) poate adauga latenta/contentie pe SQLite (WAL). Mitigare: insert minimal, best-effort, nivel filtrat; eveniment per cerere agregat (1 rand), nu per camp. De masurat la VERIFY.
- Scurgere PII e riscul central. Mitigare: redactare la SCRIERE (nu la afisare), testata adversarial (US-007); nimic din payload integral nu intra in jurnal.
- Volum jurnal poate umfla DB-ul. Mitigare: retentie + nivel (US-008),
INFOdefault. - Dublu canal divergent (DB vs fisier). Mitigare: un singur
log_eventca sursa unica (US-003), ca dry-run/erori la PRD 5.2/5.4 — imposibil de divergat. - Migrare schema
app_events. Mitigare: migrare defensiva idempotenta in_migrate(caaccounts.active/override_json). - US-012 schimba semantica
dedupedla enqueue. Risc: re-activare nedorita a unui rand trimis. Mitigare: re-activarea e strict peerror(nusent/queued/sending); teste explicite pe fiecare stare; UNIQUE(idempotency_key) garanteaza un singur rand per continut. - Re-pune in coada cu creds gresite (US-009/010/011): daca creds-urile contului sunt
inca gresite, randul re-intra in
error. Acceptat — actiunea nu garanteaza succesul, doar reda dreptul la o noua incercare; jurnalul (US-005) arata de ce a reesuat.
5. Decizii (rezolvate cu utilizatorul — poarta de aprobare PRD)
Rezolvate 2026-06-23. Sunt obligatorii pentru executie.
- Retentie jurnal: 90 zile (aliniat cu
submissions/import_batches). [US-008] - Tipuri de evenimente: lista extensibila, nu fixata acum —
tipe text liber documentat, adaugam tipuri pe parcurs fara migrare. [US-003] - Log text:
RotatingFileHandlerin aplicatie (rotatie pe dimensiune; nu depindem de deploy). [US-008] - Vizibilitate jurnal: non-admin vede DOAR evenimentele contului sau; adminul vede tot, cu filtru pe cont. [US-006]
- Resubmit peste blocate (US-012): doar
errorse re-activeaza (re-ruleaza classify- actualizeaza creds).
needs_data/needs_mappingramandeduped— corectia exclusiv prin UI (corectia inline existenta).sent/queued/sendingramandeduped(neschimbat).
- actualizeaza creds).
- Retentie randuri blocate (US-013): 30 zile (mai scurt decat cele 90 ale
sent; un blocat n-are valoare de audit ca o trimitere reusita). Configurabil prinAUTOPASS_BLOCKED_RETENTION_DAYS, default 30. - Stergere din UI (US-011): confirmare simpla (dialog) + actiune in bloc pe lista (selectie multipla + "Sterge selectate"), pe modelul panoului admin (PRD 5.5).
6. Valuri de executie (graful de dependente)
Val 1: [US-002 request_id] [US-003 logger+schema] ← fundatii, fisiere disjuncte → paralel
Val 2: [US-001 handler 500] [US-004 audit API] [US-005 audit worker] [US-007 redactare]
← deblocate de US-003 (+US-002)
Val 3: [US-006 tab Jurnal] [US-008 retentie] ← consuma datele/coloanele din Val 1-2
--- Lifecycle trimiteri blocate (independent de observabilitate; poate rula in paralel) ---
Val A: [US-009 helper sterge/repune] [US-012 dedup peste error] [US-013 retentie blocate]
← fisiere disjuncte, fara dependente
Val B: [US-010 API lifecycle] [US-011 UI lifecycle] [US-014 banner actionabil]
← deblocate de US-009 (US-014 indep., dar grupat cu UI)
Nota scope: 5 stories de lifecycle (US-009..US-013) in loc de 3 din schita initiala — regula proiectului separa backend + UI in stories distincte (helper / API / UI). Daca vrei livrare mai mica, US-010 (API) e optional pentru un MVP "doar dashboard".
Anexa /autoplan — Raport de review (2026-06-23)
Generat de
/autoplan(CEO -> Design -> Eng -> DX), commitf48346d, branchmain. Voci: Claude subagent per faza + Codex. Codex INDISPONIBIL (usage limit la runtime) -> toate fazele ruleaza[subagent-only]. Premisa "app_events table + tab Jurnal" confirmata de utilizator la poarta de premise (vs alternativa stdout-first). Restore point: vezi comentariul HTML din capul fisierului.
Consensus tables (Codex = N/A, subagent-only)
CEO: 1 premise flagged (substrate, CONFIRMAT keep) · 3 right-problem/scope · 4 alt-uri necomparate
DESIGN: 3 high (poll vs select, deep-link inexistent, banner->panel) · stari lipsa
ENG: 2 CRITICAL (US-012 race+JWT stale, purge_after) · 0 concurrency tests · WAL contention
DX: 5 high (500 envelope 6 chei, 403/404 oracle, deduped breaking, docs, rar_error allowlist)
Diagrame
US-012 reactivare error — masina de stari + cursa (fix necesar T1):
POST /v1/prezentari (acelasi payload, parola corectata)
|
v
SELECT status WHERE idempotency_key=? ---- error ----> UPDATE ... SET status='queued',
| rar_creds_enc=<nou>, retry=0,
| sent/queued/sending/needs_* next_attempt_at=NULL,
v purge_after=NULL
deduped:true (neschimbat) |
v
CURSA (fara CAS): worker.claim_one (BEGIN IMMEDIATE) queued->sending
CURSA (JWT): AccountSessions[account_id] are token vechi (30h) din creds GRESITE
-> trimite cu parola veche, ignora corectia <-- BUG CENTRAL
FIX: UPDATE ... WHERE id=? AND status='error' (CAS; rowcount 0 -> deduped) +
la claim, daca randul poarta rar_creds_enc != NULL -> sessions.invalidate(account_id)
Retentie / purjare (fix T2):
mark(sent) -> purge_after = now + 90z (existent)
mark(blocate) -> purge_after = now + 30z (US-013 nou; error/needs_data/needs_mapping)
reactivare/ -> purge_after = NULL (US-009/012; ALTFEL purjat inainte de claim)
re-pune coada
purge_expired WHERE purge_after<now AND status IN ('sent','error','needs_data','needs_mapping')
EXCLUDE explicit 'queued'/'sending'
Failure Modes Registry (noi, din review)
CODEPATH | FAILURE MODE | RESCUED? | TEST? | USER SEES | LOGGED?
---------------------------------|-------------------------------|----------|-------|------------------|--------
create_prezentari reactivare | cursa cu claim_one / 2x POST | FIX T1 | FIX T3| queued det. | US-004
worker JWT cache dupa creds noi | trimite cu parola veche | FIX T1 | FIX T3| ramane error | US-005 <- CRITICAL
reactivare fara purge_after=NULL | purjat inainte de claim | FIX T2 | FIX T3| dispare tacit | US-005 <- CRITICAL
log_event own-conn pe hot path | WAL write-lock pana la 15s | FIX T4 | da | latenta POST | -
RotatingFileHandler 2 procese | rotatie rename race | FIX T5 | n/a | log corupt | -
500 envelope 4 chei | parser client crapa pe 5xx | FIX T7 | da | KeyError client | US-001
403 sent vs 404 cross-acct | oracol de existenta | FIX TD2 | da | leak | US-004
bulk select vs poll 15s | selectie stearsa mid-actiune | FIX T12 | da | frustrare | -
deep-link status inexistent | banner duce la lista nefiltr. | FIX T13 | da | dead-end | -
Decision Audit Trail (auto-decis cu cele 6 principii)
| # | Faza | Decizie | Clasificare | Principiu | Rationament |
|---|---|---|---|---|---|
| 1 | CEO | Premisa app_events table + tab | GATE (user) | - | Confirmat de utilizator: web-visibility e scop de produs (operator fara SSH) |
| 2 | Eng | US-012 = CAS guarded + invalidare sesiune worker la creds noi (T1) | Mechanical | P1 completeness | Bug central; fara el US-012 nu-si atinge scopul |
| 3 | Eng | reactivare/requeue purge_after=NULL; purge exclude queued/sending (T2) | Mechanical | P1 | Altfel randul reactivat e purjat tacit |
| 4 | Eng | teste concurenta + purge-before-claim (T3) | Mechanical | P1 well-tested | Lista de teste US-012 era single-thread |
| 5 | Eng | log_event(conn opt) reuse hot-path (T4) | Mechanical | P3 pragmatic | Evita contentie WAL |
| 6 | Eng | log-uri per-proces api.log/worker.log (T5) | Mechanical | P5 explicit | RotatingFileHandler nu e multiproces-safe |
| 7 | Eng | vin_partial() + context curat (T6) | Mechanical | P1 | scrub() nu acopera VIN (US-007) |
| 8 | DX | EROARE_INTERNA in CATALOG; 500 = 6 chei + request_id (T7) | Mechanical | P1 | Contract 6 chei (PRD 5.4) |
| 9 | DX | X-Request-ID pe TOATE raspunsurile (T8) | Mechanical | P1 | Corelare si pe 422/401/404 |
| 10 | DX | rar_error in _PREZENTARE_FIELDS (T9) | Mechanical | P6 action | Recovery API observabil fara dashboard |
| 11 | DX | update api-rar-contract.md + reconcile de-scope (T10) | Mechanical | P1 | Sursa de adevar trebuie sa includa endpointurile noi |
| 12 | DX | DELETE -> 200+JSON, nu 204 (T11) | Mechanical | P5 | Consistent cu restul v1; clienti VFP string-parse |
| 13 | Design | poll vs bulk-select rezolvat (T12) | Mechanical | P1 | Selectie stearsa la 15s = defect |
| 14 | Design | plumbing deep-link status (T13) | Mechanical | P1 | Destinatia US-014 nu exista azi |
| 15 | Design | banner -> panou detaliu (T14) | Mechanical | P3 | Duce direct la butonul de actiune |
| 16 | Design | stari empty/loading/partial + collision checkbox (T15) | Mechanical | P1 | Acoperire stari = scope, nu afterthought |
| 17 | CEO | REZOLVAT: DA — resubmit/requeue cu creds noi reimprospateaza si accounts.rar_creds_enc (T16) |
Taste | P1 | Utilizator: ambele canale converg pe parola corectata |
| 18 | CEO | REZOLVAT: pastram bundled, lifecycle (Val A) PRIMUL | Taste | P6 | Utilizator: §6 izoleaza deja valurile; overhead minim pe PRD aprobat |
| 19 | DX | REZOLVAT: camp aditiv reactivated:true (NU repurpose deduped) |
Taste | P5 | Utilizator: backward-compat pentru clienti care testeaza deduped |
| 20 | DX | REZOLVAT: cross-account 404 INAINTE de verificare status; own-account sent/sending -> 409 | Taste(sec) | P1 | Utilizator: inchide oracolul de existenta (B3) |
Decizii /autoplan rezolvate la poarta finala (2026-06-23, obligatorii pentru executie)
- Bundling [#18]: PRD 5.6 ramane unitar; ordinea de executie pune Val A (lifecycle: US-009/012/013/011/014) inaintea observabilitatii. Un singur VERIFY.
- US-012 raspuns [#19]: la reactivarea unui rand
errorse intoarce camp aditivreactivated: truepeSubmissionResult(NU se repurpose-azadeduped).dedupedramane cu semantica actuala; clientii vechi nu se sparg. Updateapp/models.py+ contract. - US-010 coduri [#20]: scope-ul (cross-account) se evalueaza inaintea starii. Cross-account / inexistent -> 404 (acelasi mesaj, B3). Own-account
sent/sending-> 409 (conflict de stare, nu 403). Test noutest_delete_cross_account_sent_404. - US-009/012 creds [#17]: cand resubmit/requeue aduce creds noi, se reimprospateaza si
accounts.rar_creds_enc(canalul web durabil), nu doarsubmissions.rar_creds_enc. Combinat cu invalidarea sesiunii worker (T1).
Implementation Tasks (auto-generate, vezi JSONL ~/.gstack/projects/romfast-rar-autopass/)
P1 (blocheaza ship): T1 (US-012 CAS+sesiune), T2 (purge_after), T3 (teste concurenta), T4 (log_event conn), T5 (log per-proces), T7 (500 6-chei), T8 (X-Request-ID global), T9 (rar_error allowlist), T10 (docs contract), T12 (poll vs select), T13 (deep-link). P2 (acelasi branch): T6 (vin_partial), T11 (DELETE 200+body), T14 (banner->panou), T15 (stari UI), T16 (creds web).
Completion Summaries
CEO | premise 1 (confirmat keep) · right-problem OK (lifecycle=10x) · 1 challenge bundling · F6 creds web
DESIGN | 3 high (poll/select, deep-link, banner) · stari lipsa · checkbox collision · AA de verificat
ENG | 2 CRITICAL (race+JWT, purge) · 0 concurrency tests · WAL contention · IDOR ordine 404
DX | 5 high (500 envelope, oracle, deduped, docs, rar_error) · recovery matrix per-stare de documentat
Lake | toate auto-deciziile au ales optiunea completa (16/16 mechanical = ADD/fix complet)
Raport VERIFY
Executie completa 2026-06-23 (TDD, RED->GREEN per story). Toate cele 14 stories livrate.
Rezultat teste
python3 -m pytest -q -> 741 passed, 0 failed (~64s). Baseline inainte de 5.6: 561 teste
(restul de 114 "esecuri" de la pornire erau artefact de mediu — .env-ul de testare live are
AUTOPASS_REQUIRE_API_KEY=true; rulat cu override-urile standard de test, baseline-ul e verde).
Teste noi adaugate (toate verzi):
- US-001
tests/test_error_handler.py(5) — 500 structurat 6-chei + request_id, fara traceback/creds. - US-002
tests/test_request_id.py(4) — X-Request-ID pe toate raspunsurile, contextvar. - US-003
tests/test_observ.py(4) — dublu canal DB+fisier, redactare, nivel din env, best-effort. - US-004
tests/test_audit_api.py(3) —api_prezentari(count+distributie),api_auth_esuat(IP+prefix). - US-005
tests/test_worker_observ.py(3) —rar_loginok/esuat fara parola, tranzitii sent/error. - US-007
tests/test_jurnal_redactare.py(4) — parola/token/VIN niciodata integral; fuzz chei sensibile. - US-006
tests/test_web_jurnal.py(5) — scope non-admin/admin, filtru tip/nivel/cont, deep-link tab. - US-008
tests/test_jurnal_retentie.py(5) — purge_after pe app_events, purjare, RotatingFileHandler. - US-009
tests/test_submissions_admin.py(6) — sterge/repune scoped, 404 cross-account, classify la repune. - US-010
tests/test_api_lifecycle.py(7) — DELETE/repune 200+JSON, scope-before-state (404 vs 409). - US-011
tests/test_web_lifecycle.py(7) — butoane doar pe blocate, CSRF, bulk scoped. - US-012
tests/test_dedup_error.py(5) — reactivare pesteerror+reactivated:true, creds noi; sent/queued/needs_* raman deduped. - US-013
tests/test_purge_blocate.py(5) — purge_after pe blocate (30z), purjare exclude queued/sending. - US-014
tests/test_web_status_fragment.py(+3) — categorie linkeaza la lista filtrata, identificator partial, scope.
Fix-uri tehnice cheie (din /autoplan)
- T1 (CRITICAL): reactivarea e UPDATE compare-and-swap (
WHERE id=? AND status='error'); worker-ul invalideaza sesiunea RAR cache-uita cand randul claim-uit poartarar_creds_enc != NULL(JWT vechi 30h din parola gresita nu mai trimite). Creds noi se propaga si inaccounts.rar_creds_enc. - T2: reactivare/requeue seteaza
purge_after=NULL;purge_expiredexclude explicitqueued/sending. - T7: 500 = envelope 6-chei (catalog) +
request_id. T8: X-Request-ID pe TOATE raspunsurile (middleware). - T9:
rar_errorin allowlist-ulGET /v1/prezentari/{id}(recovery observabil; test vechi actualizat).
Note
- Teste modificate intentionat (comportament schimbat de PRD):
test_t16_purjare(error primeste acum purge_after — US-013),test_get_scope_prezentari(rar_errorexpus acum — T9). - E2E live pe RAR test: NEPROBAT in aceasta sesiune (necesita creds RAR test +
--send). Backend-ul de trimitere e neatins ca logica; modificarile worker sunt aditive (evenimente + invalidare sesiune la creds noi). Recomandat la deploy: o trimitere--sendpentru a confirmarar_loginok +submission_sentin jurnal.