Files
rar-autopass/docs/prd/prd-5.19-auto-send-manual-coada.md
Claude Agent a29896a790 docs(5.19): PRD bifa "Trimite automat la RAR" + coada tinuta/eliberare manuala
PRD prin /prd + /autoplan (CEO/Design/Eng/DX, voce unica - Codex la plafon).
Per-cont accounts.auto_send_enabled (default OFF time-boxed) + per-rand
submissions.held; snapshot la TOATE ~8 situri queued via held_for_account()
(Eng a prins bug reactivare router:237 ce ocolea Auto OFF); claim_one AND held=0.
Crescut 6->10 stories: US-007 banner/metrics coada imbatranita, US-008 retentie
held (GDPR/L.142), US-009 fixturi teste + audit, US-010 onestitate API (invariant
5.7). 26 taskuri. Eticheta redenumita; testare sigura (rar_env/valideaza) -> TODOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 15:46:23 +00:00

32 KiB

PRD 5.19 — Bifa "Auto": transmitere automata sau manuala din coada

Status: DRAFT (asteapta aprobare). Sursa de contract: docs/api-rar-contract.md. Limba: romana, fara emoji. Stil: aditiv, nedistructiv pe backend-ul de trimitere.

1. Introducere

Astazi transmiterea catre RAR e controlata de un singur comutator global (AUTOPASS_WORKER_SEND_ENABLED, env): cand e pornit, worker-ul ia ORICE rand queued al unui cont active si il trimite imediat. Nu exista un control per-cont care sa permita unui service sa-si tina prezentarile in coada pentru verificare umana inainte de a pleca la RAR.

Cazul concret care motiveaza feature-ul: utilizatorul testeaza canalul API din ROAAUTO (Visual FoxPro) direct in productie (autopass.romfast.ro), pe contul lui de test. Vrea ca prezentarile sa apara in coada si sa astepte, nu sa plece automat la RAR, pana cand le verifica si apasa explicit "Trimite". Reper vizual: bifa "Auto" din dashboard-ul gomag-vending (image.png).

2. Obiective

Obiectiv principal

  • Un comutator "Auto" per-cont, persistat pe contul service-ului: cand e bifat, prezentarile pleaca automat la RAR (comportament actual); cand e debifat, randurile asteapta vizibil in coada pana cand un operator le trimite manual.

Obiective secundare

  • Trimitere manuala per rand ("Trimite") si in bloc ("Trimite toate (N)", analogul "Start Sync" din gomag).
  • La activarea Auto (OFF -> ON), randurile deja tinute sunt eliberate automat spre transmitere.
  • Vizibilitate: randurile tinute apar in coada cu o stare umana clara ("In asteptare (manual)"), separate de cele in curs de trimitere.

Metrici de succes

  • Cu Auto OFF, un POST /v1/prezentari valid creeaza un rand care NU e trimis de worker (ramane vizibil in coada) pana la actiune umana.
  • Cu Auto ON, acelasi rand pleaca la RAR fara interventie (zero regresie fata de azi).
  • Bifa supravietuieste restartului (persistata in accounts), per-cont (un cont OFF nu afecteaza alt cont).

3. Design (decizii luate cu utilizatorul)

# Decizie Alegere
D1 Default bifa "Auto" pe conturi (inclusiv noi) OFF (manual) — sigur, nimic nu pleaca fara confirmare
D2 La OFF -> ON, randurile deja tinute Eliberate automat spre transmitere
D3 Plasare in UI Bara de status (langa contoare, ca in mockup gomag)
D4 Trimitere manuala Per rand + buton "Trimite toate (N)"
D5 Persistenta Bifa salvata pe contul service-ului (accounts)

Mecanica aleasa: flag held pe submission (NU stare noua)

Randurile tinute raman in starea queued (sunt logic in coada, doar puse pe pauza), marcate cu o coloana booleana noua submissions.held. Motiv: evitam atingerea CHECK-ului de stari si a masinii de stari (queued/sending/sent/needs_mapping/ needs_data/error), a pill-urilor, filtrelor si contoarelor — schimbare strict aditiva. Eticheta umana "In asteptare (manual)" se deriva din status='queued' AND held=1 in stratul de afisaj (labels.py).

  • Comutatorul de cont (accounts.auto_send_enabled) guverneaza DOAR: (a) valoarea implicita a lui held la ingestie; (b) eliberarea in bloc la OFF -> ON.
  • Worker-ul (claim_one) ia doar status='queued' AND held=0. Nu mai stie de comutatorul de cont — ramane simplu si robust.
  • Trimiterea manuala (per rand sau bulk) = held: 1 -> 0; worker-ul preia randul la urmatorul poll. Functioneaza chiar daca contul e pe Auto OFF (override uman per rand).

