Migrare internal-app · 17 august 2026

Producția e sus. Lanțul git → producție e tăiat.

Comutarea pe codebase-ul unificat a reușit: app.elevarts.com răspunde normal, iar clienții nu simt nimic. Dar munca din git nu mai are pe unde ajunge în producție, iar o parte din automatizări s-au oprit. Mai jos: ce e rupt exact și cei 4 pași care repun totul.

Aplicația:3002 · 307 · app.elevarts.com 200
Cliențioferte, portal, semnături: OK
Deploy din gitoprit, nu ajunge în prod
Automatizări14 rute API dau 401

Cum se adăuga funcționalitate

Toți lucram pe același drum: George, Andreea, Florin, agenții. Astăzi drumul se termină la pasul 5.

Până pe 17 august

1

ia-worktree <temă>
worktree propriu + branch din main

2

editezi doar acolo; guardrails blochează clona-sursă

3

ia-ship "mesaj"
rebase pe main + tsc + build + gate vizual

4

push → CI (lint, tsc, teste, build) → squash-merge automat

5

auto-deploy la 120s → build → rsync → restart → smoke test

Acum

1

merge neschimbat

2

merge neschimbat

3

merge neschimbat

4

merge. Azi a trecut PR #597, CI verde, 20 de commituri în main

5

tăiat. Deploy-ul construiește și copiază în ~/.local/share/internal-app, director care nu mai servește nimic, apoi încearcă să pornească com.elevarts.brain-app, serviciu care nu mai există. main e corect, verificat — și inert

De ce contează: producția e acum /Users/Shared/elevarts-next, care nu e repo git. Codul nostru e acolo: fișierele coincid la bit. Dar copiat, nu tras. Deci nu există cale înapoi: nici PR, nici review, nici rollback pe o singură modificare. INAPOI.sh întoarce tot app-ul la varianta veche, nu o schimbare anume.

Practic, azi un coleg nu poate livra o funcționalitate: ori scrie cod care rămâne în main, ori editează direct în producție fără plasă.

Automatizările — ce ține, ce s-a rupt

Portul 3002 a rămas același, de-aia majoritatea n-a simțit nimic.

Tot ce vorbește direct cu Postgres

Baza elevarts a rămas live ca PG_BRAIN_URL. Payroll, sync catalog, briefuri, memoria, task runner, health-monitor: neatinse. Asta e majoritatea.

Apelurile autentificate spre API 401

Scoase din PUBLIC în proxy.ts. Middleware-ul respinge înainte ca ruta să apuce să-și verifice x-cron-token.

Moarte azi: draft-uri CRM (crm-autodraft, pipeline-heal, reply-triage, auto-read), ingestia de taskuri, notificarea de mail nou din n8n.

Orbi: captura de erori /api/ops/client-error și telemetria de cron. Nu mai vedem erorile aplicației.

Pontajul prin Telegram adormit

/api/parse-report dă 401, dar nu doare încă: în ultimele 14 zile 137 din 137 de rapoarte au intrat prin interfața web, zero prin Telegram. Florin a înregistrat ore și după comutare. Se repară odată cu restul.

Oprite deliberat de noi

internal-app-deploy (construia și copia în gol la fiecare 120s) și request-deploy (chema deploy-ul și aplica migrări pe producție autonom, pe baza veche). Ambele scrise în ops.changes, cu comanda de repornire.

Ce ne trebuie de la Dorin

În ordine. Primul deblochează tot restul.

01 Folderul de producție în git

/Users/Shared/elevarts-next să primească git init, remote pe 1elevarts/internal-app, un commit cu starea curentă, apoi merge cu main.

Fără pasul ăsta nimic din ce lucrăm nu mai ajunge la clienți, iar producția rămâne fără istoric și fără rollback pe modificare. În main te așteaptă deja 20 de commituri verificate: portalul de agenție, licențierea fonturilor, opțiunile de catalog.

02 Peticul pentru automatizări

