didi-lot2-backend/backend/services/orchestration-layer/agent-v3/VERDICT_BUGS_AUDIT.md
2026-07-10 03:39:53 -07:00

18 KiB
Raw Permalink Blame History

VERDICT BUGS AUDIT — Verified with Redis + PostgreSQL Data

Data: 2026-02-26 Status: Auditat si confirmat cu date reale din Redis si PostgreSQL Fisiere afectate:

  • src/components/pipeline/verdict-calculator.ts — buguri #1, #2, #6, #7
  • src/components/techniques/executor.ts — bug #3
  • src/components/claims/executor.ts — bug #4
  • src/config/analysisLimits.ts — context pentru bug #4 (MIN_CLAIMS_TEXT_LENGTH = 100)

Sesiuni de referinta (din PostgreSQL bos_analysis)

Sesiunea 7cc2cca8 — DOVADA BUG #1

risk_score = 90  |  risk_category = RELIABLE  |  risk_level = CRITICAL  |  severity = CRITICAL
score_manipulation = 87  |  score_claims = 75  |  score_ai = 0  |  score_source = NULL
override_applied = true  |  override_type = SYNERGY  |  override_adjustment = 20
override_reason = "2 components with high risk (+10%); 2 severe techniques (+10%)"
input_text = "UK and France plan to send nuclear weapons to Ukraine to fight Russian..."
components_run = {ai_tampered, techniques, claims}

Sesiunea d07266ca — DOVADA BUG #3

risk_score = 68  |  risk_category = QUESTIONABLE  |  risk_level = HIGH
manipulation_score = 100  |  techniques_count = 1  |  total_severity = 60  |  dimensions_affected = {D8}
credibility_score = 50  |  total_claims = 2  |  verified_false = 0
input_text = "In februarie 2026 in bucuresti a nins masiv si la 4 dimineata autoritatile au tr..."

Sesiunea 2adf7ace — DOVADA BUG #4 si #5

risk_score = 0  |  risk_category = RELIABLE  |  risk_level = VERY_LOW
score_manipulation = 0  |  score_claims = NULL  |  score_ai = 0  |  score_source = NULL
manipulation_score = 0  |  techniques_count = 0  |  dimensions_affected = {D7, D8}
credibility_score = NULL  |  interpretation = "Text too short for claims analysis (54 chars)"
input_text = "Uk and France plan to send nuclear weapons to ukraine."  (54 chars)

Sesiunea 4fa38b53 — Context BUG #3 si LLM false positives

risk_score = 58  |  risk_category = QUESTIONABLE  |  risk_level = HIGH  |  severity = MEDIUM
manipulation_score = 100  |  techniques_count = 3  |  total_severity = 240  |  dimensions_affected = {D1}
credibility_score = 92  |  total_claims = 10  |  verified_true = 9  |  verified_false = 0
override_applied = true  |  override_adjustment = 10  (CRITICAL: severe techniques)
input_type = url  |  input_text = "Title: Nancy Guthrie's family offers $1m reward..."
score_source = 30  (domain trusted)

Sesiunea ee58753d — DOVADA BUG #2

risk_score = 80  |  risk_category = UNRELIABLE  |  risk_level = VERY_HIGH  |  severity = MEDIUM
manipulation_score = 100  |  techniques_count = 13  |  total_severity = 820  |  dimensions_affected = {D1,D3,D7,D8}
credibility_score = 45  |  total_claims = 7  |  verified_false = 1
override_applied = true  |  override_adjustment = 10
input_text = "Un studiu secret al Universitatii Harvard arata ca 80% dintre romani vor ramane..."

Severity=MEDIUM la risk_score=80 e gresit. Conform severity_assessment: 80 e in range CRITICAL (80-100). Floatul a fost probabil ~79.5 care a cazut in gap intre HIGH (60-79) si CRITICAL (80-100).


Date framework din Redis SI PostgreSQL (identice in ambele)

Verdict Categories (bos_parammgmt.verdict_category)

RELIABLE          start=0   end=15    color=green
MOSTLY_RELIABLE   start=16  end=30    color=lightgreen
MIXED             start=31  end=55    color=yellow
QUESTIONABLE      start=56  end=75    color=orange
UNRELIABLE        start=76  end=90    color=red
DISINFORMATION    start=91  end=100   color=darkred