Comutatorul global AUTOPASS_WORKER_SEND_ENABLED ramane kill-switch master (productia il porneste). Feature-ul nou se aseaza DEASUPRA lui: held tine randul indiferent de env.

4. User Stories

US-001: Schema — comutator cont + flag held

Ca dezvoltator Vreau coloanele de persistenta pentru bifa Auto si pentru randurile tinute Pentru ca starea sa supravietuiasca restartului si sa fie per-cont.

Acceptance Criteria:

  • accounts.auto_send_enabled INTEGER NOT NULL DEFAULT 0 CHECK (auto_send_enabled IN (0,1)) adaugat in app/schema.sql + migrare defensiva in app/db.py::_migrate (ALTER idempotent, ca la email/tier).
  • submissions.held INTEGER NOT NULL DEFAULT 0 CHECK (held IN (0,1)) adaugat + migrare defensiva. Index partial idx_submissions_held ON submissions(held) WHERE held=1.
  • Index in _migrate, nu doar schema.sql (Eng MEDIUM): CREATE TABLE IF NOT EXISTS nu se declanseaza pe DB existent -> indexul partial trebuie creat si in _migrate (ca idx_submissions_batch la db.py:155), altfel un DB prod upgradat capata coloana (ALTER) dar NU si indexul.
  • Contul implicit id=1 (dev) ramane pe default (0) — fara tratament special.
  • Helperi in app/accounts.py: get_auto_send(conn, account_id) -> bool si set_auto_send(conn, account_id, enabled: bool) (idempotent, scoped pe cont).
  • python3 -m pytest -q ramane verde (migrare aditiva, fara regresie pe schema).

US-002: Ingestie respecta comutatorul de cont

Ca operator de service cu Auto OFF Vreau ca prezentarile noi sa intre in coada tinute (held=1) Pentru ca sa le verific inainte sa plece la RAR.

Acceptance Criteria:

  • La INSERT-ul status='queued' pe canalul API (app/api/v1/router.py, ~l.282), held = 0 daca accounts.auto_send_enabled=1 altfel 1 (snapshot la ingestie).
  • Acelasi snapshot la commit-ul de import (app/api/v1/import_router.py, ~l.1193).
  • La reresolve (un needs_mapping rezolvat trece pe queued, app/mapping.py ~l.895), held se seteaza tot din comutatorul contului.
  • held NU intra in payload_json, NU in build_key/idempotenta, NU in payload-ul RAR — e pur control de coada (ca reviewed la import).
  • DRY + acoperire COMPLETA (review CEO + Eng Finding A — HIGH): calculul held e UN SINGUR helper held_for_account(conn, account_id) -> int, chokepoint pt. TOATE situri SET status='queued', nu doar 3. Codebase-ul are ~8 scriitori de queued: router.py:282 (enqueue), import_router.py:1190 (commit), mapping.py:895 (reresolve), router.py:237 (reactivare error->queued la re-POST — BUG real de bypass: randul pastra held VECHI -> se auto-trimite desi Auto OFF), si rutele web de operator routes.py mapeaza-inline / corecteaza / repune / bulk-fix.
  • Politica rute operator: pentru tranzitiile declansate de operator in panoul de detaliu (corecteaza/repune/mapeaza/bulk-fix), held=0 (actiunea operatorului = intentie explicita de trimitere) — DAR e o DECIZIE documentata, nu o omisiune, si respecta UX-ul de confirmare cand contul e OFF. Canalele de ingestie (API/import/reresolve/reactivare) = held_for_account.
  • requeue_with_backoff (worker :154) NU atinge held (tranzitie interna worker).
  • Echo pe dedup (Eng MEDIUM): ramura de dedup (router.py:264, re-POST pe rand existent) intoarce si ea held (azi ar da un "queued" curat fals — vezi US-009).
  • Test: cont Auto OFF -> POST /v1/prezentari valid -> rand queued, held=1; cont Auto ON -> queued, held=0.
  • Test reresolve: cont Auto OFF, submission needs_mapping -> mapare salvata -> rand devine queued, held=1 (nu pleaca automat).

US-003: Worker nu trimite randurile tinute

Ca sistem Vreau ca worker-ul sa sara peste randurile held=1 Pentru ca transmiterea sa astepte decizia umana.

