didi-lot2-backend/backend/REFACTOR_CONTEXT.md
2026-07-10 03:39:53 -07:00

26 KiB

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

// 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:

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

// 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

// 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

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)

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

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:

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

// 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

// 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

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)

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

# 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

# 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ă:

tar -xzf /home/admin365/didi_mono/backups/<snapshot>.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: anyerror (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