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>
961 lines
67 KiB
Markdown
961 lines
67 KiB
Markdown
<!-- /autoplan restore point: /home/claude/.gstack/projects/romfast-rar-autopass/docs-prd-5.16-5.17-design-tiers-autoplan-restore-20260628-212453.md -->
|
||
# 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: `free` → `monthly_limit=60`, `api_access=False`;
|
||
`standard` → `monthly_limit=None` (nelimitat), `api_access=False`; `pro` → `monthly_limit=None`,
|
||
`api_access=True`; `premium` → `monthly_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: <tier> [· 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)
|
||
- [x] ~~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.
|
||
|
||
<!-- AUTONOMOUS DECISION LOG -->
|
||
## 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/plans** — `accounts.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) — accounts** — `create_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 API** — `require_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
|