GAP-URI FLOAT: 15.x, 30.x, 55.x, 75.x, 90.x — niciun range nu acopera aceste valori.

Risk Mappings (bos_parammgmt.risk_mapping) — 6 nivele, NU 4 ca in cod defaults

VERY_LOW   start=0   end=10    level=1  color=green
LOW        start=11  end=25    level=2  color=lightgreen
MEDIUM     start=26  end=50    level=3  color=yellow
HIGH       start=51  end=75    level=4  color=orange
VERY_HIGH  start=76  end=90    level=5  color=red
CRITICAL   start=91  end=100   level=6  color=darkred

GAP-URI FLOAT: 10.x, 25.x, 50.x, 75.x, 90.x Cod defaults au 4 nivele (LOW/MODERATE/HIGH/CRITICAL), Redis are 6 (VERY_LOW/LOW/MEDIUM/HIGH/VERY_HIGH/CRITICAL). Fallback in cod: risk_mappings[length - 1] = ultimul element = CRITICAL (cel mai sever).

Severity Assessments (bos_parammgmt.severity_assessment)

LOW       start=0   end=29
MEDIUM    start=30  end=59
HIGH      start=60  end=79
CRITICAL  start=80  end=100

GAP-URI FLOAT: 29.x, 59.x, 79.x Fallback in cod: severity_assessments[length - 1] = ultimul element. NOTA: Redis are trailing whitespace pe valori ("CRITICAL "), codul face .trim() la linia 318.

Component Weights (bos_parammgmt.component_weight)

manipulation = 35   (cod default: 0.35)
claims       = 25   (cod default: 0.25)
source       = 20   (cod default: 0.20)
ai           = 10   (cod default: 0.10)
context      = 10   (cod default: 0.10)

Unitati diferite (35 vs 0.35) dar FARA impact — calculateWeightedRisk imparte la totalWeight, deci se normalizeaza.

Multipliers (doar in Redis, cod defaults = array gol)

Topic (type 1):
  elections      = 150   (intentie: 1.5x)
  security       = 140   (intentie: 1.4x)
  public_health  = 130
  dimension_d4   = 130
  dimension_d8   = 130
  dimension_d5   = 120
  dimension_d7   = 110
  dimension_d6   = 90

Temporal (type 2):
  election_window = 140
  crisis_window   = 130

Reach (type 3):
  high_reach   = 125
  medium_reach = 110
  low_reach    = 100

Verdict Config (Redis didi:config:pipeline:v1:verdict_config)

synergy:           enabled=true, threshold=70, bonus_per_component=5, max_bonus=15
false_claims:      enabled=true, threshold=3, bonus_per_claim=5, max_bonus=20
severe_techniques: enabled=true, threshold=2, bonus=10
undisclosed_ai:    enabled=true, bonus=15
untrusted_domain:  enabled=true, untrusted_bonus=20, suspicious_bonus=10, blacklisted_bonus=25
domain_red_flags:  enabled=true, threshold=2, bonus_per_flag=5, max_bonus=15
confidence:        base_per_component=12.5, domain_strong=15, domain_weak=8, techniques_max=12
                   ai_high=12, ai_medium=8, ai_low=4, claims_max=11
confidence_levels: HIGH=75, MEDIUM=50, LOW=0

Scenarii redistribuire (doar Redis, ignorate de cod)

Full Analysis:  manipulation=35, claims=25, source=20, ai=10, context=10
No Source:      manipulation=44, claims=31, source=0,  ai=12, context=13
No Claims:      manipulation=44, claims=0,  source=25, ai=12, context=19
Text Only:      manipulation=47, claims=33, source=0,  ai=0,  context=20
Minimal:        manipulation=70, claims=0,  source=0,  ai=0,  context=30

Techniques Scoring Config (Redis didi:config:techniques:v3:scoring_config)

manipulation_levels: LOW=0-0.25, MEDIUM=0.25-0.5, HIGH=0.5-0.75, CRITICAL=0.75-1.0
bonuses:
  count_bonus:     threshold_3=+5%, threshold_5=+10%
  dimension_bonus: per_dimension=2%, max_bonus=15%
  intensity_bonus: formula = (avgIntensity - 1) * 0.05

BUGURI CONFIRMATE — DETALII TEHNICE

BUG #1 — CRITIC: Float gap in mapToVerdictCategory

Fisier: src/components/pipeline/verdict-calculator.ts Linii: 292 (mapare), 313 (rotunjire), 568-573 (functia)

