docs(prd): PRD-uri Etapa 3 (3.1/3.2/3.3) aprobate dupa autoplan

Faza PLAN pentru multi-cont / self-onboarding. Trei PRD-uri scrise, ancorate in
cod, trecute prin autoplan (voci Claude independente; Codex degradat pe usage-limit)
si aprobate la poarta umana.

- 3.1 creare cont: CLI tools/account.py + accounts.active; CUI unic prin index partial
- 3.2 filtrare GET pe cont: scope pe cheie, allowlist campuri, nomenclator global
- 3.3 self-onboarding web: sesiuni + cont 'in asteptare' + CSRF + interfata admin web
  + email; US-007 promovat in MVP (7->12 stories)

Dashboard ROADMAP actualizat (stare 'PRD aprobat', linkuri PRD).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-17 12:22:28 +00:00
parent 5dc3a477de
commit 6515de415b
4 changed files with 743 additions and 4 deletions

View File

@@ -0,0 +1,152 @@
# PRD 3.1 — Creare cont nou
**Stare**: aprobat
> Proces complet: `docs/ROADMAP.md` §5. Contract RAR (sursa de adevar): `docs/api-rar-contract.md`.
> Starea trece: `draft → aprobat → in-executie → verify-pass → inchis` (actualizata de lead).
## 1. Obiectiv
Inlocuieste crearea conturilor prin `INSERT` SQL manual cu un tool admin dedicat
(`tools/account.py`), simetric cu `tools/apikey.py`. Este **fundatia Etapei 3**: 3.2
(filtrare GET pe cont) si 3.3 (self-onboarding web) au nevoie de o cale curata si testata
de a materializa conturi. Pastram filozofia din `app/auth.py`: lifecycle-ul de admin
traieste in CLI pe masina gateway, **fara suprafata HTTP de admin**.
## 2. Non-Goals (anti scope-creep)
- **Fara endpoint HTTP** de creare cont in 3.1. (Nota: la poarta de aprobare utilizatorul a cerut o
**interfata web de admin** pentru activarea conturilor — vezi 3.3; aceasta inverseaza stanta
initiala "fara suprafata HTTP de admin". Admin web traieste in 3.3, nu aici. CLI-ul 3.1 ramane
pentru bootstrap/suport.)
- **Fara autentificare de utilizator** (email/parola, sesiuni) — apartine 3.3. Aici contul are
doar `name` + `cui`, ca azi.
- **Fara setare creds RAR** in acest tool — exista deja `POST /v1/conturi/rar-creds` (T1).
- **Fara coloane de user/parola** — identitatea de login (email/parola) vine in 3.3. Aici adaugam
doar `accounts.active` (lifecycle de cont), de care depinde gate-ul „cont in asteptare" din 3.3.
- **Fara stergere cont** — momentan nu e nevoie; conturile sunt putine si durabile. (Dezactivarea
exista, via `active`.)
## 3. Stories atomice
### US-001: Coloana `accounts.active` + helper-e cont in `app/accounts.py`
**Ca** dezvoltator **vreau** functii pure de creare/listare/(de)activare cont **pentru ca** atat
CLI-ul (3.1) cat si fluxul web (3.3) sa materializeze si sa activeze conturi prin aceeasi cale testata.
- **Depinde de**: —
- **Fisiere**: `app/schema.sql` (`accounts.active`), `app/db.py` (migrare `_migrate`),
`app/accounts.py` (nou), `tests/test_accounts.py` (nou) (~4 fisiere)
- **Test intai (RED)**: `tests/test_accounts.py``test_create_account_returneaza_id`,
`test_create_account_activ_implicit`, `test_create_account_name_gol_ridica_eroare`,
`test_create_account_cui_duplicat_respins`, `test_set_active_comuta`, `test_list_accounts_ordonat`
- **Acceptance criteria**:
- [ ] `accounts.active INTEGER NOT NULL DEFAULT 1` adaugata in `schema.sql` + migrata idempotent in
`_migrate` (conturi existente raman active; default id=1 activ).
- [ ] `create_account(conn, name, cui=None, active=True) -> int` insereaza si intoarce `id`-ul nou.
- [ ] `name` gol/whitespace → `ValueError` (nu insereaza).
- [ ] `cui` ne-nul duplicat → `ValueError` (un CUI = un cont — decizie confirmata §5).
`cui=None` se accepta multiplu (conturi fara CUI, ex. default).
- [ ] `set_active(conn, account_id, active: bool)` comuta starea; cont inexistent → `ValueError`.
- [ ] `list_accounts(conn) -> list[dict]` intoarce `id, name, cui, active, created_at`, ordonat dupa
`id`, **fara** `rar_creds_enc`.
- **Verificare E2E**: n/a (helper pur) — acoperit de teste unitare.
### US-002: CLI `tools/account.py` (create/list/activate/deactivate)
**Ca** admin gateway **vreau** `python -m tools.account create|list|activate|deactivate` **pentru ca**
sa onboardez si sa **activez** un client fara SQL manual, optional emitand prima cheie API intr-un pas.
- **Depinde de**: US-001
- **Fisiere**: `tools/account.py` (nou), `tests/test_tools_account.py` (nou) (~2 fisiere)
- **Test intai (RED)**: `tests/test_tools_account.py``test_create_afiseaza_id`,
`test_create_with_key_emite_cheie`, `test_create_cui_duplicat_exit_2`,
`test_activate_comuta_starea`, `test_list_afiseaza_activ`
- **Acceptance criteria**:
- [ ] `create --name "Service X" [--cui RO123] [--inactive]` creeaza contul (implicit activ) si
tipareste `id`-ul; `--inactive` creeaza cont in asteptare.
- [ ] `--with-key` emite si o cheie API (`app.auth.create_api_key`), afisata **o singura data**
(reuseaza pattern-ul din `tools/apikey.py`).
- [ ] `activate --id N` / `deactivate --id N` comuta `active` (mesaj de confirmare).
- [ ] `name` gol, `cui` duplicat sau `id` inexistent → mesaj la stderr + exit code 2.
- [ ] `list` tipareste tabelul `id | name | cui | activ | created_at`.
- [ ] `init_db()` apelat la start (asigura schema + migrare `active`), ca in `tools/apikey.py`.
- **Verificare E2E**: `python -m tools.account create --name "Test SRL" --cui RO99 --inactive --with-key`
→ cont inactiv + cheie; `python -m tools.account activate --id <id>``list` arata `activ=da`.
## 4. Riscuri
- **Unicitate CUI** — daca un client are mai multe puncte de lucru sub acelasi CUI, constrangerea
blocheaza al doilea cont. Mitigare: o aplicam la nivel de helper (nu schema UNIQUE) ca sa fie usor
de relaxat; decizia finala in §5.
- **Coliziune cu cont default (id=1)** — `INSERT OR IGNORE ... id=1 'default'` exista in schema.
`create_account` foloseste AUTOINCREMENT, deci nu atinge id=1. Test: primul cont creat are id≥2.
## 5. Intrebari deschise (REZOLVATE la poarta de aprobare)
- **CUI unic** — REZOLVAT: unic cand e prezent (un CUI = un cont), `NULL` permis multiplu.
- **`--with-key`** — REZOLVAT: optional (flag), emiterea cheii e o decizie constienta.
- **Coloana `active`** — adaugata aici (lifecycle de cont) pentru ca 3.3 sa creeze conturi „in
asteptare" (`active=0`) si gate-ul de trimitere sa le opreasca pana la activarea de catre admin.
## 6. Valuri de executie (graful de dependente)
```
Val 1: [US-001] ← schema active + helper, fisiere (cvasi-)noi → fara dependente
Val 2: [US-002] ← deblocat de US-001 (CLI peste helper)
```
---
## Addendum review (autoplan, `[subagent-only]` — Codex indisponibil: usage limit)
> Doua voci Claude independente (Eng + Produs/DX). Schimbarile de mai jos sunt **obligatorii la
> executie** (auto-decise prin principiile autoplan: completitudine, explicit, DRY). Convergenta
> mare intre voci pe primele doua.
**A1 [HIGH, ambele voci] — Unicitatea CUI prin index partial, NU check in helper.** Check-ul
`SELECT cui → ValueError → INSERT` are o fereastra de coliziune (doi `create_account` concurenti,
relevant cand 3.3 reuseaza helperul din web). Fix: in `schema.sql` + `_migrate`
`CREATE UNIQUE INDEX IF NOT EXISTS ux_accounts_cui ON accounts(cui) WHERE cui IS NOT NULL` (SQLite
trateaza NULL ca distincte nativ → `cui=NULL` multiplu merge gratis). Helperul **normalizeaza**
(`trim` + `upper`) si prinde `IntegrityError → ValueError` pentru mesaj prietenos. Inlocuieste AC
"cui duplicat" + adauga `test_create_cui_null_multiplu_permis`, `test_create_cui_normalizat`.
**A2 [HIGH, ambele voci] — `accounts.active` este INERT pana la 3.3.** Nimic nu citeste `active`
azi (`resolve_account_id` verifica doar `api_keys.active`; worker-ul nu se uita la cont). Pana la
3.3/US-008 (gate worker), `deactivate` NU opreste trimiterile. Documenteaza-l explicit ca lifecycle
flag consumat de 3.3; nu lasa asteptarea falsa ca dezactiveaza trimiterile. (Decizia keep-in-3.1 vs
move-to-3.3 = taste, vezi poarta.)
**A3 [MEDIUM, Produs] — Flag consistent cu `tools/apikey.py`:** `activate`/`deactivate` folosesc
`--account N` (nu `--id N`), aliniat cu sora. `create` pastreaza `--name`/`--cui`.
**A4 [MEDIUM, ambele] — Mesaje de eroare cu cauza+fix.** CUI duplicat → numeste contul existent:
`eroare: CUI RO123 e deja folosit de contul 4 (foloseste 'activate --account 4' sau alt CUI)`.
Specifica textul ca tinta de test.
**A5 [LOW→MEDIUM] — `--with-key` atomic:** `create_account` + `create_api_key` in aceeasi
tranzactie (`BEGIN IMMEDIATE` → ambele → COMMIT; pe esec ROLLBACK). DB ruleaza autocommit
(`isolation_level=None`) — tranzactia trebuie explicita. Pe esec emitere cheie: mesaj clar ca
contul A fost creat.
**A6 [MEDIUM, Produs — pentru 3.3] — adauga comenzi CLI ceruta de 3.3:**
- `list --pending` (filtreaza `active=0`) — adminul descopera conturile de activat (3.3 nu are
notificare; vezi addendum 3.3).
- `set-password --account N` — scapare de reset parola pentru 3.3 (Non-Goal SMTP), altfel "reset
prin admin" e o promisiune fara implementare. (Implementarea hash-ului = `app/users.py` din 3.3;
comanda poate astepta US din 3.3, dar planific-o aici ca sa nu se piarda.)
**A7 [MEDIUM] — Teste RED lipsa de adaugat:** `cui=None` multiplu (A1), `list` fara
`rar_creds_enc`, `set_active` idempotent (set activ pe activ nu arunca), `create_account(active=False)`
la nivel de helper, primul cont creat are `id>=2` (nu atinge default id=1).
**A8 [LOW] — Obiectiv corectat:** 3.1 e fundatie pentru **3.3** (care reuseaza `create_account` +
`active`); pentru 3.2 e doar suport de VERIFY (al doilea cont). 3.2 NU materializeaza conturi.
**Deferat (P3, in afara scope-ului imediat — noteaza in ROADMAP/TODO):** `rename`/`set-cui`
(corectie typo fara SQL manual), `--if-not-exists` (provisioning idempotent). Regret probabil, dar
nu blocheaza livrabila.
## Raport VERIFY
> Completat de subagentul verificator (context curat) in faza VERIFY — vezi ROADMAP §5.6.
> PASS/FAIL per criteriu, cu dovezi. Lipseste pana la VERIFY.