Files
rar-autopass/docs/prd/prd-5.1-hub-integrare.md
Claude Agent f0786051f5 feat(web): hub integrare /integrare — exemple cod + retetar VFP + ping + export (PRD 5.1)
Pagina /integrare (tab autentificat, scoped pe cont): exemple cod multi-limbaj
(curl/Python/PHP/C#/Node) + retetar Visual FoxPro (MSXML2 + WinHttp) pe ambele
canale (prezentari JSON + import fisier), export Postman/OpenAPI/Swagger si buton
"Testeaza conexiunea".

- US-001: GET /v1/ping (readiness: account_id/mediu/autentificat_cu_cheie/
  are_creds_rar/ts) + GET /v1/integrare/postman.json (v2.1.0, allowlist 3 rute)
- US-002: app/web/integrare_examples.py pur (7 limbaje x 2 canale, drift-test
  is_required(), JSON compact pentru C#/VFP)
- US-003: tab "Integrare" IA pe 2 niveluri (limbaj->canal, VFP cu dialecte),
  copy din <pre><code>, empty-state CTA, export .cardlink, script scoped
- US-004: POST /integrare/test-cheie (account_for_key direct, scoped sesiune,
  no-echo cheie)

Backend trimitere (worker/masina stari/idempotenta/mapping) si schema neatinse.
568 teste pass. VERIFY context curat + E2E browser (Playwright) + code-review high.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:16:41 +00:00

282 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD 5.1 — Hub de integrare (pagina /integrare cu exemple de cod + retetar VFP + ping + export)
**Stare**: inchis (2026-06-22 — VERIFY PASS + E2E browser PASS + `/code-review` high incorporat; 568 teste pass; writeback ROADMAP facut; asteapta poarta umana de commit)
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
> **Backend trimitere (worker, masina stari, idempotenta-logica, mapping-rezolvare) NU se atinge.**
> Aceasta livrabila adauga DOAR suprafata de onboarding/integrare (citire) peste API-ul existent + stratul web.
> Schema neatinsa (zero coloane noi).
## 1. Obiectiv
Produsul e functional pe ambele canale (API + import web), dar un service nou care vine din **Visual FoxPro**
sau soft propriu nu are de unde sa inceapa integrarea: cheia API se creeaza/roteste din web (`/cont`), insa nu
exista nicio pagina cu **cum chem API-ul**, ce payload, ce header, in ce limbaj. Singurele referinte sunt
`README.md` (curl) si Swagger `/docs` — invizibile din aplicatie si fara exemple in limbajul integratorului.
Livram o pagina **`/integrare`** (tab nou, autentificat, scoped pe contul din sesiune) care:
- afiseaza exemple de cod **pre-completate cu endpoint-ul real + account_id-ul utilizatorului** pentru ambele
canale (`POST /v1/prezentari` JSON si `POST /v1/import` upload fisier), in **curl, Python, PHP, C#, Node**;
- include un **retetar Visual FoxPro** (POST via `MSXML2.ServerXMLHTTP.6.0` SI `WinHttp.WinHttpRequest.5.1`,
plus upload CSV);
- ofera **export OpenAPI** (`/openapi.json` din FastAPI) + **colectie Postman** + link Swagger `/docs`;
- are un buton **"Testeaza conexiunea"**: utilizatorul lipeste cheia salvata, iar serverul confirma ca e valida
si mapeaza pe contul lui (acelasi drum de auth ca API-ul real).
Constrangere de securitate (din `app/auth.py`): stocam DOAR SHA-256 al cheii API, deci **nu putem reafisa cheia**.
Exemplele folosesc placeholder `rfak_...`; cheia reala se obtine o singura data la creare/rotire din `/cont`.
## 2. Non-Goals (anti scope-creep)
- **Nu reafisam cheia API** — exemplele au placeholder, niciodata cheia in clar (SHA-256-only).
- **Fara markdown versionat in `docs/`** (decis cu utilizatorul: doar pagina in-app dinamica). Daca apare nevoia
de referinta indexabila, e o livrabila viitoare separata.
- **Fara generare automata de SDK-uri** — OpenAPI + Postman sunt punctul de plecare; integratorul genereaza singur.
- **Fara dry-run** — validarea de payload fara enqueue e livrabila 5.2 (separata). `GET /v1/ping` verifica DOAR
autentificarea (cheie valida + cont activ), nu valideaza continut.
- **Fara editarea exemplelor de catre user** — continut read-only generat din schema.
- **Nu atinge backend-ul de trimitere** (worker/masina stari/idempotenta-logica/mapping-rezolvare) si **nici schema**.
- **Fara i18n** — pagina e in romana, ca restul UI-ului.
## 3. Stories atomice
> Backend + UI pentru acelasi comportament = stories separate. Toate rutele web noi sub `require_login` +
> **scoped pe contul din sesiune**. CSRF pe toate POST-urile web. Endpoint-urile `/v1/*` folosesc `resolve_account_id`.
### US-001: Backend — endpoint-uri de integrare (`GET /v1/ping` + export Postman)
**Ca** integrator extern **vreau** un endpoint care confirma ca cheia mea functioneaza si o colectie Postman gata
de importat **pentru ca** sa-mi validez conexiunea inainte sa trimit prezentari reale.
- **Depinde de**: —
- **Fisiere**: `app/api/v1/integrare_router.py` (NOU), montare in `app/main.py` (`app.include_router`),
`tests/test_integrare_api.py` (~3 fisiere)
- **Montare (decizie eng-review P1)**: router nou cu `prefix="/v1"`, inclus in `app/main.py` cu `app.include_router(...)`
(consistent cu `import_v1_router`). NU sub-include in `app/api/v1/router.py` (acela are deja `prefix="/v1"` → ar dubla prefixul).
- **Test intai (RED)**: `tests/test_integrare_api.py`
`test_ping_cu_cheie_valida_200` (raspuns: `account_id`, `mediu` test/prod, `autentificat_cu_cheie=true`, `are_creds_rar`, `ts`),
`test_ping_cu_bearer_valid_200` (`Authorization: Bearer rfak_...`),
`test_ping_fara_cheie_dev_cont_implicit` (flag off → cont 1, `autentificat_cu_cheie=false`),
`test_ping_x_api_key_gol_in_dev_cont_implicit` (whitespace tratat ca lipsa),
`test_ping_are_creds_rar_reflecta_contul` (cont cu `accounts.rar_creds_enc``true`; fara → `false`),
`test_ping_cheie_invalida_401`,
`test_ping_prod_fara_cheie_401` (flag on),
`test_ruta_ping_inregistrata_o_singura_data` (assert pe `app.routes` — fara dubla inregistrare),
`test_postman_export_json_valid` (Content-Type JSON, parseaza, schema Postman v2.1.0),
`test_postman_contine_exact_trei_requesturi` (prezentari, import, ping; header `X-API-Key: {{api_key}}`; `{{base_url}}`),
`test_postman_nu_deriva_din_app_routes` (allowlist hardcodat — NU expune `/v1/conturi/rar-creds` etc.).
- **Acceptance criteria**:
- [x] `GET /v1/ping` (dependinta `resolve_account_id`) = **readiness check**, nu doar auth-echo: cheie valida → 200
`{account_id, mediu, autentificat_cu_cheie, are_creds_rar, ts}`; cheie invalida → 401; fara cheie cu
`require_api_key=true` → 401; fara cheie in dev → cont 1 cu `autentificat_cu_cheie=false`. (`are_creds_rar` = contul are
`accounts.rar_creds_enc` setate; citire-only, raspunde la "esti gata sa trimiti?", nu doar "cheia merge". CEO-review P1/P2.)
- [x] Suporta si `Authorization: Bearer rfak_...` (al doilea header documentat in pagina).
- [x] `GET /v1/integrare/postman.json`: colectie Postman v2.1.0 cu variabile `base_url` + `api_key`, **exact 3 requesturi**
(allowlist hardcodat, NU derivat din `app.routes`): `POST /v1/prezentari`, `POST /v1/import`, `GET /v1/ping`, fiecare cu
header `X-API-Key: {{api_key}}` si body exemplu (pt prezentari: payload din schema reala `PrezentareRequest`, inclusiv `prestatii`).
- [x] `/openapi.json` ramane accesibil (FastAPI implicit) — niciun regres.
- [x] NU atinge `submissions`, worker, schema. `are_creds_rar` = SELECT read-only pe `accounts`.
- **Verificare E2E**: `curl -H "X-API-Key: rfak_..." http://localhost:8010/v1/ping` pe instanta locala → 200 cu contul corect +
`are_creds_rar`; import `postman.json` in Postman/Swagger.
### US-002: Helper pur — generator de exemple de cod multi-limbaj
**Ca** dezvoltator al gateway-ului **vreau** un modul pur care produce snippet-urile de integrare dintr-un
`(base_url, account_id)` **pentru ca** pagina sa fie testabila si snippet-urile sa ramana langa schema reala.
- **Depinde de**: —
- **Fisiere**: `app/web/integrare_examples.py` (NOU), `tests/test_integrare_examples.py` (~2 fisiere)
- **Test intai (RED)**: `tests/test_integrare_examples.py`
`test_snippet_curl_prezentari_contine_endpoint_si_header` (`{base_url}/v1/prezentari`, `X-API-Key: rfak_...`),
`test_snippet_python_import_upload_multipart` (foloseste `files=` / multipart pe `/v1/import`),
`test_toate_limbajele_prezente` (curl, python, php, csharp, node, vfp_msxml, vfp_winhttp),
`test_ambele_canale_per_limbaj` (fiecare limbaj are si prezentari JSON, si upload fisier),
`test_vfp_msxml_si_winhttp_distincte` (MSXML2.ServerXMLHTTP.6.0 vs WinHttp.WinHttpRequest.5.1),
`test_payload_acopera_campurile_obligatorii_din_model` (snippet-ul prezentari contine TOATE campurile obligatorii
derivate prin `field.is_required()` din `PrezentareIn` — INCLUSIV `prestatii` cu ≥1 item — si `RarCredentials{email,password}`;
drift-test corect: `{c for c,f in PrezentareIn.model_fields.items() if f.is_required()}` ⊆ campurile din snippet; campurile cu
default — `odometru_initial`, `obs`, `b64_image`, `sistem_reparat` — NU dau drift fals),
`test_prestatii_in_snippet_are_cod` (item-ul `prestatii` are `cod_prestatie` SAU `cod_op_service` — vezi `PrestatieItem._require_one`),
`test_placeholder_cheie_nu_e_valoare_reala` (mereu `rfak_...`, niciodata o cheie injectata).
- **Acceptance criteria**:
- [x] Functie pura `exemple(base_url: str, account_id: int) -> dict` care intoarce, per limbaj, snippet pt ambele canale.
- [x] Placeholder cheie = `rfak_...` (constant), endpoint-ul derivat din `base_url`.
- [x] Payload-ul JSON reflecta schema reala (`PrezentareRequest`): `rar_credentials{email,password}` + `prezentari[]` cu
**toate** campurile obligatorii prin `is_required()`, **inclusiv `prestatii` cu cel putin un item valid** (eng-review P1:
`prestatii` e fara default → obligatoriu; un payload fara el ar fi nevalid, exact ce pagina trebuie sa previna).
- [x] Drift-test foloseste `field.is_required()` (nu doar prezenta cheii), ca sa nu raporteze fals campurile cu default.
- [x] Fara I/O, fara DB, fara stare globala — pur (usor de testat).
- **Verificare E2E**: acoperita de UI (US-003) + unit teste.
### US-003: UI — tab "Integrare" cu exemple, retetar VFP si export
**Ca** utilizator **vreau** o pagina cu exemple gata de copiat in limbajul meu + linkuri de export **pentru ca**
sa integrez gateway-ul fara sa caut prin README/Swagger.
- **Depinde de**: US-001, US-002, US-004
- **Fisiere**: `app/web/routes.py` (`_render_integrare` + `/_fragments/integrare` + `integrare` in `_TABS_VALIDE` +
**branch nou in `_render_panel_for_tab`** — necesar pt deep-link server-side, altfel `/?tab=integrare` cade pe Acasa),
`app/web/templates/dashboard.html` (tuplu nav nou + generalizarea scriptului ARIA tablist), `app/web/templates/_integrare.html` (NOU),
`tests/test_web_integrare.py` (~4 fisiere)
- **Test intai (RED)**: `tests/test_web_integrare.py`
`test_tab_integrare_in_nav`,
`test_deeplink_tab_integrare_randeaza_panou_server_side` (`/?tab=integrare` → panou randat, NU Acasa — prinde branch-ul lipsa din `_render_panel_for_tab`),
`test_fragment_integrare_necesita_login` (`require_login`),
`test_pagina_contine_account_id_si_endpoint_real` (din `request.base_url`),
`test_pagina_are_tab_limbaje_si_vfp_cu_dialecte` (limbaj = tablist primar; VFP = un tab cu sub-tablist MSXML2/WinHttp),
`test_canal_secundar_prezentari_si_import`,
`test_export_card_openapi_postman_swagger`,
`test_buton_copiaza_citeste_din_pre_code` (markup copy citeste din `<pre><code>`, nu din atribut),
`test_empty_state_cta_cont_cand_fara_cheie_sau_creds`,
`test_fara_culori_hardcodate_doar_tokens` (snippet-ul template-ului nu contine `#`-hex; doar `var(--...)`).
- **Acceptance criteria**:
- [x] Tab nou "Integrare" in nav (`dashboard.html`), `integrare` in `_TABS_VALIDE`, **branch nou in `_render_panel_for_tab`**,
deep-link `/?tab=integrare` randat server-side, fragment lazy pe click (pattern identic cu Mapari/Cont). NU atinge `_render_panel_import`/`coada`.
- [x] **IA pe doua niveluri (design-review P1):** tab primar = LIMBAJ (curl/Python/PHP/C#/Node/VFP) — VFP e UN tab, nu sectiune
separata; in panelul VFP, al doilea tablist = dialect (MSXML2 / WinHttp). Tab secundar (in fiecare panel de limbaj) = CANAL
(Prezentari JSON / Import fisier). Se vede UN singur snippet o data (fara produs cartezian 14-snippet pe ecran).
- [x] **Refolosire ARIA (design-review P1):** generalizeaza scriptul de keyboard-nav din `dashboard.html` (sageti/Home/End,
roving `tabindex`, sync `aria-selected`+`aria-controls`) ca sa prinda ORICE `[role=tablist]`, scoped pe containerul propriu
(nu primul `querySelector` global) — altfel tab-urile imbricate (limbaj/canal/VFP-dialect) intra in conflict pe `ArrowRight`.
- [x] **Copy-to-clipboard (design-review P2):** buton per snippet care citeste textul din `<pre><code>` asociat (NU din `data-*`);
feedback in `aria-live="polite"` + label "Copiat" cu revenire la "Copiaza" dupa ~2s; `navigator.clipboard` cu `.catch()` +
fallback pt context ne-securizat (selecteaza textul + "Ctrl+C") — butonul nu ramane blocat fals pe "Copiat".
- [x] **Empty-state onboarding (design-review P2 + CEO P1):** cand sesiunea NU are cheie rotita sau NU are creds RAR, afiseaza
deasupra snippet-urilor un empty-state cu CTA direct `href="/?tab=cont"` ("Genereaza cheia din Cont; o vezi o singura data").
Snippet-urile raman vizibile (placeholder-based). Flux de onboarding continuu, nu doar o nota text.
- [x] Card separat "Export & referinta" (componenta `.cardlink`): Swagger `/docs`, `GET /openapi.json`, `GET /v1/integrare/postman.json`
(Postman = `download`; restul `target="_blank" rel="noopener"`).
- [x] Integreaza formularul "Testeaza conexiunea" din US-004 (primul card al panoului; markup-ul tinteste ruta US-004).
- [x] **Tokens de tema (design-review P3, pregatire 5.3):** template-ul foloseste DOAR variabilele din `:root`
(`--bg`/`--card`/`--ink`/`--muted`/`--line`/`--accent`), zero culori hex hardcodate; fara highlight de sintaxa colorat
(ar introduce paleta noua). Cod copiabil pe `--ink`, nu `--muted`.
- **Verificare E2E**: browser pe `/integrare` — comuta limbaj (inclusiv VFP cu dialect), comuta canal, copiaza un snippet (vezi
"Copiat" + revenire), vede account_id-ul + endpoint-ul reale, deschide `/openapi.json` si importul Postman.
### US-004: Backend web — "Testeaza conexiunea" (verifica cheia lipita, scoped pe sesiune)
**Ca** utilizator **vreau** sa lipesc cheia mea si sa primesc confirmare ca e valida **pentru ca** sa stiu ca
integrarea va autentifica corect inainte sa scriu cod.
- **Depinde de**: —
- **Fisiere**: `app/web/routes.py` (`POST /integrare/test-cheie`, `require_login` + CSRF),
`app/web/templates/_integrare_test_rezultat.html` (NOU — fragment de raspuns dedicat, NU inline in `_integrare.html` care
apartine US-003; eng-review P2: evita fisier partajat intre valuri), `tests/test_integrare_test_cheie.py` (~3 fisiere)
- **Test intai (RED)**: `tests/test_integrare_test_cheie.py`
`test_cheie_valida_a_contului_curent_ok` (mesaj succes + account_id),
`test_cheie_a_altui_cont_respinsa` (cheia mapeaza pe alt cont → mesaj "nu apartine contului tau", NU dezvaluie care),
`test_cheie_invalida_mesaj_clar`,
`test_cheie_revocata_dupa_rotire_respinsa` (eng-review P2: cel mai probabil caz de suport — lipesti cheia veche dupa rotire),
`test_cheie_goala_nu_da_fals_pozitiv_in_dev` (eng-review P2: foloseste `account_for_key` direct, NU `resolve_account_id` cu fallback cont 1),
`test_fara_login_redirect_sau_401`,
`test_csrf_lipsa_respinsa`,
`test_cheia_nu_apare_in_raspuns_sau_log` (no echo).
- **Acceptance criteria**:
- [x] `POST /integrare/test-cheie` (`require_login`, CSRF): valideaza cheia lipita prin **`account_for_key` direct** (NU
`resolve_account_id` — altfel in dev o cheie goala/gunoi ar cadea pe cont 1 si ar raporta fals "valida"); confirma DOAR
daca mapeaza pe **contul sesiunii**; altfel mesaj neutru ("nu apartine contului tau", fara sa dezvaluie care cont).
- [x] Cheie revocata (dupa rotire) → tratata ca invalida (`account_for_key` filtreaza `active=1`).
- [x] Niciun echo al cheii in raspuns/log (regula 422-no-echo din `main.py`).
- [x] Raspuns HTML fragment dedicat (`_integrare_test_rezultat.html`), htmx swap intr-un container `aria-live="polite"`:
succes pe `.flash` (border `--ok`, "Cheie valida — cont X"), eroare pe `.banner` (border `--err`). Label uman, fara emoji.
- [x] Input `type=password` + `autocomplete="off"`; microcopy anti-confuzie langa camp: "Verificam doar daca cheia e valida.
Nu o salvam si nu o memoram — cheia se gestioneaza in Cont." Buton "Testeaza conexiunea" (NU "Salveaza").
- [x] NU creeaza/roteste chei (doar verifica) — fara efecte secundare.
- **Verificare E2E**: browser — lipeste cheia reala (din rotire `/cont`) → "valida, cont X"; lipeste gunoi → eroare clara;
lipeste cheia veche dupa rotire → respinsa.
## 4. Graful de valuri
- **Val 1 (paralel, fisiere disjuncte):** US-001 (`integrare_router.py` NOU) ‖ US-002 (`integrare_examples.py` NOU)
‖ US-004 (`routes.py` + test). US-001 si US-002 sunt fisiere noi; US-004 atinge `routes.py` (ruta noua, fara
suprapunere cu US-003 care vine in Val 2). Atentie: US-003 si US-004 ating ambele `routes.py` → NU in acelasi val.
- **Val 2:** US-003 (UI: `routes.py` + `dashboard.html` + `_integrare.html`) — depinde de US-001/US-002/US-004.
Nota (eng-review P2): US-004 isi pune raspunsul in `_integrare_test_rezultat.html` (fisier propriu), NU in `_integrare.html`
(al US-003) — asa cele doua valuri nu impart un template.
**Prioritate de descope (CEO-review P2/P3)** — daca timpul se scurteaza, taie in aceasta ordine inversa de valoare:
1. (pastreaza ultimul) **US-002 + US-003 exemple + retetar VFP in pagina** = wedge-ul de adoptie, ne-negociabil.
2. US-004 (test cheie) — potentator.
3. US-001 ping — potentator.
4. (primul taiat) **export Postman/OpenAPI** — audienta moderna (Python/Node), public secundar fata de VFP. Retetarul VFP
ramane prim-clas indiferent de descope.
## 5. Considerații tehnice
- **Auth**: header `X-API-Key: rfak_...` (sau `Authorization: Bearer rfak_...`), dependinta `resolve_account_id`
(`app/auth.py`). In dev (`require_api_key=false`) lipsa cheii → cont 1; `ping` semnaleaza asta cu `autentificat_cu_cheie`.
- **Schema request** (din `app/models.py`): `PrezentareRequest{rar_credentials{email,password}, prezentari[PrezentareIn]}`;
`PrezentareIn{vin, nr_inmatriculare, data_prestatie(YYYY-MM-DD), odometru_final(str), odometru_initial?, sistem_reparat="null",
obs?, b64_image?, prestatii?[{cod_prestatie|cod_op_service, denumire}]}`.
- **Import**: `POST /v1/import`, multipart, camp `file` (`app/api/v1/import_router.py`).
- **base_url** derivat din `request.base_url`; documenteaza override in spatele proxy (X-Forwarded). Risc minor de
base_url gresit in spatele reverse-proxy — mitigat prin nota in pagina.
- **Pattern UI**: tab nou = tuplu in `dashboard.html` + `integrare` in `_TABS_VALIDE` + `_render_integrare` +
`/_fragments/integrare` (identic cu Mapari/Cont/Nomenclator).
- **OpenAPI/Postman**: `/openapi.json` e gratuit din FastAPI; colectia Postman e construita o data din endpoint-urile cunoscute.
## 6. Riscuri
- **Drift de schema** intre snippet-uri si `PrezentareIn` real → test care leaga snippet-ul de `model_fields` (US-002).
- **base_url** gresit in spatele proxy → nota + derivare din request.
- **Confuzie dev "merge fara cheie"** (cont 1) → `ping` raporteaza `autentificat_cu_cheie=false`.
- **Scurgere cheie in test-cheie** → no-echo enforce + test dedicat (US-004).
## 7. Intrebari deschise
Rezolvate cu utilizatorul (2026-06-22): test conexiune = `GET /v1/ping` nou (independent de 5.2); exemple pe ambele
canale (prezentari + import); continut doar in-app dinamic (fara markdown versionat); VFP = ambele dialecte (MSXML2 + WinHttp).
Niciuna ramasa deschisa.
## 8. Verificare (rezumat E2E per canal)
- API: `curl GET /v1/ping` cu cheie reala pe instanta locala (8010) → 200 cont corect; import `postman.json`.
- Web: browser pe `/integrare` — comuta limbaj, copiaza snippet, vede account_id+endpoint reale, testeaza cheia.
- Regresia de aur (neatinsa, dar de confirmat ca nu s-a stricat): `POST /v1/prezentari` (sau import → commit) →
worker → `FINALIZATA` la RAR test.
**Criteriu de succes al wedge-ului (CEO-review P3):** un service nou (stil VFP) ajunge de la zero la o prezentare
`FINALIZATA` la RAR test folosind DOAR pagina `/integrare` (copiaza snippet → testeaza cheia → vede `are_creds_rar`
trimite), fara suport uman si fara sa atinga README/Swagger separat.
---
## Review-uri de plan (§5.3 — rulate 2026-06-22)
- [x] **CEO** (valoare/scope) — APROB CU MODIFICARI. Aplicat: ping/test ca readiness-check (`are_creds_rar`), CTA continuu spre
`/cont` in empty-state, VFP prim-clas + prioritate de descope (§4), criteriu de succes masurabil (§8). (P2 "ping redundant"
rezolvat prin diferentierea payload-ului: ping poarta `are_creds_rar`+`mediu`, deci e readiness, nu auth-echo.)
- [x] **Eng** (fezabilitate/teste) — APROB CU MODIFICARI. Aplicat: montare router `prefix="/v1"` in `main.py` fara dublare +
test inregistrare unica; drift-test cu `is_required()` + `prestatii` obligatoriu (US-002); template dedicat
`_integrare_test_rezultat.html` (US-004, anti fisier-partajat intre valuri); teste Bearer/cheie-goala/cheie-revocata;
`account_for_key` nu `resolve_account_id`; Postman allowlist exact 3 (nu `app.routes`); `_render_panel_for_tab` in fisierele US-003.
- [x] **Design** (UI) — APROB CU MODIFICARI, claritate plan UI 6/10 → adresat. Aplicat: IA pe 2 niveluri (limbaj primar, canal
secundar, VFP cu sub-dialect); refolosire + generalizare script ARIA tablist (scoped per container); copy din `<pre><code>` +
`aria-live` + fallback non-HTTPS; empty-state cu CTA `/?tab=cont`; card "Export & referinta" cu `.cardlink`; doar tokens de
tema (pregatire 5.3); microcopy anti-confuzie + `type=password` la test-cheie.
Toate cele trei: **APROB CU MODIFICARI**, modificarile incorporate mai sus. Asteapta poarta umana de aprobare (§5.8) → `aprobat`.
---
## Raport VERIFY (2026-06-22 — subagent context curat + E2E browser lead)
**Rezultat: PASS** (dupa o runda de fix pe 4 discrepante minore).
- **Suita**: `python3 -m pytest -q`**564 passed**, 0 failed (523 baseline + 41 noi: 11 API ping/postman, 8 examples, 10 web integrare initial + 4 lock-uri fix, 8 test-cheie).
- **US-001..US-004 (criterii de acceptare)**: toate PASS (verificator independent, dovezi in cod + teste + `curl` live pe :8010 — ping cont 1 `autentificat_cu_cheie=false`, cheie valida `=true`, Bearer ok, cheie invalida 401, postman.json 3 items allowlist fara `/v1/conturi/rar-creds`, openapi 200).
- **Fix dupa VERIFY r1** (4 discrepante PRD-vs-implementare, toate in `_integrare.html`, lock-uite cu teste noi): cardul Export foloseste `.cardlink`; linkul Postman are `download` (fara `target=_blank`); microcopy anti-confuzie la test-cheie ("Nu o salvam si nu o memoram…"); butonul Copiaza isi schimba label-ul in "Copiat" + revine la 2s.
- **E2E browser (Playwright, lead, dev `web_auth_required=false`→cont 1)**: PASS pe `/?tab=integrare` — randare server-side, IA pe 2 niveluri (limbaj→canal), VFP cu al 3-lea nivel (dialect MSXML2↔WinHttp comuta corect, snippet WinHttp.WinHttpRequest.5.1), endpoint+account_id reale, empty-state CTA `/?tab=cont`, card Export, microcopy prezent, **htmx test-cheie**: cheie invalida → fragment eroare in container `aria-live` ("Cheie invalida sau revocata"). **0 erori in consola** (clasa de bug-uri htmx care a muscat 3.6 — absenta aici).
- **Regresia de aur (enqueue)**: `POST /v1/prezentari``status: queued` (live :8010). **Backend trimitere NEATINS** (doar rute noi de citire + UI; zero modificari worker/masina-stari/idempotenta/schema) — confirmat la diff. **Live RAR test (`postPrezentare`→FINALIZATA) NEPROBAT** in sesiune: `AUTOPASS_CREDS_KEY`/creds RAR test indisponibile in mediu. Risc minim (cod de trimitere neatins).
Toate PASS → CLOSE.
### `/code-review` high (2026-06-22 — CLOSE)
8 unghiuri (3 correctness + cleanup/altitude/conventii), verificare 1-vot. **4 bug-uri reale reparate** (toate in suprafata noua, backend trimitere neatins), lock-uite cu teste:
1. **C# snippet** — payload JSON multi-linie (`json.dumps indent=2`) intr-un string literal C# obisnuit → CS1010 (nu compileaza). Fix: `_payload_json_compact` (separators `(",",":")`, fara newline) pentru C#.
2. **VFP snippet**`json.dumps(indent=0)` PRODUCE TOTUSI newline-uri → `cPayload = "..."` rupt (VFP nu suporta string literal multi-linie) in ambele dialecte. Fix: acelasi helper compact.
3. **Node import snippet**`await import("node:buffer")` nu exporta `FormData``new FormData()` arunca TypeError pe orice Node; plus import mort `readFileSync`/`import("fs")` duplicat. Fix: `FormData`/`Blob` globale (Node 18+) + `readFileSync` static folosit direct.
4. **Script `_integrare.html` ne-scoped**`document.querySelectorAll('[role=tablist]')` ataseaza handlere si pe tab-bar-ul PRINCIPAL din `dashboard.html`, acumuland listeneri la fiecare swap htmx. Fix: scoping pe `#integrare-section` (`root.querySelectorAll(...)`), nu mai atinge tab-bar-ul principal.
**Notat ca cleanup viitor (NEREPARAT, disciplina backend/altitudine — low value, fara risc):** `_render_integrare` dubleaza SQL-ul `are_creds`/`are_cheie` din `_get_acasa_context`/`_render_panel_cont` (candidat de helper partajat); `ping` deschide 2 conexiuni DB + apeleaza `account_for_key` de 2 ori (derivabil din `account_id != DEFAULT_ACCOUNT_ID`); `_campuri_obligatorii()` necache-uit (apelat ~6×/render); cele 6 panouri de limbaj din `_integrare.html` copy-paste (candidat macro Jinja2); `{{ mesaj }}` fara `| e` explicit (salvat acum de autoescape, no-echo confirmat). **Pre-existente (in afara 5.1):** `GET /v1/nomenclator` + `/_fragments/nomenclator` neprotejate (deja notate in ROADMAP "de remediat").
568 teste pass.