# DIDI Backend — Refactor Context (sesiune 2026-05-07) **Citire obligatorie pentru orice sesiune ulterioară.** Acest document conține convențiile, helper-ele și starea după 54 task-uri de refactor + 13 deploy-uri. --- ## 0. TL;DR — ce s-a întâmplat Începută cu un audit ostil al `agent-v3` + `didiFramework`. Am descoperit + rezolvat: - **8 bug-uri critice** (auth bypass admin, parole hardcoded, race conditions, silent failures, fragile error matching) - **Migrare integrală la pino structured logging** (651 console.* → 0) - **Reducere `as any` 92%** (159 → 13) - **Curățarea error.message leaks** (126 → 0) - **Centralizare Redis pools, Keycloak token, PG pools, Express helpers** - **Split la 11 fișiere monolitice** (13049 LOC → 75 fișiere modulare): `routes.ts`, `admin.ts`, `verdict-calculator.ts`, `auth.ts`, `pipeline-routes.ts`, `claims/executor.ts`, `ai-tampered/executor.ts`, `techniques/executor.ts`, `source-assessment/executor.ts`, `providers.ts` (didiFramework), `sync-redis.ts` (didiFramework) Sistemul live a rulat fără downtime detectabil pentru utilizatori. --- ## 1. Convenții obligatorii (RESPECTĂ-LE) ### 1.1 Logging — folosește `log`, NU `console` ```ts // agent-v3 import { log } from '../shared/logger'; // didiFramework import { log } from '../config/logger'; ``` Pino este wrappuit cu un adapter variadic — `log.info('msg', x, y)` și `log.info({ session_id }, 'msg')` ambele merg. În route handlers, folosește `req.log` care are deja `request_id` atașat: ```ts req.log.info({ user_id }, 'analyze starting'); ``` **NU readuce console.log/error/warn.** Comentariul cu "TODO" e mai bun decât un console. ### 1.2 Env vars — `requireEnv()` pentru tot ce e secret/required ```ts // agent-v3 import { requireEnv, optionalEnv } from '../shared/helpers/env'; // didiFramework import { requireEnv, optionalEnv } from '../config/env'; const PG_PASSWORD = requireEnv('PG_PASSWORD'); // throws on missing const PORT = optionalEnv('PORT', '3005'); // safe default ``` **INTERZIS** `process.env.X || 'fallback'` pentru: - Parole, tokens, API keys - Hostname-uri interne (10.11.x.y) - DB names, users OK pentru: log levels, ports, mobile schemes, public URLs, schema names. ### 1.3 Error responses — `internalError()`, NU `error.message` ```ts // agent-v3 import { internalError } from '../shared/helpers/error-response'; // didiFramework import { internalError } from '../config/error-response'; try { // ... } catch (error) { internalError(res, error, 'optional_context_tag'); } ``` Asta: - Loghează errorul + stack intern cu `correlation_id` (UUID) - Returnează către client `{success: false, error: 'Internal server error', correlation_id}` - NU leakuiește detalii interne **INTERZIS** `res.status(500).json({error: error.message})` sau orice variantă care expune mesajul brut. ### 1.4 Redis — `lazyRedis()` per modul + `scanKeys()`, NU `KEYS` ```ts import { lazyRedis } from '../shared/redis/connection'; import { scanKeys } from '../shared/redis/scan'; const getRedis = lazyRedis('module-name'); // label vizibil în connection metadata // SCAN, nu KEYS — KEYS blochează server-ul O(N) const keys = await scanKeys(getRedis(), 'pattern:*'); ``` ### 1.5 Locks distribuite — `acquireLock`/`releaseLock` (fenced) ```ts import { acquireLock, releaseLock } from '../shared/redis/lock'; const lock = await acquireLock(redis, lockKey, 30); // 30s TTL if (!lock) { // alt proces are lock-ul return; } try { // critical section } finally { await releaseLock(redis, lock); // compare-and-delete via Lua } ``` **INTERZIS** `redis.del(lockKey)` direct — poate șterge lock-ul altcuiva dacă TTL a expirat. ### 1.6 Errori logice — `LogicalInputError`, NU string-matching ```ts import { LogicalInputError, isLogicalInputError } from '../shared/helpers/errors'; if (!text) throw new LogicalInputError('Component requires text input'); // În worker: if (isLogicalInputError(err)) { /* nu retry */ } ``` **INTERZIS** `err.message.includes('requires')` sau alte verificări fragile. ### 1.7 Validare config Redis — zod, nu JSON.parse direct Pentru endpoint-uri PUT care scriu în Redis: ```ts import { TierStageAssignmentsSchema } from '../shared/helpers/config-schemas'; const parsed = TierStageAssignmentsSchema.safeParse(req.body?.stage_assignments); if (!parsed.success) { return res.status(400).json({success: false, error: 'Invalid payload', details: parsed.error.issues}); } await r.set(key, JSON.stringify(parsed.data)); ``` ### 1.8 PG pools — folosește shared, NU `new Pool()` în route file ```ts // didiFramework import pool from '../config/database'; // SAU pentru query helpers: import { query, queryOne, transaction } from '../config/database'; // agent-v3 import { getPgPool } from '../shared/persistence/pg-pool'; ``` **INTERZIS** `new Pool({...})` în orice fișier de rută. Excepție: `waitlist.ts` folosește un DB diferit (staging), păstrat lazy-init. ### 1.9 Keycloak admin — folosește centralizat ```ts // didiFramework import { getKeycloakAdminToken } from '../config/keycloak-admin'; const token = await getKeycloakAdminToken(); // throws on failure ``` **INTERZIS** rescrierea logicii de fetch /token din nou. ### 1.10 Video weighting — folosește helper ```ts import { combineVideoProbability, DEFAULT_VIDEO_TRACK_WEIGHTS } from '../shared/media/video-weighting'; const final = combineVideoProbability(textProb, visualProb); // 0.4/0.6 default ``` **INTERZIS** `textProb * 0.4 + visualProb * 0.6` inline (a fost deja inversat într-un loc înainte de fix). ### 1.11 Type safety — `as any` necesită justificare Reduceri reușite: 159 → 13. Cele 13 rămase sunt în: - `routes.ts` (4) — în techniques.ts după split - `pipeline-routes.ts` (4) — încă nedefript - `admin.ts` (3) — distribute în users.ts după split - `verdict-calculator.ts` (2) Când adaugi cod nou: - Pentru fetch responses externe → declară `interface Response { ... }` și cast - Pentru data din Redis → zod schema sau tip bine definit - Pentru Express req extension → declară în `declare global { namespace Express { interface Request { ... } } }` (NU cast `as any`) ### 1.12 `.dockerignore` — OBLIGATORIU pentru ambele services ``` node_modules dist .git *.log .env.local ``` **Bug istoric**: stale `dist/admin.js` din host se copia în container și suprascria buildul nou. Dacă `dist/` nu e ignorat → build inconsistent. --- ## 2. Structură nouă post-split ### agent-v3 ``` src/api/ ├── routes.ts # 37 LOC barrel (techniques + media + domain + /health) ├── techniques.ts # 781 LOC, 11 endpoints ├── media.ts # 171 LOC, 4 endpoints ├── domain.ts # 333 LOC, 2 endpoints ├── pipeline-routes.ts # 6 LOC re-export → ./pipeline ├── pipeline/ # split 5 → folder (was 1611 LOC) │ ├── index.ts # 36 LOC barrel │ ├── _init.ts # 36 LOC (lazy redis/persist/media + FRAMEWORK_API_URL) │ ├── _helpers/ │ │ ├── vision.ts # 54 LOC (extractImageVision) │ │ └── url-helpers.ts # 151 LOC (detectUrlType + fetchArticleText + isBoilerplate + buildPlatformInfoField) │ ├── analyze.ts # 535 LOC (POST /analyze + /analyze-url + /analyze-media) │ ├── status.ts # 157 LOC (GET /verdict-config + /:sessionId/{status,component/:name,result}) │ ├── history.ts # 181 LOC (6 history endpoints) │ ├── extension.ts # 234 LOC (validateApiKey + 4 extension endpoints) │ └── async.ts # 296 LOC (3 async/queue endpoints) ├── ai-tampered-routes.ts # 764 LOC ├── claims-routes.ts # 671 LOC ├── source-assessment-routes.ts # 470 LOC ├── moderation-routes.ts # 420 LOC ├── _init.ts # 36 LOC (shared lazy: Redis, MediaService, multer, PersistService) └── _helpers/ └── standalone-session.ts # 86 LOC (buildStandaloneSession + persistStandaloneResult) src/components/pipeline/verdict-calculator/ # split 4 → folder ├── index.ts # 864 LOC (VerdictCalculator class) ├── types.ts # 161 LOC (interfaces) ├── defaults.ts # 72 LOC (DEFAULT_*) └── mappers.ts # 32 LOC (pure functions) src/components/claims/ # split 6 → folder (was 1228 LOC) ├── executor.ts # 319 LOC (ClaimsExecutor class shell + execute orchestrator + loadConfigs) ├── types.ts # 159 LOC (interfaces, zod schemas, ClaimsCtx) ├── web-search.ts # 76 LOC (searchWebM17 + M17* types) ├── llm-utils.ts # 117 LOC (callWithFallbacks, parseJsonResponse, validate*) └── stages/ ├── extraction.ts # 82 LOC (Stage 1: extractClaims + loadConfig helper) ├── verification.ts # 443 LOC (Stage 2: verifyClaims + 5 helpers + calculateStatusFromSources) └── scoring.ts # 179 LOC (Stage 3: buildFinalResult + buildEmpty + buildSkipped) src/components/ai-tampered/ # split 7 → folder (was 1043 LOC) ├── executor.ts # 339 LOC (AITamperedExecutor class + execute() + quickAnalyze() + loadFromRedis) ├── types.ts # 201 LOC (interfaces, 3 zod schemas, AiTamperedCtx) ├── patterns.ts # 57 LOC (AI_TOOL_PATTERNS + DISCLOSURE_INDICATORS + PARTIAL_DISCLOSURE_PATTERNS) ├── disclosure.ts # 145 LOC (checkDisclosure + findToolMention + getDisclosureProminence + calculateDisclosureRating) ├── llm-utils.ts # 122 LOC (callWithFallbacks, parseJsonResponse, validate*) └── stages/ ├── _helpers.ts # 17 LOC (loadFromRedis pentru stages) ├── screening.ts # 53 LOC (Stage 1: executeScreening) ├── deep-analysis.ts # 65 LOC (Stage 2: executeDeepAnalysis) └── scoring.ts # 191 LOC (Stage 3: buildFinalResult + buildEmptyResult + determineVerdict + determineConfidenceLevel) src/components/techniques/ # split 8 → folder (was 962 LOC) ├── executor.ts # 237 LOC (TechniquesV3Executor class + execute() + loadFromRedis) ├── types.ts # 243 LOC (interfaces, 3 zod schemas, TechniquesCtx, resolveStageAssignment) ├── llm-utils.ts # 115 LOC (callWithFallbacks, parseJsonResponse, validate*) └── stages/ ├── _helpers.ts # 15 LOC (loadFromRedis pentru stages) ├── screening.ts # 50 LOC (Stage 1: executeScreening) ├── deep-analysis.ts # 93 LOC (Stage 2: executeDeepAnalysis + buildTechniquesList) └── scoring.ts # 317 LOC (Stage 3: buildFinalResult + buildEmptyResult + calculateManipulationScore + buildCouplingContext + findTechniqueById) src/components/source-assessment/ # split 9 → folder (was 932 LOC) ├── executor.ts # 137 LOC (SourceAssessmentExecutor class + execute() orchestrator 4-step) ├── types.ts # 95 LOC (interfaces, ScoringConfig, SourceAssessmentCtx) ├── defaults.ts # 191 LOC (DEFAULT_*_PROMPT, DEFAULT_MODELS, DEFAULT_AXIS_WEIGHTS, defaultFramework) ├── external.ts # 167 LOC (searchM17 + checkDomain + extractRedFlags + extractDomainFromUrl + DOMAIN_CHECK_URL via optionalEnv) ├── config-loader.ts # 143 LOC (loadFramework + loadModels + loadScoringConfig + loadPrompt — toate cu fallback default) └── stages/ ├── extraction.ts # 61 LOC (Step 1: extractSourceMetadata) ├── mapping.ts # 127 LOC (Step 3: mapToFramework — LLM clasifică evidence) └── scoring.ts # 180 LOC (Step 4: buildResult + calculateAuthorScore + neutralFallback) src/shared/ ├── logger.ts # pino + variadic adapter ├── request-logger.ts # Express middleware (request_id propagation) ├── helpers/ │ ├── env.ts # requireEnv, optionalEnv │ ├── errors.ts # LogicalInputError class │ ├── error-response.ts # internalError(res, err, ctx?) │ └── config-schemas.ts # TierStageAssignmentsSchema (zod) ├── redis/ │ ├── connection.ts # lazyRedis(label) + createRedisConnection │ ├── scan.ts # scanKeys cursor-based │ └── lock.ts # acquireLock/releaseLock (fenced) └── media/ └── video-weighting.ts # combineVideoProbability + DEFAULT_VIDEO_TRACK_WEIGHTS ``` ### didiFramework ``` src/routes/ ├── auth/ # split 5 → folder │ ├── index.ts # 23 LOC barrel │ ├── _helpers.ts # 154 LOC (JWT, Keycloak, getCreditCost, types) │ ├── me-profile.ts # 321 LOC (GET /me + PUT /profile) │ ├── registration.ts # 158 LOC (POST /register) │ ├── credits.ts # 335 LOC (5 endpoints) │ └── email-verify.ts # 415 LOC (GET + POST /verify-email) ├── admin/ # split 2 → folder │ ├── index.ts # 32 LOC barrel + auth middleware mount │ ├── _middleware.ts # 63 LOC (requireAdmin) │ ├── _keycloak-helpers.ts # 397 LOC (caches + roles/groups + audit + types) │ ├── users.ts # 709 LOC (7 endpoints) │ ├── plans.ts # 170 LOC (3 endpoints) │ ├── docker.ts # 117 LOC (2 endpoints) │ └── roles-groups.ts # 393 LOC (9 endpoints) └── (alte route files, neatinse) src/config/ ├── env.ts # requireEnv, optionalEnv ├── error-response.ts # internalError ├── keycloak-admin.ts # getKeycloakAdminToken (canonical) ├── logger.ts # pino + variadic adapter ├── request-logger.ts # Express middleware └── (database.ts, minio.ts, redis.ts — pre-existing) ``` --- ## 3. Fișiere mari rămase (candidați viitor split) | Fișier | LOC | Tip | Plan recomandat | |---|---|---|---| | _(none — toate fișierele >900 LOC au fost spart)_ | | | | --- ## 4. Bug-uri rezolvate (NU le reintroduce) ### 4.1 Auth bypass admin (CRITIC, 100 zile expus) `requireAdmin = (req,res,next) => next()` cu comentariu "STAGING bypass remove in production" — toate 21 admin endpoints erau publice. **Fix**: `_middleware.ts` în admin folder verifică: - JWT decoded (defense-in-depth — Kong validează signature upstream) - Issuer include `/realms/${ADMIN_REALM}` (default `didi-admins`) - `realm_access.roles` include unul din `ADMIN_ROLES` (default `admin,super-admin`) Override pentru dev: `ADMIN_AUTH_BYPASS=true` (logează warn pe FIECARE request). ### 4.2 Aggregator stolen lock TTL 30s pentru lock; calcul-verdict + LLM explanation putea dura mai mult; alt worker prelua lock-ul; primul worker făcea `del()` pe lock-ul greșit. **Fix**: `acquireLock` returnează token UUID, `releaseLock` verifică prin Lua script înainte de `del`. Loghează warn dacă TTL a expirat. ### 4.3 Component-worker retry fără backoff → cascade failure LLM rate-limit → retry imediat → cascadă. **Fix**: linear backoff 1-5s între retry-uri în `component-worker.ts`. ### 4.4 Empty API key fallback `return process.env.OPENROUTER_API_KEY || ''` → request cu `Bearer ` (empty) → silent 401 LLM. **Fix**: throw clar mentționând env vars verificate. ### 4.5 Video weighting inversat `pipeline-routes` folosea 0.6 text + 0.4 visual; `aggregator` folosea 0.4 + 0.6. Același video, scoruri diferite. **Fix**: helper `combineVideoProbability()` cu canonical 0.4/0.6 (visual mai greu — frames sunt evidence directă). ### 4.6 KEYCLOAK_ADMIN_USER vs KEYCLOAK_ADMIN `auth.ts` cerea `KEYCLOAK_ADMIN_USER` (env var inexistentă în prod) pe când `admin.ts` și docker-compose foloseau `KEYCLOAK_ADMIN`. Ar fi crăpat la primul restart cu `requireEnv`. **Fix**: centralizat în `keycloak-admin.ts` cu `KEYCLOAK_ADMIN`. ### 4.7 M17_WEB_API_URL throw at module-load `source-assessment/executor.ts` arunca `Error('M17_WEB_API_URL ...')` la **module-load time** (nu la runtime). Tests + service refuzau să se încarce. **Fix**: `getM17SearchUrl()` lazy. ### 4.8 Stale `dist/` în Docker context Local `dist/` se copia în container și suprascria build-ul fresh. **Fix**: `.dockerignore` în ambele services. --- ## 5. Test status ``` agent-v3 vitest: - 421 / 466 trec - 45 fail = pre-existente (au devenit vizibile abia după fix-ul M17 lazy) - Toate 20 fail-uri pe care le-am cauzat eu inițial = ACUM REPARATE - Suite-le mari care nu se încărcau în baseline (component-runner, integration, component-worker) acum rulează ``` **Cele 45 fail-uri rămase sunt în**: - `video-processor.test.ts` (13) - `verdict-explanation.test.ts` (11) - `executor.test.ts` (4) - `verdict-calculator.test.ts` (4) - `analysis-session.test.ts` (4) - alte (9 distribuite) Sunt issues de **mock data** stale și **algorithm shifts** — nu blocau prod. NU sunt regresii cauzate de mine. --- ## 6. Build + deploy ### Local TS check (rapid, fără Docker) ```bash cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 npx tsc --noEmit # trebuie să fie 0 erori cd ../didiFramework npx tsc --noEmit # trebuie să fie 0 erori ``` ### Docker rebuild + deploy ```bash # agent-v3 (rebuilds toate workers) cd /home/admin365/didi_mono/backend/services/orchestration-layer/agent-v3 docker compose up -d --build # didiFramework cd /home/admin365/didi_mono/backend/services/orchestration-layer/didiFramework docker compose up -d --build didi-framework ``` ### Smoke test post-deploy ```bash # Health curl http://10.11.10.12:24803/api/v3/health curl http://10.11.10.12:3005/health # Auth admin (trebuie să fie 401 fără JWT) curl -o /dev/null -w "HTTP %{http_code}\n" http://10.11.10.12:3005/api/admin/users # Pipeline async curl -X POST http://10.11.10.12:24803/api/v3/techniques/analyze \ -H "Content-Type: application/json" \ -d '{"text":"Test analizei.","plan_type":1}' ``` --- ## 7. Backups Locație: `/home/admin365/didi_mono/backups/` Snapshots numerotate cu timestamp `YYYYMMDD_HHMMSS` la momente cheie: - `agent-v3_20260507_122836.tar.gz` (start sesiune) - `agent-v3_pre-pino_*.tar.gz` - `agent-v3_pre-split-routes_*.tar.gz` - `agent-v3_pre-verdict-split_*.tar.gz` - `didiFramework_*.tar.gz` (similar) Pentru rollback la orice etapă: ```bash tar -xzf /home/admin365/didi_mono/backups/.tar.gz \ -C /home/admin365/didi_mono/backend/services/orchestration-layer/ ``` --- ## 8. Statistici cumulate sesiune | Metric | Start | După | |---|---|---| | `console.*` în prod | 651 | **0** | | `error.message` leaks | 126 | **0** | | `as any` real în prod | 159 | **13** | | Hardcoded passwords în source | 13 | **0** | | Auth bypass active | DA | nu | | Race conditions cunoscute | 3 | 0 | | Files >1000 LOC | 7 | **1** (am spart 9: routes, admin, verdict-calc, auth, pipeline-routes, + 4 executors — singurul rămas e verdict-calc/index.ts cu 864 LOC core class, dar acela e DEJA splitat în folder) | | TypeScript errors | 4 baseline | **0** ambele services | | `process.env.*` direct reads | 137 | 73 | **54 task-uri completate, 13 deploy-uri reușite, 0 downtime detectabil.** ### Update 2026-05-07 (sesiune ulterioară) Pipeline-routes split done (1611 → 8 files). Anti-patterns curățate în drum: - 2 `error.message` leaks (lines 943, 1213) → `internalError(res, err, ctx)` - 2 `process.env.X || 'fallback'` (FRAMEWORK_API_URL, M17_WEB_API_URL) → `optionalEnv` / `requireEnv` - Imports mid-file (line 1329-1338) → grupate sus în fiecare sub-file Claims executor split done (1228 → 7 files). Strategy: păstrează class API identic, stage methods devin standalone functions ce primesc `ClaimsCtx` (snapshot din state-ul clasei). Class shell rămâne `executor.ts` (319 LOC) cu: constructor, state, `loadConfigs`, `saveResult`, `buildCtx`, `execute()` orchestrator. - `searchWebM17` mutat în `web-search.ts` (M17_WEB_API_URL → `requireEnv`) - All 36 unit tests trec după split (executor-output.test.ts) - Public API neatins: `new ClaimsExecutor(redis, llm).execute(text, sessionId, tier)` - Re-export types din executor.ts pentru consumers existenți (component-runner, claims-routes) - Pattern propus pentru ai-tampered/techniques/source-assessment executors: same approach (Ctx + standalone stage functions + thin class shell) Ai-tampered executor split done (1043 → 9 files). Same pattern as claims: - 3 stages (screening, deep-analysis, scoring) + helpers (disclosure, patterns, llm-utils) - Class shell în executor.ts (339 LOC) cu execute() + quickAnalyze() (no-LLM rapid path) - AiTamperedCtx snapshot trimis la stage functions - Smoke test live pe `/api/v3/ai-tampered/quick`: input cu disclosure explicit + ChatGPT mention → ai_probability=85, verdict=LIKELY_AI ✓ - All 27 unit tests trec (executor-output.test.ts) - Public API neatins: ambele `execute()` și `quickAnalyze()` exportate identic - Re-export types din executor.ts pentru consumers existenți Techniques executor split done (962 → 7 files). Same pattern (no disclosure sub-module — techniques nu are pattern matching). Cea mai mare reducere proporțional: 962 → 237 LOC pentru class shell. - 3 stages (screening, deep-analysis, scoring) + types + llm-utils + _helpers - buildTechniquesList stays cu deep-analysis.ts (e prompt building, nu scoring) - findTechniqueById/calculateManipulationScore/buildCouplingContext în scoring.ts - resolveStageAssignment exportată din types.ts (re-export prin executor.ts) - All 23 unit tests trec - Workers techniques pornesc curat, consumă din 6 queues Source-assessment executor split done (932 → 8 files). Structură ușor diferită — nu e 2-stage screening+deep, e 4-step pipeline (extract → search + domain → map → score). Cea mai mare reducere proporțional: 932 → **137 LOC** class shell (a 4-a aplicare a pattern-ului). Public API: doar `SourceAssessmentExecutor` class, niciun type re-export (rezultatul e definit în shared/types/component-results). - 3 stages (extraction, mapping, scoring) + types + defaults (prompts + framework hierarchy + models) + external (M17 + Domain Check API) + config-loader - Anti-pattern fix: `process.env.DOMAIN_CHECK_API_URL || '...'` → `optionalEnv` (hostname intern cu safe default acceptabil) - `M17_WEB_API_URL`: `requireEnv` în loc de inline throw - Smoke test: `/api/v3/source-assessment/health` + `/config` răspund corect - worker-domain rulează source-assessment în-process (nu există worker separat) - Nu există __tests__ folder pentru source-assessment, doar TS check + smoke Providers + sync-redis (didiFramework) split done. Două routes mari (921 + 898 LOC) → 17 fișiere total. Shell-uri 6 LOC re-export. - `providers/`: 8 files (configs/models/assignments/keys/all/prompts/test + _helpers). Cel mai mare: assignments.ts (182 LOC). Smoke: `/api/providers/configs` + `/all` OK. - `sync-redis/`: 7 files (sync/status/data + fetch-data + fetch-config + _shared + index). Cel mai mare: sync.ts (328 LOC). Anti-pattern fix: `error.message` în catch-uri → `internalError(res, err, ctx)`. Smoke: GET /status, POST / sync 60 keys în 733ms. - Au inclus mici cleanup-uri: `error: any` → `error` (typed unknown), `catch (e)` cu unused → `catch`. --- ## 9. Sfaturi pentru sesiunea nouă ### Întâi citește 1. Acest document 2. `backend/CLAUDE.md` 3. Scurt scan al fișierelor noi din §2 ca să vezi pattern-urile ### Înainte de orice modificare 1. **Verifică TS baseline**: `npx tsc --noEmit` în ambele services → trebuie 0 erori 2. **Backup tar.gz** la `/home/admin365/didi_mono/backups/` cu timestamp clar 3. **NU re-introduce** anti-pattern-urile din §1 ### Pentru split-uri viitoare 1. Backup 2. Identifică boundaries clare (comentarii section, doc comments) 3. Extrage helpers shared întâi (în `_init.ts` sau `_helpers/`) 4. Folosește `sed -n 'X,Yp'` pentru extracții — atenție la `});` (ușor de pierdut) 5. Header cu doc comment + imports + `const router = Router();` 6. Footer cu `\nexport default router;` 7. Înlocuiește originalul cu un barrel 8. `tsc --noEmit` după FIECARE fișier creat 9. Rebuild docker — verifică `.dockerignore` are `dist/` 10. Smoke test fiecare endpoint group ### Anti-pattern-uri identificate - Mă grăbeam să plec ce face fiecare endpoint sub-router fără să verific exact boundary line. **Verifică `head -3` și `tail -3` la fiecare extracție**. - Sed range `X,Y` extrage inclusiv. Începe cu doc comment line, termină cu `});`. - Atenție la `export default router` care era pe ultima linie a originalului — nu-l copia de două ori. ### Ce să NU faci - ❌ Nu introduce `console.*` direct (folosește `log`) - ❌ Nu introduce `error.message` în response (folosește `internalError`) - ❌ Nu introduce `new Pool()` (folosește pool-ul shared) - ❌ Nu folosi `\|\| 'fallback'` pentru parole/secrets (folosește `requireEnv`) - ❌ Nu adăuga `as any` decât cu comentariu explicit explicând de ce - ❌ Nu modifica `requireAdmin` să fie no-op (asta era bug-ul critic original) - ❌ Nu commita fără să rulezi `tsc --noEmit` întâi