Files
rar-autopass/docs/prd/prd-5.17-tipuri-cont-planuri-trial.md
Claude Agent 9eccb9f6fa docs(autoplan): review 5.16+5.17 + decizii porti umane + ROADMAP
Rulare /autoplan in paralel (2 agenti) pe PRD 5.16 si 5.17, faze
CEO/Design/Eng(/DX), single-voice (Codex la plafon pana 2026-07-18).
Audit trail + GSTACK REVIEW REPORT scrise in fiecare PRD; 0 decizii
deschise pe ambele.

Decizii inchise cu user:
- 5.16 User Challenge -> system-ui (scoate IBM Plex self-hostat; risc
  per-OS + design slop acceptat constient). Pre-ship: teste Eng E1/E3.
- 5.17 User Challenge -> enforcement DUR direct de la deploy; CRITICAL
  GAP migrare legacy = MOOT (pre-productie/fara conturi legacy); flag
  AUTOPASS_ENFORCE_PLANS optional; 3 taste decisions rezolvate pe
  recomandare (limita 60 = constanta config; banner one-time
  trial->Gratuit; valideaza dry-run permis pe orice plan).

ROADMAP: linia "Ultima actualizare" + randuri noi 5.16/5.17 (TODO,
gata de implementare) in tabelul Etapa 5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:58:33 +00:00

67 KiB
Raw Permalink Blame History

PRD 5.17 — Tipuri de cont (planuri) + trial Pro 30 zile + enforcement

Stare: draft

Proces complet: docs/ROADMAP.md §5. Contract RAR (sursa de adevar): docs/api-rar-contract.md. Landing comercial cu planurile: app/web/templates/landing.html (sectiunea PRICING). Lifecycle cont existent: app/accounts.py, app/schema.sql (tabela accounts, coloana status). Signup: app/web/auth_routes.py (signup_post, butoanele landing trimit data-plan). Starea trece: draft -> aprobat -> in-executie -> verify-pass -> inchis.

1. Introducere

Landing-ul comercial promite patru planuri — Gratuit, Standard (39 lei), Pro (59 lei, cu API), Premium (la cerere) — si afirma ca fiecare cont incepe cu acces gratuit 30 de zile la un plan superior. In aplicatie insa nu exista deloc conceptul de tip de cont: tabela accounts are doar status (pending/active/blocked/archived/deleted) si on_unmapped_error_default. Nimic nu diferentiaza un cont gratuit de unul platit, nimic nu aplica limita de volum sau gate-ul de API, si nu exista niciun trial.

In plus, userul a decis doua corectii fata de landing-ul actual:

  1. Trial-ul de 30 de zile e pe Pro, NU pe Premium (landing-ul scrie azi "Premium gratuit 30 de zile" — gresit; trebuie "Pro 30 de zile").
  2. Limita planului Gratuit scade de la 100 la 60 de prestatii/luna — actualizata si in landing si in aplicatie.

5.17 introduce modelul de tipuri de cont, trial-ul Pro de 30 de zile, enforcement DUR al diferentelor (volum lunar + acces API), si downgrade automat la expirarea trial-ului. NU include integrare de plata (nu exista inca sistem de facturare) — alocarea planului platit ramane manuala (admin), iar trial-ul porneste automat la creare cont.

2. Obiective

Obiectiv principal

Aplicatia sa sustina real diferentele dintre planuri pe care landing-ul le promite: cont nou → trial Pro 30 zile → la expirare downgrade pe Gratuit (60/luna, fara API), cu enforcement efectiv.

Obiective secundare

  • Sursa unica de adevar pentru definitia planurilor (limite + capabilitati), consumata de backend si UI.
  • Mesaje oneste cand un cont atinge limita sau cere o capabilitate neinclusa (3 niveluri, ca 5.4).
  • Vizibilitate in dashboard: planul curent + zile ramase din trial + consum lunar.