Acceptance Criteria:

  • claim_one (app/worker/__main__.py) adauga AND s.held = 0 la WHERE-ul de claim.
  • Test: rand queued, held=1 cu cont active si send pornit -> claim_one intoarce None (nu il ia); acelasi rand cu held=0 -> e luat (sending).
  • Recuperarea orfanilor / reconcilierea NU sunt afectate (held se aplica doar la claim din queued; un rand deja sending ramane gestionat normal).

US-004: Bifa "Auto" in bara de status (toggle + persistenta + auto-release)

Ca operator Vreau o bifa "Auto" in bara de status, salvata pe cont Pentru ca sa pornesc/opresc transmiterea automata dintr-un click.

Acceptance Criteria:

  • Control checkbox HTMX cu eticheta vizibila "Trimite automat la RAR" (decizie user; NU "Auto" — eviti coliziunea cu "Trimitere automata" worker din labels.py) + helptext ("Debifat: prezentarile asteapta confirmarea ta"), in clusterul de header langa .rar-chip SAU pe rand propriu in bara de status (vezi D1). Reflecta accounts.auto_send_enabled al contului din sesiune.
  • POST /auto-send (ruta web, sub require_login + scope cont + CSRF) comuta bifa si o persista pe cont; raspuns OOB care re-randeaza bara de status.
  • La trecerea OFF -> ON: toate randurile queued AND held=1 ale contului devin held=0 (eliberare in bloc), scoped strict pe contul curent. Eliberarea e o SINGURA instructiune SQL atomica (UPDATE ... WHERE account_id=? AND status='queued' AND held=1), NU un loop (review CEO: atomicitate + evita contention cu worker-ul).
  • Garda de confirmare (review CEO F4): daca exista N>0 randuri tinute la activarea Auto, comutatorul cere o confirmare explicita cu numarul si destinatia ("Activarea Auto trimite imediat N prezentari catre RAR PRODUCTIE — FINALIZATA e ireversibila"). Fara confirmare, randurile tinute NU pleaca. Motiv: pe contul de test, un OFF->ON necugetat ar arunca toate prezentarile de proba in RAR real.
  • La trecerea ON -> OFF: randurile deja queued held=0 NU sunt retrase (doar ingestiile NOI vor fi tinute); randurile in sending/sent neatinse.
  • Verify in browser: comuti bifa, se salveaza, ramane dupa refresh; cu OFF un rand nou apare tinut; comutand pe ON randurile tinute pleaca.

US-005: Trimitere manuala — per rand + "Trimite toate (N)"

Ca operator cu Auto OFF Vreau sa trimit un rand tinut sau toate odata Pentru ca sa eliberez selectiv sau in bloc spre RAR.

Acceptance Criteria:

  • Buton "Trimite" pe fiecare rand queued held=1 in lista de trimiteri/coada (_submissions.html / _coada.html), scoped + CSRF.
  • POST /trimitere/{id}/trimite-acum: 404-before-leak pe id strain; seteaza held=0 DOAR daca randul e queued held=1 (no-op sigur altfel); OOB refresh.
  • Buton bulk "Trimite toate (N)" (N = nr. randuri tinute ale contului) -> POST /trimite-toate: elibereaza toate queued AND held=1 ale contului (held=0), cu confirmare tipata (count + "catre RAR PRODUCTIE", review CEO F5). Update atomic scoped pe cont (NU poate elibera randuri ale altui cont).
  • POST /trimitere/{id}/trimite-acum UPDATE include AND status='queued' ca un rand deja sending (luat de worker intre afisaj si click) sa fie no-op sigur (edge race).
  • Eliberarea seteaza doar held=0; worker-ul preia randul la urmatorul poll (trimitere asincrona, ca azi). Necesita worker pornit + send master ON + cont activ.
  • Butonul "Trimite toate (0)" e ascuns cand nu exista randuri tinute.
  • Test: rand tinut -> trimite-acum -> held=0; apoi claim_one il ia.

US-006: Afisaj stare "In asteptare (manual)"

Ca operator Vreau sa disting randurile tinute de cele in curs de trimitere Pentru ca sa stiu ce asteapta decizia mea.