Decizia de a scoate rutele de automatizare din PUBLIC e corectă: la noi erau apelabile fără sesiune. Dar cronurile trimit x-cron-token, nu cookie, iar middleware-ul le taie înainte. Cinci linii în proxy.ts, după verificarea PUBLIC și înainte de cerința de JWT, repun toată coloana:

// automatizări interne:
// token, nu sesiune
const t = req.headers
  .get("x-cron-token");
if (t && t === process.env.CRON_TOKEN) {
  return NextResponse.next();
}

03 Drepturile pe folderul live

.env.local are 54 de chei și ACL group:staff allow read, deci orice cont local le citește. Folderul e drwxrwxr-x dorin:staff, iar din staff fac parte elevartsbrain, florin și root: trei conturi pot rescrie codul live fără nicio urmă.

Recomandat: chmod 600 .env.local și chmod 750 pe folder.

04 Parola de test

Contul lui George a rămas cu o parolă trivială, trimisă și pe WhatsApp. De schimbat înainte ca aplicația să fie considerată deschisă pentru echipă.

Un lucru mărunt care ne-ar scuti o alarmă: comutarea a fost făcută manual, deci n-a lăsat urmă în ops.changes. Monitorizarea a văzut „un proces necunoscut a luat portul 3002" și era gata să propună repornirea aplicației vechi, adică anularea migrării. A oprit-o o poartă care escaladează orice atinge cuvântul „producție". Am completat noi intrarea acum; data viitoare, o linie în ops.changes înainte de comutare scutește exact alarma asta.

Ce e bine scris: COMUTARE.sh și INAPOI.sh verifică, nu șterg nimic și au drum de întoarcere. Iar comentariul din proxy.ts care explică de ce s-au închis rutele ne-a economisit o oră de diagnostic: era exact cauza.

Brainul — cum e construit

Contextul de care ai nevoie ca să judeci. Nu e o listă de features, e ce se întâmplă singur pe mașină în fiecare noapte.

Substratul de date

Un singur Postgres, 18 scheme, ~340 de tabele. app (business, oferte, taskuri), crm, core.events ca spinare a evenimentelor, dashboards (pontaj și salarii), ops (incidente, schimbări, rulări de cron), memory+mnemo (memorie semantică cu pgvector), digests.

Regula de bază: orice decizie sau eșec ajunge într-un tabel, nu doar în log. De-aia se poate audita dimineața ce a făcut mașina noaptea.

Executorii

130 de joburi launchd (124 încărcate acum), 137 de scripturi în ~/.local/bin, 9 workflowuri n8n active. Cadențele merg de la 60s (scoring urgență CRM, notificări) prin 5 min (health monitor, secret reaper) până la ferestre nocturne.

n8n e ținut strict ca magistrală de evenimente: primește și rutează. Logica de business stă în aplicație și în scripturi, nu în noduri.

Buclele autonome — partea care te interesează

nightly-autopilot (00:00–05:00): ia toate taskurile pending eligibile din app.tasks și rulează Claude pe fiecare, secvențial, până se termină coada sau se face 05:00. Filtrul de eligibilitate exclude explicit infra/bug de prioritate 1, deci criticele nu se ating fără om. Raportul de dimineață se injectează în prima sesiune.

opus-autonomous-agent (01:30): agentul de auto-vindecare. Citește semnalele deschise: incidente, servicii nesănătoase, taskuri blocate, skilluri propuse de memorie, decide cu Opus și creează taskuri de îmbunătățire sau respinge zgomotul. Jurnal în ops.autonomous_decisions.

ralph loop: auto-corecție în stil TDD. Îi dai un task și o comandă care iese 0 la PASS; iterează maxim N ori, rescriind până trece criteriul. Folosit pe taskuri care au eșuat o dată.

