# requires -version 5.1 # verify_kb_nonregression_v1.0.ps1 # Lecture-seule : vérifie que la KB du ZIP ne réduit pas le nombre d'entrées # ni ne supprime d'IDs sans ALLOW_PURGE__CONFIRM.txt (opt-in explicite). # ASCII-only; UTF-8 BOM; no here-strings; no ternary. param( [Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$ZipPath ) function Ensure-Dir([string]$Path) { if(-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null } } function Read-All([string]$Path) { Get-Content -LiteralPath $Path -Raw } # ---- Self-check (PS5.1) ---- $__scriptPath = $MyInvocation.MyCommand.Definition $__src = Read-All -Path $__scriptPath $__noComments = [regex]::Replace($__src, '(?m)#.*$', '') $__masked = [regex]::Replace($__noComments, "('[^']*')", "''") $__masked = [regex]::Replace($__masked, '("(?:[^"`]|`".)*")', '""') if($__masked -match '\?\s*:'){ Write-Host "[FAIL] STYLE_GUARD_PS51: opérateur ternaire détecté dans ce script." -ForegroundColor Red exit 2 } if(-not (Test-Path -LiteralPath $ZipPath)){ Write-Host "[FAIL] ZIP introuvable: $ZipPath" -ForegroundColor Red; exit 2 } $kbCurrent = Join-Path $Root 'bug_kb\BUG_KB.json.txt' if(-not (Test-Path -LiteralPath $kbCurrent)){ Write-Host "[FAIL] KB actuelle absente: $kbCurrent" -ForegroundColor Red exit 2 } # Unpack temporaire $un = "C:\Temp_Gouvernance\_verify_nonreg" Ensure-Dir -Path $un $tag = Get-Date -Format "yyyyMMdd_HHmmss" $dest = Join-Path $un ("_unpack_" + $tag) Expand-Archive -LiteralPath $ZipPath -DestinationPath $dest -Force # Cherche fichier KB proposé $kbProposed = $null Get-ChildItem -LiteralPath $dest -File | ForEach-Object { if($_.Name -match '^BUG_KB__canonical_.*\.json\.txt$'){ $script:kbProposed = $_.FullName } } if($null -eq $kbProposed){ Write-Host "[FAIL] Aucun fichier BUG_KB__canonical_*.json.txt trouvé dans le ZIP." -ForegroundColor Red exit 2 } # ALLOW_PURGE opt-in $allowPurge = $false if(Test-Path -LiteralPath (Join-Path $dest 'ALLOW_PURGE__CONFIRM.txt')){ $allowPurge = $true } # Parse IDs function Get-IdSet([string]$Path){ $ids = New-Object System.Collections.Generic.HashSet[string] Get-Content -LiteralPath $Path | ForEach-Object { $t = $_.Trim() if($t -match '^\{.*\}\s*$'){ try{ $o = $t | ConvertFrom-Json $id = "" if($o.PSObject.Properties.Name -contains 'id'){ $id = [string]$o.id } if([string]::IsNullOrWhiteSpace($id)){ $id = "" } if($id -ne ""){ [void]$ids.Add($id) } } catch {} } } return $ids } $currentIds = Get-IdSet -Path $kbCurrent $proposedIds = Get-IdSet -Path $kbProposed $currentCount = $currentIds.Count $proposedCount = $proposedIds.Count # Diff sets $removed = New-Object System.Collections.Generic.List[string] $added = New-Object System.Collections.Generic.List[string] foreach($id in $currentIds){ if(-not $proposedIds.Contains($id)){ $removed.Add($id) } } foreach($id in $proposedIds){ if(-not $currentIds.Contains($id)){ $added.Add($id) } } $fail = $false $reasons = @() if($proposedCount -lt $currentCount){ $fail = $true $reasons += "[COUNT] proposed < current (" + $proposedCount + " < " + $currentCount + ")" } if(($removed.Count -gt 0) -and (-not $allowPurge)){ $fail = $true $reasons += "[REMOVED IDs] " + $removed.Count + " (pas d'ALLOW_PURGE__CONFIRM.txt)" } Write-Host "================ NON-REGRESSION CHECK ================" Write-Host ("ZIP : " + $ZipPath) Write-Host ("KB current : " + $kbCurrent) Write-Host ("Current IDs: " + $currentCount) Write-Host ("Proposed IDs: " + $proposedCount) Write-Host ("Added IDs : " + $added.Count) Write-Host ("Removed IDs: " + $removed.Count) if($removed.Count -gt 0){ Write-Host "REMOVED LIST (max 20):" $i = 0 foreach($id in $removed){ Write-Host (" - " + $id) $i = $i + 1 if($i -ge 20){ break } } } if($allowPurge){ Write-Host "[NOTICE] ALLOW_PURGE__CONFIRM.txt présent dans le ZIP — suppression d'IDs autorisée." } if($fail){ Write-Host "RESULT : [FAIL]" -ForegroundColor Red Write-Host "REASONS:" foreach($r in $reasons){ Write-Host (" - " + $r) } exit 2 } else { Write-Host "RESULT : [PASS]" exit 0 }