Acceptance Criteria:

  • app/web/labels.py: pentru status='queued' AND held=1 -> eticheta umana "In asteptare (manual)" + clasa CSS de avertizare (ca needs_*); held=0 ramane "In asteptare" (queued normal).
  • Bara de status arata un contor separat "In asteptare (manual): N" cand N > 0 (derivat din queued AND held=1); contorul queued total ramane corect.
  • Lista de trimiteri marcheaza randurile tinute (badge/pill), butonul "Trimite" apare doar pe ele.
  • Verify in browser: un rand tinut afiseaza eticheta corecta si butonul; dupa trimitere trece la "In curs de trimitere" -> "Trimisa".

US-007: Vizibilitate coada tinuta imbatranita (mitigare OBLIGATORIE pt. default OFF)

Ca operator / admin Vreau un semnal vizibil cand prezentari raman tinute prea mult Pentru ca default OFF (decizie user, pana devine stabil) lasa altfel prezentari nedeclarate tacit — exact esecul silentios pe care L.142/2023 il face risc legal.

Conditie: user a ales DELIBERAT default OFF "pana devine stabil" peste avertismentul de conformitate (review CEO F1/F3). Aceasta US e atenuarea agreata si e BLOCANTA, nu optionala.

Acceptance Criteria:

  • Bara de status: cand exista randuri queued AND held=1 mai vechi de N zile (config AUTOPASS_HELD_WARN_DAYS, default 7), afiseaza un banner de avertizare ("M prezentari tinute de >N zile — declarare obligatorie L.142") cu deep-link la lista filtrata pe tinute.
  • /metrics expune un gauge autopass_held_submissions (total randuri tinute) si autopass_held_oldest_age_seconds (varsta celui mai vechi rand tinut), scoped global (observabilitate ops, review CEO F3).
  • Bannerul + gauge sunt derivate (zero stare noua); contorul varstei foloseste created_at al randului.
  • Test: rand tinut cu created_at vechi -> bannerul apare; gauge raporteaza varsta.

US-008: Retentie randuri tinute (inchide gaura GDPR/L.142, review CEO F6)

Ca sistem Vreau ca randurile tinute la nesfarsit sa aiba o politica de expirare Pentru ca un queued held=1 nu e nici sent nici blocat -> azi NU primeste purge_after -> PII criptat (si rar_creds_enc efemer pe canalul API) ar sta vesnic.

Acceptance Criteria:

  • Worker-ul expira randurile queued AND held=1 mai vechi de held_retention_days (config, default 90, aliniat T16): le trece la error cu mesaj TINUT_EXPIRAT (terminal) + seteaza purge_after DIRECT la momentul expirarii (NU lasa mark() sa aplice blocked_retention_days=30). Eng MEDIUM: altfel viata reala = 90 (held) + 30 (error) = 120 zile, nu 90. Fie purge_after explicit la tranzitie, fie documenteaza 120.
  • La eliberarea manuala/auto a unui rand tinut, daca rar_creds_enc (canal API) e prea vechi, worker-ul cade pe accounts.rar_creds_enc (fallback re-login) ca azi — verificat ca creds efemere expirate nu blocheaza trimiterea.
  • Test: rand tinut vechi -> ciclul de purjare al worker-ului il expira + seteaza purge_after; PII devine purjabil.

US-009: Fixturi teste + jurnal audit (review CEO F7 + observabilitate)

Ca dezvoltator Vreau ca suita existenta sa nu se blocheze pe default OFF si ca actiunile sa fie auditate Pentru ca default OFF + claim_one ... AND held=0 face ca lantul POST -> claim -> sent din testele existente (+ test_live_rar) sa stagneze tacit daca nu setam Auto ON.

Acceptance Criteria:

  • conftest/factory de cont seteaza auto_send_enabled=1 (sau held=0) pe conturile folosite de testele care exercita lantul de trimitere; test_live_rar seteaza explicit Auto ON. pytest -q ramane verde.
  • Subtilitate id=1 (Eng HIGH/test): contul implicit id=1 e creat de schema.sql (INSERT OR IGNORE), NU de create_account -> un fix care patcheaza doar factory-ul NU acopera contul folosit de majoritatea testelor (test_import_e2e, test_creds_delivery, test_live_rar ar stagna). Conftest face explicit UPDATE accounts SET auto_send_enabled=1 WHERE id=1 (autouse). E un fix de STARE DB, nu env var (coloana e per-rand in accounts).
  • Audit app_events: comutarea Auto (auto_send_schimbat cu valoarea + cont) si eliberarile manuale/bulk (held_eliberat cu count) sunt jurnalizate (redactat, scoped).
  • Echo onest pe canalul API (aliniat invariant 5.7): raspunsul POST /v1/prezentari pentru un rand tinut indica starea reala (held=true / nota umana "tinut pentru verificare"), nu un fals "queued" curat. Dev-ul ROAAUTO vede ca randul NU a plecat.
  • Test: eveniment audit scris la toggle + la eliberare; raspuns API reflecta held.

