Vectorii corpusului k-NN persista in tabela embedding_cache (PK model+text_hash,
blob float32 LE); la warmup se vectorizeaza doar textele lipsa din cache, deci
restartul cu corpus neschimbat nu mai plateste ~1-2 min de embed (embed=0).
- app/embedding_cache.py: serializare array('f'), load/save/purge chunk 500 cu
BEGIN/COMMIT explicit (conexiuni autocommit), validare dimensiune la scriere
si citire, orchestrare sync_corpus_vectors cu embed_fn injectat
- index_corpus(vectors=): vectori precalculati cu validare aliniere; mismatch
-> fallback embed complet
- ensure_embeddings_corpus: warmup in thread la startup (block=True), calea de
request ne-blocanta (acquire non-blocking pe lock; warmup in curs -> return
imediat); purjare orfane + modele vechi doar dupa indexare reusita
- log warmup: cache=N embed=M in Xs
- 31 teste noi (cold/warm/incremental, model schimbat, concurenta, ranking
exact, echivalenta float32); suita completa 1596 passed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
28 KiB
PRD: Cache persistent de vectori embeddings in SQLite
1. Introducere
Vectorii corpusului k-NN (17.181 exemple etichetate din mapping_suggestions) traiesc doar in RAM si se recalculeaza integral la fiecare pornire a API-ului (~1-2 minute de CPU), desi textele nu s-au schimbat. Warmup-ul in fundal (deja implementat) tine pagina Mapari rapida, dar lasa o fereastra de 1-2 minute dupa restart in care sugestiile embeddings lipsesc. Acest feature persista vectorii intr-o tabela SQLite si vectorizeaza incremental doar textele noi/modificate, reducand fereastra la doar incarcarea modelului (~10-20s).
2. Obiective
Obiectiv Principal
- Elimina re-vectorizarea integrala a corpusului la fiecare restart: vectorii se calculeaza o singura data si se refolosesc.
Obiective Secundare
- Fereastra fara sugestii embeddings dupa restart scade de la ~1-2 minute la ~10-20s (doar incarcarea modelului ONNX).
- Cresterea corpusului SILVER nu mai scumpeste restarturile (cost proportional doar cu randurile NOI).
- Cache reconstruibil oricand (
DELETE FROM embedding_cache+ restart), fara alte efecte.
Metrici de Succes
- La corpus neschimbat: zero apeluri
embed()pe corpus la startup; indexarea din cache < 5s. - La N randuri noi in corpus: exact N texte vectorizate la urmatorul warmup.
- Sugestiile embeddings (sursa "similaritate") functioneaza identic inainte/dupa (acelasi cod sugerat pentru acelasi query).
3. User Stories
US-001: Tabela embedding_cache in schema
Ca sistem
Vreau o tabela dedicata pentru vectorii corpusului
Pentru ca vectorii sa supravietuiasca restartului fara sa ating schema mapping_suggestions.
Acceptance Criteria:
app/schema.sqlcontine tabelaembedding_cachecu:text_hash TEXT NOT NULL(SHA-256 al textului normalizat),model TEXT NOT NULL(numele modelului fastembed),vector BLOB NOT NULL(float32 little-endian, 384 valori),created_at TEXT NOT NULL DEFAULT (datetime('now')),PRIMARY KEY (model, text_hash).init_db()creeaza tabela idempotent (CREATE TABLE IF NOT EXISTS), pe baza de date existenta si pe una noua.python3 -m pytest -qtrece.
US-002: Serializare + acces cache (functii pure)
Ca developer Vreau functii de citire/scriere vectori in cache Pentru ca logica de (de)serializare sa fie testabila izolat.
Acceptance Criteria:
- Functii in
app/embeddings.py(sau modul nouapp/embedding_cache.py):vector_to_blob(list[float]) -> bytessiblob_to_vector(bytes) -> list[float](float32 LE,array('f')din stdlib, fara dependinta noua). load_cached_vectors(conn, model, hashes) -> dict[hash, vector]— un singur SELECT (chunk-uit la limita de parametri SQLite), intoarce doar hash-urile gasite.save_vectors(conn, model, items: list[(hash, vector)])— INSERT OR REPLACE batch, commit la final.- Round-trip exact:
blob_to_vector(vector_to_blob(v)) == pytest.approx(v)pentru vectori de 384 float. python3 -m pytest -qtrece.
US-003: ensure_embeddings_corpus foloseste cache-ul
Ca operator Vreau ca warmup-ul sa vectorizeze doar textele lipsa din cache Pentru ca restartul sa nu mai coste 1-2 minute de CPU.
Acceptance Criteria:
ensure_embeddings_corpus(conn, block=True): calculeaza hash perdenumire_normalizata, citeste vectorii existenti dinembedding_cachepentru modelul curent, apeleazaembed()DOAR pe textele fara vector in cache, salveaza vectorii noi in cache, apoi indexeaza corpusul complet (cache + noi) inEmbeddingEngine.EmbeddingEngine.index_corpusaccepta vectori precalculati (parametru nouvectorssau metodaindex_corpus_precomputed) fara sa apeleze backend-ul pentru ei; comportamentul existent (fara vectori -> embed tot) ramane pentru compatibilitate.- Semantica semnaturii corpusului ramane neschimbata: acelasi corpus SILVER -> nu se reindexeaza; corpus schimbat -> reindexare (dar embed doar pe diferenta).
- Calea de request (block=False) ramane non-blocanta, neatinsa.
- Degradare gratioasa pastrata: orice eroare de cache (tabela lipsa, blob corupt) -> fallback pe embed complet, fara exceptie propagata.
- Blob corupt (lungime gresita) e ignorat si re-vectorizat, nu crapa indexarea.
python3 -m pytest -qtrece.
US-004: Invalidare pe model + curatare orfane
Ca sistem Vreau cache-ul legat de numele modelului si curatat de intrari moarte Pentru ca schimbarea modelului sa nu serveasca vectori incompatibili, iar cache-ul sa nu creasca nelimitat.
Acceptance Criteria:
- Cheia de cache include
model(=FASTEMBED_MODEL): la schimbarea constantei, vectorii vechi NU sunt folositi (se re-vectorizeaza sub noul model, intrarile vechi raman inerte). - La finalul unei indexari reusite, intrarile
embedding_cacheale modelului curent cutext_hashcare nu mai exista in corpus sunt sterse (DELETE orfane). - Test: schimbarea modelului (mock cu nume diferit) -> zero hit-uri din cache; stergerea unui rand din corpus -> intrarea orfana dispare dupa reindexare.
python3 -m pytest -qtrece.
US-005: Teste de flux cold/warm start
Ca developer Vreau teste care fixeaza comportamentul incremental Pentru ca regresiile de performanta la startup sa fie prinse de suita.
Acceptance Criteria:
- Test cold start: cache gol + corpus de N randuri -> backend mock primeste exact N texte, cache-ul contine N intrari dupa.
- Test warm start: al doilea proces (engine nou, acelasi conn) -> backend mock primeste ZERO texte de corpus,
has_corpus()True,suggest_nearestfunctioneaza cu vectorii din cache. - Test incremental: se adauga 1 rand in
mapping_suggestions-> backend mock primeste exact 1 text la reindexare. - Testele folosesc backend mock (fara fastembed real), ruleaza in suita implicita (fara marker
live). python3 -m pytest -qtrece.
US-006: Verificare end-to-end pe instanta reala
Ca operator Vreau confirmarea pe serverul real ca restartul e ieftin Pentru ca metrica de succes sa fie masurata, nu presupusa.
Acceptance Criteria:
- Log la finalul warmup-ului: numar vectori din cache / numar vectori calculati / durata totala (ex.
embeddings: warmup ok cache=17181 embed=0 in 12.3s). - Verify in browser: dupa
./start.sh stop && ./start.sh test both --send, primul restart populeaza cache-ul; la al DOILEA restart, log-ul arataembed=0si sugestia "similaritate" apare in preview-ul regulilor text in < 30s de la pornire. - Dimensiunea bazei creste cu ~26-30MB (17k x 384 float32) — documentat in log/PRD, acceptat.
4. Cerinte Functionale
- [REQ-001] Sistemul trebuie sa persiste vectorii corpusului SILVER in tabela
embedding_cache, cheie(model, text_hash). - [REQ-002] La warmup, sistemul trebuie sa vectorizeze DOAR textele fara intrare in cache pentru modelul curent.
- [REQ-003] Cand corpusul SILVER se schimba (semnatura diferita), sistemul reindexeaza folosind cache-ul si vectorizeaza doar diferenta.
- [REQ-004] Cand modelul se schimba, cache-ul vechi nu este folosit; vectorii se recalculeaza integral sub noua cheie de model.
- [REQ-005] Orice eroare legata de cache degradeaza la comportamentul actual (embed complet), fara a bloca API-ul sau a arunca in request.
- [REQ-006] Intrarile orfane (texte disparute din corpus) se curata la reindexare.
- [REQ-007] Vectorii query (textul operatiei/pattern-ului cautat) NU se cacheaza — se calculeaza la cerere (milisecunde).
5. Non-Goals (Ce NU facem)
- NU introducem vector store extern (sqlite-vec, Qdrant, FAISS) — cosine in Python peste 17k vectori ramane suficient.
- NU cacheam embeddings pentru query-uri (texte noi cautate de utilizatori).
- NU modificam pipeline-ul de etichetare (
tools/mapare-llm/) sau formatul seeduluiapp/data/operatii-etichetate.json. - NU vectorizam GOLD (
shared_mappings) sau nomenclatorul — sursa corpusului ramane exclusivmapping_suggestions. - NU schimbam modelul de embeddings, pragul
EMB_MIN_SIMILARITATEsau precedenta GOLD > SILVER > embeddings. - NU eliminam warmup-ul din fundal — ramane, doar devine ieftin.
6. Consideratii Tehnice
Stack/Tehnologii
- SQLite existent (acelasi fisier
data/autopass.db, WAL), stdlibarray/hashlib— zero dependinte noi. - fastembed/ONNX ramane sursa vectorilor la miss.
Patterns de Urmat
- Degradare gratioasa ca in
ensure_embeddings_corpus/enrich_suggestions(except -> pass, corpus gol e acceptabil). - Schema idempotenta in
app/schema.sql+init_db()(CREATE IF NOT EXISTS), ca restul tabelelor. - Semnatura corpusului (
_corpus_signature_silver) ramane mecanismul de decizie "reindexez sau nu". - Backend injectabil in
EmbeddingEnginepentru teste (mock, fara model real).
Dependente
- Warmup-ul din fundal existent (
app/main.py::_warmup_embeddings,ensure_embeddings_corpus(block=True)). mapping_suggestionspopulat lainit_dbdin seedul comis (app/operatii_seed.py).
Riscuri Tehnice
- Scriere concurenta API/worker pe SQLite: scrierile de cache se fac in tranzactii scurte, batch, doar din thread-ul de warmup al API-ului (worker-ul NU incarca modelul — invariant existent).
- Blob corupt/lungime gresita: tratat ca miss (re-embed), nu ca eroare.
- Crestere DB ~26-30MB: acceptata; curatarea orfanelor previne cresterea nelimitata.
- Hash-ul textului trebuie calculat pe
denumire_normalizataEXACT cum intra inindex_corpus— altfel miss permanent si cache inutil (test dedicat in US-005).
7. Consideratii UI/UX
Fara schimbari de UI. Efect indirect: sugestia "similaritate" (editor mapari + preview reguli text) devine disponibila la ~10-20s dupa restart in loc de 1-2 minute.
8. Success Metrics
- Apeluri
embed()pe corpus la restart cu corpus neschimbat: 0 (logembed=0). - Durata warmup la warm start: < 30s total (dominata de incarcarea modelului), fata de ~1-2 min acum.
- Zero regresii in suita:
python3 -m pytest -qverde.
9. Open Questions
- Vrem o comanda CLI de administrare (
python3 -m tools.embcache rebuild|clear|stats)? DECIS (/autoplan): manual (DELETE FROM embedding_cache) suficient in v1; CLI deferat la TODOS.md. - Purjarea orfanelor sterge si intrarile modelelor VECHI? DECIS (/autoplan): DA, la aceeasi trecere — simplu si sigur.
10. Amendamente /autoplan (2026-07-06)
Acceptate in scope (auto-decizii, vezi Decision Audit Trail):
- A1 Codul de cache traieste in modul NOU
app/embedding_cache.py(functiile cuconn);app/embeddings.pyramane fara acces DB.EmbeddingEngine.index_corpusprimeste parametru nouvectors: list[list[float]] | None, aliniat pozitional cuitems;None= comportamentul actual (embed tot). - A2
save_vectorsscrie in tranzactii chunk-uite de 500 randuri/commit — AMENDEAZA AC-ul US-002 "commit la final". Motiv: un commit unic de ~26MB tine write-lock in timp ce worker-ul faceBEGIN IMMEDIATEpesubmissions. - A3 Purjarea orfanelor: diff in Python (set hash-uri corpus vs set hash-uri cache), apoi
DELETEpe chunk-uri de 500 pe PK. Include intrarile modelelor vechi (model != curent). Motiv: tranzactii scurte; (limita de parametri SQLite e 32766 pe 3.32+, nu 999 — chunking-ul e pentru durata lock-ului). - A4 Orice eroare a cailor de cache (SELECT/INSERT/blob corupt) se logheaza cu
log.warningINAINTE de fallback-ul pe embed complet. Degradarea existenta (except -> passpe indexare) ramane neschimbata — scoping strict pe codul nou. - A5 Metrica "acelasi cod sugerat" se verifica cu toleranta float32 (round-trip float64->float32 poate muta scoruri la a 7-a zecimala): test de echivalenta a RANKING-ULUI, nu egalitate stricta de scor.
- A6 Teste suplimentare la US-005: (a) chunking >500 randuri (scriere multi-tranzactie completa), (b) idempotenta la cache partial (populare intrerupta -> urmatorul warmup completeaza, fara duplicate,
INSERT OR REPLACE). - A7 Nota bounded acceptata: purjarea ruleaza doar la finalul unei indexari reusite; cu embeddings dezactivat sau warmup esuat permanent, cache-ul mort ramane pana la reset manual (
DELETE FROM embedding_cache).
Adaugate de review-ul Eng (subagent independent, toate verificate in cod):
- A8
index_corpus(items, signature, vectors)VALIDEAZAlen(vectors) == len(items)si absentaNone-urilor; mismatch =>log.warning+ fallback pe embed complet. Test obligatoriu de ranking EXACT la warm start: >=2 query-uri distincte care aserteaza CODUL vecinului cel mai apropiat (nu doar non-empty) — singurul test care prinde o permutare de aliniere (misalignment = coduri gresite sugerate, silentios). - A9 Secventa hash->load->embed->save->purge->index e protejata de un lock de modul (
threading.Lock, pattern_engine_lockexistent). CORECTEAZA riscul din sectiunea 6: "scrierile doar din thread-ul de warmup" e FALS — calea de request (block=False) trece de gate-ulis_loaded()(mapping.py:675) dupa incarcarea modelului si ar scrie/purja concurent cu warmup-ul (purjare falsa de randuri noi). - A10 Conexiunile sunt autocommit (
isolation_level=None, db.py:16):save_vectors/purge_staleemit EXPLICITBEGIN/COMMITper chunk —conn.commit()singur e no-op si ar produce silentios o tranzactie per rand (17k commit-uri), anuland exact motivul A2. - A11 Numele modelului = PARAMETRU explicit pe tot lantul (inclusiv
ensure_embeddings_corpus); nu se hardcodeazaFASTEMBED_MODELin adancime — altfel testele cu mock scriu sub cheia modelului real si US-004 nu are unde injecta numele. - A12 Vectorii din cache raman
array('f')end-to-end (fara conversie lalist[float]) — RAM ~8x mai mic; prag documentat: designul curent (cosine pur Python) e validat pana la ~50k randuri de corpus; peste, vezi TODOS (numpy). - A13 Teste suplimentare obligatorii: (a) purjarea NU ruleaza cand indexarea a esuat (altfel un esec golește cache-ul permanent); (b)
len(vectors) != len(items)=> fallback gratios; (c) save partial esuat => indexarea continua din vectorii din RAM; (d) TOATE testele noi seteaza explicitAUTOPASS_EMBEDDINGS_ENABLED=1— gate-ul din mapping.py:671 face altfel testele vacuos-verzi. - A14 Validare
len(vector) == 384si la SCRIERE (nu doar la citire) — un backend defect nu umple DB-ul cu blob-uri arbitrare. - A15 Orchestrarea hash->load->embed->save->purge se extrage intr-o functie in
embedding_cache.pycare primesteembed_fn—ensure_embeddings_corpusramane citibila (~30 linii, nu ~80 cu 6 responsabilitati).
Diagrama arhitectura
API startup (lifespan) request path (neatins)
| |
_warmup_embeddings (thread) enrich_suggestions
| |
ensure_embeddings_corpus(conn, block=True) suggest_nearest (query embed la cerere)
|
|-- SELECT mapping_suggestions ----------- corpus SILVER (sursa)
|-- _corpus_signature_silver ------------- decizia reindexarii (NESCHIMBATA)
|-- [NOU] embedding_cache.load_cached_vectors(conn, model, hashes)
| (SELECT chunk-uit; blob corupt => miss)
|-- embed() DOAR pe miss-uri (backend fastembed/mock)
|-- [NOU] embedding_cache.save_vectors(conn, model, items_noi)
| (INSERT OR REPLACE, commit la 500)
|-- [NOU] purge_stale(conn, model, hash-uri_corpus)
| (orfane + modele vechi, DELETE chunk-uit)
`-- EmbeddingEngine.index_corpus(items, signature, vectors=toate)
SQLite (WAL, busy_timeout 15s): embedding_cache (model, text_hash) PK
Worker: NU atinge embedding_cache (nu incarca modelul — invariant existent)
Data flow cu shadow paths
corpus rows ─▶ filtrare denumire goala ─▶ hash sha256(text) ─▶ cache lookup ─▶ embed(miss) ─▶ save ─▶ index
│ │ │ │ │ │
[gol? => return] [toate goale? => [hash pe lista [tabela lipsa/ [backend [OperationalError
cache neatins] corpus gol, no-op] FILTRATA, nu pe DB locked => esueaza => la commit =>
rows brute!] warning + warning + warning + fallback;
embed complet] corpus gol] cache partial OK]
Error & Rescue Registry
| Codepath | Ce poate esua | Exceptie | Rescued? | Actiune | User vede |
|---|---|---|---|---|---|
| load_cached_vectors | DB locked/busy | sqlite3.OperationalError | DA | log.warning + fallback embed complet | nimic (warmup mai lung) |
| load_cached_vectors | blob lungime gresita | (validare explicita) | DA | tratat ca miss, re-embed, log count | nimic |
| save_vectors | lock la commit chunk | sqlite3.OperationalError | DA | log.warning; cache partial ramane valid (idempotent) | nimic |
| purge_stale | lock la DELETE | sqlite3.OperationalError | DA | log.warning; orfanele raman pana la urmatoarea trecere | nimic |
| hash drift (normalizare schimbata) | miss permanent | — (nu e exceptie) | DA (observabil) | log warmup arata embed=N cand se astepta 0 (US-006) | nimic |
| ensure_embeddings_corpus | orice alta exceptie | Exception (catch-all EXISTENT) | DA | pass (degradare documentata REQ-005) | sugestii embeddings lipsesc |
Failure Modes Registry
| Codepath | Failure mode | Rescued? | Test? | User vede? | Logged? |
|---|---|---|---|---|---|
| cold start | cache gol | DA (embed tot) | US-005 | fereastra 1-2 min (o data) | DA (US-006) |
| warm start | cache complet | — | US-005 | fereastra ~10-20s | DA |
| incremental | N texte noi | DA | US-005 | — | DA |
| blob corupt | re-embed silentios | DA | US-003 AC | nimic | DA (A4) |
| populare intrerupta | cache partial | DA (idempotent) | A6b | nimic | DA |
| model schimbat | zero hit, re-embed sub cheie noua | DA | US-004 | fereastra 1-2 min (o data) | DA |
| warmup esuat permanent | cache mort ne-purjat | partial (A7, manual) | — | nimic (bounded, ~30MB) | DA |
Zero CRITICAL GAP (niciun rand cu Rescued=N + Test=N + Silent).
NOT in scope (deferate, cu motiv)
- CLI
tools/embcache— manual suficient in v1 (Open Q1). - Precompute vectori la build (artefact Docker) — ar face si primul start ieftin; separat.
- Graceful reload / zero-downtime — ar elimina complet fereastra post-restart; infra, nu app.
- Optimizare latenta per-query
suggest_nearest(numpy) — de masurat intai; PRD-ul declara cosine Python suficient (non-goal). - Cache query-uri (REQ-007), vector store extern, panou admin stats — non-goals/skip.
What already exists (reuse)
_corpus_signature_silver(mapping.py:635) — decizia reindexarii, pastrata identic.ensure_embeddings_corpus(mapping.py:649) — singurul punct de interceptie pe flux.EmbeddingEngine(backend=...)(embeddings.py:84) — mock-ul de test existent acopera US-005.- Schema idempotenta
schema.sql+init_db()— pattern-ul tabelei noi. PRAGMA busy_timeout=15000+ WAL (db.py:18-20) — deja setate, sustin scrierile concurente.
Dream state delta
Planul duce sistemul de la "restart plateste intreg corpusul" la "restart plateste doar delta"; cheia per-text face re-etichetarile (cod/is_nul) GRATUITE la restart — exact traiectoria 12 luni (corpus SILVER in crestere organica). Ramane in afara idealului: fereastra de incarcare a modelului (~10-20s) si costul per-query al cosine-ului pur Python (ambele in TODOS).
Diagrama de acoperire teste (Eng S3)
CODE PATHS (toate NOI — teste planificate, nu existente) ACOPERIT DE
[+] app/embedding_cache.py
├── vector_to_blob/blob_to_vector round-trip US-002 AC (+A5 toleranta)
├── blob corupt (lungime gresita) => miss US-003 AC
├── len(vector)!=384 la SCRIERE => respins A14
├── save_vectors chunk 500 + BEGIN/COMMIT explicit A6a + A10
├── save partial esuat => indexare continua din RAM A13c
└── purge orfane + modele vechi; NU ruleaza la index esuat US-004 + A13a
[+] app/embeddings.py :: index_corpus(vectors=)
├── len(vectors)!=len(items) => fallback embed complet A8/A13b
└── ranking EXACT la warm start (>=2 query-uri, cod asertat) A8
[+] app/mapping.py :: ensure_embeddings_corpus
├── cold start: exact N texte la backend mock US-005
├── warm start: ZERO texte, has_corpus True, suggest ok US-005
├── incremental: +1 rand => exact 1 embed US-005
├── model schimbat => zero hit-uri cache US-004
├── hash pe lista FILTRATA (denumire goala exclusa) risc §6, test dedicat
└── concurenta warmup/request sub lock A9 (test cu 2 threaduri)
TOATE testele: AUTOPASS_EMBEDDINGS_ENABLED=1 explicit A13d (anti-vacuos)
COVERAGE PLANIFICAT: 15/15 cai identificate au test specificat | GAPS ramase: 0
E2E: verify pe instanta reala (US-006) — operational, in afara suitei
Paralelizare worktree (Eng)
Implementare secventiala, fara oportunitate de paralelizare: lantul schema.sql → embedding_cache.py → embeddings.py (vectors=) → mapping.py (wiring) → teste e strict dependent, toate in acelasi modul de suggestii. Un singur lane.
Implementation Tasks
Sintetizate din finding-urile review-ului. P1 blocheaza ship-ul.
- T1 (P1, human: ~3h / CC: ~20min) — embedding_cache — Modul nou: serializare array('f') end-to-end, load/save chunk 500 cu BEGIN/COMMIT explicit, validare dim la scriere/citire, purge diff-Python (orfane + modele vechi), log.warning pe erori — Surfaced by: US-001/002/004 + A2/A3/A4/A10/A12/A14 — Files: app/schema.sql, app/embedding_cache.py — Verify: pytest teste noi serializare/chunk/purge
- T2 (P1, human: ~2h / CC: ~15min) — embeddings —
index_corpus(vectors=)cu validare aliniere + fallback; model ca parametru pe lant — Surfaced by: A8/A11 — Files: app/embeddings.py — Verify: test ranking exact + test mismatch - T3 (P1, human: ~2h / CC: ~15min) — mapping — Wiring
ensure_embeddings_corpus: orchestrare extrasa cu embed_fn (A15), lock de modul (A9), hash pe lista filtrata — Surfaced by: US-003 + A9/A15 — Files: app/mapping.py, app/embedding_cache.py — Verify: teste cold/warm/incremental + test concurenta - T4 (P1, human: ~2h / CC: ~15min) — teste — Suita completa din diagrama (15 cai), toate cu AUTOPASS_EMBEDDINGS_ENABLED=1 — Surfaced by: US-005 + A6/A13 — Files: tests/ — Verify: python3 -m pytest -q
- T5 (P2, human: ~30min / CC: ~5min) — observabilitate — Log warmup
cache=N embed=M in Xs— Surfaced by: US-006 — Files: app/mapping.py sau app/main.py — Verify: manual pe ./start.sh, doua restarturi - T6 (P2, human: ~15min / CC: ~2min) — teste — Test echivalenta ranking cu toleranta float32 — Surfaced by: A5 — Files: tests/ — Verify: pytest
Decision Audit Trail
| # | Phase | Decision | Classification | Principle | Rationale | Rejected |
|---|---|---|---|---|---|---|
| 1 | CEO 0C-bis | Design per-text (model, text_hash), nu blob monolit pe semnatura | Mechanical | P1 | Doar per-text da embed=0 la relabel (workflow activ: re-etichetare Haiku); semnatura include cod/is_nul, textul nu se schimba | blob monolit (re-embed tot la orice relabel) |
| 2 | CEO 0E | Modul nou app/embedding_cache.py; embeddings.py ramane fara DB | Mechanical | P5 | Separare existenta curata (engine pur / mapping face DB) | functii in embeddings.py |
| 3 | CEO 0D | Open Q2: purjare include modelele vechi | Mechanical | P2/P3 | ~5 linii, previne crestere moarta; propunerea PRD | lasare inerta |
| 4 | CEO 0D | CLI embcache deferat la TODOS | Mechanical | P3/P6 | PRD propune manual in v1 | build acum |
| 5 | CEO S2 | log.warning pe erorile cailor de cache (A4) | Mechanical | P1 (zero silent failures) | fallback-ul ramane, dar devine vizibil | pass mut |
| 6 | CEO S6/S5 | Metrica cu toleranta float32 + test echivalenta ranking (A5) | Mechanical | P1 | egalitate stricta ar esua legitim pe round-trip float32 | egalitate stricta |
| 7 | CEO S7/S1 | Commit chunk-uit 500 (A2) + purjare diff-Python chunk-uita (A3) | Mechanical | P5 | tranzactii scurte vs worker BEGIN IMMEDIATE | commit unic ~26MB |
| 8 | CEO S6 | Teste chunking + idempotenta cache partial (A6) | Mechanical | P1 | fixeaza comportamentul multi-tranzactie | doar testele US-005 |
| 9 | CEO 0D | Metrici Prometheus warmup | TASTE | — | log-ul US-006 acopera nevoia; counterul e nice-to-have | — (la gate) |
| 10 | CEO OV | Sidecar DB separat (voce externa) vs acelasi autopass.db | TASTE | — | user a confirmat P3 la D1; vocea externa ridica bloat backup + no-shrink fara VACUUM | — (la gate) |
| 11 | CEO 0A | Landscape check din cunostinte in-distributie (fara WebSearch) | Mechanical | P3 | pattern Layer 1 arhicunoscut (cache pe (model, hash(text))) | cautare web |
| 12 | CEO S4 | Dublu-index teoretic warmup/request: no action | Mechanical | P6 | SUPERSEDED de #14 — evaluarea era corecta doar pentru planul FARA cache; cu scrieri+DELETE, riscul devine real | lock nou |
| 13 | Eng S1 | A8: validare aliniere vectors/items + test ranking exact | Mechanical | P1 | misalignment = coduri gresite silentios; zip nu detecteaza nimic (embeddings.py:158) | liste paralele nevalidate |
| 14 | Eng S1 | A9: lock de modul pe secventa de cache (supersedes #12) | Mechanical | P1 | calea de request scrie si ea dupa is_loaded() (mapping.py:675); purjare concurenta = stergere falsa de randuri noi | write-uri doar pe block=True (re-embed repetat pe request path) |
| 15 | Eng S2 | A10: BEGIN/COMMIT explicit per chunk (autocommit db.py:16) | Mechanical | P5 | conn.commit() e no-op in autocommit; chunking-ul A2 nu s-ar intampla de fapt | commit() naiv |
| 16 | Eng S2 | A11: model ca parametru explicit pe tot lantul | Mechanical | P5 | testabilitate US-004; mock-urile nu trebuie sa scrie sub cheia modelului real | FASTEMBED_MODEL hardcodat |
| 17 | Eng S4 | A12: array('f') end-to-end + prag documentat ~50k | Mechanical | P1 | RAM ~8x mai mic gratis; blob_to_vector trece oricum prin array('f') | list[float] boxed (~2GB la 170k) |
| 18 | Eng S3 | A13: 4 teste suplimentare (purge-dupa-esec, mismatch, save partial, env flag explicit) | Mechanical | P1 | teste vacuos-verzi cu embeddings dezactivat = mai rau decat lipsa lor | doar US-005+A6 |
| 19 | Eng S3 | A14: validare dimensiune vector la scriere | Mechanical | P1 | o linie; previne umplerea DB cu blob-uri arbitrare | validare doar la citire |
| 20 | Eng S2 | A15: orchestrare extrasa cu embed_fn injectat | Mechanical | P5 | ensure_embeddings_corpus ramane citibila | functie de 80 linii cu 6 responsabilitati |
GSTACK REVIEW REPORT
| Review | Trigger | Why | Runs | Status | Findings |
|---|---|---|---|---|---|
| CEO Review | /plan-ceo-review |
Scope & strategy | 1 | issues_open (via /autoplan) | 6 propuneri, 2 acceptate, 2 deferate, 2 taste la gate |
| Codex Review | /codex review |
Independent 2nd opinion | 1 | issues_found (subagent-only) | Codex indisponibil (usage limit); voce externa = subagent Claude, 6 finding-uri CEO + 8 Eng |
| Eng Review | /plan-eng-review |
Architecture & tests (required) | 1 | clean (via /autoplan) | 8 issues, 0 critical gaps — toate absorbite ca A8-A15 |
| Design Review | /plan-design-review |
UI/UX gaps | 0 | SKIPPED | fara scope UI (grep 0 potriviri) |
| DX Review | /plan-devex-review |
Developer experience gaps | 0 | SKIPPED | fara suprafata developer-facing noua |
CROSS-MODEL: indisponibil — Codex pe usage limit pana la 18 iul; toate vocile externe au rulat ca subagenti Claude independenti (context proaspat, fara istoricul review-ului).
VERDICT: CEO + ENG CLEARED — APPROVED la gate-ul /autoplan (2026-07-06): taste #9 = SKIP metrici Prometheus (log-ul US-006 suficient); taste #10 = cache ramane in autopass.db (sidecar respins; caveat VACUUM documentat). Ready to implement.
NO UNRESOLVED DECISIONS