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>
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.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 1adaugata inschema.sql+ migrata idempotent in_migrate(conturi existente raman active; default id=1 activ).create_account(conn, name, cui=None, active=True) -> intinsereaza si intoarceid-ul nou.namegol/whitespace →ValueError(nu insereaza).cuine-nul duplicat →ValueError(un CUI = un cont — decizie confirmata §5).cui=Nonese accepta multiplu (conturi fara CUI, ex. default).set_active(conn, account_id, active: bool)comuta starea; cont inexistent →ValueError.list_accounts(conn) -> list[dict]intoarceid, name, cui, active, created_at, ordonat dupaid, fararar_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 tiparesteid-ul;--inactivecreeaza cont in asteptare.--with-keyemite si o cheie API (app.auth.create_api_key), afisata o singura data (reuseaza pattern-ul dintools/apikey.py).activate --id N/deactivate --id Ncomutaactive(mesaj de confirmare).namegol,cuiduplicat sauidinexistent → mesaj la stderr + exit code 2.listtipareste tabelulid | name | cui | activ | created_at.init_db()apelat la start (asigura schema + migrareactive), ca intools/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>→listarataactiv=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_accountfoloseste 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),
NULLpermis 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(filtreazaactive=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.pydin 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 1inschema.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) -> intinsereaza + intoarce id.test_create_account_returneaza_id.namegol/whitespace →ValueError, nu insereaza.test_create_account_name_gol_ridica_eroare.cuiduplicat →ValueError(cauza+fix, numeste contul);cui=Nonemultiplu OK.test_create_account_cui_duplicat_respins,test_create_cui_null_multiplu_permis.set_activecomuta; inexistent →ValueError; idempotent.test_set_active_*.list_accounts→id,name,cui,active,created_at, ordonat, FARArar_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-keyemite cheie afisata o data, atomic (rollback pe esec).test_create_with_key_emite_cheie,test_with_key_atomic_pe_cui_duplicat.activate/deactivate --account Ncomuta.test_activate_comuta_starea.- erori → stderr + exit 2.
test_create_cui_duplicat_exit_2,test_activate_inexistent_exit_2. listtipareste tabelul;list --pendingfiltreaza.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.