Problema: Scorul e un float (ex: 90.4). Categoriile au ranguri integer (UNRELIABLE: 76-90, DISINFORMATION: 91-100). Valoarea 90.4 nu se potriveste nicaieri. Fallback = categories[0] = RELIABLE.

Cod buguit:

// Linia 292 — mapare pe float NEROTUNJIT
const riskCategory = this.mapToVerdictCategory(riskScore);
const riskLevel = this.mapToRiskLevel(riskScore);
const severity = this.mapToSeverity(riskScore);

// Linia 313 — rotunjire DUPA mapare
risk_score: Math.round(riskScore),

// Linia 568-573 — functia cu fallback RELIABLE
private mapToVerdictCategory(score: number): VerdictCategory {
  for (const cat of this.verdicts.categories) {
    if (score >= cat.start_range && score <= cat.end_range) return cat;
  }
  return this.verdicts.categories[0]; // RELIABLE — cel mai bun!
}

Fallback-uri contradictorii:

  • mapToVerdictCategory: categories[0] = RELIABLE (cel mai bun)
  • mapToRiskLevel: risk_mappings[last] = CRITICAL (cel mai rau)
  • mapToSeverity: severity_assessments[last] = CRITICAL (cel mai rau)

Rezultat posibil: risk_category=RELIABLE + risk_level=CRITICAL simultan.

Fix:

// INAINTE de liniile 292-294, adauga:
const roundedScore = Math.round(riskScore);

// Apoi foloseste roundedScore in loc de riskScore:
const riskCategory = this.mapToVerdictCategory(roundedScore);
const riskLevel = this.mapToRiskLevel(roundedScore);
const severity = this.mapToSeverity(roundedScore);

// La linia 313 foloseste roundedScore:
risk_score: roundedScore,

Dovada din PG: Sesiunea 7cc2cca8: risk_score=90, risk_category=RELIABLE, risk_level=CRITICAL.


BUG #2 — CRITIC: Severity float gap (aceeasi cauza ca #1)

Fisier: src/components/pipeline/verdict-calculator.ts Linii: 582-587

Dovada din PG: Sesiunea ee58753d: risk_score=80, severity=MEDIUM. Ar trebui CRITICAL (80-100).

Fix: Acelasi ca #1 — rotunjire inainte de mapare.

Nota: Ordinea elementelor in array-ul severity_assessments din Redis poate afecta fallback-ul. Trebuie verificat ordinea exacta daca se vrea un fallback inteligent.


BUG #3 — MARE: manipulation_score=100 pe 1 tehnica

Fisier: src/components/techniques/executor.ts Linii: 575-612

Problema: Formula baseScore = sum(severity * confidence/100) / sum(severity) se simplifica la confidence/100 cand e o singura tehnica (severity se anuleaza). Cu confidence=95% si bonusuri: 0.95 + 0.02 + 0.10 = 1.07 → clamped la 1.0 → manipulation_score=100.

Cod buguit:

private calculateManipulationScore(techniques: DetectedTechnique[], config: any): number {
  if (techniques.length === 0) return 0;

  let severitySum = 0;
  let weightedSum = 0;
  for (const t of techniques) {
    severitySum += t.severity;
    weightedSum += t.severity * (t.confidence / 100);
  }
  let baseScore = severitySum > 0 ? weightedSum / severitySum : 0;
  // Cu 1 tehnica: baseScore = (S * C/100) / S = C/100

  // ...bonusuri...
  const finalScore = baseScore + dimensionBonus + countBonus + intensityBonus;
  return Math.round(Math.max(0, Math.min(1, finalScore)) * 100);
}

Fix propus — Scalare bazata pe numar tehnici (cap maxim per count):

// Dupa calculul baseScore, inainte de bonusuri:
// Penalizare count mic: o singura tehnica nu poate justifica scor maxim
const countScaler = Math.min(1.0, techniques.length / 3);
// 1 tehnica -> max 33%, 2 tehnici -> max 67%, 3+ -> 100%
baseScore = baseScore * countScaler;

Alternativ, un cap per count: 1 tehnica → max 50, 2 tehnici → max 70, 3+ → max 100.

Dovada din PG: Sesiunea d07266ca: techniques_count=1, total_severity=60, manipulation_score=100.


BUG #4 — MARE: Claims credibility_score=NULL pierde ponderea

