diff --git a/CONTEXT_HANDOVER_20260830.md b/CONTEXT_HANDOVER_20260830.md new file mode 100644 index 0000000..04fd311 --- /dev/null +++ b/CONTEXT_HANDOVER_20260830.md @@ -0,0 +1,198 @@ +# CONTEXT HANDOVER — Punte Discord → Claude Code (LXC 171) + +**Data:** 2026-08-30 13:00 UTC · **Stare:** LIVRATA, in productie, pe `master` (impins) +**Commit curent:** `c8a5d41` · **Repo:** romfast/ROMFASTSQL · **Branch:** `master` + +Nu e un task intrerupt. Puntea functioneaza si e folosita. Documentul asta exista ca o +sesiune noua sa poata prelua operarea si continuarea fara sa redescopere totul. + +--- + +## 1. Ce e + +Bot Discord care comanda LXC 171 (si, prin el, infrastructura) dintr-un canal privat, de pe +telefon. Bot subtire `discord.py` peste CLI-ul `claude`, cu **proces persistent per fir** +alimentat pe stdin cu `--input-format stream-json` — de aceea un mesaj trimis in timpul unui +tur functioneaza ca **steering**, nu deschide tur nou. + +Plan sursa: `~/.gstack/projects/romfast-ROMFASTSQL/claude-master-plan-discord-bridge-20260830.md` +(15 taskuri, toate facute). Executat cu 5 agenti in 3 lane-uri paralele. + +**Cod:** `proxmox/lxc171-claude-agent/discord-bridge/` +**Documentatie:** `discord-bridge/README.md` (operare completa), `discord-bridge/INTERFACES.md` +(contractul intre module — de citit inainte de orice modificare), `discord-bridge/security/README.md`. + +--- + +## 2. Stare de rulare (verificata la 12:58 UTC) + +| | | +|---|---| +| Serviciu | `systemctl --user status claude-discord` → **active**, `NRestarts=0` | +| Bot | `ClaudeAgent#7891`, app id `1543576449624186880` | +| Guild | Romfast `1430476698264145922` | +| Canal | **privat** `#claude-agent` `1543581063496859679` | +| User permis | `949388626146517022` (marius.mutu) | +| Director de lucru | `/workspace/claude-agent/` (repo git propriu) | +| Model | `sonnet` (default), `/model opus` per fir | +| Cost ziua curenta | $1.5456 (plafon `COST_CAP_USD_DAY=5.00`) | +| Comenzi slash | 7, inregistrate pe guild | + +**Stare pe disc:** `~/.claude-discord/` — `env` (0600, token+allowlist), `state.json`, +`bot-settings.json` (hook), `approvals/`, `logs/`, `venv/`. +Cod versionat in repo; secretele NU. + +### Comenzi (slash, nu prefix `!` — migrat) +`/new [fork]` · `/cd ` · `/model ` · `/status` · `/stop` · `/cleanup [force]` · `/help` + +Mesajele obisnuite (fara comanda) merg la Claude ca prompt. + +--- + +## 3. Ce s-a verificat EMPIRIC in productie (nu doar teste) + +Astea sunt afirmatii cu dovada, nu presupuneri: + +| Ce | Dovada | +|---|---| +| Steering mid-tur | mesaj la 6s intr-un tool call de 25s a schimbat raspunsul final (ALFA→BETA), CLI 2.1.251 | +| Comenzi slash | 7 citite inapoi din API-ul Discord, nu doar din log | +| Confirmare operatiuni ireversibile | `12:24:25 PENDING` → butoane in canal → `12:24:39 ALLOW` → `/tmp/test-punte` sters | +| Fail-closed | acelasi `rm -rf` fara aprobare → `DENY` la timeout, directorul intact | +| Proces persistent | pid viu la 2 min dupa incheierea turului (reaper la 20 min) | +| `--resume` peste restart | aceeasi sesiune reluata dupa ce restartul a omorat procesul | +| `KillMode=control-group` | restart mid-sesiune, zero orfani | +| Parser tolerant | `rate_limit_event` aparut real in stream, ignorat cu un singur log | +| `/cleanup` dupa fix | rulare seaca pe container: „Niciun proces orfan" | +| Suita | **322 passed** cu discord.py, **319 passed + 3 skipped** fara | + +Rulare teste: `cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest` +E2E (CLI real, ~40s, costa): `pytest -m e2e` + +--- + +## 4. Capcane gasite pe parcurs — NU le reintroduce + +Astea au costat timp si unele erau periculoase. Sunt fixate; sunt scrise aici ca sa nu +fie „simplificate" inapoi. + +1. **`/cleanup` omora sesiunile de lucru.** Definitia initiala („orice proces `claude` + absent din `state.json`") prindea sesiunile Claude interactive de pe container: + rularea seaca propunea 25 de procese / 3864 MB, inclusiv sesiunea din care ar fi fost + data comanda. **Apartenenta la cgroup-ul `claude-discord.service` e conditie NECESARA + pentru toate familiile**, fail-closed la cgroup necitibil. Sesiunile de lucru stau in + `tmux-spawn-*.scope`. Exista test de regresie pe instantaneul real; verificat prin + mutant ca musca (fara filtru pica 3 teste). + +2. **`pid_start_time` e in SECUNDE**, nu ticks (`session_store.py` converteste; `state.json` + viu contine secunde). Un consumator care compara ticks nu se potriveste niciodata — si + cum acea comparatie protejeaza turul in desfasurare, esecul e tacut si face eligibil + pentru omorare exact ce trebuia protejat. Documentat in `INTERFACES.md`. + +3. **`/proc/uptime` e virtualizat de lxcfs, `starttime` nu.** Calculul naiv al varstei dadea + `4123168064` pentru un proces de 10 minute. Se foloseste `btime` din `/proc/stat`. + +4. **Utilizatorul containerului se numeste `claude`**, deci potrivirea pe subsirul `/claude` + marca orice proces din home-ul lui. Potrivire pe basename per argument. + +5. **Log dublat:** unitul redirecteaza stdout in `bot.log` (`StandardOutput=append:`) SI + codul avea `FileHandler` pe acelasi fisier. `FileHandler` ramane doar la rulare manuala + (`INVOCATION_ID` absent). + +6. **`View Channel` lipsea** din permisiunile de invitatie — fara ea botul nu vede canalul, + oricat de permis ar fi in allowlist. Link corect (`permissions=309237763136`): + `https://discord.com/oauth2/authorize?client_id=1543576449624186880&scope=bot%20applications.commands&permissions=309237763136` + +7. **Comenzile slash au nevoie de scope `applications.commands`** (nu doar `bot`) si de + **sync pe guild** (instantaneu; global ~1h). Fiecare comanda face `defer()` — altfel + Discord marcheaza interactiunea esuata dupa 3s desi comanda a rulat. + +8. **`~/bin` trebuie sa fie in PATH-ul unitului**, altfel wrapperul `infra` e prezent pe + disc dar negasibil de bot (stratul 4 de securitate inert). + +9. **Raportul `/cleanup`** depasea 2000 de caractere cu rezultatele atasate → mesaj respins + de Discord exact la `force:True`. Buget de caractere `MAX_REPORT_CHARS=1800`. + +10. **Discord API respinge User-Agent-ul implicit al `urllib`** cu `error code: 1010` + (Cloudflare). Arata identic cu un refuz de permisiuni. Pune un User-Agent real. + +--- + +## 5. Decizii de arhitectura — luate deliberat, NU le redeschide + +Sunt in plan si au fost reconfirmate de utilizator. Un agent nou care le „descopera" ca +probleme pierde timp si risca sa strice lucruri. + +- **`--permission-mode bypassPermissions` e intentionat.** Regulile `deny` sunt strat + cosmetic, NU bariera — verificat: `/usr/bin/ssh -V` si `bash -c "ssh -V"` trec pe langa. + Bariera reala e hook-ul PreToolUse. +- **Accesul larg la infrastructura si la `/workspace` e FUNCTIONALITATE ceruta**, nu bug. + De aceea: fara user separat `cdbot`, fara allowlist de proiecte pentru `/cd`. +- **Fara audit append-only** (stratul 3), respins constient. **Consecinta asumata:** la o + problema nu exista jurnal independent; logurile pot fi sterse de chiar procesul care le + scrie. Hook-ul si botul ruleaza sub acelasi user, deci un agent isi poate scrie singur + `"status":"allow"`. +- **Fara reluare automata a turului pierdut** — risc de dubla executie sub bypassPermissions. +- Fara Agent SDK, fara dashboard web, fara user separat, fara test golden pe JSONL inregistrat. + +--- + +## 6. CE A RAMAS DE FACUT + +### 6.1 Token Proxmox cu ACL (singurul lucru neterminat) + +**Nu e conditie de functionare.** Puntea merge acum folosind cheile SSH root deja prezente +pe container — adica orice tur are drepturi depline pe cluster. Tokenul restrange asta. + +Comenzile exacte sunt in `discord-bridge/security/README.md`. Pe scurt, ca root pe `pvemini`: + +``` +pveum user add claude-bridge@pve +pveum role add ClaudeBridge -privs "Datastore.Audit,Sys.Audit,Sys.Console,Sys.Syslog,VM.Audit,VM.Monitor,VM.Console,VM.PowerMgmt" +pveum acl modify / +pveum user token add claude-bridge@pve discord --privsep 1 +pveum acl modify / --tokens 'claude-bridge@pve!discord' +``` + +`VM.Allocate` (creare/distrugere guest) e lasat afara INTENTIONAT. + +Apoi: secretul (afisat o singura data) in `~/.claude-discord/env` ca `PVE_TOKEN_ID` / +`PVE_TOKEN_SECRET`, `chmod 600`. De decis daca ACL ramane pe `/` sau se restrange la un +subset de guest-uri. + +### 6.2 Idei ramase, neprioritizate +- Atasamente Discord → Claude (download in /tmp + cale in prompt) — era v1.1 in plan. +- Rate limit-ul Discord la render e singura degradare tacuta ramasa: are tratare (interval + adaptiv 1s→5s), nu are test. Acceptat, fiindca esecul e intarziere, nu pierdere. +- `/cleanup` nu prinde procese iesite complet din cgroup — pret deliberat, ca sa nu atinga + niciodata sesiunile interactive. + +--- + +## 7. Fisiere cheie + +| Fisier | De ce conteaza | +|---|---| +| `discord-bridge/INTERFACES.md` | contractul intre module + proprietatea pe fisiere. **De citit primul.** | +| `discord-bridge/README.md` | operare, instalare, depanare, limitari oneste | +| `discord-bridge/runner.py` | proces persistent, reaper, respawn `--resume` | +| `discord-bridge/session_store.py` | state.json atomic, lock per fir, PID reuse | +| `discord-bridge/cleanup.py` | vezi capcana #1 — cea mai periculoasa zona | +| `discord-bridge/security/confirm_hook.py` | hook PreToolUse, fail-closed | +| `discord-bridge/security/infra` | wrapper cu lista explicita de hosturi (exit 3 la host necunoscut) | +| `discord-bridge/ops/claude-discord.service` | `KillMode=control-group`, MemoryHigh 3G / Max 6G, PATH | +| `~/.gstack/.../claude-master-plan-discord-bridge-20260830.md` | planul original, cu justificarile | + +## 8. Comenzi de operare + +```bash +systemctl --user status claude-discord +journalctl --user -u claude-discord -n 200 +tail -f ~/.claude-discord/logs/bot.log +tail -f ~/.claude-discord/logs/confirm_hook.log # cine a cerut/primit aprobare +cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest +python3 cleanup.py # rulare seaca, sigura +``` + +**Memorie:** un fir costa ~440 MB (claude 300 + servere MCP 137). Plafon 4 procese → ~1,8 GB, +sub `MemoryHigh=3G`. Containerul are 16 GB.