US-010: Onestitate + observabilitate pe canalul API (review DX Faza 3.5)

Ca dezvoltator ROAAUTO/VFP care integreaza prin POST /v1/prezentari Vreau sa vad clar ca un rand e tinut si NU a plecat la RAR Pentru ca azi un rand tinut intoarce byte-identic cu unul gata de auto-send (status:queued, erori:[]) -> reintroduce exact bug-ul de succes-fals 5.7.

Acceptance Criteria:

  • Camp held: bool = False pe SubmissionResult (models.py) + plumbing din held_for_account in _rezultat_enqueue(..., held=...) SI pe ramura de dedup (router.py:264). Cand held and status=='queued', motiv devine NON-null (DX CRITICAL): mesaj uman "In asteptare — tinut pt verificare; NU trimis la RAR (Auto OFF)".
  • held in proiectiile GET (_PREZENTARE_FIELDS router.py:398 + lista cols router.py:369): un dev care face GET /v1/prezentari/{id} vede held=true, nu un queued etern fara semnal (DX HIGH).
  • Reutilizeaza vocabularul existent AUTO_SEND_OPRIT (errors.py:92) pt. mesajul held — NU inventa al treilea vocabular "auto_send" (DX + R6). Mesaj 3-niveluri (problema/cauza/fix) pe rar_error/motiv.
  • Documentatie hub /integrare (integrare_examples.py/_integrare.html): tabel "De ce nu ajunge la RAR?" (held / needs_mapping / needs_data) + nota explicita "conturi noi pornesc cu Auto OFF, randurile asteapta eliberare manuala" + cum verifici/comuti (DX HIGH — altfel primul POST da 200/queued, dev-ul crede ca merge, nimic nu ajunge).
  • (Optional, paritate API) endpoint de eliberare API simetric cu /repune (router.py:458): POST /v1/prezentari/{id}/trimite-acum, scoped pe cont, 404-before, no-op daca nu queued AND held=1 — ca integratorul API sa nu fie fortat in browser.
  • Test: held -> held=true + motiv non-null pe enqueue, dedup si GET (regresie ca test_queued_fara_erori_nemapate).

5. Cerinte functionale

  1. [REQ-001] Comutatorul "Auto" e per-cont, persistat in accounts.auto_send_enabled, default 0 (OFF) inclusiv pentru conturi noi.
  2. [REQ-002] Cu Auto OFF, orice ingestie care ar produce queued produce queued held=1.
  3. [REQ-003] Worker-ul nu trimite niciodata un rand held=1.
  4. [REQ-004] OFF -> ON elibereaza in bloc randurile tinute ale contului (atomic, scoped), DAR cu confirmare tipata cand N>0 (count + "RAR PRODUCTIE"); ON -> OFF nu retrage randuri deja eliberate.
  5. [REQ-005] Operatorul poate elibera un rand tinut individual sau toate odata (bulk cu confirmare).
  6. [REQ-006] held nu influenteaza payload-ul RAR, idempotenta sau validarea — pur coada.
  7. [REQ-007] Toate rutele noi sunt scoped pe contul din sesiune, sub require_login, cu CSRF si 404-before-leak pe id strain. account_id se deriva INTOTDEAUNA din sesiune, NICIODATA dintr-un camp de formular (Eng security): altfel un operator pe contul A ar elibera in bloc randurile contului B postand account_id=B. Per-rand prin _get_submission_scoped (404 inainte de UPDATE).
  8. [REQ-008] Randurile tinute imbatranite sunt VIZIBILE (banner + /metrics) si au politica de retentie/expirare (nu raman PII vesnic). Comutarea + eliberarile sunt auditate in app_events.