Fisiere:

  • src/components/claims/executor.ts linii 695-716 (buildSkippedResult)
  • src/components/pipeline/verdict-calculator.ts linii 358-360 (extractComponentScores)
  • src/config/analysisLimits.ts linia 21 (MIN_CLAIMS_TEXT_LENGTH = 100)

Problema: Text intre 50-99 chars trece validarea hard (MIN_TEXT_LENGTH=50) dar claims e skip-uit (MIN_CLAIMS_TEXT_LENGTH=100). buildSkippedResult returneaza credibility_score: null. VerdictCalculator: null → -1 → component indisponibil → weight redistribuit.

Diferenta comportament:

  • Text < 100 chars: claims skip → credibility=NULL → weight redistribuit (claims ignorat)
  • Text >= 100 chars, 0 claims: buildEmptyResult → credibility=50 → participa la scor
  • Text >= 100 chars, claims gasite: credibility calculat 0-100

Cod relevant:

// claims/executor.ts linia 695
private buildSkippedResult(startTime: number, textLength: number): ClaimsResult {
  return {
    credibility_score: null,  // <-- NULL
    interpretation: `Text too short for claims analysis (${textLength} chars)`,
    // ...
  };
}

// claims/executor.ts linia 672
private buildEmptyResult(...): ClaimsResult {
  return {
    credibility_score: 50,  // <-- DEFAULT 50
    interpretation: 'No claims to verify',
    // ...
  };
}

// verdict-calculator.ts linia 358
const claimsRisk = claims && claims.credibility_score != null && claims.credibility_score >= 0
  ? Math.round(100 - claims.credibility_score)
  : -1;  // -1 = indisponibil, weight redistribuit

Fix propus: In buildSkippedResult, returneaza credibility_score: 50 in loc de null (neutral, ca si 0 claims). Sau: in verdict-calculator, trateaza null ca 50 in loc de -1.

Dovada din PG: Sesiunea 2adf7ace: credibility_score=NULL, interpretation="Text too short for claims analysis (54 chars)".


BUG #5 — MARE: Text scurt vs lung inconsistenta dramatica

Cauza: Combinatie buguri #3 + #4 + dampening din verdict-calculator.

Mecanism:

  1. Text scurt (54 chars) → screening LLM nu detecteaza tehnici → manipulation=0
  2. 54 < 100 → claims skip → credibility=NULL → weight redistribuit
  3. dampenBenignContent (verdict-calculator.ts:455-469): techniques=0, ai=0 → maxStrongSignal=0 < 15 → cap = 0 + 15 = 15
  4. Rezultat: risk_score capeat la max 15 → in practica 0

Cod dampening:

// verdict-calculator.ts linia 455
private dampenBenignContent(rawScore: number, scores: ComponentScores): number {
  const BENIGN_THRESHOLD = 15;
  const strongSignals = [scores.manipulation, scores.ai].filter(s => s >= 0);
  if (strongSignals.length === 0) return rawScore;
  const maxStrongSignal = Math.max(...strongSignals);
  if (maxStrongSignal >= BENIGN_THRESHOLD) return rawScore;
  const cap = maxStrongSignal + BENIGN_THRESHOLD;
  return Math.min(rawScore, cap);
}

