Commit Graph

259 Commits

Author SHA1 Message Date
Claude Agent
5342875aa2 feat(maria): triaj — raspuns pentru utilizatori, escaladare automata la programatori
Pe captura cu ORA-06550, avand in context chiar intrarea din dictionar care spune
"nu se rezolva din aplicatie", modelul i-a raspuns unui contabil sa verifice
schema de date si sa foloseasca "procedura alternativa MI_pack_parteneri_old" —
inventata. Si nu escalada, fiindca escaladarea se declansa doar cand NU exista
acoperire in documente, iar aici exista.

rag/triaj.py, trei decizii inaintea modelului:

- **Mesaj prea vag.** "Am o eroare" -> Maria cere operatiunea, ecranul si textul
  erorii, in loc sa ghiceasca. Regula e ingusta: text scurt, fara captura, fara cod.
- **Erori marcate "Intotdeauna"/"Imediat" in dictionar** (9 din 29) -> raspunsul se
  compune din campurile dictionarului, fara model, si pleaca automat la suport cu
  referinta si captura. Textul de acolo e scris pentru clienti; parafrazarea lui nu
  adauga nimic si poate strica tot.
- **Urgenta se intreaba, nu se deduce.** Raspunsul ("ma blocheaza" / "pot continua")
  se ataseaza escaladarii deschise, nu deschide alta referinta. Fereastra 30 min.

In plus: SYSTEM_PROMPT interzice explicit vocabularul tehnic (procedura, schema,
tabela, PL/SQL, compilare, sesiune) si propunerile de verificat baza de date; cele
mai tehnice cinci intrari din dictionar rescrise in limbaj de client.

Teste: 85 pass. Calibrare 21/21. Reindexare: 5 embeddings noi, 164 refolosite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-09-01 05:26:33 +00:00
Claude Agent
b7d75f05ee fix(maria): codul de eroare decide acoperirea, nu cosinusul
O captura cu ORA-06550 / PLS-00906 primea raspunsul despre ORA-12541 (TNS no
listener) si nu pleca la suport. Cauza: doua erori Oracle diferite se scriu
aproape la fel ("[Oracle][ODBC][Ora]ORA-..."), asa ca chunk-ul gresit a iesit la
cosinus 0,736 — peste RANK_STRONG_COSINE, care scurtcircuita orice alta dovada.
Chunk-ul corect din dictionar exista, dar statea pe pozitia 4, in afara TOP_K:
OCR-ul unei ferestre intregi aduce zeci de tokeni de zgomot, iar BM25 ineca tocmai
codul.

- `rank.codes()` scoate codurile de oriunde din text, nu doar ca token intreg —
  OCR-ul le lipeste de ce e langa ("OraJORA-06550").
- `rank.by_codes()` e un al treilea clasament in fuziune: chunk-ul care poarta
  codul cerut urca peste cele care doar seamana. Acum iese primul.
- `rank.assess()`: cand intrebarea are coduri, decid doar ele. Niciun cod gasit =
  escaladare la suport, oricat de mare ar fi cosinusul.
- `consumer.search()` judeca acoperirea pe cosinusul chunk-urilor date chiar
  modelului, nu pe cel mai bun din tot indexul.

Calibrare 21/21 (cele 19 cazuri + captura reala + ORA-00600, cod care nu e in
documente si trebuie sa plece la suport). Teste: 79 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 22:25:45 +00:00
Claude Agent
44763869ce feat(maria): modelul de embedding in cheia de cache si listarea grupurilor WhatsApp
Cheia de refolosire a embeddings-urilor era doar textul chunk-ului. La schimbarea
lui EMBED_MODEL indexul ar fi ramas un amestec de vectori din doua modele, iar
cautarea ar fi dat rezultate aiurea fara nici un mesaj de eroare. Acum fiecare
intrare poarta modelul cu care a fost calculata si se refolosesc doar cele cu
modelul curent; intrarile vechi, fara camp, se recalculeaza o singura data.
Indexul de pe container a fost stampilat manual cu `nomic-embed-text` (singurul
folosit pana acum), deci nu s-au recalculat cele 169 de chunk-uri.

`GET /groups` listeaza grupurile contului cu JID, nume si numar de participanti.
JID-ul unui grup ("120363...@g.us") nu se vede nicaieri in WhatsApp, iar el e
singurul mod de a scrie SUPPORT_JID pentru un grup de suport.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 22:20:08 +00:00
Claude Agent
52d1e9f205 docs: context handover — de ce reindexarea refacea toate embeddings-urile
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 22:15:55 +00:00
Claude Agent
c514818ebe feat(maria): depozit local, in afara oglinzii Drive
Dictionarul de erori Oracle pus in `documents/` ar fi disparut in 10 minute:
`rclone sync` sterge de acolo tot ce nu exista in Drive. Aceeasi problema o avea
si butonul de adaugare document din dashboard — documentul traia pana la
urmatorul tur de sincronizare, ceea ce README-ul mentiona ca pe o ciudatenie, nu
ca pe un bug.

Acum sunt doua directoare cu un singur spatiu de nume: `documents/` (oglinda
Drive) si `documents-local/` (ce nu vine din Drive). La acelasi nume castiga
Drive-ul — e sursa comuna a echipei, iar copia locala poate fi o versiune veche
uitata acolo. Dashboard-ul scrie in cel local si marcheaza documentele „local".