6. Non-Goals (ce NU facem)

  • Fara interval/programare de sync (dropdown "1 min" + buton "Start Sync" din gomag): worker-ul autopass e continuu, nu pe interval. "Trimite toate" e analogul lui "Start Sync".
  • Fara stare noua de submission (held/tinut): folosim flag boolean pe queued.
  • Fara comutator per-operatie sau per-canal: granularitatea e per-cont (decizie D5). (Nota: coloanele auto_send ramase pe operations_mapping/operation_text_rules sunt neutralizate din 5.11 si NU se reactiveaza aici.)
  • Fara modificarea kill-switch-ului global AUTOPASS_WORKER_SEND_ENABLED.
  • Fara retragerea randurilor deja in sending/sent (FINALIZATA e terminal la RAR).
  • held NU e sandbox de testare (avertisment de onestitate — tema cross-faza CEO F2 + DX4): eliberarea unui rand tinut declara REAL la RAR (FINALIZATA ireversibila). "Tinut" doar AMANA o trimitere reala. Ca sa testezi fara consecinte cu functia asta: tii randul si il STERGI (nu-l eliberezi). Decizie user (poarta finala): 5.19 = doar tinut operational; fara documentare /valideaza ca unealta de testare si fara rutare per-cont la RAR test (rar_env). Acestea raman posibile follow-up-uri (TODOS), neangajate in 5.19.

7. Consideratii tehnice

Stack / fisiere atinse

  • Schema: app/schema.sql + app/db.py::_migrate (2 coloane aditive + 1 index).
  • Backend cont: app/accounts.py (get/set toggle).
  • Ingestie: app/api/v1/router.py, app/api/v1/import_router.py, app/mapping.py (reresolve) — set held din comutator.
  • Worker: app/worker/__main__.py::claim_one (+AND s.held=0).
  • Web: app/web/routes.py (rute /auto-send, /trimite-toate, /trimitere/{id}/trimite-acum), app/web/labels.py, template-uri _status.html / _submissions.html / _coada.html.

Patterns de urmat

  • Migrare defensiva aditiva (model accounts.email / accounts.tier din 5.12/5.17).
  • Rute web scoped + CSRF + OOB HTMX (model submissions_admin.py / butoanele de lifecycle 5.6).
  • Strat de afisaj pur in labels.py (model 5.4) — fara logica de stare in template.

Riscuri tehnice

  • R1 (default OFF schimba comportamentul): azi nu exista hold; cu default 0, conturile ar tine totul. Acceptabil — productia e pre-lansare, fara conturi legacy active (cf. 5.17), iar utilizatorul vrea explicit OFF pe contul de test. Documentat ca decizie constienta (D1).
  • R2 (reresolve scapa snapshot-ul): daca uitam held pe calea de reresolve (mapping.py), un rand deblocat din needs_mapping ar pleca automat desi contul e OFF. Acoperit explicit de US-002 AC.
  • R3 (idempotenta): held NU intra in cheie -> un re-POST al aceluiasi continut loveste randul existent (dedup), nu creeaza dublura. Confirmat de invariantul build_key.
  • R4 (hazard de rollback — review CEO + Eng, HIGH operational): daca se da revert pe cod DUPA ce randuri au held=1, worker-ul pierde filtrul AND held=0 -> ar trimite TOATE randurile tinute la RAR (FINALIZATA ireversibila). Atenuare OBLIGATORIE: livreaza ODATA cu feature-ul un helper tools/ care carantineaza randurile tinute (UPDATE submissions SET status='error', rar_error='ROLLBACK_QUARANTINE' WHERE held=1) + pas de runbook scris in §9 (copy-paste, nu improvizat sub presiune).
  • R7 (eroziune creds efemere — Eng low-med): la orice login reusit worker-ul NULL-eaza TOATE submissions.rar_creds_enc ale contului (worker:382), nu doar randul trimis. Un cont hibrid web+API cu keepalive-login poate sterge creds-urile efemere ale unui rand tinut API -> la eliberare se cade pe accounts.rar_creds_enc (fallback). Acoperit de US-008, dar triggerul e login-frate, nu varsta creds — de formulat corect.
  • R5 (contention SQLite la bulk release): UPDATE masiv pe "Trimite toate" concureaza cu BEGIN IMMEDIATE al worker-ului -> posibil database is locked. Update-ul atomic (o instructiune) + retry/backoff scurt; sau chunking daca N e mare.
  • R6 (naming): apare al TREILEA auto_send (cont auto_send_enabled vs operations_mapping.auto_send vs operation_text_rules.auto_send). Comentariu clar in schema.sql care le distinge, ca un viitor dezvoltator sa nu le confunde.