Metrici de succes

  • Un cont Gratuit care depaseste 60 prestatii/luna primeste un raspuns clar de respingere (API + web), iar contoarele lunare se reseteaza corect la inceput de luna (timp local RO).
  • Un cont fara plan Pro+ primeste 403 onest pe /v1/* de import API.
  • Un cont nou are trial Pro activ; dupa 30 zile (sau setand trial_until in trecut in test) trece automat pe Gratuit, cu enforcement-ul aferent.
  • Landing + app afiseaza coerent "60 prestatii/luna" si "Pro gratuit 30 de zile".

3. User Stories

Database → backend → API → UI (ordinea dependentelor). Un singur autor pe accounts.py/schema.sql in valul de model.

US-001: Schema — accounts.tier + trial_until + definitia planurilor

Ca sistem vreau sa stiu planul fiecarui cont si pana cand e in trial pentru ca restul logicii depinde de asta.

  • Depinde de: —
  • Fisiere: app/schema.sql (coloane noi + migrare defensiva), app/accounts.py (helperi), app/plans.py (NOU — definitia planurilor, sursa de adevar), tests/test_accounts.py / tests/test_plans.py (~4 fisiere)
  • Test intai (RED): test_migrare_tier_trial_defensiva, test_plan_definitii, test_cont_nou_trial_pro_30z
  • Acceptance criteria:
    • accounts capata (migrare aditiva defensiva, ca email/status in 5.5/5.12): tier TEXT NOT NULL DEFAULT 'free' CHECK (tier IN ('free','standard','pro','premium')) si trial_until TEXT (nullable; ISO datetime UTC sau NULL daca nu e in trial).
    • app/plans.py = SINGURA sursa de adevar: dict PLANS cu, per plan, {label, monthly_limit, api_access, ...}. Valori: freemonthly_limit=60, api_access=False; standardmonthly_limit=None (nelimitat), api_access=False; promonthly_limit=None, api_access=True; premiummonthly_limit=None, api_access=True. (Aliniat landing-ului, cu limita Gratuit 60.)
    • Helper effective_tier(account): daca trial_until e in viitor → randeaza ca pro (trial); altfel tier. (Trial-ul = acces Pro temporar peste tier-ul de baza free.)
    • create_account seteaza tier='free' si trial_until = now + 30 zile (trial Pro automat la creare). Contul implicit id=1 (dev) e exceptat / setat coerent (nu blocheaza dev-ul).
    • Migrare idempotenta (re-rulabila); conturile legacy fara tier primesc free + fara trial (sau trial calculat din created_at — decizie la executie; implicit: legacy → free fara trial).
  • Verificare E2E: creez cont nou → tier=free, trial_until ≈ now+30z, effective_tier=pro.

US-002: Numarator de consum lunar (prestatii/luna pe cont)

Ca sistem vreau sa stiu cate prestatii a trimis un cont in luna curenta pentru ca limita Gratuit (60/luna) se aplica pe acest numar.

  • Depinde de: US-001
  • Fisiere: app/accounts.py SAU app/plans.py (monthly_usage(conn, account_id)), tests/test_plans.py (~2 fisiere)
  • Test intai (RED): test_consum_lunar_numara_sent_si_queued, test_consum_lunar_timp_local_ro, test_consum_lunar_resetare_luna_noua
  • Acceptance criteria:
    • monthly_usage(conn, account_id) numara prestatiile contului in luna calendaristica curenta. Definitia "prestatie consumata" (de fixat la executie, propus): randuri submissions ale contului cu status in (queued,sending,sent) cu created_at in luna curenta — adica prestatiile ACCEPTATE in coada, nu cele respinse/blocate. (Justificare: limita e pe ce trimitem la RAR, nu pe incercari esuate.) Alternativ doar sent — de decis; implicit: acceptate-in-coada.
    • Timp local RO (ca E7 din 5.15): bucketarea lunii foloseste offset RO (created_at,'+3 hours' sau echivalent), nu UTC pur, ca prestatiile de la granita de luna sa cada corect. Test la granita.
    • Scoped strict pe cont (nu numara cross-account).
    • Fara coloana noua daca submissions.created_at ajunge (respecta non-goal migrare minima).
  • Verificare E2E: cont cu N trimiteri in luna → monthly_usage == N; luna urmatoare → reset la 0.

US-003: Enforcement DUR — limita lunara Gratuit (60) pe ambele canale

Ca owner vreau ca un cont Gratuit care depaseste 60 prestatii/luna sa fie oprit pentru ca asa sustinem diferenta de plan promisa.

  • Depinde de: US-001, US-002
  • Fisiere: app/api/v1/router.py (create_prezentari), app/api/v1/import_router.py (commit import), app/errors.py (cod nou PLAN_LIMITA_LUNARA), app/web/routes.py (commit web), tests/test_api_scope.py / tests/test_web_* / tests/test_plans.py (~6 fisiere)
  • Test intai (RED): test_free_peste_60_respins_api, test_free_peste_60_respins_import_web, test_pro_si_trial_nelimitat, test_eroare_3_niveluri_plan_limita
  • Acceptance criteria:
    • La enqueue (API POST /v1/prezentari + commit import web + commit import API), daca effective_tier are monthly_limit si monthly_usage + nr_cerut > monthly_limit → cererea e respinsa (sau respinsa partial, la limita) cu eroare 3 niveluri (app/errors.py, cod PLAN_LIMITA_LUNARA: problema "Ai atins limita planului Gratuit (60/luna)", cauza, fix "Treci pe Standard/Pro sau astepti luna viitoare"). NU se face enqueue peste limita.
    • standard/pro/premium si conturile in trial Pro → fara limita de volum.
    • Comportament la cerere de lot care depaseste partial limita (ex. 50 folosite, vin 20): decizie la executie — implicit RESPINGERE clara a intregului lot cu mesaj cat mai e disponibil ("mai poti trimite 10 luna asta"), NU enqueue partial tacut (evita surprize). De confirmat.
    • Enforcement aliniat cu AUTOPASS_REQUIRE_API_KEY (dev vs prod): in dev, contul id=1 nu e blocat artificial (trial/standard coerent), ca dogfooding-ul sa nu se loveasca de limita.
    • Idempotenta neatinsa: respingerea pe limita se face INAINTE de build_key/enqueue; un retry idempotent al unei prestatii deja acceptate nu consuma din nou cota.
  • Verificare E2E: cont free cu 60 trimise → a 61-a respinsa cu mesaj 3 niveluri (API si import web); cont pro → trece.

US-004: Enforcement DUR — gate API doar pe Pro/Premium

Ca owner vreau ca importul prin API sa fie disponibil doar pe Pro+ pentru ca landing-ul spune ca API-ul e o capabilitate Pro.

  • Depinde de: US-001
  • Fisiere: app/auth.py (sau dependinta de ruta), app/api/v1/router.py, app/api/v1/import_router.py, app/errors.py (cod PLAN_FARA_API), tests/test_api_scope.py (~5 fisiere)
  • Test intai (RED): test_free_fara_api_403, test_standard_fara_api_403, test_pro_api_ok, test_trial_pro_api_ok, test_dry_run_valideaza_ramane_permis
  • Acceptance criteria:
    • Rutele de import/ingestie prin API (POST /v1/prezentari, POST /v1/import, etc.) cer effective_tier.api_access == True (pro/premium sau trial Pro). Altfel 403 cu eroare 3 niveluri (PLAN_FARA_API: "Importul prin API e disponibil pe planul Pro", fix).
    • Canalul web ramane neafectat — operatorii pe plan gratuit pot folosi import xlsx/csv prin dashboard (asa promite landing-ul: Gratuit are import manual, NU API). Doar suprafata API e gated.
    • GET /v1/nomenclator ramane public (coduri RAR, fara PII) — invariant CLAUDE.md.
    • POST /v1/prezentari/valideaza (dry-run) — decizie: ramane permis pe orice plan (read-only, ajuta integrarea inainte de upgrade) SAU gated ca restul API. Implicit: PERMIS (read-only, fara enqueue). De confirmat.
    • In dev (AUTOPASS_REQUIRE_API_KEY=false), contul id=1 are acces API (tier coerent), ca testele API existente sa nu pice.
  • Verificare E2E: cheie API pe cont free → 403 onest pe import; cheie pe cont pro/trial → 200.

US-005: Downgrade automat la expirarea trial-ului

Ca owner vreau ca la expirarea celor 30 de zile contul sa treaca automat pe Gratuit pentru ca landing-ul spune "apoi trece automat pe Gratuit, fara plata".

  • Depinde de: US-001, US-003, US-004
  • Fisiere: app/plans.py (effective_tier deja trateaza expirarea — lazy), optional app/worker/__main__.py SAU un job de intretinere (eager), tests/test_plans.py (~3 fisiere)
  • Test intai (RED): test_trial_expirat_efective_free, test_trial_expirat_aplica_limita_60, test_trial_expirat_pierde_api
  • Acceptance criteria:
    • Lazy-first: effective_tier returneaza tier de baza (free) imediat ce trial_until <= now — fara job necesar pentru corectitudine (enforcement-ul US-003/004 se bazeaza pe effective_tier, deci downgrade-ul e automat la prima cerere dupa expirare).
    • Optional (eager, non-blocant): un pas in purjarea orara a worker-ului (T16 existent) poate normaliza trial_until expirat → NULL pentru igiena (NU obligatoriu pentru corectitudine).
    • Un cont cu tier='standard'/'pro'/'premium' setat de admin NU e downgradat de expirarea trial-ului (trial-ul e un BONUS peste free; un plan platit alocat persista).
    • Mesajele de limita/API dupa expirare sunt cele 3-niveluri din US-003/004.
  • Verificare E2E: setez trial_until in trecut → contul aplica limita 60 + pierde API, fara restart.

US-006: UI dashboard — plan curent + zile ramase din trial + consum lunar

Ca operator vreau sa vad pe ce plan sunt, cat mi-a mai ramas din trial si cat am consumat luna asta pentru ca vreau sa stiu cand ma apropii de limita.

  • Depinde de: US-001, US-002
  • Fisiere: app/web/routes.py (context), app/web/templates/_status.html SAU _cont.html (afisaj plan), tests/test_web_status.py / tests/test_dashboard.py (~4 fisiere)
  • Test intai (RED): test_afisaj_plan_si_zile_trial, test_afisaj_consum_lunar, test_avertizare_aproape_de_limita
  • Acceptance criteria:
    • Dashboard-ul afiseaza discret planul curent (ex. "Plan: Pro · trial 18 zile ramase" sau "Plan: Gratuit · 47/60 luna asta"). In trial → eticheta "trial" + zile ramase; pe Gratuit → consum N/60.
    • Plasare (aliniat cu PRD 5.16): planul apare ca badge in titlul din antet (Gratuit/Standard/Pro/Premium) SI ca linie in meniul burger ("Plan: [· trial N zile]"), nu doar intr-un card pe Acasa. Vezi mockup-urile 5.16 (docs/mockups/prd-5.16-dashboard.html / ...-mobil.html). 5.16 furnizeaza locul de afisare (antet + meniu); 5.17 furnizeaza datele (tier, trial, consum).
    • Avertizare vizuala cand consumul Gratuit se apropie de limita (ex. ≥80% → ton warn), fara a ingropa stripul de sanatate (zero-silent-failures pastrat).
    • Scoped pe cont; design conform 5.15/5.16 (tokeni, fonturi system, fara hex hardcodat).
    • Pagina "Cont" arata planul + (daca exista) o explicatie "cum trec pe alt plan" (contact, ca nu exista plata self-service inca).
  • Verificare E2E: cont trial → "trial N zile"; cont free aproape de 60 → avertizare; cont pro → fara contor de limita.

US-007: Aliniere landing — limita 60 + trial pe Pro (nu Premium)

Ca vizitator vreau ca landing-ul sa spuna adevarul pentru ca azi promite "100/luna" si "Premium gratuit 30 zile", dar realitatea va fi 60/luna si trial pe Pro.

  • Depinde de: — (copy-only; aliniaza cu modelul din US-001)
  • Fisiere: app/web/templates/landing.html, tests/test_web_* (~2 fisiere)
  • Test intai (RED): test_landing_limita_60, test_landing_trial_pro_nu_premium
  • Acceptance criteria:
    • Toate aparitiile "100 de prestatii/luna" / "100/luna" / meta description (landing.html:7,65,266 + oriunde apar) → 60. Inclusiv cardul Gratuit din sectiunea PRICING.
    • Textul "Fiecare cont incepe cu Premium gratuit 30 de zile" (landing.html:256) → "Pro gratuit 30 de zile" (planul corect). Restul frazei ("Apoi trece automat pe Gratuit…") ramane.
    • Coerenta: orice alt loc care implica trial/limita reflecta 60 + Pro.
    • Fara alte schimbari de pret/continut (39/59 lei raman).
  • Verificare E2E: landing in browser — "60 prestatii/luna" peste tot, "Pro gratuit 30 de zile".

US-008: Admin — alocare manuala de plan (fara plata self-service)

Ca admin vreau sa pot seta planul unui cont pentru ca nu exista inca facturare automata, dar trebuie sa pot acorda Standard/Pro/Premium.

  • Depinde de: US-001
  • Fisiere: tools/account.py (CLI set-tier), optional app/web/routes.py (/admin actiune), tests/test_accounts.py / tests/test_web_admin*.py (~3 fisiere)
  • Test intai (RED): test_cli_set_tier, test_admin_set_tier_scoped, test_tier_invalid_respins
  • Acceptance criteria:
    • CLI python3 -m tools.account set-tier --account N --tier pro [--trial-days 30|--no-trial] seteaza tier/trial_until. Tier invalid → eroare clara.
    • Optional (la executie): actiune in panoul /admin pentru a seta planul unui cont (scoped, CSRF, ca bulk-ul de status din 5.5). Daca nu intra in 5.17, CLI e suficient (admin-only).
    • Alocarea unui plan platit de catre admin NU e suprascrisa de expirarea trial-ului (US-005).
    • Audit: schimbarea de plan se logheaza in app_events (reuse jurnalul din 5.6), fara PII nou.
  • Verificare E2E: set-tier --account 2 --tier pro → contul 2 are API + volum nelimitat.

US-009: Teste de regresie + E2E plan/trial/enforcement

Ca dezvoltator vreau acoperire completa pentru ca enforcement-ul atinge ambele canale de ingestie si nu vreau sa blochez gresit conturi legitime.

  • Depinde de: US-003, US-004, US-005, US-006, US-007
  • Fisiere: tests/test_plans.py, tests/test_api_scope.py, tests/test_web_* (~3 fisiere)
  • Test intai (RED): matricea plan × capabilitate (volum, API) × canal (API, web) × trial activ/expirat.
  • Acceptance criteria:
    • python3 -m pytest -q -m "not live" verde; regresia de aur (POST /v1/prezentari → queued pe un cont cu drept) ramane verde.
    • Matrice testata: free(volum-blocat/API-blocat), standard(volum-ok/API-blocat), pro(ok/ok), trial-pro(ok/ok), trial-expirat(=free).
    • Contoarele lunare resetate la luna noua (test la granita timp local RO).
    • Dev (id=1) nu e blocat de enforcement (dogfooding).
  • Verificare E2E: rulare completa documentata in Raportul VERIFY.

4. Cerinte functionale (rezumat)

  1. [REQ-001] accounts.tier ∈ {free,standard,pro,premium} + trial_until; migrare aditiva defensiva.
  2. [REQ-002] app/plans.py = sursa unica: limite (free=60/luna) + capabilitati (API doar Pro+).
  3. [REQ-003] Cont nou → trial Pro 30 zile automat; effective_tier randeaza Pro in trial, free dupa.
  4. [REQ-004] Enforcement DUR: free peste 60/luna respins (API + import web) cu eroare 3 niveluri.
  5. [REQ-005] Enforcement DUR: import API gated pe Pro+ (403 onest); canalul web ramane liber.
  6. [REQ-006] Downgrade automat la expirare trial (lazy via effective_tier).
  7. [REQ-007] Dashboard arata plan + zile trial + consum lunar; landing aliniat (60, Pro).
  8. [REQ-008] Admin aloca planuri manual (CLI set-tier), audit in app_events.

5. Non-Goals (anti scope-creep)

  • Fara integrare de plata / facturare / abonamente (Stripe etc.) — alocarea platita = manuala (admin).
  • Fara self-service upgrade din UI (doar afisare plan + "contacteaza-ne"); plata vine intr-un PRD viitor.
  • Fara modificari pe backend-ul de trimitere (worker, masina de stari, idempotenta build_key, reconciliere, contract RAR). Enforcement-ul se face la ingestie/enqueue, INAINTE de coada.
  • Fara schimbarea capabilitatilor de produs in sine (sugestii/mapare exista deja pe toate planurile in cod; diferentierea 5.17 e pe VOLUM + ACCES API, exact ce promite landing-ul ca diferentiator hard).
  • Fara modificari de design (tipografia/temele sunt 5.16/5.15); doar reuse-ul stilurilor existente.

6. Consideratii tehnice

  • Stack: SQLite (migrare aditiva defensiva ca 5.5/5.12), FastAPI, Jinja2/HTMX.
  • Patterns de urmat: sursa unica (app/plans.py ca app/errors.py); eroare 3 niveluri (5.4); scope pe cont (5.15/US-011); timp local RO la bucketare (5.15/E7); audit app_events (5.6).
  • Riscuri:
    • Blocare gresita a unui cont legitim (enforcement prea agresiv) — risc de business. Mitigare: dev id=1 exceptat; teste matrice; mesaje 3 niveluri cu cale de iesire; respingere INAINTE de enqueue (nu pierde date).
    • Definitia "prestatie consumata" (acceptate-in-coada vs sent) schimba cand musca limita. Mitigare: o decidem explicit (US-002 AC) + test; documentam.
    • Granita de luna / fus orar — off-by-a-day la reset. Mitigare: timp local RO + test la granita (lectia E7 din 5.15).
    • Idempotenta vs cota — un retry idempotent nu trebuie sa consume cota de doua ori. Mitigare: enforce inainte de build_key; testul de retry.
    • Conturi legacy fara tier — migrare le pune free; un cont real activ ar putea fi limitat brusc la 60. Mitigare: decizie de migrare (legacy activ → ce plan?) confirmata cu user inainte de deploy.

7. Consideratii UI/UX

  • Afisaj plan discret, conform 5.16 (fonturi system, tokeni --fs-*, fara hex).
  • Stari: trial activ (zile ramase) / free (consum N/60, warn la ≥80%) / platit (fara contor limita).
  • Mesaje de respingere oneste, actionabile (cum trec pe alt plan), nu doar "403".

8. Open Questions

  • "Prestatie consumata" = acceptate-in-coada (queued+sending+sent) sau doar sent? (implicit: acceptate)
  • Lot care depaseste partial limita → respingere totala sau enqueue partial? (implicit: respingere totala clara)
  • POST /v1/prezentari/valideaza (dry-run) — gated pe Pro sau permis tuturor? (implicit: permis)
  • Migrare conturi legacy active: raman free sau primesc un trial/plan? REZOLVAT (user, 2026-06-28): NU exista conturi legacy (produs in TESTE, pre-productie) -> intrebare moot; enforcement DUR direct de la deploy.
  • Standard (39 lei) si Premium difera de Pro doar prin API + suport in landing — pastram exact maparea de capabilitati din landing in plans.py? (implicit: da)

9. Valuri de executie

Val 1: [US-001]                  schema tier+trial + app/plans.py (autor unic schema/accounts)
Val 2: [US-002]                  numarator consum lunar (dupa model) ||
       [US-007]                  landing copy 60 + Pro (independent, copy-only)
Val 3: [US-003] [US-004] [US-005]  enforcement volum + API + downgrade (consuma plans.py)
Val 4: [US-006] [US-008]          UI dashboard plan/consum || admin set-tier
Val 5: [US-009]                   regresie + E2E matrice (dupa toate)

Secventiere fata de 5.16: independent (5.16 = design/tipografie; 5.17 = model de cont). Pot rula in paralel; doar US-006 (afisaj plan in _status.html) atinge un fisier pe care 5.16/US-003 il modifica (dot RAR) — serializeaza acel template daca ambele PRD-uri sunt in executie simultan.


Acest PRD nu a fost inca trecut prin /plan-ceo-review / /plan-eng-review. Recomandat inainte de executie (enforcement de business cu risc de blocare gresita + decizia de migrare a conturilor legacy).


REVIZIE /autoplan (2026-06-28)

Pipeline complet rulat: CEO -> Design -> Eng -> DX. Mod: SELECTIVE EXPANSION. Sesiune spawned (non-interactiva): fiecare AskUserQuestion intermediar a fost auto-decis cu cele 6 principii; deciziile "taste" si "user challenges" sunt colectate la poarta finala (Faza 4). Codex INDISPONIBIL (limita de utilizare atinsa pana la 2026-07-18) -> toate vocile duale ruleaza [codex-unavailable] / [subagent-only] cu vocea analitica independenta Claude ca model unic. Restore point: vezi comentariul HTML din capul fisierului.

Faza 0 — Intake

  • Scop UI detectat: DA (dashboard, badge antet, meniu burger, _status.html/_cont.html, avertizare vizuala, mockup-uri 5.16) -> Faza 2 (Design) ruleaza.
  • Scop DX detectat: DA (endpointuri /v1/*, 403/erori 3-niveluri, CLI tools.account set-tier, cheie API, mesaje pentru integratori) -> Faza 3.5 (DX) ruleaza.
  • Cod citit: app/accounts.py, app/schema.sql (accounts/submissions/app_events), app/errors.py, app/auth.py (resolve_account_id), app/api/v1/router.py (create_prezentari/valideaza), app/api/v1/import_router.py (commit_import), tools/account.py, app/web/templates/landing.html.

Faza 1 — CEO Review (Strategie & Scop) [subagent-only]

0B. Ce exista deja (leverage map)

Sub-problema 5.17 Cod existent reutilizabil Reuse?
Sursa unica de adevar (definitii) app/errors.py (pattern CATALOG + eroare()) DA — plans.py copiaza pattern-ul
Eroare 3 niveluri app/errors.py::eroare() (problema/cauza/fix) DA — adauga PLAN_LIMITA_LUNARA, PLAN_FARA_API in CATALOG
Migrare aditiva defensiva _migrate in db.py (ALTER ca email/status 5.5/5.12) DA
Scope pe cont la ingestie auth.py::resolve_account_id (Depends) DA — gate API se ataseaza aici/ruta
Lifecycle cont + protectie id=1 accounts.py (set_status, _PROTECTED_ACCOUNT_ID) DA — set_tier urmeaza acelasi tipar
Audit fara PII observ.py::log_event -> app_events (5.6) DA — log schimbare plan
CLI admin tools/account.py (argparse) DA — subcomanda set-tier
Consum lunar submissions.created_at + idx_submissions_account_status DA — fara coloana noua

0C. Dream state

  CURENT                          5.17                             IDEAL 12 LUNI
  landing promite 4 planuri,  ->  model de cont real (tier+trial), ->  facturare self-service
  app nu stie de tipuri;          enforcement volum+API,               (Stripe), upgrade din UI,
  trial inexistent;               downgrade lazy la expirare,          dunning, conversie masurata,
  limita 100 doar pe hartie       admin manual aloca plan platit       re-trial/nurture automat

Delta: 5.17 aliniaza app-ul cu promisiunea landing-ului, DAR ramane fara calea de conversie (plata self-service) — enforcement-ul musca inainte sa existe un buton de upgrade.

0C-bis. Alternative de implementare

APROACH A: Enforcement DUR (planul actual)
  Rezumat: respinge la enqueue free>60 + 403 API non-Pro; downgrade lazy.
  Efort: M (human ~2-3z / CC ~45min)  Risc: Mediu-Inalt (blocare gresita fara cale de upgrade)
  Pro: aliniere completa cu landing; diferentiator hard real.
  Contra: friction fara conversie self-service; risc fals-block legacy.
  Reuse: errors.py, auth.py, app_events.

APROACH B: Soft-first (warn + overgrace + flag admin)  [recomandat de revizie]
  Rezumat: la depasire limita -> avertizare clara + enqueue permis cu marcaj, alerta admin;
  API gate ramane DUR (capability, nu volum). Hard-block volum activabil ulterior prin flag.
  Efort: M (human ~2-3z / CC ~45min)  Risc: Scazut.
  Pro: zero fals-block; conversie prin contact, nu prin churn; deploy mai sigur.
  Contra: nu "forteaza" upgrade; cota e mai degraba un semnal decat un zid.
  Reuse: identic cu A.

APROACH C: Model + copy now, enforcement sub feature flag (deferat)
  Rezumat: adauga tier/trial + plans.py + fix landing; enforcement scris dar OFF (flag),
  pornit dupa migrare legacy confirmata.
  Efort: S-M  Risc: Foarte scazut.
  Pro: deploy incremental, decuplaza copy-fix (banal) de enforcement (riscant).
  Contra: promisiunea landing nu e inca "reala" la deploy.

RECOMANDARE revizie: combina C (feature flag de enforcement) + B (soft-first pe VOLUM), pastrand A pe gate-ul API (capability, risc mic). Principii P1 (completeness pe model) + P6 (bias to action: deploy incremental). Vezi TASTE DECISION T-CEO-1 si T-CEO-2 la poarta.

0E. Interogare temporala

  • HOUR 1 (foundations): valorile exacte ale planurilor (sursa unica plans.py); valoarea 60 ca CONSTANTA unica; politica legacy (free fara trial vs trial calculat din created_at).
  • HOUR 2-3 (core): definitia "prestatie consumata" (acceptate-in-coada vs sent); bucketare luna timp local RO (lectia E7/5.15); interactiunea enforce-inainte-de-build_key (idempotenta).
  • HOUR 4-5 (integrare): unde se ataseaza gate-ul API (dependinta de ruta vs in resolve_account_id); lot care depaseste partial limita (respingere totala vs partial); valideaza dry-run gated sau nu.
  • HOUR 6+ (polish/teste): matrice plan x capabilitate x canal x trial; granita de luna; dev id=1 exceptat.

0F. Mod: SELECTIVE EXPANSION (default pentru iteratie pe sistem existent). Approach: B+C pe volum, A pe API.

Voci duale (CEO)

CODEX SAYS (CEO — strategy challenge): [codex-unavailable] — limita de utilizare (pana 2026-07-18). Voce omisa; consensul se calculeaza N/A pe coloana Codex.

CLAUDE SUBAGENT (CEO — strategic independence) (voce analitica independenta, inainte de orice Codex):

  1. Problema corecta? Gap real: landing-ul promite planuri pe care app-ul nu le sustine. DAR enforcement-ul DUR pe volum apare INAINTEA oricarei cai de plata. Reframe: "onestitate landing + diferentiere capability" se poate atinge fara a ZIDI free-ul la 60. (HIGH)
  2. Premise asumate: (a) "promisiunile trebuie impuse DUR acum" — asumata; un fix de copy + gate API ar inchide 80% din gap cu 20% din risc. (b) "60 in loc de 100" — decizie user, dar fara rationament; scade atractivitatea free-ului exact cand nu exista upgrade self-service. (MEDIUM)
  3. Regret la 6 luni: un cont free real face 80/luna, e migrat la free si blocat brusc la 60 -> churn in loc de conversie (nu exista buton de upgrade, doar "contacteaza-ne"). (HIGH, deploy-blocker pe migrarea legacy.)
  4. Alternative neexplorate: soft-enforcement (warn+overgrace) vs hard-block; planul sare direct la hard.
  5. Risc competitiv: nisa B2B reglementata (RAR), switching cost real -> risc competitiv scazut; riscul dominant e INTERN (friction fara conversie).
CEO DUAL VOICES — CONSENSUS TABLE:
═══════════════════════════════════════════════════════════════
  Dimensiune                            Claude  Codex  Consensus
  ───────────────────────────────────── ─────── ─────── ─────────
  1. Premise valide?                     Partial  N/A    N/A (Codex indisp.)
  2. Problema corecta?                   Da*      N/A    N/A
  3. Calibrare scop corecta?             Nu**     N/A    N/A
  4. Alternative explorate suficient?    Nu       N/A    N/A
  5. Riscuri piata acoperite?            Da       N/A    N/A
  6. Traiectorie 6 luni sanatoasa?       Partial  N/A    N/A
═══════════════════════════════════════════════════════════════
* problema reala, dar solutia (hard enforce) e mai agresiva decat o cere problema.
** scop corect ca model; enforcement-ul DUR pe volum e calibrat prea agresiv pentru un produs fara plata.
Single-model: niciun consens incrucisat; constatarile critice ale vocii Claude sunt semnalate oricum.

Sectiunile 1-11 (CEO)

S1 Arhitectura. Componenta noua plans.py = modul PUR (ca errors.py), fara import DB/HTTP, dict PLANS + effective_tier(account_row, now) + monthly_usage(conn, account_id, now). Cuplare noua: rutele de ingestie (router.py, import_router.py, routes.py commit) depind de plans.py + citesc accounts.tier/trial_until -> cuplare justificata (un singur punct de adevar). Diagrama: vezi Faza 3 (Eng). Constatare CEO-S1-1 (MEDIUM): effective_tier are nevoie de now injectabil (nu datetime.now() intern) ca testele de granita trial/luna sa fie deterministe. Auto-decis (P5 explicit): semnatura cu now parametru.

S2 Error & Rescue (registry mai jos). Coduri noi: PLAN_LIMITA_LUNARA, PLAN_FARA_API. Ambele sunt erori de business (nu exceptii) -> 3 niveluri din errors.py, returnate ca raspuns structurat (nu 500). Fara catch-all. Constatare CEO-S2-1 (LOW): trial expirat NU e o eroare — e o stare; nu necesita cod de eroare, doar effective_tier care vede free.

S3 Securitate (detaliu in Eng S3). Suprafata: gate API (autorizare pe capability) + enforce volum. DOR (direct object reference) la set-tier admin: trebuie scoped + protejat id=1 (ca set_status). Risc privilege: un cont free NU trebuie sa-si poata seta singur tier (doar admin CLI / panou admin CSRF). Constatare CEO-S3-1 (HIGH): enforce pe volum/API trebuie sa ruleze DUPA resolve_account_id (cont autenticat), niciodata pe baza unui camp din body. Auto-decis (P1): gate ca dependinta server-side.

S4 Data flow & edge cases. Granita de luna (timp local RO), idempotenta vs cota (retry nu consuma de 2x), lot care depaseste partial. Vezi Failure Modes Registry. Edge: 2 cereri concurente la 59/60 -> race pe cota (ambele trec checkul, ajung la 61). Constatare CEO-S4-1 (MEDIUM): cota nu e tranzactionala cu enqueue -> mic overshoot posibil sub concurenta. Auto-decis (P3 pragmatic): accepta overshoot mic (±lot) documentat; un lock per-cont ar fi over-engineering pentru un cap soft. (Daca se alege hard-block, re-evalueaza.)

S5 Code quality. plans.py sursa unica evita DRY-violation intre backend si UI. Risc: valoarea 60 sa fie hardcodata in 3 locuri (router, import, web). Auto-decis (P4 DRY): O singura definitie in PLANS, consumata peste tot; templating UI primeste monthly_limit din context, nu literal.

S6 Teste (diagrama in Eng S3). Matrice plan x capabilitate x canal x trial. Gap-uri critice: granita luna timp local RO; retry idempotent; dev id=1 ne-blocat. Toate cerute in US-009.

S7 Performanta. monthly_usage = un COUNT cu WHERE account_id=? AND status IN (...) AND created_at>=.... Exista idx_submissions_account_status(account_id,status) dar NU acopera created_at. Constatare CEO-S7-1 (MEDIUM): la volume mari un COUNT pe luna per-cerere e O(randuri luna); acceptabil la scara curenta, dar indexul nu acopera intervalul de timp. Auto-decis (P3): acceptabil acum (SQLite, volume mici); TODO index (account_id, created_at) daca apar conturi cu mii/luna. -> TODOS.

S8 Observabilitate. Fiecare respingere pe plan (volum/API) trebuie sa emita app_events (cod + cont + count), nu doar sa intoarca 4xx. Altfel "de ce a fost blocat clientul X?" e invizibil. Auto-decis (P_prime zero-silent-failures): log_event pe fiecare respingere de plan. (Adaugat ca AC.)

S9 Deploy. Migrare aditiva defensiva (idempotenta). REZOLVAT (decizie user 2026-06-28): enforcement DUR direct de la deploy — fara conturi legacy, produs in TESTE (pre-productie), deci riscul de fals-block e moot. Feature-flag AUTOPASS_ENFORCE_PLANS ramane OPTIONAL (nice-to-have de operare, kill-switch), NU blocant pentru deploy. Vezi T-CEO-1 (rezolvat).

S10 Traiectorie. Reversibilitate 4/5 (model aditiv; enforcement sub flag = usor de oprit). Path dependency: fara billing, set-tier manual devine gatuire daca adoptia creste -> Phase 2 = plata self-service. Datorie: cuplarea enforcement de ingestie e curata; datoria reala e "lipsa caii de upgrade".

S11 Design & UX (deep in Faza 2). Plasare badge plan in antet + meniu burger (aliniat 5.16), avertizare la >=80%, mesaje oneste cu cale de iesire. Recomand /plan-design-review (rulat ca Faza 2).

Iesiri obligatorii CEO

NOT in scope (deferat, cu rationament):

  • Integrare plata/facturare (Stripe) — non-goal explicit; Phase 2.
  • Upgrade self-service din UI — depinde de billing; doar afisaj + "contacteaza-ne".
  • Index (account_id, created_at) — deferat pana apar conturi de volum mare (TODO P3).
  • Job eager de normalizare trial_until expirat -> NULL — optional, igiena; lazy acopera corectitudinea.
  • Diferentiere capability de produs (sugestii/mapare) pe planuri — non-goal; diferentierea e volum+API.

What already exists: vezi tabelul 0B (errors.py, auth.py, accounts.py, observ/app_events, db._migrate, submissions.created_at + index, tools/account.py — toate reutilizate; 5.17 nu reconstruieste nimic).

Dream state delta: 5.17 face promisiunea landing-ului REALA in app, dar lasa golul "conversie self-service"; urmatorul pas logic e billing (Phase 2). Enforcement-ul fara upgrade self-service e delta-ul de risc.

Error & Rescue Registry (S2)

  CODEPATH                         | CE POATE ESUA                  | COD / EXCEPTIE
  ---------------------------------|--------------------------------|------------------------
  create_prezentari (enqueue)      | free peste 60/luna             | PLAN_LIMITA_LUNARA (business)
  commit_import (web+API)          | free peste 60/luna             | PLAN_LIMITA_LUNARA (business)
  import API / POST /v1/prezentari | cont fara api_access (non-Pro)  | PLAN_FARA_API (403, business)
  effective_tier(account, now)     | trial_until malformat/NULL     | trateaza ca free (fallback)
  monthly_usage(conn, acct, now)   | created_at NULL/malformat       | exclus din count (defensiv)
  set-tier (CLI/admin)             | tier invalid                    | ValueError -> mesaj clar
  set-tier pe id=1                 | mutare cont sistem              | protejat (ca set_status)

  COD / STARE             | RESCUED? | ACTIUNE                          | USER VEDE
  ------------------------|----------|----------------------------------|---------------------------
  PLAN_LIMITA_LUNARA      | Y        | respinge inainte de build_key    | "Ai atins limita Gratuit (60/luna)" + fix
  PLAN_FARA_API           | Y        | 403 inainte de procesare         | "Importul API e pe Pro" + fix
  trial_until malformat   | Y        | fallback free, log WARNING       | comportament free (fara crash)
  created_at malformat     | Y        | exclus din count, log WARNING    | nimic (transparent)
  tier invalid (set-tier) | Y        | ValueError, exit!=0              | "tier invalid: X"

Failure Modes Registry

  CODEPATH                  | FAILURE MODE             | RESCUED? | TEST? | USER VEDE        | LOGGED?
  --------------------------|--------------------------|----------|-------|------------------|--------
  enforce volum (enqueue)   | free peste 60            | Y        | Y     | eroare 3 niveluri| Y (app_events)
  enforce volum             | race concurent la 59/60  | Partial  | Y(*)  | overshoot mic    | Y
  gate API                  | non-Pro pe /v1 import    | Y        | Y     | 403 onest        | Y
  downgrade lazy            | trial expirat            | Y        | Y     | aplica free      | N (stare, nu eveniment)
  migrare legacy            | cont activ -> free brusc | N/A(MOOT)| n/a   | n/a              | n/a
  bucketare luna            | granita timp local RO    | Y        | Y     | reset corect     | n/a
  idempotenta vs cota       | retry consuma cota 2x    | Y        | Y     | nimic            | n/a

CRITICAL GAP REZOLVAT (MOOT, 2026-06-28): decizia userului — NU exista conturi legacy, produsul e in TESTE (pre-productie). Migrarea unui cont activ -> free brusc nu se poate produce (nu exista conturi reale de migrat). Gap inchis ca N/A. Enforcement DUR de la deploy, fara mitigare necesara.

Completion Summary (CEO)

  +====================================================================+
  |            MEGA PLAN REVIEW — COMPLETION SUMMARY (CEO)             |
  +====================================================================+
  | Mode                 | SELECTIVE EXPANSION                          |
  | Approach ales        | B+C pe volum, A pe gate API                  |
  | S1 Arhitectura       | 1 (now injectabil)                          |
  | S2 Errors            | 2 coduri noi, 0 GAP-uri rescue              |
  | S3 Securitate        | 1 HIGH (gate server-side), DOR set-tier      |
  | S4 Data/UX           | 1 race cota (overshoot mic acceptat)        |
  | S5 Quality           | 1 (DRY pe valoarea 60)                      |
  | S6 Teste             | matrice ceruta, 3 gap-uri acoperite US-009  |
  | S7 Perf              | 1 (index timp) -> TODO                      |
  | S8 Observ            | 1 (log pe respingere plan) -> AC nou        |
  | S9 Deploy            | enforcement DUR direct (user); flag optional |
  | S10 Future           | Reversibilitate 4/5; datorie = lipsa billing|
  | S11 Design           | -> Faza 2                                   |
  | NOT in scope         | scris (5 items)                             |
  | Failure modes        | 7 total, 0 CRITICAL GAP (legacy REZOLVAT moot)|
  | Outside voice        | codex indisponibil (subagent-only)          |
  | Unresolved decisions | 0 (toate inchise 2026-06-28: challenge + 3 taste)|
  +====================================================================+

Phase 1 complete. Codex: indisponibil. Claude subagent: 9 constatari (2 HIGH, 5 MEDIUM, 2 LOW) + 1 USER CHALLENGE + 2 TASTE. Consens: N/A (single-model). Trec la Faza 2.

Faza 2 — Design Review [subagent-only]

Scop UI confirmat. 5.17 aduce DATELE (tier/trial/consum); 5.16 aduce LOCUL (antet + meniu burger). Aceasta revizie e la nivel de plan (intentionalitate de design), nu audit de pixeli. Completitudine design initiala: 6/10 (plasare numita, dar stari incomplete + copy nespecificat).

CODEX SAYS (design — UX challenge): [codex-unavailable].

CLAUDE SUBAGENT (design — independent review):

  1. Ierarhie informatie: badge plan in antet e corect (status, nu actiune); consumul N/60 apartine contextului secundar (meniu/Cont), NU trebuie sa concureze cu stripul de sanatate. OK.
  2. Stari lipsa: planul numeste "trial activ / free consum / platit fara contor" dar NU specifica: (a) ULTIMA zi de trial ("expira azi" vs "1 zi"), (b) starea "limita ATINSA" (60/60, nu doar >=80%), (c) ce vede operatorul in MOMENTUL respingerii (toast? banner persistent?). GAP (HIGH).
  3. Arc emotional: trial -> "ai Pro 18 zile" (pozitiv) -> ziua 30 trecere tacuta pe free -> prima respingere la 61 = surpriza negativa daca nu a existat avertizare progresiva. Avertizarea >=80% e buna; lipseste un semnal la trecerea trial->free (ziua 0). GAP (MEDIUM).
  4. Specificitate vs generic: "afiseaza discret planul" e generic; mockup-urile 5.16 dau forma, dar copy-ul exact al badge-ului ("Pro · trial 18 zile" / "Gratuit · 47/60") trebuie fixat ca string-uri, nu lasat implementatorului. GAP (MEDIUM).
  5. Decizii care vor bantui implementatorul: prag exact warn (>=80% = 48/60?), pluralizare RO ("1 zi" vs "18 zile", "1 zile" e gresit), ce se intampla la 0 zile ramase in trial in aceeasi zi.
DESIGN LITMUS SCORECARD (0-10):
  Dimensiune                         Claude  Codex  Consensus
  ────────────────────────────────── ─────── ─────── ─────────
  1. Ierarhie informatie             8       N/A    N/A
  2. Acoperire stari (load/empty/err) 5       N/A    N/A  <- gap
  3. Coerenta user journey           6       N/A    N/A
  4. Specificitate (nu generic)      5       N/A    N/A  <- gap
  5. Aliniere design system (5.15/16) 8       N/A    N/A
  6. Intentie responsive             7       N/A    N/A
  7. Accesibilitate (contrast/kbd)   6       N/A    N/A
  ────────────────────────────────── ─────── ─────── ─────────
  Overall design (plan-level)        ~6.4/10

Pass-uri 1-7 (constatari + auto-decizii)

  • P1 Ierarhie: badge in antet (status), consum in meniu/Cont. OK, fara modificare.
  • P2 Stari (CRITIC): adauga stari explicite: trial-activ(N zile), trial-ultima-zi, free-sub-prag, free-warn(>=80%), free-limita-atinsa(60/60), platit(fara contor). Auto-decis (P1 completeness): toate 6 stari intra ca AC in US-006. Matrice stare->afisaj in plan.
  • P3 Journey: adauga un semnal one-time la trecerea trial->free (banner discret "Trial Pro expirat — esti pe Gratuit, 60/luna"). Auto-decis (P1): adaugat ca AC optional in US-006 (non-blocant daca lazy; afisat la prima incarcare dupa expirare). TASTE T-DES-1 (banner one-time vs doar badge).
  • P4 Specificitate: fixeaza string-urile de copy exact (RO, cu pluralizare corecta) in US-006. Auto-decis (P5 explicit): tabel de copy in plan (vezi mai jos).
  • P5 Design system: tokeni --fs-*, fonturi system, fara hex hardcodat (5.16). OK; reuse _status.html.
  • P6 Responsive: badge in antet + linie in burger acopera desktop+mobil (mockup-uri 5.16). OK.
  • P7 Accesibilitate: tonul "warn" NU doar prin culoare (adauga text/icon); contrast pe badge; badge-ul nu e buton (status) -> fara rol interactiv inselator. Auto-decis (P1): warn = culoare + text.

Copy fix (RO, propus, auto-decis P5):

  trial activ:        "Plan: Pro · trial {n} {zi|zile} ramase"   (1->"zi", 2+->"zile")
  trial ultima zi:    "Plan: Pro · trial expira azi"
  free sub prag:      "Plan: Gratuit · {u}/60 luna asta"
  free warn (>=80%):  "Plan: Gratuit · {u}/60 — aproape de limita"
  free limita atinsa: "Plan: Gratuit · 60/60 — limita atinsa"
  platit:             "Plan: {Standard|Pro|Premium}"

Required: user flow ASCII (stari + tranzitii)

  [cont nou] --create--> (TRIAL Pro: badge "trial N zile") --N scade zilnic-->
       (trial ultima zi) --trial_until<=now (lazy)--> (FREE sub prag: "u/60")
            --u>=48--> (FREE warn ">=80%") --u==60--> (FREE limita atinsa "60/60")
                                                          |
                                              a 61-a cerere -> RESPINS (eroare 3 niveluri / toast)
       (admin set-tier pro) --------------------------------> (PLATIT: fara contor)

Phase 2 complete. Codex: indisponibil. Claude subagent: 4 constatari design (1 HIGH stari, 2 MEDIUM, 1 accesibilitate) + 1 TASTE (T-DES-1). Overall ~6.4/10 -> tinta dupa AC-uri ~8.5/10. Trec la Faza 3.

Faza 3 — Eng Review (Arhitectura & Teste) [subagent-only]

Step 0 — Scope challenge (cod citit)

  • app/errors.py: CATALOG + eroare(cod, field, cauza) -> pattern de copiat exact pentru coduri noi.
  • app/auth.py: resolve_account_id (Depends) intoarce account_id; gate-ul API se ataseaza ca a doua dependinta (require_api_access) care reuseaza account_id -> nu reimplementa auth.
  • app/api/v1/router.py: create_prezentari itereaza prestatiile, face canonicalize_row -> build_key -> enqueue. Gate-ul de VOLUM trebuie INAINTE de bucla de build_key/enqueue (idempotenta intacta).
  • app/api/v1/import_router.py: commit_import face enqueue per-rand cu ON CONFLICT DO NOTHING; gate volum la inceputul commit-ului (nr randuri ok vs cota ramasa).
  • app/accounts.py: set_status + _PROTECTED_ACCOUNT_ID=1 -> set_tier urmeaza acelasi tipar (validare tier, protectie id=1, update). create_account adauga tier='free' + trial_until=now+30z.
  • tools/account.py: argparse; adauga subparser set-tier.
  • Complexitate: ramane sub 8 fisiere de logica + plans.py nou. Sub pragul de smell. OK.

CLAUDE SUBAGENT (eng — independent review):

  1. Arhitectura: plans.py PUR + consum din rute = curat. Singura cuplare noua justificata.
  2. Edge: race pe cota sub concurenta (overshoot ±lot); now trebuie injectabil pentru teste de granita.
  3. Teste: matricea e ceruta, dar lipsesc explicit: testul de retry idempotent care NU re-consuma cota, si testul ca valideaza dry-run NU consuma cota. (HIGH — sunt invariante usor de stricat.)
  4. Securitate: gate API server-side (nu din body); set-tier scoped + protejat id=1.
  5. Complexitate ascunsa: definitia "prestatie consumata" + bucketarea lunii timp local RO sunt sursa reala de bug-uri (off-by-a-day, status care iese din count cand un rand devine error).
ENG DUAL VOICES — CONSENSUS TABLE:
═══════════════════════════════════════════════════════════════
  Dimensiune                            Claude  Codex  Consensus
  ───────────────────────────────────── ─────── ─────── ─────────
  1. Arhitectura sanatoasa?             Da       N/A    N/A
  2. Acoperire teste suficienta?        Partial  N/A    N/A
  3. Riscuri performanta tratate?       Partial  N/A    N/A
  4. Amenintari securitate acoperite?   Da       N/A    N/A
  5. Cai de eroare tratate?             Da       N/A    N/A
  6. Risc deploy gestionabil?           Partial  N/A    N/A  (flag + legacy)
═══════════════════════════════════════════════════════════════
Single-model (codex indisponibil).

Section 1 — Architecture (ASCII)

                         ┌─────────────────────┐
                         │   app/plans.py (NOU) │  modul PUR (ca errors.py)
                         │  PLANS{tier->limite} │  effective_tier(acct,now)
                         │  api_access, limita  │  monthly_usage(conn,acct,now)
                         └──────────┬──────────┘
            ┌───────────────┬───────┼───────────────┬──────────────────┐
            ▼               ▼       ▼               ▼                  ▼
   api/v1/router.py  import_router  web/routes.py  auth.py           web/templates
   create_prezentari  commit_import  commit web    require_api_access _status/_cont.html
        │  gate VOLUM      │ gate VOLUM   │ gate VOLUM   │ gate API (403)   badge plan
        ▼                  ▼              ▼              ▼
   errors.eroare(PLAN_LIMITA_LUNARA / PLAN_FARA_API)   observ.log_event(app_events)
        │
        ▼ (daca trece)
   canonicalize_row -> build_key -> enqueue submissions   <-- NESCHIMBAT (worker/idempotenta/reconcile)

   accounts.py: create_account(tier='free', trial_until=now+30z) ; set_tier(acct,tier,trial)
   db._migrate: ALTER accounts ADD tier / trial_until (aditiv defensiv, idempotent)
   tools/account.py: subcomanda set-tier
   config.py: AUTOPASS_ENFORCE_PLANS (flag, vezi T-CEO-1)

Cuplare before/after: inainte rutele depind doar de auth+idempotency+validation; dupa adauga o dependinta catre plans.py (pur, fara cicluri). Single point of failure: niciunul nou (modul pur, fara IO). Rollback: revert + flag OFF; migrarea e aditiva (coloanele raman, inofensive).

Section 2 — Code quality

  • DRY: valoarea 60 + maparea capability EXCLUSIV in PLANS. Constatare ENG-S2-1: nu duplica status IN (...) (definitia consumului) intre monthly_usage si teste — exporta o constanta CONSUMED_STATUSES.
  • Naming: effective_tier, monthly_usage, api_access, monthly_limit — clare.
  • Over/under-engineering: NU adauga tabela plan_usage (coloana noua) — submissions.created_at ajunge (respecta non-goal migrare minima). Lock per-cont pe cota = over-engineering pentru cap soft.

Section 3 — Test Review (diagrama completa — NU se sare)

  NEW DATA FLOWS:
    - cerere ingestie -> citeste effective_tier -> compara monthly_usage+nr vs limita -> permite/respinge
    - cont nou -> create_account seteaza trial_until
    - trial_until <= now -> effective_tier randeaza free (lazy)
  NEW CODEPATHS / BRANCHES:
    - tier in {free,standard,pro,premium}; api_access T/F; monthly_limit None/60
    - effective_tier: trial activ vs expirat vs plan platit (nu downgrada)
    - enforce volum: sub limita / la limita / peste / lot care depaseste partial
    - gate API: free/standard -> 403 ; pro/premium/trial -> ok ; nomenclator public ; valideaza permis
    - dev id=1: ne-blocat (AUTOPASS_REQUIRE_API_KEY=false)
  NEW INTEGRATIONS/EXTERNAL: niciuna (totul intern; worker/RAR neatins)
  NEW ERROR/RESCUE: PLAN_LIMITA_LUNARA, PLAN_FARA_API (+ log_event)

  ITEM                                  | TIP TEST     | EXISTA? | HAPPY / FAIL / EDGE
  --------------------------------------|--------------|---------|---------------------------------
  migrare tier+trial defensiva          | unit (db)    | NOU     | re-rulare idempotenta; legacy->free
  PLANS definitii + capability map      | unit         | NOU     | free=60/noAPI; pro=None/API
  effective_tier trial activ/expirat    | unit (now inj)| NOU    | viitor->pro; trecut->free; platit persista
  monthly_usage count                   | unit         | NOU     | numara queued+sending+sent; reset luna noua
  monthly_usage granita timp local RO   | unit         | NOU     | rand la 23:30 UTC ultima zi -> luna RO corecta
  enforce volum free>60 API             | integration  | NOU     | a 61-a respinsa 3 niveluri
  enforce volum free>60 import web      | integration  | NOU     | commit respins peste cota
  enforce volum lot partial             | integration  | NOU     | 50 folosite + lot 20 -> respingere totala (default)
  retry idempotent NU re-consuma cota   | integration  | NOU     | <-INVARIANT critic
  valideaza dry-run NU consuma cota     | integration  | NOU     | <-INVARIANT critic
  gate API free/standard 403            | integration  | NOU     | 403 onest
  gate API pro/trial 200                | integration  | NOU     | trece
  nomenclator public ramane            | integration  | reuse   | fara cheie -> 200
  dev id=1 ne-blocat                    | integration  | NOU     | dogfooding nu pica
  set-tier CLI + invalid + id=1 protejat| unit         | NOU     | tier ok; invalid err; id=1 respins
  regresie aur (POST -> queued)         | integration  | reuse   | ramane verde

Test 2am-Friday: "un cont Pro NU e blocat niciodata pe volum, indiferent de consum". Test ostil: "trimit 100 cereri concurente la 59/60 pe free" -> verifica overshoot marginit + log. Flakiness: testele de granita luna/trial trebuie sa injecteze now (fara datetime.now() intern) — altfel flaky. LLM/eval: 5.17 NU atinge prompturi/mapare LLM -> fara eval suites (confirmat: non-goal pe backend trimitere).

Section 4 — Performance

  • monthly_usage: COUNT per-cerere; index (account_id,status) exista, NU acopera created_at. ENG-S4-1 (MEDIUM): la conturi de volum mare scaneaza randurile lunii. Auto-decis (P3): acceptabil acum; TODO index (account_id, created_at) (P3) cand apar conturi cu mii/luna.
  • Fara N+1, fara conexiuni noi, fara job nou (downgrade = lazy).

Iesiri obligatorii Eng

NOT in scope (eng): tabela plan_usage dedicata (nu necesara); lock tranzactional pe cota (overshoot mic acceptat); job eager downgrade (lazy ajunge); index timp (TODO). What already exists (eng): errors.eroare, auth.resolve_account_id, accounts.set_status pattern, db._migrate, observ.log_event, idempotency.build_key/canonicalize_row, submissions index — toate reutilizate. Failure modes (eng) cu gap critic: vezi Failure Modes Registry (CEO) — singurul CRITICAL GAP = migrare legacy active (acoperit de flag + decizie user T-CEO-1).

Completion Summary (Eng)

  | S1 Arhitectura  | curata, 1 cuplare justificata, diagrama produsa     |
  | S2 Quality      | 1 (CONSUMED_STATUSES constanta)                     |
  | S3 Teste        | diagrama produsa; 2 invariante critice (retry, dry-run) |
  | S4 Perf         | 1 (index timp -> TODO P3)                           |
  | Artifact teste  | scris in ~/.gstack/projects/romfast-rar-autopass/   |
  | Critical gaps   | 1 (legacy) -> flag + decizie user                   |
  | Outside voice   | codex indisponibil (subagent-only)                  |

Phase 3 complete. Codex: indisponibil. Claude subagent: 4 constatari (1 HIGH teste-invariante, 3 MEDIUM). Artifact test-plan scris pe disc. Trec la Faza 3.5 (DX).

Faza 3.5 — DX Review [subagent-only]

Scop DX confirmat: integratorul ROAAUTO/soft propriu foloseste /v1/* cu cheie API; adminul foloseste CLI tools.account. Tip produs: gateway API B2B + CLI admin. Persona: dezvoltator integrator RO (consuma POST /v1/prezentari) + admin gateway.

CODEX SAYS (DX — developer experience challenge): [codex-unavailable].

CLAUDE SUBAGENT (DX — independent review):

  1. Time-to-hello-world: neschimbat de 5.17 pentru cont cu drept; DAR un integrator pe cont free care incearca POST /v1/prezentari va primi acum 403 (PLAN_FARA_API) la primul apel. Daca mesajul nu spune clar "API e pe Pro, dar valideaza merge", dezvoltatorul crede ca integrarea e stricata. (HIGH)
  2. Mesaje de eroare: PLAN_FARA_API si PLAN_LIMITA_LUNARA trebuie problema+cauza+fix (au structura din errors.py). Fix-ul trebuie sa fie actionabil ("Treci pe Pro: contacteaza-ne / set-tier"), nu doar 403.
  3. API/CLI naming: set-tier --tier pro --trial-days 30|--no-trial e consistent cu tools.account existent (create/activate/deactivate). OK. Sugestie: si --account (deja folosit).
  4. Docs: /v1/nomenclator ramane public (bun pentru explorare pre-upgrade). valideaza permis pe orice plan = excelent DX (integrezi+testezi inainte de a plati). Trebuie documentat explicit ca "poti dezvolta pe free cu valideaza, dar trimiterea reala cere Pro".
  5. Upgrade path: fara self-service -> 403 zice "contacteaza-ne"; un dezvoltator vrea un link/email concret, nu "contact". (MEDIUM)
DX DUAL VOICES — CONSENSUS TABLE:
  Dimensiune                            Claude  Codex  Consensus
  ───────────────────────────────────── ─────── ─────── ─────────
  1. Getting started < 5 min?           Da*      N/A    N/A  (*free->403 surprinde)
  2. Naming API/CLI ghicibil?           Da       N/A    N/A
  3. Mesaje de eroare actionabile?      Partial  N/A    N/A
  4. Docs gasibile & complete?          Partial  N/A    N/A
  5. Upgrade path sigur?                Partial  N/A    N/A  (fara self-service)
  6. Mediu dev fara friction?           Da       N/A    N/A  (valideaza permis)

Developer journey map (9 etape)

Etapa Azi Cu 5.17 Friction
1 Descoperire landing landing aliniat (60, Pro)
2 Signup cont + trial Pro trial Pro 30z automat
3 Cheie API CLI apikey idem
4 Primul apel 200 200 in trial; 403 pe free dupa trial mesaj clar necesar
5 Dezvoltare valideaza permis pe orice plan excelent
6 Trimitere reala 200 gated pe Pro+ upgrade path
7 Atingere limita free 60/luna -> respins mesaj 3 niveluri
8 Upgrade contact admin (fara self-service) link concret
9 Operare dashboard + badge plan/consum

Developer empathy narrative (persoana intai)

"Mi-am facut cont, am cheia, trimit prima prestatie — merge (sunt in trial). Construiesc integrarea, folosesc valideaza ca sa testez fara sa consum nimic — perfect. Peste o luna, trial-ul expira; brusc POST /v1/prezentari da 403. Daca mesajul zice doar '403 Forbidden', cred ca mi-am stricat cheia si pierd o ora. Daca zice 'Importul prin API e pe planul Pro — scrie-ne la X ca sa activam', stiu exact ce sa fac."

DX Scorecard (8 dimensiuni, 0-10)

  1. TTHW                      7   (free->403 dupa trial surprinde fara mesaj clar)
  2. Naming consistency        9
  3. Error actionability       6   -> tinta 9 dupa copy fix
  4. Docs/exemple              6   -> documenteaza valideaza-pe-free + upgrade
  5. Progressive disclosure    8   (nomenclator+valideaza publice/permise)
  6. Escape hatches            7   (dev id=1; flag enforcement)
  7. Upgrade safety            6   (manual; link concret lipseste)
  8. Consistency cross-canal   8
  --------------------------------
  Overall DX                  ~7.1/10  (TTHW: ~5 min ramane; tinta erori/docs ~8.5)

DX Implementation Checklist

  • PLAN_FARA_API: fix actionabil cu canal de contact concret (email/telefon), mentioneaza valideaza.
  • PLAN_LIMITA_LUNARA: fix cu "mai poti trimite N luna asta" + cum treci pe alt plan.
  • Doc scurt pentru integratori: "dezvolta pe free cu valideaza; trimiterea reala cere Pro".
  • set-tier help text clar (CLI) + audit in app_events.
  • Confirma valideaza ramane permis pe orice plan (decizie -> default PERMIS).

Phase 3.5 complete. DX overall ~7.1/10. TTHW ~5 min (neschimbat pentru cont cu drept). Codex: indisponibil. Claude subagent: 3 constatari (1 HIGH mesaj-403, 2 MEDIUM docs/upgrade-link) + leaga T-CEO-3 (valideaza gated vs permis). Trec la Faza 4.

Decision Audit Trail

# Faza Decizie Clasificare Principiu Rationament Respins
1 CEO Mod = SELECTIVE EXPANSION Mechanical override autoplan iteratie pe sistem existent EXPANSION/HOLD/REDUCTION
2 CEO effective_tier(acct, now) cu now injectabil Mechanical P5 explicit teste de granita deterministe now intern (flaky)
3 CEO Coduri noi ca erori business 3-niveluri (nu 500) Mechanical P4 DRY/errors.py reuse pattern existent exceptii/catch-all
4 CEO Gate volum/API server-side dupa resolve_account_id Mechanical P1 completeness/sec nu pe camp din body gate din body (nesigur)
5 CEO Accepta overshoot mic cota sub concurenta Taste->auto P3 pragmatic lock per-cont = over-eng pt cap soft lock tranzactional
6 CEO Valoarea 60 + capability EXCLUSIV in PLANS Mechanical P4 DRY o singura sursa hardcodare in 3 locuri
7 CEO log_event pe fiecare respingere de plan Mechanical zero-silent-failures "de ce blocat X?" vizibil doar 4xx tacut
8 CEO Index (account_id,created_at) deferat -> TODO Mechanical P3 volume mici acum index acum (premature)
9 CEO T-CEO-1: enforcement sub flag + soft-first volum USER CHALLENGE -> REZOLVAT decizie user (2026-06-28) enforcement DUR direct de la deploy; fara conturi legacy, pre-productie -> riscul de fals-block e moot soft-first / flag-OFF respinse
10 CEO T-CEO-2: limita 60 ca o constanta config Taste P5 tunabila fara cod hardcodat
11 Design 6 stari explicite afisaj in US-006 Mechanical P1 completeness acoperire stari doar 3 stari
12 Design Copy RO fix cu pluralizare (zi/zile) Mechanical P5 explicit nu lasa implementatorului generic
13 Design T-DES-1: banner one-time la trial->free Taste P1 semnal la trecere doar badge tacut
14 Design warn = culoare + text (nu doar culoare) Mechanical P1 a11y accesibilitate doar culoare
15 Eng CONSUMED_STATUSES constanta exportata Mechanical P4 DRY nu duplica definitia consum duplicare in teste
16 Eng Fara tabela plan_usage (foloseste created_at) Mechanical P3/non-goal migrare minima coloana/tabela noua
17 Eng 2 invariante critice ca teste (retry, dry-run) Mechanical P1 completeness usor de stricat a le omite
18 DX valideaza ramane PERMIS pe orice plan (default) Taste->auto P1 DX dezvolti pe free, trimiti pe Pro gated ca restul API
19 DX Fix erori plan cu canal de contact concret Mechanical P1 completeness actionabil "contacteaza-ne" vag
20 All "prestatie consumata" = queued+sending+sent Taste->auto P1 limita pe ce trimitem la RAR doar sent
21 All Lot peste limita -> respingere totala clara Taste->auto P5 explicit evita surprize enqueue partial partial tacut
22 All Enforcement DUR direct de la deploy (rezolva T-CEO-1) USER DECISION (2026-06-28) user-stated fara conturi legacy, produs in TESTE/pre-productie -> riscul de fals-block e moot; flag = optional kill-switch soft-first / flag-OFF
23 CEO T-CEO-2 REZOLVAT: limita 60 = constanta config tunabila (o singura sursa in plans.py/config) USER DECISION (2026-06-28) user-stated (pe recomandare) DRY/tunabil fara arheologie de cod hardcodat
24 Design T-DES-1 REZOLVAT: banner one-time la expirarea trial->Gratuit USER DECISION (2026-06-28) user-stated (pe recomandare) semnal clar la trecere, evita surpriza la prima respingere doar badge
25 DX T-DX-3 REZOLVAT: valideaza dry-run ramane PERMIS pe orice plan USER DECISION (2026-06-28) user-stated (pe recomandare) dezvolti pe free, trimiti pe Pro — DX excelent gated ca restul API

Cross-Phase Themes

  • Tema: enforcement fara cale de conversie — semnalata in CEO (S9/S10) + DX (upgrade path). Semnal inalt: hard-block + lipsa self-service = friction. -> sustine T-CEO-1.
  • Tema: mesaje oneste, actionabile — CEO (S2/S8) + Design (P4 copy) + DX (erori). Convergent: fiecare respingere are problema+cauza+fix + canal de contact.
  • Tema: determinism temporal — CEO (S1 now injectabil) + Eng (S3 teste granita) + Design (pluralizare zile). now injectabil + timp local RO sunt fundatia testelor.

TODOS.md (propuneri)

  • [P3] Index (account_id, created_at) pe submissions — cand apar conturi cu mii prestatii/luna, monthly_usage scaneaza randurile lunii. Efort S. Depinde de: aparitia volumului mare. (A: adauga la TODOS)
  • [P2] Job eager downgrade trial_until expirat -> NULL — igiena in purjarea orara T16; lazy acopera corectitudinea. Efort S. (A: adauga la TODOS, optional)
  • [P1->Phase 2] Billing self-service (upgrade din UI) — golul strategic; fara el enforcement-ul produce churn in loc de conversie. Efort XL. PRD separat. (A: adauga la TODOS ca Phase 2)
  • [P3] Re-trial / nurture la expirare — email "trial expirat, treci pe Pro". Efort M. (A: TODOS)

Implementation Tasks (sintetizate)

  • T1 (P1, human ~3h / CC ~25min) — schema/plansaccounts.tier+trial_until (migrare aditiva defensiva) + app/plans.py (PLANS, effective_tier(acct,now), monthly_usage(conn,acct,now), CONSUMED_STATUSES). Surfaced by: CEO S1 / Eng S1-S2. Files: schema.sql, db.py, app/plans.py, accounts.py. Verify: test_migrare_, test_plan_definitii, test_effective_tier_.
  • T2 (P1, human ~2h / CC ~15min) — accountscreate_account seteaza trial Pro 30z; set_tier (protejat id=1); legacy -> free fara trial. Surfaced by: CEO 0B / Eng. Files: accounts.py, tools/account.py.
  • T3 (P1, human ~3h / CC ~25min) — enforce volum — gate INAINTE de build_key pe ambele canale + cod PLAN_LIMITA_LUNARA + log_event; lot peste limita -> respingere totala. Surfaced by: CEO S3/S4/S8. Files: api/v1/router.py, import_router.py, web/routes.py, errors.py. Verify: test_free_peste_60_*, retry.
  • T4 (P1, human ~2h / CC ~15min) — gate APIrequire_api_access (Pro+/trial) pe rutele de ingestie API; valideaza+nomenclator raman permise; dev id=1 exceptat; cod PLAN_FARA_API (403 actionabil). Files: auth.py, api/v1/router.py, import_router.py, errors.py. Verify: test_*_api_403/ok.
  • T5 (P3 OPTIONAL, human ~30min / CC ~5min) — flag enforcement (kill-switch)AUTOPASS_ENFORCE_PLANS (config). NU blocant: enforcement DUR e activ implicit de la deploy (decizie user). Flag-ul = doar comoditate de operare. Files: config.py + gate-urile. Surfaced by: CEO S9 (rezolvat).
  • T6 (P2, human ~3h / CC ~20min) — UI dashboard — badge plan antet + linie burger + consum N/60 + warn>=80% + 6 stari + copy RO pluralizat + pagina Cont. Surfaced by: Design P2/P4. Files: web/routes.py, templates/_status.html,cont.html. Verify: test_afisaj*, test_copy_pluralizare.
  • T7 (P1, human ~30min / CC ~5min) — landing copy — 100->60 (linii 7,65,92,266,388); "Premium gratuit 30 zile"->"Pro gratuit 30 zile" (256,350). Files: landing.html. Verify: test_landing_*.
  • T8 (P2, human ~1h / CC ~10min) — teste matrice E2E — plan x capabilitate x canal x trial + granita luna RO + dev id=1. Files: tests/test_plans.py, test_api_scope.py, test_web_*. Verify: pytest -q.
  • T9 (P2, human ~30min / CC ~5min) — docs integrator — "dezvolta pe free cu valideaza, trimiterea reala cere Pro". Surfaced by: DX. Files: docs/ + integrare_examples.

GSTACK REVIEW REPORT

Review Trigger Why Runs Status Findings
CEO Review /plan-ceo-review Scop & strategie 1 issues_open 9 constatari, CRITICAL GAP legacy REZOLVAT (moot), mode SELECTIVE_EXPANSION
Codex Review /codex review A 2-a opinie 0 indisponibil limita utilizare (pana 2026-07-18)
Eng Review /plan-eng-review Arhitectura & teste (required) 1 issues_open 4 constatari, gap legacy moot, 2 invariante critice teste
Design Review /plan-design-review UI/UX 1 issues_open 4 constatari, overall ~6.4/10
DX Review /plan-devex-review Developer experience 1 issues_open 3 constatari, DX ~7.1/10
  • VERDICT: CEO + Design + Eng + DX rulate (subagent-only, codex indisponibil). Toate deciziile inchise (2026-06-28): USER CHALLENGE rezolvat (enforcement DUR direct de la deploy; CRITICAL GAP migrare = moot, fara conturi legacy/pre-productie) + cele 3 taste decisions rezolvate pe recomandare (T-CEO-2 constanta config, T-DES-1 banner one-time trial->Gratuit, T-DX-3 valideaza permis pe orice plan). Plan gata de executie.

NO UNRESOLVED DECISIONS