advisor pe blocaje: când un task se blochează cerând input, un Opus cu unelte read-only răspunde în locul lui George. Instrucțiunea centrală e „verifică premisa înainte să răspunzi la ea". Foarte des întrebarea e prost pusă. Trece prin două porți; alarma din capul paginii a fost oprită exact de prima.

Memoria

Observațiile din sesiune → buffer SQLite → Postgres (flush la 10 min). La 03:30 un LLM local le comprimă, nomic-embed-text le vectorizează, notele ajung în vault. Recall hibrid vector + full-text. 520 de fișiere de memorie acum.

Costul e ținut jos deliberat: volumul de clasificare rulează pe modele locale (0 lei), iar Claude real se folosește doar unde output-ul e proză pe care o citește un om. Acolo o cifră greșită arată exact ca una corectă.

Regulile — corecțiile devin comportament

Când George corectează ceva, corecția se distilează într-o regulă cu „de ce". 125 de reguli acum. Livrarea e deterministă, nu semantică: un hook injectează la fiecare prompt doar regulile al căror domeniu și cuvinte-cheie se potrivesc, iar altul le verifică la scriere de fișier.

Am încercat întâi căutare vectorială și am măsurat-o: scor mediu 0,0195 pe top-hit, sub pragul de zgomot. O regulă trebuie să se aplice de fiecare dată, nu probabilistic. De-aia potrivirea e pe cuvinte exacte.

Porțile

Stratul care ține agenții în frâu: guardrails care blochează editarea directă a clonei de deploy și tiparele distructive (rm -rf, git branch -D, force-push), leak-check pe diff, CI într-un singur job, gate vizual la 390px, rebase obligatoriu în ia-ship, și poarta de escaladare de la advisor.

Au fost scrise una câte una, după câte un incident. Fiecare are un „de ce" datat.

Canalele umane

Telegram pentru ce cere decizie (deliberat nu pentru progres, fiindcă notificările de pas ascund alertele reale), digest de dimineață, și Request Desk: o cerere formală intră prin /cere, un worker o lucrează autonom, iar livrarea trecea prin poller-ul de deploy, pasul care e oprit acum.

Harta dependențelor

Scanată din sistem acum, nu desenată din memorie: fiecare plist citit, scriptul lui deschis, dependențele detectate din cod. 130 de joburi, 124 încărcate.

De ce depinde fiecare job

Câte din cele 130 ating fiecare resursă. Cifra care contează: aplicația e o dependență mică. Coloana vertebrală e altundeva.

Docker86
Postgres84
Telegram42
LLM local21
Claude Max17
app :300212
GitHub8
Drive3
punct unic de cădere atins de migrare

Cele 12 care vorbesc cu aplicația

Singurele afectate direct de comutare. Unul e rupt, restul merg pentru că portul a rămas 3002.

crm-autodraft30 min · 401
telegram-notify-assignments1 min
health-monitor5 min
telegram-listenerpermanent
elebot-telegrampermanent
catalog-thumbs-warm02:40
ui-uniformity-nightly03:15
daily-brief-generate06:30
request-digest08:00
telegram-daily-reminder09:00
dorin-activity-watch10:00
daily-team-audit20:30

Când rulează mașina

Cele 80 de joburi cu oră fixă, pe 24 de ore. 44 dintre ele între 22:00 și 06:00.

00123045670891011121314151617181920212223
fereastra nocturnă vârful de 09:00 — 13 joburi

Două lucruri de citit aici. Între 11:00 și 19:00 nu rulează nimic programat — ziua de lucru e liberă, deliberat. Și vârful de dimineață cade exact când George deschide laptopul: 13 joburi la 09:00, plus 8 la 06:00.

Impactul, numărat

123merg neatinse — vorbesc direct cu Postgres, nu cu API-ul
1rupt: crm-autodraft, pe rută 401
2oprite de noi: cele două de deploy
4neîncărcate: brain-app plus cele 3 de whisper, care pornesc pe program