Rafinari UI (review design Faza 2 — OBLIGATORII la implementare)

  • D1 (container real): RAR dot e in base.html (header .rar-chip), NU in _status.html. US-004 AC corectat: comutatorul Auto sta in clusterul de header langa .rar-chip (vizibilitate maxima, langa semnalul RAR real) SAU pe un rand propriu etichetat in bara de status — NU "langa dot" in _status.html (dot-ul nu e acolo).
  • D2 (toggle non-optimist): checkbox HTMX flip-uie vizual indiferent de raspuns. Necesita hx-indicator + revert-on-failure (la esec POST /auto-send -> bifa revine + toast eroare). Fara fals-sigur tacit pe un comutator de transmitere guvernamentala.
  • D3 (poller nu inghite toggle-ul): #status-bar are hx-trigger="every 15s" + hx-swap="outerHTML" -> ar inlocui comutatorul la fiecare 15s (pierdere focus tastatura + flicker). Exclude comutatorul din swap-ul periodic (container separat sau hx-preserve).
  • D4 (modal de confirmare real): confirmarea tipata (count + "RAR PRODUCTIE") NU se poate face cu hx-confirm (doar OK/Cancel nativ). Necesita un component modal (count, destinatie, type-to-confirm) — adaugat in lista de fisiere. Per-rand "Trimite" primeste si el o confirmare (1 linie + microcopy de ireversibilitate), nu doar bulk-ul.
  • D5 (camp derivat, nu in template): held NU e stare noua -> pill-ul existent ar randa "In coada" identic pt held si non-held. Calcul UN camp de afisaj derivat in routes.py (regula "display layer pur"), nu in template. Culoare --warn (amber), NU clasa needs_* (rosu/eroare) — held e asteptare benigna, nu eroare.
  • D6 (mobil 390px): per-rand actiune = afordanta dedicata pe .trimitere-slim cu event.stopPropagation() (randul e el insusi role=button), NU buton-copil nestat. Al 6-lea contor "In asteptare (manual)" se pliaza in celula "In coada" pe bara compacta (nu adauga a 6-a celula la 10px). Pill scurt ("Manual"/"Tinut") cu fraza completa in title.
  • D7 (ordonare bannere): _status.html poate avea deja 3 bannere (cont inactiv / trial / RAR jos) + al 4-lea (US-007 held). Regula de prioritate un-singur-banner ca sa nu impinga contoarele sub fold pe mobil.

Dependente

  • Trimiterea manuala produce efect doar cu worker pornit + send master ON + cont active (mediul de productie real). In dev (send OFF) randul eliberat ramane queued held=0.

8. Open Questions

  • Trimiterea manuala se face asincron (flip held=0, worker preia la poll). Acceptam latenta de un poll (cateva secunde) sau vrem feedback "in curs" imediat in UI? (Propunere: asincron + OOB refresh, fara sincron — consistent cu arhitectura.)
  • Pe mobil, butonul "Trimite" per rand + "Trimite toate" incap in layout-ul compact (5.13)? (Propunere: "Trimite toate" in bara sticky, "Trimite" iconita pe card.)

9. Plan de verificare

  • Regresie python3 -m pytest -q verde (baseline curent ~1392) + teste noi per story.
  • E2E browser (Playwright, logat): comutare bifa persistenta dupa refresh; rand nou tinut cu Auto OFF; eliberare per rand si bulk; tranzitie OFF -> ON elibereaza in bloc.
  • Optional live RAR (AUTOPASS_LIVE_RAR=1): cont OFF -> rand tinut -> "Trimite" -> sent idPrezentare=... confirmat in finalizate.

10. Decizii /autoplan — audit trail

Pipeline: CEO -> Design -> Eng -> DX, voce unica (Codex indisponibil pana 2026-07-18, plafon utilizare). Deciziile intermediare auto-decise pe 6 principii; portile umane = premise + taste.

Poarta de premise (decizia ta)

  • Scop: AMBELE — control operational permanent + ajutor de testare.
  • Default Auto: OFF, pastrat "pana devine stabil" (ales constient peste avertismentul de conformitate L.142). Inverseaza recomandarea CEO F1 (default ON). Acceptat ca decizie de domeniu; declanseaza atenuari OBLIGATORII (US-007/008/009).

Decizii auto (6 principii)

