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>
580 lines
39 KiB
Markdown
580 lines
39 KiB
Markdown
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/main-autoplan-restore-20260623-165442.md -->
|
|
# 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:
|
|
|
|
1. **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).
|
|
2. **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.
|
|
3. **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_after` se seteaza DOAR la `status='sent'`
|
|
(`mark()`), iar `purge_expired` sterge DOAR randuri `sent` expirate. Randurile `error`/
|
|
`needs_data`/`needs_mapping` nu primesc niciodata `purge_after` → **nu se purjeaza
|
|
niciodata**. Login 401 (creds RAR gresite) → `error` direct, **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 pentru `needs_*`, nu `error`).
|
|
- **Un rand `error` blocheaza 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, primeste `deduped: true` cu `status: error` si 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_signup` ramane 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 `sent`** prin noile actiuni de lifecycle: sunt dovada de
|
|
trimitere la RAR (audit). Stergerea/re-punerea opereaza DOAR pe `error`/`needs_data`/
|
|
`needs_mapping`; `sending` (in zbor) e protejat de lease-ul worker-ului.
|
|
- **Nu anulam nimic la RAR** — `FINALIZATA` ramane terminal acolo (fara API de anulare).
|
|
Stergem doar randul LOCAL din coada gateway-ului, nu inregistrarea de la RAR.
|
|
- **Nu modificam `/healthz` si `/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 in `main.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 nou `EROARE_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_id` se scriu in log
|
|
(fisier) prin `scrub` (`app/security.py`), niciodata in raspuns.
|
|
- [ ] Handlerele existente (LoginRequired/AdminRequired/CSRF/RequestValidationError)
|
|
raman neatinse; doar `Exception` generic e nou.
|
|
- **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 in `main.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_id` e 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)`).
|
|
- **Verificare E2E**: doua cereri → doua `X-Request-ID` distincte, 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`
|
|
(tabela `app_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 in `app_events` SI 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`, default `INFO`).
|
|
- [ ] 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_event` din 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/prezentari` emite eveniment `api_prezentari` cu: `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_esuat`
|
|
cu 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` (inlocuieste `print(...)` cu `log_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_login` cu `account_id`, rezultat,
|
|
cod HTTP; **fara** email/parola in clar (scrub).
|
|
- [ ] Tranzitiile `sending→sent` / `→needs_data` / `→error` / reconciliere →
|
|
evenimente cu `submission_id`, `account_id`, cod eroare din catalog.
|
|
- [ ] `print(...)` existente din worker migrate la `log_event` (sursa=`worker`),
|
|
fara a pierde mesajele in stdout (logul text ramane).
|
|
- **Verificare E2E**: o trimitere live pe RAR test (`--send`) → `rar_login` ok +
|
|
`sent` cu `idPrezentare` in 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 pe `rar_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` (extinde `scrub`/`SENSITIVE_KEYS` daca 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 contine `password`/`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.
|
|
- **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_expired` extins pe `app_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_events` primeste `purge_after = now + 90 zile`
|
|
(`AUTOPASS_LOG_RETENTION_DAYS`, default 90 — decizie §5).
|
|
- [ ] Purjarea orara existenta (T16) sterge si `app_events` expirate.
|
|
- [ ] Logul text foloseste `RotatingFileHandler` in aplicatie (rotatie pe dimensiune,
|
|
N fisiere de backup) — decizie §5; nu depindem de deploy pentru rotatie.
|
|
- **Verificare E2E**: insereaza eveniment cu `purge_after` in 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 e
|
|
`error`/`needs_data`/`needs_mapping` SI apartine contului; altfel ridica/Intoarce
|
|
refuz (sent/sending → interzis, cross-account → inexistent).
|
|
- [ ] `requeue_submission(conn, account_id, sid)` muta `error → queued`, reseteaza
|
|
`retry_count=0`, `next_attempt_at=NULL`, `sending_since=NULL`, re-ruleaza `classify`
|
|
pe payload (poate ajunge `needs_data`/`needs_mapping` daca continutul cere).
|
|
- [ ] Niciuna nu atinge `sent` (audit) sau `sending` (lease worker).
|
|
- [ ] Ambele emit eveniment in jurnal (US-003): `submission_sters` / `submission_repus`.
|
|
- **Verificare E2E**: helper apelat din shell pe submission 15 → `queued`; pe un `sent` → 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). Test `test_delete_cross_account_sent_404`.
|
|
- [ ] `POST /v1/prezentari/{id}/repune` → randul devine `queued` (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` (rute `POST /trimitere/{id}/sterge`,
|
|
`POST /trimitere/{id}/repune`, `POST /trimiteri/sterge-bulk` cu 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.
|
|
- **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.py` enqueue + `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_key` e `error`:
|
|
se RE-ACTIVEAZA acelasi rand (re-ruleaza `classify`, **actualizeaza `rar_creds_enc`**
|
|
cu creds-urile noi din cerere, reset `retry_count`/`next_attempt_at`, **`purge_after=NULL`**),
|
|
si raspunsul poarta **camp aditiv `reactivated: true`** + starea noua (ex. `queued`);
|
|
`deduped` ramane cu semantica actuala (decizie /autoplan #19, NU se repurpose-aza).
|
|
- [ ] **Reactivarea e un UPDATE compare-and-swap** (`WHERE id=? AND status='error'`); daca
|
|
`rowcount==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 poarta
|
|
`rar_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`: raman `deduped` la 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).
|
|
- **Verificare E2E**: POST cu parola gresita → `error`; re-POST acelasi payload cu parola
|
|
corecta → `queued` (nu `deduped`); 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` (`mark` seteaza `purge_after` si pe stari blocate;
|
|
`purge_expired` extins pe `error`/`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_mapping` primesc `purge_after`
|
|
(`AUTOPASS_BLOCKED_RETENTION_DAYS`, default **30 zile** — decizie §5, mai scurt decat 90z `sent`).
|
|
- [ ] 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).
|
|
- **Verificare E2E**: rand `error` cu `purge_after` in 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=error` etc.), 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.
|
|
- [ ] 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).
|
|
- **Verificare E2E**: browser HTMX → "Eroare la trimitere (1)" arata `#15 WVW…0001 / B123ABC`
|
|
si linkeaza in Trimiteri filtrat pe `error`; 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), `INFO` default.
|
|
- **Dublu canal divergent** (DB vs fisier). Mitigare: un singur `log_event` ca 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`
|
|
(ca `accounts.active`/`override_json`).
|
|
- **US-012 schimba semantica `deduped`** la enqueue. Risc: re-activare nedorita a unui rand
|
|
trimis. Mitigare: re-activarea e strict pe `error` (nu `sent`/`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 — `tip` e text liber
|
|
documentat, adaugam tipuri pe parcurs fara migrare. [US-003]
|
|
- **Log text**: **`RotatingFileHandler` in 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 `error` se re-activeaza** (re-ruleaza classify
|
|
+ actualizeaza creds). `needs_data`/`needs_mapping` raman `deduped` — corectia exclusiv
|
|
prin UI (corectia inline existenta). `sent`/`queued`/`sending` raman `deduped` (neschimbat).
|
|
- **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 prin
|
|
`AUTOPASS_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), commit `f48346d`, branch `main`.
|
|
> 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 `error` se intoarce camp **aditiv `reactivated: true`** pe `SubmissionResult` (NU se repurpose-aza `deduped`). `deduped` ramane cu semantica actuala; clientii vechi nu se sparg. Update `app/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 nou `test_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 doar `submissions.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_login` ok/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 peste `error` + `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 poarta `rar_creds_enc != NULL`
|
|
(JWT vechi 30h din parola gresita nu mai trimite). Creds noi se propaga si in `accounts.rar_creds_enc`.
|
|
- **T2**: reactivare/requeue seteaza `purge_after=NULL`; `purge_expired` exclude explicit `queued`/`sending`.
|
|
- **T7**: 500 = envelope 6-chei (catalog) + `request_id`. **T8**: X-Request-ID pe TOATE raspunsurile (middleware).
|
|
- **T9**: `rar_error` in allowlist-ul `GET /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_error` expus 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 `--send` pentru a confirma `rar_login` ok + `submission_sent` in jurnal.
|