Dovada din PG:

  • 2adf7ace (54 chars): risk_score=0, RELIABLE
  • 7cc2cca8 (aceeasi info, ~200+ chars): risk_score=90, RELIABLE (bug #1, ar trebui UNRELIABLE)

BUG #6 — MARE (LATENT): Multiplicatori ×150 in loc de ×1.5

Fisier: src/components/pipeline/verdict-calculator.ts Linii: 279-289

Problema: Redis stocheaza multiplicatori ca procente (elections=150 = 1.5x). Codul aplica direct: riskScore * 150 in loc de riskScore * 1.5.

Cod buguit:

// Linia 284
if (options?.topic) {
  const topicMultiplier = this.getMultiplier('topic', options.topic);
  if (topicMultiplier) {
    riskScore = Math.min(100, riskScore * topicMultiplier.multiplier);
    // riskScore * 150 = scor × 150, nu × 1.5
    // Math.min(100, ...) salveaza de catastrofa dar orice scor > 0.67 devine 100
  }
}

Date Redis:

elections=150, security=140, public_health=130, dimension_d4=130, dimension_d8=130
election_window=140, crisis_window=130, high_reach=125, medium_reach=110, low_reach=100

Fix:

riskScore = Math.min(100, riskScore * (topicMultiplier.multiplier / 100));

Status: Bug latent — multiplicatorii se aplica doar daca options.topic e setat, ceea ce pare sa nu fie folosit activ momentan. Dar cand se va activa, va fi catastrofal.


BUG #7 — MIC: Scenarii Redis ignorate

Fisier: src/components/pipeline/verdict-calculator.ts Linia: 385

Problema: Parametrul _scenario (cu underscore = nefolosit) in calculateWeights. Codul face redistribuire proportionala proprie in loc sa foloseasca scenariile pre-definite din Redis.

Redis contine 5 scenarii:

Full Analysis:  manipulation=35, claims=25, source=20, ai=10, context=10
No Source:      manipulation=44, claims=31, source=0,  ai=12, context=13
No Claims:      manipulation=44, claims=0,  source=25, ai=12, context=19
Text Only:      manipulation=47, claims=33, source=0,  ai=0,  context=20
Minimal:        manipulation=70, claims=0,  source=0,  ai=0,  context=30

Impact: Redistribuirea proportionala din cod poate diferi de scenariile gandite de admin. Nu e un bug functional, dar e un feature ignorat.


BUG #8 — MIC: coupling_context flags din screening, nu deep analysis

Fisier: src/components/techniques/executor.ts Linii: 664-666

Problema: dimensionsAffected vine din screening (linia 544), nu din tehnicile confirmate in deep analysis. Daca screening detecteaza D7/D8 dar deep analysis nu gaseste tehnici → coupling_context flags raman true cu 0 tehnici.

Cod buguit:

// Linia 664-666 in buildCouplingContext
for_context_analysis: {
  narrative_manipulation_detected: dimensionsAffected.includes('D7'),  // din SCREENING
  context_manipulation_detected: dimensionsAffected.includes('D8'),    // din SCREENING
  amplification_detected: dimensionsAffected.includes('D6'),
  suspicious_patterns: warningFlags,
},

Fix: Foloseste dimensiunile din tehnicile efectiv detectate (deep analysis) in loc de cele din screening:

const confirmedDimensions = new Set(techniques.map(t => t.dimension));
// apoi: confirmedDimensions.has('D7') in loc de dimensionsAffected.includes('D7')

Dovada din PG: Sesiunea 2adf7ace: dimensions_affected={D7,D8}, techniques_count=0.


PRIORITIZARE FIX-URI

Prioritate Bug Efort Impact
1 #1 + #2: Rotunjire inainte de mapare 1 linie Elimina bug CRITIC (RELIABLE la risk 90)
2 #3: Formula manipulation_score ~10 linii Elimina scor 100% pe 1 tehnica
3 #6: Multiplicatori /100 1 linie Previne bug catastrofal cand se activeaza topics
4 #4: Claims null → 50 1 linie Elimina pierdere ponderi pe text scurt
5 #8: coupling_context flags ~5 linii Corecteaza metadata inconsistenta
6 #7: Scenarii Redis ~20 linii Feature, nu bug critic

REFERINTE FISIERE

verdict-calculator.ts  — src/components/pipeline/verdict-calculator.ts
  Linia 292-294: mapare categorii (BUG #1, #2)
  Linia 279-289: multiplicatori (BUG #6)
  Linia 313: Math.round output
  Linia 358-360: claims null handling (BUG #4)
  Linia 385: _scenario ignorat (BUG #7)
  Linia 455-469: dampenBenignContent (context BUG #5)
  Linia 568-573: mapToVerdictCategory cu fallback RELIABLE
  Linia 575-579: mapToRiskLevel cu fallback CRITICAL (ultimul)
  Linia 582-587: mapToSeverity cu fallback ultimul

techniques/executor.ts — src/components/techniques/executor.ts
  Linia 575-612: calculateManipulationScore (BUG #3)
  Linia 664-666: coupling_context flags (BUG #8)

claims/executor.ts — src/components/claims/executor.ts
  Linia 695-716: buildSkippedResult credibility=null (BUG #4)
  Linia 672-693: buildEmptyResult credibility=50

analysisLimits.ts — src/config/analysisLimits.ts
  Linia 14: MIN_TEXT_LENGTH = 50
  Linia 21: MIN_CLAIMS_TEXT_LENGTH = 100