# Faza Decizie Clasif. Principiu Motiv
1 CEO Approach A (held boolean) ca baza, nu stare noua (B) sau enum mod (C) Mecanica P5+P3 aditiv, reuse pattern reviewed; B atinge masina de stari pazita
2 CEO US-007 vizibilitate coada imbatranita OBLIGATORIE Mecanica P1+observ atenuarea agreata pt default OFF; inchide esecul silentios F3
3 CEO US-008 retentie randuri tinute Mecanica P1 F6: held nu primeste purge_after -> PII vesnic (GDPR/L.142)
4 CEO US-009 fixturi teste Auto ON + audit + echo API held Mecanica P1 F7: default OFF stagneaza testele; invariant 5.7 raspuns onest
5 CEO Garda de confirmare OFF->ON + bulk (count + RAR PRODUCTIE) Mecanica P1 F4/F5: flush ireversibil de randuri test in RAR real
6 CEO held_for_account() helper unic (DRY) Mecanica P4 calcul held inline de 3x = sit uitat trimite automat
7 CEO Enum mod cont (live/hold/test) -> TODOS Mecanica P3 scope dincolo de cerere; dream-state, nu blocant

Decizii de taste / provocari -> poarta finala (Faza 4)

  • T-EXP1 (reframe testare, CEO F2 + DX4) -> REZOLVAT: user a ales "doar tinut". Nici rar_env, nici documentarea /valideaza ca unealta de testare in 5.19. Ambele -> TODOS (posibil follow-up). Pastrat doar avertismentul de onestitate ca eliberarea declara real.
  • T-LABEL (eticheta toggle, Design HIGH) -> REZOLVAT: user a ales REDENUMIREA. Eticheta vizibila = "Trimite automat la RAR" (nu "Auto"), ca sa nu se ciocneasca cu "Trimitere automata" (worker viu) din labels.py. Conceptul/coloana raman auto_send_enabled.

Faze Design/Eng/DX (audit)

Faza Decizie cheie Clasif. Motiv
Design D1-D7 rafinari UI (non-optimist, poller, modal, mobil, camp derivat, --warn, bannere) Mecanica structural, P5 explicit
Eng held_for_account la TOATE ~8 situri queued (bug reactivare router:237) Mecanica P5; bypass real Auto OFF
Eng conftest UPDATE id=1; index in _migrate; purge_after direct; account_id din sesiune Mecanica corectitudine/securitate
DX held pe SubmissionResult+GET; reuse AUTO_SEND_OPRIT; hub docs Mecanica P1; invariant 5.7

Sumar completare review

+====================================================================+
|        /autoplan — MEGA PLAN REVIEW — COMPLETION SUMMARY            |
+====================================================================+
| Mod                  | SELECTIVE EXPANSION                          |
| Voci                 | Claude subagent (CEO/Design/Eng/DX);          |
|                      | Codex INDISPONIBIL (plafon -> 2026-07-18)    |
| Poarta premise       | scop=AMBELE; default OFF (user, time-boxed)  |
| CEO                  | 7 findings, 2 critice -> atenuate            |
| Design               | 5->8/10; 13 findings, 3 critice -> D1-D7     |
| Eng                  | 7 issues; 1 BUG real (reactivare bypass)     |
| DX                   | 5->8/10; onestitate API -> US-010            |
| Stories              | 6 -> 10 (US-007/008/009/010 adaugate)        |
| Taskuri              | 26 (14 P1, 9 P2, 3 P3), agregate pe faze     |
| Tema cross-faza      | hold != sandbox testare (CEO F2 + DX4)       |
| Taste rezolvate      | T-EXP1=doar tinut; T-LABEL=redenumire        |
| Deferate (TODOS)     | enum mod cont; rar_env; doc /valideaza       |
| Test plan            | scris pe disc (~/.gstack/.../test-plan)      |
| Artefacte taskuri    | 4 JSONL pe faza                              |
| Decizii nerezolvate  | 0                                            |
+====================================================================+

GSTACK REVIEW REPORT

Review Trigger Why Runs Status Findings
CEO Review /plan-ceo-review Scope & strategy 1 issues_open->resolved 5 propuneri, 4 acceptate, 2 deferate; 2 gap critice atenuate
Eng Review /plan-eng-review Architecture & tests (required) 1 issues_open->resolved 7 issues (1 bug bypass reactivare), 0 gap critice ramase
Design Review /plan-design-review UI/UX gaps 1 issues_open->resolved 5->8/10, 13 findings (3 critice) -> D1-D7
DX Review /plan-devex-review Developer experience gaps 1 issues_open->resolved 5->8/10, onestitate API (US-010)
  • CROSS-MODEL: N/A — Codex indisponibil (plafon utilizare pana 2026-07-18); voce unica Claude subagent pe toate fazele.
  • VERDICT: CEO + DESIGN + ENG + DX CLEARED (voce unica) — PRD revizuit, gata de implementare. Toate deciziile portilor inchise cu user.

NO UNRESOLVED DECISIONS