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 any92% (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ă splitpipeline-routes.ts(4) — încă nedefriptadmin.ts(3) — distribute în users.ts după splitverdict-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 castas 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}(defaultdidi-admins) realm_access.rolesinclude unul dinADMIN_ROLES(defaultadmin,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.gzagent-v3_pre-split-routes_*.tar.gzagent-v3_pre-verdict-split_*.tar.gzdidiFramework_*.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.messageleaks (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.
searchWebM17mutat înweb-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()șiquickAnalyze()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+/configră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+/allOK.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
- Acest document
backend/CLAUDE.md- Scurt scan al fișierelor noi din §2 ca să vezi pattern-urile
Înainte de orice modificare
- Verifică TS baseline:
npx tsc --noEmitîn ambele services → trebuie 0 erori - Backup tar.gz la
/home/admin365/didi_mono/backups/cu timestamp clar - NU re-introduce anti-pattern-urile din §1
Pentru split-uri viitoare
- Backup
- Identifică boundaries clare (comentarii section, doc comments)
- Extrage helpers shared întâi (în
_init.tssau_helpers/) - Folosește
sed -n 'X,Yp'pentru extracții — atenție la});(ușor de pierdut) - Header cu doc comment + imports +
const router = Router(); - Footer cu
\nexport default router; - Înlocuiește originalul cu un barrel
tsc --noEmitdupă FIECARE fișier creat- Rebuild docker — verifică
.dockerignorearedist/ - 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șitail -3la fiecare extracție. - Sed range
X,Yextrage inclusiv. Începe cu doc comment line, termină cu});. - Atenție la
export default routercare era pe ultima linie a originalului — nu-l copia de două ori.
Ce să NU faci
- ❌ Nu introduce
console.*direct (foloseștelog) - ❌ Nu introduce
error.messageîn response (foloseșteinternalError) - ❌ Nu introduce
new Pool()(folosește pool-ul shared) - ❌ Nu folosi
\|\| 'fallback'pentru parole/secrets (foloseșterequireEnv) - ❌ Nu adăuga
as anydecât cu comentariu explicit explicând de ce - ❌ Nu modifica
requireAdminsă fie no-op (asta era bug-ul critic original) - ❌ Nu commita fără să rulezi
tsc --noEmitîntâi