Files
rar-autopass/docs/prd/prd-3.1-creare-cont.md
Claude Agent 1c5b0cbc18 feat(account): CLI lifecycle conturi + accounts.active (PRD 3.1)
Inlocuieste crearea conturilor prin INSERT SQL manual cu un tool admin
dedicat, simetric cu tools/apikey.py. Fundatia Etapei 3 (3.2/3.3).

- app/accounts.py: create_account/set_active/list_accounts (helper pur,
  partajat CLI + viitor flux web 3.3). Normalizeaza CUI (trim+upper),
  prinde IntegrityError -> ValueError cu cauza+fix.
- accounts.active (lifecycle cont) + index unic partial ux_accounts_cui
  (unicitate la nivel de index, fara fereastra de coliziune). Migrare
  idempotenta in _migrate.
- tools/account.py: create (--name/--cui/--inactive/--with-key atomic),
  list [--pending], activate/deactivate --account N. Erori -> exit 2.
- 20 teste noi (12 helper + 8 CLI); suita 299 passed.

active e inert pana la gate-ul worker din 3.3 (documentat).

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

11 KiB

PRD 3.1 — Creare cont nou

Stare: verify-pass

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.pytest_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.pytest_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

Executat 2026-06-17. Suita completa: 299 passed (20 teste noi: 12 helper + 8 CLI).

US-001 — PASS

  • accounts.active INTEGER NOT NULL DEFAULT 1 in schema.sql + migrat idempotent in _migrate (conturi existente raman active; default id=1 activ). Dovada: test_foundation + migrare ALTER.
  • create_account(conn, name, cui=None, active=True) -> int insereaza + intoarce id. test_create_account_returneaza_id.
  • name gol/whitespace → ValueError, nu insereaza. test_create_account_name_gol_ridica_eroare.
  • cui duplicat → ValueError (cauza+fix, numeste contul); cui=None multiplu OK. test_create_account_cui_duplicat_respins, test_create_cui_null_multiplu_permis.
  • set_active comuta; inexistent → ValueError; idempotent. test_set_active_*.
  • list_accountsid,name,cui,active,created_at, ordonat, FARA rar_creds_enc. test_list_accounts_ordonat_fara_creds.

US-002 — PASS (E2E PRD reprodus: create --inactive --with-key → id=2 + cheie; activate → list activ=da)

  • create --name [--cui] [--inactive] creeaza (implicit activ), tipareste id. test_create_afiseaza_id.
  • --with-key emite cheie afisata o data, atomic (rollback pe esec). test_create_with_key_emite_cheie, test_with_key_atomic_pe_cui_duplicat.
  • activate/deactivate --account N comuta. test_activate_comuta_starea.
  • erori → stderr + exit 2. test_create_cui_duplicat_exit_2, test_activate_inexistent_exit_2.
  • list tipareste tabelul; list --pending filtreaza. test_list_afiseaza_activ, test_list_pending_filtreaza.
  • init_db() la start.

Addendum — A1 (index unic partial ux_accounts_cui + normalizare trim/upper, test_create_cui_normalizat), A3 (--account pe activate/deactivate), A4 (mesaj cauza+fix), A5 (--with-key atomic), A6 (list --pending; set-password deferat la 3.3), A7 (teste RED suplimentare) — toate aplicate. A2 (active inert pana la 3.3) documentat in app/accounts.py + tools/account.py.