Scanerul a dat inițial 2 rupte. Al doilea, iboga-waitlist-drain, lovea /api/health — dar pe portul 3015, dashboardul iboga, nu aplicația. Condiția corectă e „rută închisă și gazda aplicației", altfel orice serviciu cu un endpoint de health apare victimă a unei migrări care nu l-a atins.

Ce am aflat construind harta: aplicația se simțea ca centrul lumii, dar 12 din 130 o ating. Riscul real e concentrat în Docker și Postgres — 86 și 84 de joburi. Docker a picat o dată 9 ore printr-un blocaj de kernel, iar singura ieșire a fost repornirea mașinii; atunci n-a mers nimic din cele 86. Și Postgres e tocmai piesa pe care ai schimbat-o azi.

A doua descoperire: 5 scripturi din repo lovesc rute acum închise, dar nimic nu le programează — nici launchd, nici n8n. crm-pipeline-heal, crm-reply-triage, crm-auto-read, task-extractor, classify-replies. Ori sunt chemate de undeva ce n-am găsit, ori sunt cod mort care arată viu. Le-am lăsat neatinse până se lămurește care.

Ce te rugăm să evaluezi

Ce e bun, ce e de îmbunătățit, ce e inutil, din punctul tău de vedere, ca profesionist. Începem cu slăbiciunile pe care le știm deja, ca să nu-ți pierzi timpul găsindu-le.

124 de joburi active, fără hartă de dependențe

Când producția s-a mutat azi, am descoperit ce s-a rupt bucată cu bucată, prin sondare, nu dintr-un manifest care spune „jobul X depinde de serviciul Y și de ruta Z". La 124 de joburi, asta nu scalează.

Merită un registru de dependențe, sau e semn că sunt prea multe joburi?
Jurnalul lui ralph loop n-a scris niciodată nimic

Găsit azi, cât pregăteam pagina: scriptul inserează în ops.changes coloane care nu există (kind, summary, details). Eroarea e înghițită de 2>/dev/null. Zero rânduri, de la început. Bucla rulează, urma e goală.

Tiparul „nu opri sesiunea la eroare" ne-a ascuns asta. Cum ai separa erorile care pot fi înghițite de cele care nu?
ops.changes se scrie, dar aproape nimeni nu-l citește

Două scripturi din 137 îl consultă. De-aia comutarea de azi a arătat ca „un proces necunoscut a luat portul": datele existau pe jumătate, dar nu în calea de decizie.

Un jurnal de schimbări pe care nu-l citește nimeni înainte să acționeze e o mincinoasă liniște. Cum l-ai lega?
Nu există staging

Porțile sunt locale plus CI. Nu există un loc unde aplicația să ruleze cu date reale înainte de producție. Probabil cea mai vizibilă gaură după migrarea de azi.

Merită un staging pe aceeași mașină, sau e mai ieftin să întărim rollback-ul?
Fereastra de 5 ore e o resursă comună fără arbitru

Toate buclele (autopilot, agent autonom, ralph, advisor, digesturi) consumă din același abonament, fără planificator global sau priorități. Când se termină fereastra, se opresc în ordine arbitrară.

Cum ai pune priorități peste ceva ce nu se poate măsura decât după consum?
Alertele: unele au buton de închidere, majoritatea nu

Regula există: o constatare care se repetă zilnic până iese din fereastra de timp nu mai e semnal. Dar e aplicată neuniform.

Merită impus ca ack-ul să fie obligatoriu la orice check nou?
Întrebarea de fond

George nu scrie cod. Tot ce e mai sus a fost construit ca să compenseze asta: agenții scriu, porțile îi opresc, memoria le ține contextul, regulile le impun stilul.

Din experiența ta: unde e complexitatea justificată și unde ne-am construit un aparat mai mare decât problema?

Cum ne răspunzi: pe WhatsApp, în cuvintele tale. N-avem nevoie de document. Dacă e ceva ce ai vrea să vezi în detaliu (un script, o schemă, un jurnal), zi-ne și îl deschidem pe masă.