Recalibrat cu dictionarul indexat (169 chunk-uri): 19/19, cele patru intrebari
Oracle noi ies la 0,73-0,75. Reindexarea a durat cat cele 29 de chunk-uri noi,
nu cat toate 169 — refolosirea vectorilor isi face treaba (log: "29 embeddings
noi, 140 refolosite").

Verificat pe canalul viu: o escaladare cu captura chiar pleaca pe WhatsApp,
imagine + rezumat + referinta, si apare in dashboard.

73 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 22:13:09 +00:00
Claude Agent
ca6b53e0ca feat(maria): rank hibrid, escaladare la suport si dictionar de erori Oracle
Trei probleme legate, gasite testand cu capturi reale.

1. RASPUNSURI GENERICE. Cand raspunsul nu era in documente, modelul primea
   oricum top-3 chunk-uri si compunea ceva plauzibil — utilizatorul pleca cu
   impresia ca a primit ajutor. Acum, daca nu avem acoperire, modelul nu mai e
   intrebat deloc: intrebarea pleaca la suport, cu referinta (M-260831-A1B2) si
   confirmare onesta — "am trimis" doar daca notificarea chiar a plecat, altfel
   "am inregistrat, dar nu am putut trimite". Jurnalul se scrie intotdeauna, in
   ~/.maria-bridge/escalations/, si se vede in dashboard. Daca a fost o captura,
   imaginea insasi se retrimite la suport (endpoint nou /send-image, limitat la
   MEDIA_DIR).

2. ORDONARE (rank.py). Un prag pe cosinus nu putea decide "avem raspunsul?":
   masurat pe indexul viu, intrebarile bune dau 0,600-0,816 si cele straine
   0,534-0,685 — intervale care SE SUPRAPUN. Lipseau codurile: ORA-01722, D406,
   CIF sunt tokeni exacti pe care cautarea semantica ii topeste. Acum se ordoneaza
   de doua ori — embeddings si BM25 — si se fuzioneaza clasamentele (RRF), iar
   decizia de acoperire se ia pe dovezi: cosinus mare, sau cosinus de mijloc cu o
   fractiune din termenii distinctivi gasita chiar in chunk-uri. Fractiunea conteaza:
   cu un singur termen, "cum imi resetez parola de la Windows" trecea drept
   acoperita fiindca "parola" apare in documente.
   Pragurile sunt masurate, nu alese: ops/calibrate-rank.py ruleaza un set de cazuri
   pe indexul real si matura grila. 15/15 la strong=0.70 weak=0.58 ratio=0.5.

3. DICTIONAR DE ERORI ORACLE. 29 de erori uzuale, scrise pentru utilizatorul din
   fata aplicatiei: ce inseamna, ce poate incerca singur, cand sa sune. Fara nume
   de servere, IP-uri sau pasi de administrare — Maria e chatbot pentru clienti.
   Fisierul din git e SAMANTA: depozitul e oglinda Drive-ului, deci trebuie pus in
   document_store ca sa nu dispara la sincronizare.

Pe drum, doua lucruri gasite in log:
- fiecare mesaj din self-chat sosea de DOUA ori, pe @s.whatsapp.net si pe @lid,
  deci Maria raspundea de doua ori. Dedup pe key.id in punte.
- reindexarea reface toate embeddings-urile (~7 s fiecare pe CPU): adaugarea unui
  document la 170 de chunk-uri insemna 20 de minute. Acum refoloseste vectorii
  chunk-urilor nemodificate din indexul precedent, fara vreun fisier nou de stare.

Textul OCR se logheaza acum INTEGRAL, cu interogarea de cautare si sursele alese
cu scorurile lor — fara asta un raspuns gresit nu se poate explica: nu stiai daca
a citit prost captura sau a cautat prost in documente.

6 fisiere de test noi/extinse, 68 pass (de la 42).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 22:08:50 +00:00
Claude Agent
ecf5f2fa7a docs: de ce nu raspund ambele punti in chatul "Eu", si re-legarea fara QR
Corectie la ce am scris mai devreme in aceeasi zi: ambele punti chiar PRIMESC
mesajele din self-chat, dar Echo le arunca pe toate cu `fromMe && !isGroup`, iar
in self-chat tot ce scrii e fromMe. Deci raspunde doar Maria; Echo foloseste "Eu"
in celalalt sens, ca destinatie de notificari prin /send. Echilibrul e insa
accidental — scoaterea acelui `continue` aduce doua raspunsuri la fiecare mesaj.

Adaugat si cum se re-leaga o punte prin cod, nu prin QR (ambele au /pair), si de
ce nu se poate ocoli slotul de dispozitiv: doua procese cu acelasi auth/ folosesc
aceleasi chei si se dau afara reciproc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:28:15 +00:00
Claude Agent
7a1d2a2073 feat(maria): asociere prin cod de telefon si citirea capturilor cu erori
Doua lucruri cerute de utilizator, ambele pe acelasi drum: sa poti lega
puntea fara sa ai un ecran de scanat, si sa poti trimite Mariei o poza cu
eroarea in loc sa transcrii mesajul.

Asociere prin cod de 8 caractere (alternativa la QR):
- endpoint-ul /pair exista, dar nu era folosibil: fara `browser` explicit
  WhatsApp refuza codul, iar dupa introducerea lui corecta serverul cere un
  restart (515) care consuma din bugetul de reincercari si putea opri puntea.
  Acum descriptorul e Browsers.ubuntu('Chrome') si restartRequired reconecteaza
  imediat, fara sa numere.
- codul are TTL de 3 minute, iar /status il da doar cat timp e valabil —
  un cod expirat afisat in dashboard trimite omul sa tasteze degeaba.
- dashboard: camp pentru numar + buton, in acelasi card cu QR-ul.

Imagini cu erori (capturi de ecran):
- puntea descarca imaginile in ~/.maria-bridge/media/ (imageMessage sau
  document cu mimetype image/*, si prin ambalajele efemer/"vezi o data" —
  fara despachetare pareau mesaje fara continut si se aruncau tacut).
- rag/ocr.py: tesseract ron+eng. Modelul de raspuns e strict text, deci OCR
  nu e o optiune de calitate, e singura cale.
- cautarea in index merge DOAR pe liniile care arata a eroare; o fereastra
  intreaga de meniuri si totaluri dilueaza embedding-ul si scoate chunk-uri
  fara legatura. Modelul primeste captura intreaga, marcata ca text OCR.
- capturile se sterg imediat dupa citire (pot contine date de client).
- cand nu se citeste nimic si nu exista legenda, Maria cere textul erorii
  in loc sa raspunda in gol.

16 teste noi (42 in total). install.sh verifica tesseract; README documenteaza
ambele metode de asociere si drumul unei capturi.

Separat, in docs/chatboti-si-punti.md: chatul "Eu" e vazut de AMBELE punti de pe
numar. Puntea lui Echo (LXC 110) e asociata ca dispozitiv :11 al aceluiasi cont,
momentan nelegata dar pornita — daca se reasociaza, raspunde in "Eu" langa Maria.
Notat si ca serviciile lui Echo sunt unitati de UTILIZATOR: `systemctl is-active`
ca root raspunde "inactive" desi botul ruleaza.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:22:42 +00:00
Claude Agent
f5c8df7adf fix(docs): LXC 301 e template Proxmox, nu container oprit — riscul de IP nu exista
Documentatia de ieri descria LXC 301 ca un container oprit cu `onboot: 1` care
ar fura 10.0.20.37 de la VM 109 la reboot-ul lui pveelite. Configul are insa
`template: 1`: un template nu poate fi pornit, iar `onboot` e ignorat pentru el.
Riscul reapare doar daca e convertit inapoi in container.

Verificat si ca `basevol-301-disk-0@__base__` nu are niciun clon, ceea ce
infirma cealalta grija din pagina — se poate sterge in siguranta (~916 MB).

Recomandarea `pct set 301 -onboot 0` din handover e marcata infirmata, nu
stearsa, ca sa nu reapara intr-o sesiune viitoare.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGMEagPnRavdLhWFqJHdja
2026-08-31 21:15:31 +00:00
Claude Agent
b4472511c9 docs: context handover pentru sesiunea 2026-08-31
Nu repeta infrastructura — e deja documentata in repo — ci doar ce s-a schimbat
azi si ce a ramas: starea serviciilor la finalul sesiunii, verificarea reindexarii
care era in curs, cele patru fire terminate (Gitea/repo-uri, memorie comuna +
flux plan-Opus/exec-Sonnet, atasamente Discord, mutarea Mariei pe LXC 171), si
ce a ramas de decis (onboot pe LXC 301, auditul sistematic, modelul Mariei).

Include si capcanele invatate pe pielea noastra azi, ca sa nu se repete: verifica
pe ce container esti inainte de a concluziona ceva, nu reporni prototipul de pe
104, rclone sync sterge la destinatie, si nu taia iesirea comenzilor de diagnostic
cu `head` — exact asta a produs concluzia gresita "nu asculta nimic pe 8091".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:08:34 +00:00
Claude Agent
824b215cf0 docs: LXC 301 — singurul guest nedocumentat, si conflictul de IP cu VM 109
LXC 301 (docker-portainer-template, pveelite, oprit) e predecesorul lui VM 109:
vechiul DR Oracle 19c, inlocuit de VM-ul Windows. Nu era documentat nicaieri.

Important: si-a pastrat IP-ul static 10.0.20.37 — acelasi pe care VM 109 l-a
mostenit de la el ("IP: 10.0.20.37 (same as current LXC)", din planul de
implementare DR) — si are onboot=1. La un reboot al lui pveelite porneste singur
si ocupa adresa. VM 109 are onboot=0, deci nimeni nu observa nimic pana la
urmatorul test DR, cand VM 109 gaseste adresa luata.

Recomandarea (nefacuta, e decizia utilizatorului): `pct set 301 -onboot 0`.
Stergerea completa cere verificat intai ca basevol-301-disk-0 nu e sablonul din
care s-au clonat alte containere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:06:57 +00:00
Claude Agent
3c8a29c2e9 fix(maria): recunoaste JID-urile LID, altfel toate mesajele erau aruncate tacut
Dupa asocierea de azi, puntea nu raspundea la niciun mesaj desi `connected: true`
si consumer-ul rula. WhatsApp livreaza self-chat-ul pe conturile noi cu un JID de
tip LID (`51947713372214@lid`), nu cu `<numar>@s.whatsapp.net`, iar filtrul
`TEST_MODE_SELF_CHAT_ONLY` compara doar cu al doilea — deci arunca tot.

- `ownLid` calculat din `sock.user.lid` (cu rezerva pe `creds.me.lid`), fara
  sufixul de dispozitiv: credentialele au "51947713372214:13@lid", dar
  remoteJid-ul mesajelor vine fara ":13".
- `isSelfChat` accepta acum ambele formate.
- Logul de conectare si `/status` arata si LID-ul.

Cauza pentru care a fost greu de gasit: mesajele respinse dispareau fara nicio
urma. Acum fiecare motiv de respingere se logheaza (nu e self-chat, fromMe in
afara self-chat, fara continut text), cu JID-urile comparate. Exact linia asta a
aratat problema in 30 de secunde:
  [whatsapp] ignorat (nu e self-chat): remoteJid=51947713372214@lid ownJid=40723197939@s.whatsapp.net

Verificat live: "token efactura expirat" -> preluat, cautat in RAG, raspuns trimis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:02:24 +00:00
Claude Agent
d0224263f3 docs(maria): rolul a trecut pe LXC 171 — prototipul de pe 104 e oprit
LXC 171 a fost asociat la WhatsApp (+40723197939, dispozitiv nou al aceluiasi
cont), iar pe 104 `maria-whatsapp-bridge` si `maria-whatsapp-consumer` au fost
oprite si dezactivate ca sa nu raspunda amandoua la acelasi mesaj.
`llama-qwen35.service` ramane pornit acolo: el e LLM-ul de raspuns, folosit acum
de 171 prin retea.

docs/chatboti-si-punti.md a fost scris cu cateva ore inainte de mutare si spunea
ca instanta vie e cea de pe 104 — actualizat peste tot (tabel, sectiuni, porturi,
comenzi de verificare), plus un avertisment sa nu se reporneasca prototipul ca
"reparatie": ar raspunde in paralel, pe acelasi numar, cu un index de 24 de
chunk-uri.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 20:57:50 +00:00
Claude Agent
ae8acfbc8b docs: care chatbot e care — trei "Maria", doi boti de Discord, doua punti WhatsApp
Pe 2026-08-31 s-a pierdut aproape o ora de diagnostic fiindca Maria raspundea pe
WhatsApp, dar verificarile se faceau pe containerul gresit: pe LXC 171 totul parea
rupt (niciun LLM pe 8091, WhatsApp neasociat, log fara activitate) in timp ce
raspunsurile veneau de pe LXC 104. Nicio pagina nu spunea ca exista doua instalari.

docs/chatboti-si-punti.md, verificat live: tabel scurt "cine e cine", cate o
sectiune per instanta (Maria pe Flowise, Maria WhatsApp de pe 104 care e cea vie,
Maria din git de pe 171 care nu e asociata, Echo pe 110, puntea Discord), tabel de
porturi cu notarea ca 8099 si 11434 exista pe ambele containere cu continut
diferit, comenzi de verificat cu cine vorbesti, si capcanele (prototipul de pe 104
nu e in git, depozitul e oglinda a Drive-ului, reindexarea dureaza ~21 min,
granita Mariei fata de infrastructura).

Faptul care lega totul: WhatsApp-ul lui Echo (110) si al Mariei (104) sunt legate
la acelasi numar, +40723197939, ca dispozitive diferite ale aceluiasi cont.

Documentul e legat din CLAUDE.md si dintr-un citat pus in capul celor cinci
README-uri implicate, ca sa fie gasit de cine aterizeaza direct acolo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 19:30:06 +00:00
Claude Agent
16ac5a289f docs(maria): a doua instalare, pe LXC 104, si de ce diagnosticul a dus in eroare
Prototipul a ramas sa ruleze pe LXC 104 (/root/maria-whatsapp-bridge/), in afara
git-ului, si aceea era Maria conectata la WhatsApp — nu cea din git, de pe 171.
Indexul ei avea 24 de chunk-uri dintr-un singur fisier, deci inventa raspunsuri
la orice iesea din subiectul roaauto.

Pe 171 totul parea rupt (nimic pe 8091, WhatsApp neasociat, rag.log fara
activitate) desi utilizatorul primea raspunsuri pe telefon. Notat in README cum
se verifica intai care instanta e conectata, ca sa nu se mai piarda timp asa.

Notat si ca LLM-ul de raspuns (llama.cpp, Qwen3.5-2B-Q4, port 8091) ruleaza pe
LXC 104 si se foloseste prin retea; pe 171 Ollama are doar embeddings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 19:17:22 +00:00
Claude Agent
3b7069e954 fix(maria): lacat intre reindexari, scriere atomica a indexului, XML invalid raportat
Descoperit la prima sincronizare reala din Drive: `maria-sync.timer` a pornit
peste rularea manuala si doua procese faceau embeddings in paralel pe acelasi
Ollama, ambele urmand sa scrie acelasi rag_index.json. Embedding-ul a incetinit
de la ~7s la ~20s din concurenta, iar ultimul care termina ar fi suprascris
munca celuilalt.

- config.exclusive(): lacat `flock` intre procese, luat la intrarea in sync.py si
  indexer.py. Nu asteapta — a doua rulare iese curat cu "o reindexare e deja in
  curs", fiindca ar reface exact acelasi lucru. Verificat pe procese reale.
- indexer scrie indexul atomic (tmp + os.replace): consumer-ul reciteste fisierul
  la 30s si putea prinde un JSON pe jumatate scris.
- build() intoarce `warnings` pentru XML-urile care nu se pot parsa, iar rularea
  din linia de comanda le scrie in stderr. Pana acum, un XML invalid se indexa
  tacut ca text simplu, cu o singura linie pierduta in log.

Context de performanta, masurat pe LXC 171 fara alta incarcare: un embedding
`nomic-embed-text` ia ~7,3s, deci o reindexare completa a celor 173 de chunk-uri
dureaza ~21 de minute — mai mult decat intervalul timer-ului. Nu e o problema
practica (amprenta reindexeaza doar la schimbare, iar lacatul opreste
suprapunerea), dar explica de ce prima rulare pare blocata.

docs/rclone-google-drive-headless.md: procedura de conectare a unui container
headless la Drive prin `rclone authorize`, cu transcriptul rularii reale de pe
Windows, capcanele (sync e distructiv pe destinatie, connection string in loc de
cale pe nume, unde stau secretele) si de ce nu contul de serviciu. Indexata in
CLAUDE.md.

6 teste noi (lacat, eliberare la exceptie, scriere atomica, avertismente).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 18:42:01 +00:00
Claude Agent
f3f5eedb79 feat(maria): ops/setup-drive.sh — configurare Drive dintr-un token, intr-un pas
Pasii 2-4 din README (creare remote, verificare acces, scriere DRIVE_REMOTE,
prima sincronizare) erau de facut de mana, cu ID-ul dosarului copiat corect de
fiecare data. Scriptul ii face pe toti dintr-un token `rclone authorize`:
valideaza ca tokenul e JSON, refuza devreme daca rclone sau env-ul lipsesc,
face backup la env inainte de a-l rescrie, si e idempotent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 18:16:07 +00:00
Claude Agent
1c7899753a feat(maria): accepta .xml in depozit, preferat peste .md, cu chunking pe probleme
Dosarul document_store din Drive are 3 surse .xml pe care depozitul le ignora
complet, fiindca store.py accepta doar .txt/.md. La d406_saft_knowledge exista
ambele formate, iar .xml e cu trei luni mai nou (2026-01-28 vs 2025-10-15) si cu
50% mai mare (64 KB vs 41 KB) — deci indexam varianta mai saraca.

- store.py devine sursa unica pentru extensii (DOC_EXTENSIONS = .txt/.md/.xml).
  Cand acelasi nume de baza exista in mai multe formate, la indexare intra unul
  singur, cel mai bogat (.xml > .md > .txt); celalalt ramane pe disc, marcat
  `shadowed_by`. Fara asta, acelasi raspuns ar aparea de doua ori in rezultate.
  `list_documents()` arata tot (dashboard), `documents_for_index()` doar
  castigatorii (indexer).
- indexer.py taie XML-ul altfel: un chunk per element de nivel 1, adica o
  problema = un chunk, cu <mesaj_eroare> si <rezolvare> impreuna. Taierea pe
  linii goale le-ar separa si cautarea ar returna eroarea fara raspuns.
  Etichetele raman prefixe lizibile ("mesaj eroare: ..."), fara paranteze
  unghiulare care doar dilueaza embedding-ul. XML invalid nu opreste indexarea:
  cade pe taierea obisnuita, cu o linie in log. Elementele peste 4000 de
  caractere se taie mai departe pe granite de cuvant — `chunk_text` imparte doar
  pe linii goale, deci un element scris ca un paragraf lung ar fi ramas intreg
  (prins de test).
- sync.py: amprenta si `rclone --include` derivate din DOC_EXTENSIONS.
- dashboard: acelasi filtru si aceeasi preferinta (copie, fiindca nu poate
  importa `store` — coliziune de nume pe `config`), plus marcajul "umbrit de X"
  in tabelul de documente si numarul de documente chiar indexate.
- README: sectiunea Drive rescrisa pe `rclone authorize` (autorizezi pe o masina
  cu browser, muti tokenul) in loc de cont de serviciu — mai putini pasi, fara
  consola Google Cloud. Documentat si ca `sync` sterge local ce nu mai e in Drive.

tests/ nou (20 de teste, fara retea si fara Ollama): preferinta de format,
vizibilitatea in dashboard, taierea XML, entitati, comentarii, XML invalid,
elemente uriase. Suita puntii Discord: 426 pass, neafectata.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 18:15:03 +00:00
Claude Agent
c42b95b7db feat(discord-bridge): imagini si fisiere text din Discord ajung la Claude
Puntea trimitea doar `content`-ul mesajului; orice atasament disparea tacut, iar
un mesaj fara text (doar poza) era respins ca "empty". Acum mesajul de utilizator
se construieste ca blocuri, in formatul pe care CLI-ul il accepta pe
`--input-format stream-json` (verificat pe CLI real: Claude descrie corect o
imagine trimisa asa).

- imagini png/jpeg/gif/webp -> blocuri `image` base64, max 4, max 3,5 MB brut
  (base64 umfla cu ~4/3, iar API-ul refuza peste ~5 MB codate)
- fisiere text (mime `text/*`, `application/json`, sau extensie cunoscuta) ->
  continutul intra in prompt, max 4, trunchiat la 100 KB
- restul (PDF, Office, arhive, svg, heic) -> doar numite, cu motivul

Detalii care conteaza:
- `image/jpg` si `image/png; charset=...` se normalizeaza; cand Discord nu
  trimite content_type cadem pe extensie
- marimea se verifica de doua ori: cea declarata (ca sa nu descarcam degeaba) si
  cea reala dupa descarcare
- nimic nu dispare tacut: ce n-a putut fi citit apare in prompt ca
  "Atasamente ignorate: ..."; o imagine stricata nu anuleaza restul mesajului
- merge si mid-tur: o poza trimisa in timpul unui tur intra pe stdin ca steering

31 de teste noi in tests/test_attachments.py (normalizare tipuri, limite,
trunchiere, erori de descarcare, integrare prin punte, steering). Suita: 426 pass.
README: sectiune "Atasamente" + limitarea veche corectata (ramane doar vocea).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 17:47:48 +00:00
Claude Agent
c5af5f2380 feat(maria): interzice explicit discutiile despre infrastructura interna
Maria raspunde clientilor ERP ROA. Depozitul ei de documente contine doar
material orientat spre client final, dar adaugam si o bariera in SYSTEM_PROMPT:
refuza intrebarile despre servere, Proxmox, containere, IP-uri, baze de date,
parole sau chei, chiar daca ceva de acest fel ajunge in context.

Regula completa (ce are voie sa intre in ~/.maria-bridge/documents/) e scrisa
in /workspace/claude-agent/CLAUDE.md, sectiunea "Maria (RAG) - GRANITA
OBLIGATORIE".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 17:38:51 +00:00
Claude Agent
4993148597 docs(maria): record local Ollama install and rclone setup progress
Ollama (+ nomic-embed-text) is now installed locally on LXC 171 for
embeddings — previously OLLAMA_URL was an unverified placeholder pointing
at nothing. rclone is installed too, but DRIVE_REMOTE still needs a Google
service account the user must create in GCP Console; noted the
document_store folder ID found via Drive search to save that step later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-31 17:38:51 +00:00
Claude Agent
18977cd029 Consolidate Maria control into the shared Discord-bridge dashboard
Drop the standalone maria-dashboard.service — one common panel now
controls both bridges, at the operator's request. discord-bridge's
dashboard/api.py gains a Maria section (fixed-unit start/stop/restart
for maria-whatsapp/maria-rag, WhatsApp status+QR proxy, document CRUD,
reindex, Drive sync trigger, log tail) reached via subprocess/HTTP —
no cross-module imports, to avoid colliding with discord-bridge's own
`config` module name. index.html gets a matching "Maria — WhatsApp +
RAG" section. maria-whatsapp-bridge loses its dashboard/ folder and
DASHBOARD_* env keys; install.sh/README point at the shared panel.

Co-Authored-By: Claude Agent <noreply@anthropic.com>
2026-08-31 17:38:51 +00:00
Claude Agent
cfe01135d1 Fix maria-dashboard.service symlink path in install.sh
The unit file lives in ops/ (alongside the other three units), not in
dashboard/ — install.sh linked the wrong path, so `enable --now` for the
dashboard failed silently. Caught by running the installer for real.

Co-Authored-By: Claude Agent <noreply@anthropic.com>
2026-08-31 17:38:51 +00:00
Claude Agent
dd5553e327 Add Maria WhatsApp+RAG bridge as a service (LXC 171)
Move the /tmp prototype (Baileys bridge + RAG consumer) into git as a
proper sibling project to discord-bridge/: own systemd --user units
(whatsapp bridge, rag consumer, dashboard, periodic Drive sync timer),
a filesystem document store with a stdlib control dashboard (start/
stop/restart, document CRUD, reindex, Google Drive sync via rclone),
and an idempotent ops/install.sh following the same conventions.

Co-Authored-By: Claude Agent <noreply@anthropic.com>
2026-08-31 17:38:51 +00:00
Marius
b29b9f2548 docs: unde sunt scripturile sys_* si cum se actualizeaza kitul din ele
Corpusul publicat e in D:\ROA\DATABASE\SCRIPTURI_CLAR\<an>\<luna>\sys_*.sql (36
fisiere, 2009-2026). Restul copiilor de pe statie si din repo
(ALTELE\Creare_server_scripturi, new-roa-oracle-server\) sunt snapshoturi vechi,
fara nimic din 2026 -- un find peste D:\ROA da ~61 de rezultate din cauza lor.

Documentul spune: unde e sursa de adevar si ce arata ca sursa dar nu e; conventia
de nume si cum se marcheaza aplicarea in CONTAFIN_ORACLE.VERSIUNE (si de ce nu
poti intreba tabela aia pe un server nou); interogarile care dau diferenta reala
fata de productia 10.0.20.36; unde intra fiecare tip de obiect gasit in kit;
capcanele la editare (CRLF, UpdateVersiune, grant direct vs prin rol, comparatie
pe synonym_name nu pe table_name).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J7vUBDUY5hvLwRZ45Uqzcf
2026-08-31 13:59:05 +03:00
Marius
e69fc07e46 fix(roa-setup): sinonime publice si obiecte SYS lipsa pe serverele instalate cu kitul
Pe VADECO (instalat 25.08.2026) salvarea in istoric coduri fiscale pica cu
PLS-00905: VADECO.PACK_PARTENERI invalid. Cauza: pachetul isi declara parametrii
ca ISTORIC_CODURI_FISCALE.<col>%TYPE, nume necalificat, iar sinonimul public
lipsea. Serverul avea 81 de sinonime publice catre CONTAFIN_ORACLE, productia
10.0.20.36 are 100 -- lipseau exact 20.

Gaura apare pentru ca sinonimele publice nu fac parte dintr-un export
schema-mode (impdp nu le aduce), singurul loc care le creeaza este o lista
enumerata manual, iar CONTAFIN_ORACLE.VERSIUNE vine cu DMP-ul si marcheaza
scripturile co_*/sys_* drept aplicate, deci nici ROAACTUALIZARI nu le mai
ruleaza. Din acelasi motiv lipsea si SYS.NEWSCHEMAPROGRESS, o functie din 2014.

- synonyms-public.sql: cele 20 de sinonime (81 -> 101 CREATE)
- sys-objects.sql: pas [9b/10], SYS.NEWSCHEMAPROGRESS (sys_2014_11_06_01_FIRMA)
- sys-grants.sql: SYN_NEWSCHEMAPROGRESS in [3/6]; granturi directe SELECT pe
  SYS.DBA_DATAPUMP_JOBS si SYS.AUTH_SERII in [1/6] (sys_2013_01_23_02)
- docs/refresh-scripturi-dupa-instalare.md: de ce completarea listelor e paliativ
  si ce ar trebui sa faca un pas de refresh rulat dupa instalare, nu la instalare
- sys-updates/README.md, CLAUDE.md: trimiteri catre documentul nou

Aplicat pe VADECO: PACK_PARTENERI si PACK_IMPORT_COMENZI sunt VALID pe VADECO,
DANUBE, LACERTA si SPACE. Obiectele ramase invalide sunt cele preexistente,
invalide si pe 10.0.20.36.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J7vUBDUY5hvLwRZ45Uqzcf
2026-08-31 13:57:08 +03:00
Claude Agent
a89cf201ab fix(discord-bridge): zgomot in bot.log si doua tipuri de stream necunoscute
Trei lucruri observate in logurile de productie dupa restartul precedent.

1. Ecoul propriilor mesaje umplea bot.log cu WARNING. Propriile mesaje au si
   ele `author.bot == True`, iar verificarea generica de bot venea INAINTEA
   celei pe `self_id` — deci raspunsurile botului se jurnalizau ca "bot
   strain", la fiecare mesaj. Verificarea pe `self_id` trece prima (motivul e
   acum precis), iar refuzurile de rutina — propriile mesaje si ceilalti boti —
   merg la DEBUG. Guild / canal / utilizator strain si webhook raman WARNING:
   alea chiar sunt semnal de securitate si erau inecate in zgomot.

2. `tool_progress` (heartbeat la 30s cat timp o unealta ruleaza) devine
   eveniment `ToolProgress`. Mesajul live arata acum "⏳ ruleaza de 2m30s" sub
   unealta curenta — singurul semn ca un tur lung lucreaza si nu a inghetat.

3. `rate_limit_event` devine eveniment `RateLimit` si apare in `/status` la
   randul `utilizare`. Cum nu exista plafon de cost (abonament, nu API),
   fereastra de utilizare e singura limita reala; se avertizeaza in log o data
   per schimbare de stare, nu la fiecare eveniment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-31 07:46:51 +00:00
Claude Agent
1cc1a572d9 feat(discord-bridge): aprobari valabile pe tot firul (buton "Allow (tot firul)")
Confirmarea per comanda devenea obositoare intr-o sesiune care lucreaza pe
acelasi host: `ssh pvemini ...` de zece ori la rand insemna zece butoane.

Butonul de confirmare are acum trei variante: Allow / Allow (tot firul) / Deny.
"Allow (tot firul)" memoreaza tiparul `(rule, reason)` produs de clasificator,
nu comanda: dupa o aprobare pe `ssh pvemini uptime`, orice comanda catre ACEL
host trece singura, dar `ssh 10.0.20.36` sau un `rm -rf` cer din nou
confirmare. Aprobarile stau in ~/.claude-discord/approvals/grants/<fir>.json.

Domeniul e firul Discord, nu `session_id`: acela se schimba la `--resume`, iar
aprobarile ar disparea exact cand omul se astepta sa tina.

Expirare: `/new` le sterge (sesiune noua = permisiuni noi), `/permisiuni
revoca:True` la cerere, TTL implicit 12h (CLAUDE_DISCORD_GRANT_TTL), iar
CLAUDE_DISCORD_SESSION_GRANTS=off dezactiveaza complet mecanismul.

Fail-closed peste tot, ca restul hook-ului: fara CLAUDE_DISCORD_THREAD_ID (hook
rulat in afara puntii), cu fisierul de aprobari corupt, cu un thread_id care nu
arata a id (`../`, punct la inceput, peste 128 de caractere) sau la orice
exceptie, has_grant() raspunde False si se cere confirmare in Discord.

Adaugat si `/permisiuni [revoca:True]` (listare/revocare) plus butonul
echivalent in dashboard (`decision: "allow_session"`).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-31 07:36:34 +00:00
Claude Agent
feb31eaabe fix(discord-bridge): plafonul de cost e optional (COST_CAP_USD_DAY=off)
Costul raportat de CLI e pretul echivalent la API; pe abonament nu se
factureaza, deci plafonul zilnic oprea puntea fara motiv (5.71 / 5.00 USD).

- limits.parse_cap(): `off`/`none`/`nelimitat`/`0`/gol/gunoi => fara plafon
- stopped()/record_cost() nu mai opresc si nu mai alerteaza cand e dezactivat
- implicit devine `off`; /status arata „(fara plafon)"
- dashboard: /api/status si doctor nu mai crapa pe valoare ne-numerica
- README + ops/env.example explica de ce ramane off pe abonament

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 21:02:43 +00:00
Claude Agent
b14e7bc50a feat(discord-bridge): dashboard fara token (DASHBOARD_AUTH=off)
Cerut explicit: tokenul nu se retine. Cu DASHBOARD_AUTH=off nu mai exista login,
/login.html duce inapoi la panou, iar butonul "Iesi" dispare.

Se sprijina pe doua lucruri si nu are sens fara ele: serviciul e legat de
127.0.0.1, deci din retea ajunge la el doar tailscaled; iar tailscale serve il
publica tainet only, unde accesul e deja autentificat de Tailscale.

Compensatie partiala pentru ce se pierde: fiecare start/stop/restart se scrie in
logs/dashboard.log cu identitatea din antetul Tailscale-User-Login pus de
tailscale serve (verificat: ajunge pana la noi). Antetul e DOAR pentru jurnal —
nu decide accesul, fiindca un proces local l-ar putea fabrica.

Implicitul ramane cu token: doar off/none/0/false scot login-ul, orice alta
valoare il pastreaza (are test).

Sase teste noi, 41 pe dashboard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 14:16:20 +00:00
Claude Agent
e203ee021d docs(discord-bridge): nota despre <base href> in README-ul dashboard-ului
Completeaza e0ea850: sectiunea despre montarea sub prefix descria inca vechea
solutie (URL-uri relative + 301), nu pe cea care a rezolvat de fapt problema.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 14:11:46 +00:00
Claude Agent
e0ea85005d fix(discord-bridge): dashboard-ul ramanea gol la /claude — lipsea <base href>
Simptom: pagina se incarca prin tailscale, dar niciun API nu era cerut; in
dashboard.log se vedea doar GET / si nimic altceva.

Cauza: --set-path taie prefixul, deci /claude si /claude/ ajung la server
identic, ca "/". Fara slash final, URL-urile relative se rezolvau la radacina
hostului (https://host/api/status), unde proxy-ul nu trimite nimic incoace.
Cererile nici nu ajungeau la noi, iar pagina ramanea goala fara nicio eroare.
Redirectul 301 adaugat anterior nu putea ajuta: serverul nu vede forma
originala a adresei.

Paginile se servesc acum printr-un handler propriu care pune <base href> din
DASHBOARD_PREFIX, plus Cache-Control: no-store, fiindca HTML-ul poarta de acum
configuratie si o copie veche ar trimite cererile aiurea.

Patru teste noi. Verificat in browser pe cazul reprodus (pagina servita pe
radacina, ca prin proxy): toate cererile pleaca cu /claude/ si datele se incarca.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 14:11:24 +00:00
Claude Agent
6b6f6644f9 fix(discord-bridge): dashboard-ul e responsive pe telefon
Pe un ecran de 390px pagina se intindea pe 2621px si derula orizontal. Cauza:
copiii de grid au min-width:auto, deci liniile lungi din pre.log si tabelul de
fire dilatau coloana lui .wrap, si odata cu ea tot documentul. Rezolvat cu
min-width:0 explicit; derularea ramane inauntrul containerelor cu overflow.

Sub 640px:
- tabelul de fire devine carduri stivuite (capul de tabel dispare, eticheta vine
  din data-label) — 5 coloane pe 390px insemnau derulare la fiecare rand;
- butoanele stau doua pe rand, cu tinta de atins de 44px; sub 380px, unul pe rand;
- verificarile si confirmarile se aseaza pe verticala, comenzile lungi se rup;
- antetul trece pe doua randuri, notificarile se intind pe toata latimea;
- corpul trece la 16px, ca iOS sa nu mai dea zoom la focus pe input.

Verificat la 360, 390 si 1440px: zero depasiri pe orizontala, butoanele de
confirmare egale si de 44px inaltime, desktopul neschimbat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 14:05:44 +00:00
Claude Agent
22628fb178 chore(discord-bridge): dashboard-ul se muta de pe /punte pe /claude
https://claude-agent.tailf7372d.ts.net/claude

Redenumire completa: tailscale serve, DASHBOARD_PREFIX, teste, documentatie,
ops/install.sh (implicitul pentru instalari noi). Vechiul /punte a fost scos
din serve si intoarce 404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:30:43 +00:00
Claude Agent
dd7fa28fa5 feat(discord-bridge): dashboard publicat in tailnet prin tailscale serve
https://claude-agent.tailf7372d.ts.net/punte — acelasi tipar ca /echo de pe
moltbot: procesul ramane legat de 127.0.0.1, tailscaled il proxeaza si pune HTTPS.

Montarea sub prefix a cerut doua schimbari:

- toate URL-urile din pagini sunt acum relative, fiindca --set-path TAIE prefixul
  inainte de a proxa (serverul vede /api/status, browserul cere /punte/api/status).
  DASHBOARD_PREFIX ramane necesar doar pentru redirectul de login, si e acceptat
  si intact pe intrare, ca sa mearga si curl direct pe localhost.
- adresa fara slash final (/punte) primeste 301 catre /punte/: altfel URL-urile
  relative s-ar rezolva la radacina hostului, unde proxy-ul nu trimite nimic
  incoace, si panoul ar arata gol fara nicio eroare vizibila.

ops/install.sh configureaza serve-ul daca sudo permite; altfel spune comanda.
Sase teste noi pentru montarea sub prefix (31 in total pe dashboard).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:27:59 +00:00
Claude Agent
57867672f2 docs(lxc110): stack-ul activ e echo-core, nu clawdbot
Bloc de status gasit necomis in arborele de lucru pe LXC 171 si pastrat ca atare;
verificat azi pe 10.0.20.173: unitatile active sunt echo-core, echo-taskboard si
echo-whatsapp-bridge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:18:38 +00:00
Claude Agent
7abefa2b46 feat(discord-bridge): dashboard de control si restart, dupa modelul agentului echo
Panou web pe 127.0.0.1:18790, unit systemd separat de al puntii. Server stdlib
(fara dependinte noi), tokenii de design si tiparul de endpoint-uri preluate din
/home/moltbot/echo-core/dashboard (handlers/eco.py) de pe LXC 110.

Arata: starea unitatii (uptime, PID, memoria cgroup, restarturi), firele din
state.json cu tur in zbor si cost, costul zilei fata de plafon, confirmarile
PreToolUse in asteptare (aprobabile direct din pagina), bot.log / infra.log si
opt verificari de diagnostic.

Face: start / stop / restart pe punte, cautarea si curatarea orfanilor prin
cleanup.py, repornirea propriului serviciu.

Garantii, cu teste:
- unitatea controlata e fixa in cod; un {"unit": "ssh.service"} in cerere nu
  schimba nimic, altfel panoul ar fi systemctl remote fara parola;
- stop/restart intorc 409 cu lista firelor active si cer force explicit, fiindca
  KillMode=control-group taie tururile in desfasurare;
- state.json se citeste fara lock: panoul nu are voie sa blocheze botul;
- diagnosticul pica daca reapare Bash(ssh:*) in deny (regresia de azi).

Uptime-ul se calculeaza din time.monotonic(), nu din /proc/uptime: in LXC acela
e virtualizat de lxcfs si da diferenta negativa fata de monotonic-ul systemd.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:18:38 +00:00
Claude Agent
3c769b1b53 fix(discord-bridge): scot Bash(ssh:*)/Bash(scp:*) din deny — blocau accesul la infra
Regulile deny au precedenta peste --permission-mode bypassPermissions si
opresc turul INAINTE de hook-ul PreToolUse, deci puntea nu putea ajunge la
niciun host prin ssh (simptom: agentul raporta "permisiunea a fost respinsa"
pentru ssh moltbot@10.0.20.173, desi cheia, DNS-ul si reteaua erau in regula).

Nota din README care sustinea ca deny e doar strat cosmetic era gresita:
verificarea de atunci folosise /usr/bin/ssh -V si bash -c "ssh -V", care
ocolesc potrivirea pe prefix, nu forma normala ssh host cmd.

Bariera reala ramane confirm_hook.py + wrapper-ul infra.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 13:04:49 +00:00
Claude Agent
57cd50c1e7 docs: context handover pentru puntea Discord
Starea de rulare, ce s-a verificat empiric in productie, cele 10 capcane gasite
pe parcurs (cu #1 — /cleanup care omora sesiunile de lucru — marcata explicit),
deciziile care nu se redeschid, si singurul lucru ramas: tokenul Proxmox cu ACL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 12:59:39 +00:00
Claude Agent
c8a5d418f3 fix(discord-bridge): /cleanup urmareste descendentii, dar STRICT in cgroup-ul serviciului
Doua defecte, al doilea gasit la verificarea primului.

1. Procesul `claude` al unui fir isi porneste serverele MCP (npm/sh/node pentru
   playwright), care nu se numesc `claude` si scapau cautarii. Masurat pe firul viu:
   claude 300 MB + MCP 137 MB = ~440 MB per fir, nu 300. find_orphans merge acum pe
   arbore, iar kill_orphans opreste copiii inaintea parintilor (altfel se reparenteaza
   si scapa).

2. PERICULOS: definitia initiala a orfanului ("orice `claude` absent din state.json")
   prindea sesiunile Claude interactive de pe container. Rularea seaca propunea 25 de
   procese / ~3864 MB — sesiunile de lucru din tmux, inclusiv cea din care ar fi fost
   data comanda. `/cleanup force:True` din Discord si-ar fi omorat propria sesiune.
   Apartenenta la cgroup-ul `claude-discord.service` devine conditie necesara pentru
   toate familiile, fail-closed la cgroup necitibil. Dupa fix: zero procese raportate.

   Test de regresie pe instantaneul real (doua sesiuni in tmux-spawn-*.scope, una in
   claude-discord.service): se raporteaza doar a treia. Verificat prin mutant ca testul
   musca — cu filtrul scos pica 3 teste.

3. INTERFACES.md cerea pid_start_time in ticks, dar session_store scrie secunde (si
   state.json viu contine secunde). Contractul era imprecis, nu codul: un consumator
   care compara ticks nu s-ar potrivi niciodata, iar cum acea comparatie protejeaza
   turul in desfasurare, esecul ar fi fost tacut si ar fi facut eligibil exact ce
   trebuia protejat. Documentat, cu avertismentul explicit.

Bug colateral prins de agent: raportul cu rezultate de omorare ajungea la 2289 caractere,
peste limita Discord — mesajul ar fi fost respins exact la `/cleanup force:True`. Buget
de caractere adaugat.

Suita: 322 passed cu discord.py, 319 passed + 3 skipped fara. Rulare seaca pe container: curat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 12:47:16 +00:00
Claude Agent
1deffbbe65 merge: punte Discord -> Claude Code pe LXC 171
Comanzi containerul si infrastructura dintr-un canal privat Discord, de pe telefon.
Bot subtire discord.py peste CLI-ul `claude`, cu proces persistent per fir alimentat
pe stdin (--input-format stream-json), deci mesajele trimise in timpul unui tur
functioneaza ca steering, nu deschid tur nou.

Confirmat in productie inainte de merge:
- 7 comenzi slash inregistrate pe guild, citite inapoi din API-ul Discord
- flux complet de confirmare: hook a blocat `rm -rf`, butoanele au aparut in canal,
  Allow la 14s, comanda executata
- reluarea sesiunii cu --resume peste restart de serviciu, fara orfani
- 296 teste, fara retea/Discord/API

Limitare asumata (stratul de audit respins constient): la o problema nu exista
jurnal independent care sa spuna ce s-a intamplat si pe ce host.
2026-08-30 12:25:45 +00:00
Claude Agent
5f34320e3e feat(discord-bridge): comenzi slash in loc de prefixul !
Comenzile devin application commands inregistrate pe guild (sync instantaneu,
spre deosebire de cel global care dureaza ~1h): /new [fork], /cd <cale>,
/model <sonnet|opus> cu Choice, /status, /stop, /cleanup [force], /help.

- allowlist-ul se aplica identic la interactiuni (check_ids comun, ca sa nu
  existe a doua implementare care diverge); refuz efemer, fara executie
- fiecare comanda face defer() inainte de lucru — altfel Discord marcheaza
  interactiunea esuata dupa 3s desi comanda a rulat
- sync tolerant: la esec (lipsa scope applications.commands) botul porneste
  normal si logheaza linkul de reinvitare necesar
- mesajele obisnuite raman neschimbate, inclusiv steering-ul mid-tur
- linkul de invitatie primeste scope=bot%20applications.commands; referintele
  la ! din ops/ si documentatie trecute pe /

Verificat in productie: 7 comenzi inregistrate pe guild, citite inapoi din API.
Suita: 296 passed cu discord.py, 293 passed + 3 skipped fara.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 12:23:33 +00:00
Claude Agent
c9b9e2e5da fix(discord-bridge): ~/bin in PATH-ul unitului, pentru wrapperul infra
Unitul avea PATH explicit doar pentru CLI-ul claude din nvm. Wrapperul `infra`
se leaga in ~/bin, care lipsea, deci botul nu l-ar fi gasit — stratul 4 de
securitate ar fi fost prezent pe disc si inutilizabil in practica.

Verificat: infra refuza un host din afara listei cu exit 3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 12:02:49 +00:00
Claude Agent
ddbe9de4bc fix(discord-bridge): director de lucru dedicat + log nedublat
- DEFAULT_CWD trece de la /workspace la /workspace/claude-agent, un spatiu cu git
  propriu, ca fisierele facute din Discord sa aiba istoric separat de proiectele
  reale. `!cd <cale>` ramane disponibil oriunde in /workspace, deci decizia din
  plan (fara allowlist de proiecte) nu se schimba.
- setup_logging: sub systemd unitul redirecteaza deja stdout in bot.log
  (StandardOutput=append:), iar FileHandler-ul scria fiecare linie a doua oara
  in acelasi fisier. FileHandler ramane doar la rulare manuala.

Verificat in productie: bot conectat, un tur real incheiat curat (inflight null,
cost $0.0742 contabilizat), restart fara orfani. Suita: 275 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 11:30:31 +00:00
Claude Agent
f86e9c1171 docs(discord-bridge): link de invitatie al aplicatiei in env.example
Application ID 1543576449624186880 (aplicatie dedicata, creata 2026-08-30).
Linkul e util la reinvitare, cand botul a fost scos din server sau i s-au
schimbat permisiunile. Nu e secret: Application ID e public, spre deosebire
de DISCORD_TOKEN.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 11:05:42 +00:00
Claude Agent
8c550a2cde docs(discord-bridge): corectii la partea manuala — View Channel lipsea, Add Bot depasit
- permisiunile de invitatie omiteau *View Channel*, fara de care botul nu vede
  canalul deloc, oricat de permis ar fi in allowlist
- link de invitatie gata calculat (permissions=309237763136), fiindca bifele din
  URL Generator sunt greu de nimerit pe telefon
- *Bot -> Add Bot* nu mai exista: portalul creeaza user-ul bot odata cu aplicatia
- MESSAGE CONTENT INTENT are nevoie de *Save Changes*; fara apasare setarea se pierde
- pasii de copiere ID: pe telefon e apasare lunga, nu click dreapta

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 10:55:47 +00:00
Claude Agent
d466f358ce feat(discord-bridge): punte Discord -> Claude Code pe LXC 171
Implementeaza planul claude-master-plan-discord-bridge-20260830 (15 taskuri,
3 lane-uri paralele) — un bot subtire discord.py peste CLI-ul `claude`, cu
proces persistent per fir alimentat pe stdin cu --input-format stream-json.

Nucleu: runner (proces persistent + reaper 20min + respawn --resume), stream
(parser tolerant), session_store (scriere atomica, lock per fir, detectare PID
reuse, recovery), limits (max 4 procese, timeout tur, rate per user, plafon cost
pe zi), render (un loop de editare per canal, interval adaptiv).

Adaptor: allowlist guild/canal/user fail-closed cu respingerea webhook-urilor,
comenzi !new/!cd/!model/!status/!stop/!cleanup, cost si model in subsolul
fiecarui raspuns. Mesajul sosit in timpul unui tur devine steering, nu tur nou.

Securitate: hook PreToolUse fail-closed care cere confirmare in Discord pentru
operatiuni ireversibile, wrapper `infra` cu lista explicita de hosturi. Deny
rules raman strat cosmetic, nu bariera (verificat: /usr/bin/ssh trece pe langa).

Ops: alerte email pe conventia repo-ului, !cleanup pentru orfani, unit systemd
user cu KillMode=control-group si limite de memorie, install.sh idempotent.

Verificat: 275 teste fara retea/Discord/API (10.8s), identic cu si fara
discord.py instalat; e2e pe CLI real confirma steering-ul mid-tur (mesaj la 6s
intr-un tool call de 25s schimba raspunsul final).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 10:44:39 +00:00
Marius
d7b4007af8 feat(cluster): corosync ring1 pe insula, test de repornire cu WoL, fixuri in scripturi
Ring1 aplicat si verificat pe cluster live: config_version 17, ring1_addr pe
10.10.10.20x la fiecare nod, interface{linknumber:1}. Validat cu `corosync -t`
inainte de instalare. Testat prin oprirea reala a lui ring0 pe pveelite: link0
disconnected, link1 connected, cvorum 3/3 neatins - exact scenariul care pe
27 august a lasat nodul mort 16 ore.

Test de repornire completa a clusterului, cu Wake-on-LAN (trezire in ~15s).
Insula a urcat singura pe toate trei nodurile; ipoteza enumerarii tarzii a
USB-ului nu s-a materializat. Unealta noua: wake-cluster.ps1.

Testul a scos la iveala o linie ramasa in /etc/fstab pe pve1 si pveelite, care
monta storage-ul NFS de pe IP-ul de productie inaintea lui pvestatd. Backup-ul
trecea tacut pe reteaua gresita, cu storage.cfg corect. cluster-startup verifica
acum asta automat, impreuna cu IP-urile de insula si conectivitatea reala.

Patru bug-uri gasite prin rulare pe cluster live:

- sonda Oracle nu avea timeout: un sqlplus agatat pe o instanta in pornire a
  blocat cluster-startup 14 minute, fara mesaj, cu propriul prag de 600s
  nefolosit, fiindca bucla n-a apucat o iteratie;
- `bash -c` in loc de `bash -lc`: sqlplus lipsea din PATH, deci baza nu se
  oprea si containerul s-ar fi inchis peste ea;
- PowerShell 5.1 pierde ghilimelele duble catre exe-uri native, deci comanda
  ajungea rupta pe nod - trecut pe trimitere codificata base64;
- backup-ul de crontab si fisierul de stare se rescriau la o a doua rulare,
  lasand monitorizarea oprita permanent si lista de repornit goala.

Documentatia de oprire planificata pornea de la o afirmatie devenita falsa
("nu exista datacenter.cfg") si de la o comanda care ar fi adaugat o a doua
linie `ha:`. Actualizata, impreuna cu inventarul de guest-uri (VM 304 lipsea
din toate listele de ordine si cadea in maturarea de dupa Oracle).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
2026-08-29 21:13:34 +03:00
Marius
fd2105d1f9 merge: retea dedicata de cluster pe switch separat + vmbr0 pe onboard
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
2026-08-29 19:30:54 +03:00
Marius
14e529de8f feat(retea): retea dedicata de cluster pe switch separat, vmbr0 mutat de pe USB
Replicarea, migrarea si backup-ul NFS trec acum pe o insula 10.10.10.0/24
(switch 1, fara gateway), iar vmbr0 a fost mutat de pe dongle-urile USB
Realtek pe placile Intel onboard.

Castigul nu e viteza. Masurat: replicarea mergea deja la 272 MB/s, adica
~95% din firul de 2.5G, iar criptarea SSH nu era limita. 10G e imposibil
cat timp pve1 si pveelite au doar dongle-uri USB de 2.5G si niciun slot
PCIe liber; placa X710 din pvemini e SFP+ cu cage-urile goale, iar switch-ul
are doar porturi RJ45. Replicarea ruleaza si strict secvential
(Replication.pm:138, un singur lock, fara fork), deci nici agregarea de
linkuri nu ar ajuta.

Castigul e ca IP-ul de cluster si corosync ring0 nu mai stau pe dongle-ul
USB care a lasat pveelite invizibil 16 ore pe 2026-08-27 - fara sa fie
nevoie de vreo modificare in corosync.conf, fiindca IP-urile au ramas
aceleasi. Plus izolarea replicarii de productie, WoL persistat pe onboard,
si o cale out-of-band catre noduri prin insula.

Aplicat si verificat: cvorum pe 3 pe tot parcursul, 262-273 MB/s pe insula,
job real de replicare confirmat cu numarare de octeti pe interfete
(4959 KB pe insula vs 1553 KB pe productie), NFS activ si scriibil.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
2026-08-29 19:30:50 +03:00