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>
26 KiB
PRD 3.3 — Self-onboarding web (login email+parola → emite cheie)
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).Aceasta este cea mai mare livrabila a Etapei 3. Schimba postura de securitate a intregului canal web (azi 100% deschis, hardcodat pe contul 1). Intrebarile din §5 trebuie rezolvate la poarta de aprobare ÎNAINTE de executie — in special cine are voie sa se inregistreze.
1. Obiectiv
Un service nou se inregistreaza singur din browser (companie + email + parola), primeste pe loc o
cheie API (afisata o singura data) si o sesiune web legata de contul lui. Contul se creeaza
„in asteptare" (active=0): userul poate naviga, configura creds RAR si pregati import, dar
nicio prezentare nu se trimite la RAR pana cand un admin activeaza contul (tools/account.py activate, din 3.1). Inlocuieste account_id = DEFAULT_ACCOUNT_ID hardcodat din app/web/routes.py
cu contul din sesiune, facand dashboard-ul si importul multi-tenant. Construieste peste 3.1
(helper cont + active) si peste pattern-ul de scoping din 3.2 (acum aplicat si rutelor web).
Fara dependinte noi: sesiuni via starlette.middleware.sessions.SessionMiddleware (cookie semnat,
itsdangerous deja prezent), parole via hashlib.scrypt (stdlib).
2. Non-Goals (anti scope-creep)
- Fara reset parola pe email / verificare email — MVP: parola setata la signup, schimbata doar prin admin (sau livrabila viitoare). Niciun SMTP.
- Fara multi-user per cont in UI — schema permite (tabela
userscuaccount_id), dar fluxul MVP creeaza un user → un cont nou. Invitare de colegi = viitor. - Fara roluri/permisiuni — un user vede integral contul lui.
- Fara admin web — listarea/stergerea conturilor ramane CLI (3.1).
- Fara OAuth/SSO.
- Setarea creds RAR ramane prin fluxul existent (
POST /v1/conturi/rar-creds); un buton web care il apeleaza e optional (US-007), nu inima livrabilei.
3. Stories atomice
US-001: Schema users + helper-e app/users.py (parole scrypt)
Ca dezvoltator vreau un model de utilizator cu parola hash-uita pentru ca autentificarea web are nevoie de o identitate legata de un cont, separat de cheia API si de creds RAR.
- Depinde de: 3.1/US-001 (
app/accounts.py) - Fisiere:
app/schema.sql(tabelausers),app/db.py(migrare_migrate),app/users.py(nou),tests/test_users.py(nou) (~4 fisiere) - Test intai (RED):
tests/test_users.py—test_create_user_hash_nu_e_plaintext,test_verify_parola_corecta_si_gresita,test_email_unic_global,test_get_user_by_email - Acceptance criteria:
users(id, account_id FK, email TEXT UNIQUE COLLATE NOCASE, password_hash, salt, created_at)creata inschema.sql+ migrata idempotent in_migrate(DB existenta nu se strica).create_user(conn, account_id, email, password) -> int: stocheaza doarscrypt(salt+parola)+salt(per-user,secrets.token_bytes), niciodata parola in clar.verify_password(conn, email, password) -> int | None: intoarceaccount_idla potrivire,Nonealtfel (comparatie constant-timehmac.compare_digest).emailduplicat (case-insensitive) →ValueError.get_user_by_email(conn, email)intoarce metadate fara hash/salt.
- Verificare E2E: n/a (helper) — teste unitare.
US-002: Middleware sesiune + guard web
Ca dezvoltator vreau sesiuni cookie semnate + un helper de contul-curent pentru ca rutele web sa stie cine e logat si sa redirectioneze nelogatii.
- Depinde de: US-001
- Fisiere:
app/config.py(session_secret),app/main.py(add_middleware),app/web/session.py(nou:current_account(request),require_login),tests/test_web_session.py(nou) (~4 fisiere) - Test intai (RED):
tests/test_web_session.py—test_ruta_protejata_redirect_login,test_sesiune_seteaza_si_citeste_cont,test_logout_curata_sesiunea - Acceptance criteria:
AUTOPASS_session_secretinSettings(default efemer dev, persistent in prod, cacreds_key).SessionMiddlewaremontat inmain.pycu secretul; cookiehttponly,samesite=lax.current_account(request) -> int | Nonecitesterequest.session["account_id"].require_login(dependency): nelogat →RedirectResponse('/login', 303).
- Verificare E2E: GET
/fara sesiune → redirect/login; cu sesiune setata → 200.
US-003: Signup web (GET/POST /signup)
Ca service nou vreau sa-mi creez cont din browser pentru ca sa incep fara admin.
- Depinde de: US-001, US-002, 3.1/US-001
- Fisiere:
app/web/auth_routes.py(nou),app/web/templates/signup.html(nou),tests/test_web_signup.py(nou) (~3 fisiere) - Test intai (RED):
tests/test_web_signup.py—test_signup_creeaza_cont_user_si_cheie,test_signup_email_duplicat_eroare,test_signup_parola_scurta_eroare,test_cheie_afisata_o_data - Acceptance criteria:
GET /signuprandeaza formular (companie, CUI optional, email, parola).POST /signup:create_account(active=False)(3.1) +create_user+create_api_keyintr-o tranzactie; seteaza sesiunea; randeaza pagina cu cheia afisata o singura data + avertisment + mesaj clar „cont in asteptare — trimiterea incepe dupa activarea de catre admin".- Email duplicat → re-randare cu eroare, fara a crea cont orfan (tranzactie rollback).
- Parola sub minim (10 caractere, §5) → eroare, fara creare.
- Verificare E2E: browser pe
/signup(Playwright MCP) → completare → vezi cheiarfak_...+ banner „in asteptare";python -m tools.apikey listarata cheia; sesiunea e activa.
US-004: Login / Logout web (GET/POST /login, POST /logout)
Ca utilizator existent vreau sa ma autentific pentru ca sa-mi accesez contul intre sesiuni.
- Depinde de: US-001, US-002
- Fisiere:
app/web/auth_routes.py,app/web/templates/login.html(nou),tests/test_web_login.py(nou) (~3 fisiere) - Test intai (RED):
tests/test_web_login.py—test_login_corect_seteaza_sesiune,test_login_gresit_401_fara_leak,test_logout_redirect_login - Acceptance criteria:
POST /logincu credentiale corecte → sesiune setata + redirect/.- Credentiale gresite → re-randare cu mesaj generic („email sau parola incorecte"), fara a dezvalui daca emailul exista.
POST /logout→session.clear()+ redirect/login.
- Verificare E2E: browser
/login→ logare → dashboard;/logout→ inapoi la/login.
US-005: Dashboard scoped pe sesiune (citiri)
Ca utilizator logat vreau ca dashboard-ul sa-mi arate doar contul meu pentru ca multi-tenant inseamna ca nu vad coada/banner-ul altui service.
- Depinde de: US-002
- Fisiere:
app/web/routes.py(rutele GET dashboard +_fragments/*),tests/test_dashboard_scope.py(nou) (~2 fisiere) - Test intai (RED):
tests/test_dashboard_scope.py—test_counts_doar_contul_sesiunii,test_submissions_fragment_scoped,test_nelogat_redirect - Acceptance criteria:
_status_counts,_fragments/banner,_fragments/submissionsfiltreaza pecurrent_account(request)(NULL→cont 1, ca in 3.2).- Rutele dashboard cer
require_login(sau degradeaza la/login). _fragments/nomenclatorramane global (referinta, ca in 3.2).
- Verificare E2E: doi useri, doua conturi, prezentari diferite → fiecare vede doar coada lui.
US-006: Import web legat de sesiune (scriere)
Ca utilizator logat vreau ca importul sa intre pe contul meu pentru ca azi toate upload-urile aterizeaza pe contul 1, indiferent cine le face.
- Depinde de: US-002
- Fisiere:
app/web/routes.py(rutele/_import/*+/mapari),tests/test_import_web_scope.py(nou) (~2 fisiere) - Test intai (RED):
tests/test_import_web_scope.py—test_upload_pe_contul_sesiunii,test_commit_creeaza_submissions_pe_cont,test_batch_alt_cont_inaccesibil - Acceptance criteria:
- Toate aparitiile
account_id = DEFAULT_ACCOUNT_IDdin rutele/_import/*+POST /mapariweb →current_account(request). - Un batch al contului A nu e accesibil din sesiunea contului B (preview/confirma/mapare → eroare).
submissions/import_batchescreate primescaccount_id-ul sesiunii.
- Toate aparitiile
- Verificare E2E: import complet prin browser ca user B → submissions au
account_id=B;./start.sh test finalizate(cu send) sau dashboard-ul lui B le arata, al lui A nu.
US-007 (optional): Sectiune „Cheia mea API" + creds RAR in dashboard
Ca utilizator logat vreau sa-mi rotesc cheia si sa-mi setez creds RAR din UI pentru ca sa fiu complet self-service fara CLI.
- Depinde de: US-002, US-005
- Fisiere:
app/web/routes.py,app/web/templates/_cont.html(nou),tests/test_web_cont.py(nou) (~3 fisiere) - Test intai (RED):
tests/test_web_cont.py—test_roteste_cheie_afisata_o_data,test_set_creds_rar_din_sesiune - Acceptance criteria:
- Buton „roteste cheia" →
rotate_api_key(conn, current_account), cheie noua afisata o data. - Formular creds RAR → reuseaza logica
POST /v1/conturi/rar-credspe contul sesiunii.
- Buton „roteste cheia" →
- Verificare E2E: rotire din UI → cheia veche respinsa pe
/v1/*, cea noua acceptata.
US-008: Gate de trimitere pentru conturi „in asteptare"
Ca operator RAR vreau ca prezentarile unui cont neactivat sa nu ajunga la RAR pentru ca self-signup nu trebuie sa permita trimiteri reale inainte de validarea de catre admin.
- Depinde de: 3.1/US-001 (
accounts.active) - Fisiere:
app/worker/__main__.py(claim),app/web/templates/_banner.html(+routes.py),tests/test_worker_active_gate.py(nou) (~3 fisiere) - Test intai (RED):
tests/test_worker_active_gate.py—test_claim_sare_cont_inactiv,test_claim_ia_cont_activ,test_activare_deblocheaza_trimiterea - Acceptance criteria:
- Claim-ul worker-ului nu ridica submission-uri al caror cont are
active=0(ramanqueued, fara retry/eroare — pur si simplu nealese pana la activare). - Dupa
set_active(.., True), la urmatorul poll submission-urile devin eligibile, fara re-enqueue. - Dashboard-ul contului in asteptare arata un banner „cont in asteptare — trimiterea e oprita".
- Conturile fara
activeexplicit (legacy) sunt tratate ca active (NULL/absent → activ).
- Claim-ul worker-ului nu ridica submission-uri al caror cont are
- Verificare E2E: signup → import + confirma → submission
queuedramane netrimis cat timp contul e inactiv;tools/account.py activate→ worker il trimite →FINALIZATAla RAR test.
4. Riscuri
- Signup deschis = abuz — oricine isi face cont. Pentru un gateway B2B legat de RAR, asta poate fi nedorit. Mitigare posibila: cod de invitatie / aprobare admin / rate-limit. Decizie §5 — blocant.
- Suprafata de securitate noua — canalul web trece de la „deschis" la „autentificat"; orice ruta
web nescoped ramasa = leak. Mitigare: US-005+US-006 acopera toate rutele; VERIFY enumera explicit
fiecare ruta
app/web/routes.pysi confirma scoping (grepDEFAULT_ACCOUNT_ID= 0 rezultate la final). - Secret de sesiune — daca
AUTOPASS_session_secrete efemer in prod, cookie-urile se invalideaza la restart. Mitigare: documentat ca persistent in.env, cacreds_key. - Atomicitate US-006 —
routes.pye mare; riscul e sa ramana unDEFAULT_ACCOUNT_ID. Mitigare: test care face grep in sursa + verificare comportamentala per ruta. - Interactiune cu 3.2 — daca 3.2 nu e inca livrat, scoping-ul web (US-005/006) introduce pattern-ul
separat; e ok (web nu trece prin
resolve_account_id), dar mentinem aceeasi regula NULL→cont 1.
5. Intrebari deschise
Primele doua REZOLVATE la poarta de aprobare; restul = propuneri implicite (confirmabile la review).
- [REZOLVAT] Cine se poate inregistra? Signup creeaza cont „in asteptare" (
active=0) pe care adminul il activeaza inainte de prima trimitere (optiunea (c)). Vezi US-003 + US-008. - [REZOLVAT] Useri per cont? Schema
userscuaccount_id(permite multi-user viitor); fluxul MVP = un user creeaza un cont nou, login intra pe contul lui. - Politica de parola — minim 10 caractere, fara alte reguli (lungime > complexitate). (implicit)
- Persistenta sesiunii — cookie semnat (
SessionMiddleware), suficient pentru MVP. (implicit) - Contul default (id=1) — ramane „contul dev fara login", neatins; userii reali primesc id≥2.
Dashboard fara sesiune in dev (
require_api_key=false) → cont 1, ca azi. (implicit) - US-007 in scope? Optional; se poate taia pentru o livrabila mai mica. (implicit: il pastram)
6. Valuri de executie (graful de dependente)
Val 1: [US-001] ← schema + helper users (depinde de 3.1/US-001 livrat)
Val 2: [US-002] ← sesiune + guard (peste users)
Val 3: [US-003, US-004, US-005, US-006, US-008] ← deblocate de US-002 (US-008 doar de 3.1/US-001).
US-005 si US-006 ating ambele routes.py (FISIER COMUN) →
nu paralel intre ele. US-003/US-004 (auth_routes.py),
US-008 (worker) ating fisiere distincte → pot merge paralel.
Val 4: [US-007] ← optional, peste US-005
Nota de paralelizare: US-005 + US-006 modifica acelasi
app/web/routes.py→ secvential sau worktree+merge (§5.5). Un grupaj sigur: teammate A = US-003+US-004 (auth_routes.py), teammate B = US-005 apoi US-006 (routes.py), serializat. Lead-ul ruleaza regresia dupa fiecare.
Addendum review (autoplan, [subagent-only] — Codex indisponibil: usage limit)
Doua voci Claude independente (Eng + Produs/Design/DX). Securitate notata 4/10 — cea mai slaba dimensiune, fiindca livrabila introduce suprafata cookie-auth. Doua blocante critice. Schimbari obligatorii la executie; trei decizii de scope ridicate la poarta.
Blocante (rezolvabile inainte de executie)
C1 [CRITICAL, Eng] — Dependinta 3.1 e fantoma in repo. app/accounts.py, tools/account.py,
create_account, set_active nu exista azi; accounts.active e in schema.sql dar NU in
_migrate → orice DB veche da OperationalError: no such column la US-008. Fix: 3.1 e
prerechizita HARD; la poarta de executie a lui 3.3 verifica livrarea 3.1 (inclusiv migrarea
active in _migrate). Nu porni 3.3 fara 3.1 inchis.
C2 [CRITICAL, ambele voci] — CSRF complet absent → STORY NOU US-009. Dupa 3.3 auth web e
exclusiv cookie de sesiune; fiecare POST de stare (/signup, /login, /logout, /_import/*,
/mapari, rotire cheie) devine vulnerabil CSRF — regresie introdusa DE aceasta livrabila.
SameSite=Lax (US-002) NU acopera POST-uri top-level. US-009: token CSRF in sesiune + camp ascuns
in toate formularele + validare server-side; SameSite=Strict pe cookie-ul de sesiune ca aparare
in adancime. Depinde de US-002; blocheaza US-003/004/006b/007.
Securitate (obligatoriu in stories existente)
C3 [HIGH, Eng] — Fixare sesiune: la login (US-004) request.session.clear() INAINTE de a seta
account_id (+ nonce nou). AC nou.
C4 [MEDIUM, Eng] — Cookie secure/https_only: US-002 adauga https_only=True (config-abil,
default on in prod; in spatele Cloudflare Tunnel e TLS). Plus samesite aliniat cu C2.
C5 [MEDIUM, ambele — rate-limit signup] → in US-009. active=0 opreste trimiterea, NU crearea
nelimitata de conturi/useri/chei (DoS storage + zgomot admin). Adauga rate-limit pe IP la
POST /signup (in-proces, fara dependinta noua). Reformuleaza §5.1: nu "REZOLVAT" ci "DECIS:
cont in asteptare + activare admin; risc de recon acceptat pentru MVP + rate-limit".
Atomicitate scoping (redefineste "done")
C6 [CRITICAL, Eng] — Testul grep DEFAULT_ACCOUNT_ID = 0 e fals-linistitor. Rutele GET
periculoase NU contin acel literal: _status_counts (l.59), fragment_submissions (l.146-149,
SELECT ... FROM submissions fara WHERE account_id), fragment_banner. Grep=0 ar declara "done"
in timp ce dashboard-ul lui B arata coada lui A. Fix US-005: adauga account_id +
WHERE account_id (clauza scope din 3.2/B2) la _status_counts/fragment_submissions/
fragment_banner. "Done" = comportamental (test pe 2 conturi), grep ramane doar smoke.
C7 [HIGH, Eng] — Sparge US-006 in US-006a (citiri: upload/preview/reset/mapare-coloane) +
US-006b (scrieri: confirma ~250 linii cu tranzactie/atestare + POST /mapari). Un
account_id ramas in confirma → submissions pe contul gresit, irecuperabil dupa send
(FINALIZATA terminal). 006b primeste E2E dedicat pe 2 conturi.
C8 [HIGH, Eng — OV-2] — Propagare consecventa current_account. build_key /
_build_idempotency_key / _already_sent_lookup / _web_compute_preview trebuie sa primeasca
ACELASI account_id (sesiunea). Test: un rand importat web sub cont N primeste aceeasi cheie ca
prin API sub cont N (altfel reapare OV-2). AC in US-006b.
Corectitudine helper-e / model
C9 [MEDIUM, ambele] — Parametri scrypt ficsi (US-001): hashlib.scrypt(password=parola, salt=salt, n=2**14, r=8, p=1, maxmem=64*1024*1024, dklen=32) — salt/parola argumente separate
(NU concatenate). Stocheaza si o eticheta de versiune a parametrilor (migrare cost viitoare). Plafon
lungime parola (ex. 128 char) — scrypt pe 1MB = DoS.
C10 [HIGH, Eng] — Tranzactie signup atomica (US-003): BEGIN IMMEDIATE → create_account
(active=False) → create_user (poate ridica pe email UNIQUE AICI) → create_api_key → COMMIT;
orice exceptie → ROLLBACK. Test test_signup_email_duplicat asereaza COUNT(accounts) neschimbat
(fara cont orfan). DB e autocommit → tranzactia trebuie explicita.
C11 [MEDIUM, Eng] — require_login ca dependency care intoarce RedirectResponse NU
scurtcircuiteaza handler-ul. Trebuie sa RIDICE o exceptie prinsa de un handler care
redirectioneaza (sau middleware pe prefixele web). Clarifica mecanismul in US-002.
C12 [MEDIUM, Eng] — require_login vs cont-1-in-dev. §5.5 zice "dev fara sesiune → cont 1" dar
US-005 cere require_login. Bypass cand require_api_key=false (sau flag web_auth_required),
cazand pe cont 1. Specifica conditia exacta. Pe scrieri web current_account is None → redirect
login, NICIODATA fallback account_or_default (leak silentios pe cont 1).
C13 [HIGH, Eng] — US-007 vs require_api_key. POST /v1/conturi/rar-creds cere cheie API; un
user web logat fara cheie in header → 401 in prod. US-007 are nevoie de ruta web proprie scoped
pe sesiune (nu reuseste endpoint-ul API), SAU un dependency hibrid "sesiune SAU cheie".
Gate worker (US-008)
C14 [MEDIUM, Eng] — Query claim cu LEFT JOIN + dublu-NULL.
LEFT JOIN accounts a ON a.id=s.account_id WHERE COALESCE(a.active,1)=1. Atentie la DOUA NULL-uri:
(a) cont legacy fara active → COALESCE; (b) submissions.account_id IS NULL (FK
ON DELETE SET NULL) → ar fi blocat pe veci cu JOIN intern; LEFT JOIN + s.account_id IS NULL =
tratat ca default/activ. Specifica query-ul exact in AC.
UX / produs (Produs voce)
C15 [HIGH] — Pagina "cheia o data" (US-003) subspecificata. AC noi: buton copy-to-clipboard + confirmare; gate "am salvat cheia" inainte de CTA dashboard; comportament la refresh (POST→render, cheia NU e in sesiune → refresh = pierdere; recuperare doar prin rotire US-007 / CLI admin) — scris explicit in UI.
C16 [HIGH] — Activarea admin nu are mecanism de descoperire/notificare. Fara admin web, adminul
ar rula tools/account.py list din proprie initiativa. Fix: la signup, linie de log dedicata
(SIGNUP cont=N email=...) + tools/account.py list --pending (din addendum 3.1/A6). Dashboard-ul
contului arata starea explicit ("in asteptare de la {created_at}"). Documenteaza procesul+SLA de
activare in PRD (vezi user challenge UC-2 la poarta).
C17 [MEDIUM] — Reframe pozitiv banner "in asteptare" + stari HTMX: submit dezactivat + indicator
pe POST (signup face 3 operatii); erorile re-randeaza formularul cu valorile pastrate (Mai putin
parola — confirma ca nu re-pui parola in value=). Banner: "Contul e creat. Configureaza creds RAR
si pregateste importul ACUM; trimiterea catre RAR porneste automat dupa activare."
C18 [MEDIUM] — Stari/erori lipsa: login pe cont active=0 intra (gate-ul e doar pe trimitere —
confirma explicit); cont activat intre timp → banner dispare (clarifica ca e alt banner decat cel de
submissions blocate); email normalizat (trim + COLLATE NOCASE); sesiune cu account_id pe cont
sters → redirect login, nu query gol tacut.
C19 [MEDIUM, P2 ieftin — pregatire viitor]: users.email_verified INTEGER DEFAULT 0 de la
inceput (evita migrare dureroasa cand apare verificarea email). Pune si user_id in sesiune (nu
doar account_id) — leaga import_attestations.confirmed_by de user mai tarziu fara migrare.
Decizii ridicate la poarta (taste + user challenges)
- UC-1 [user challenge, ambele voci] — NU lansa signup fara rate-limit + NU marca §5.1 "REZOLVAT". (Auto-adaugat in C5/US-009; confirma la poarta.)
- UC-2 [user challenge, Produs] — Defineste PROCESUL de activare admin (descoperire + SLA) inainte de aprobare, nu ca detaliu de implementare. Acesta e arcul care va genera cele mai multe tichete. Intrebare de produs pentru tine.
- UC-3 [user challenge, Produs] — Promoveaza US-007 din "optional" in scope (cel putin rotire cheie + formular creds RAR). Fara el livrabila nu-si tine numele "self-onboarding": userul e dependent de admin/CLI exact pentru pasii self-service. Decizie de scope pentru tine.
- Taste: scrypt stdlib vs argon2id (zero-dependinta vs purist securitate); gate-activare vs auto-activ+monitorizare reactiva.
Graf revizuit (stories adaugate/sparte)
Val 1: [US-001] (peste 3.1 livrat — C1)
Val 2: [US-002] → [US-009 CSRF+rate-limit, C2/C5]
Val 3: [US-003, US-004, US-005, US-006a, US-006b, US-008]
US-005 + US-006a/b ating routes.py (comun) → secvential; US-003/004 (auth_routes.py),
US-008 (worker), US-009 (cross-cutting) — fisiere distincte, paralelizabile cu grija.
Val 4: [US-007] ← promovat la poarta (UC-3): rotire cheie + creds RAR web (ruta proprie, C13)
Decizii poarta (pliate — aprobat de utilizator)
Acestea EXTIND scope-ul fata de draftul initial. Confirma graful revizuit la inceputul EXECUTE.
G1 — US-007 PROMOVAT in MVP (nu mai e optional). Rotire cheie (recuperare cheie pierduta) + formular creds RAR pe ruta web proprie scoped pe sesiune (C13, nu reuseste endpoint-ul API care cere cheie). Devine obligatoriu pentru ca livrabila sa fie cu adevarat self-service.
G2 — INTERFATA WEB DE ADMIN ceruta explicit ("chiar vreau interfata admin"). Inverseaza Non-Goal-ul "fara suprafata HTTP de admin" (3.1). Stories noi:
-
US-010: Rol admin + bootstrap.
users.is_admin INTEGER DEFAULT 0(migrare). Primul cont (sau marcat viatools/account.py set-admin --account N) e admin. Guardrequire_admin(pesterequire_login+ CSRF). Test: non-admin pe ruta admin → 403/redirect.- Depinde de: US-001, US-002, US-009 (CSRF).
- Intrebare deschisa (vezi §5): cum se bootstrapeaza primul admin?
-
US-011: Panou admin web
/admin— conturi in asteptare + activare. Listeaza conturileactive=0(email, companie, CUI, created_at) + buton "activeaza" (set_active(.., True)din 3.1, POST cu CSRF). Optional: dezactivare, vedere chei (fara hash). Templateadmin.html.- Depinde de: US-010, 3.1/US-001 (
set_active). - E2E: signup (cont nou inactiv) → admin se logheaza → vede contul in
/admin→ activeaza → worker il trimite →FINALIZATAla RAR test.
- Depinde de: US-010, 3.1/US-001 (
US-012: Notificare email admin la signup. La POST /signup, trimite email catre admin(i)
("cont nou N in asteptare"). Adauga dependinta SMTP (config nou AUTOPASS_smtp_*) — reverseaza
Non-Goal-ul "niciun SMTP". Esecul trimiterii email NU blocheaza signup-ul (best-effort + log).
- Depinde de: US-003, US-010 (sa stie cui trimite).
- Intrebare deschisa: provider SMTP / adresa expeditor / fallback daca SMTP pica.
Mecanismul de descoperire e acum redundant-sigur: log
SIGNUP(C16) +list --pending(CLI) + email (US-012) + panou/admin(US-011). C16 ramane ca baseline daca SMTP/panoul intarzie.
Graf revizuit final:
Val 1: [US-001]
Val 2: [US-002] → [US-009 CSRF+rate-limit]
Val 3: [US-003, US-004, US-005, US-006a, US-006b, US-008, US-010]
Val 4: [US-007, US-011 admin panou, US-012 email] (US-007/011/012 peste US-010/US-009)
Scope mai mare decat draftul (7 → 12 stories). 3.3 e acum substantiala; daca devine prea grea pentru o singura faza EXECUTE, lead-ul o poate sparge in sub-livrabile la inceputul executiei (ex. 3.3a self-onboarding core, 3.3b admin web + email) — decizie de orchestrare, nu de scope.
Intrebari deschise nou aparute (rezolva la inceputul EXECUTE)
- Bootstrap admin (US-010): primul cont devine automat admin, SAU adminul se marcheaza manual
via
tools/account.py set-admin --account N? Propunere: manual via CLI (explicit, fara magie "primul user"); contul default id=1 nu e admin automat. - SMTP (US-012): ce provider/expeditor? Daca nu exista SMTP la momentul executiei, US-012 se
livreaza degradat (doar log +
/admin+list --pending) si email-ul devine follow-up.
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.