????# gov_pipeline_doctor_v1.2.1.ps1 # Purpose: pointer sanity (Path/SHA/Entries/Size) + optional post-hook + optional acceptance. # Safe: ASCII-only, no here-strings, no regex flags, PS 5.1 compatible. param( [string]$Root = '\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry', [switch]$RunAcceptance, [switch]$FixPointer, [switch]$Utf8Console ) if($Utf8Console){ try{ [Console]::OutputEncoding = [Text.Encoding]::UTF8 }catch{} } function Exists([string]$p){ return (-not [string]::IsNullOrWhiteSpace($p)) -and (Test-Path -LiteralPath $p) } function Read-Text([string]$p){ if(-not (Exists $p)){ return '' } try{ return Get-Content -LiteralPath $p -Raw -Encoding UTF8 }catch{ return (Get-Content -LiteralPath $p -Raw) } } function To-IntSafe([object]$v){ try{ return [int]$v }catch{ return -1 } } function Count-EntriesFromJson([string]$json){ if([string]::IsNullOrWhiteSpace($json)){ return -1 } if($json.Length -gt 0 -and ([int]$json[0] -eq 0xFEFF)){ $json = $json.Substring(1) } try{ $o = $json | ConvertFrom-Json; if($o -and $o.PSObject.Properties.Name -contains 'entries'){ return @($o.entries).Count } }catch{} try{ $count=0; $i=0; while($true){ $j = $json.IndexOf('"id"',$i); if($j -lt 0){ break } $count++; $i=$j+4 } return $count }catch{} return -1 } function CleanHex([string]$s){ if([string]::IsNullOrWhiteSpace($s)){ return '' } $r = ''; foreach($c in $s.ToCharArray()){ if( ('0' -le $c -and $c -le '9') -or ('A' -le $c -and $c -le 'F') -or ('a' -le $c -and $c -le 'f') ){ $r += $c } } return $r.ToUpperInvariant() } $scriptsDir = Join-Path $Root 'scripts' $bpFile = Join-Path $Root 'bootpack\bootpack.txt' $pasteFile = Join-Path $Root 'bootpack\bootpack_paste.txt' Write-Host "== GOV PIPELINE :: DOCTOR v1.2.1 ==" Write-Host ("Root : {0}" -f $Root) if(-not (Exists $bpFile)){ Write-Host ("[FATAL] missing bootpack.txt: {0}" -f $bpFile); exit 3 } if(-not (Exists $pasteFile)){ Write-Host ("[FATAL] missing bootpack_paste.txt: {0}" -f $pasteFile); exit 3 } $rawBP = Read-Text $bpFile $rawPP = Read-Text $pasteFile try{ $psSha = (Get-FileHash -Algorithm SHA256 -LiteralPath $pasteFile).Hash.ToUpperInvariant() }catch{ $psSha=' } try{ $psSize = (Get-Item -LiteralPath $pasteFile).Length }catch{ $psSize=-1 } $psEnt = Count-EntriesFromJson $rawPP # Pointer block parsing (line by line) $P_path = ''; $P_sha=''; $P_ent=-1; $P_sz=-1; $norm = $rawBP -replace "`r`n","`n" $lines = $norm -split "`n" $inBlk = $false foreach($ln in $lines){ $trim = $ln.Trim() if($trim -ceq '[BUG_KB_JSON_POINTER]'){ $inBlk = $true; continue } if($inBlk){ if($trim.StartsWith('[')){ $inBlk = $false; break } if($trim.Length -gt 0){ $idx = $trim.IndexOf(':') if($idx -ge 0){ $name = $trim.Substring(0,$idx).Trim() $val = $trim.Substring($idx+1).Trim() if($name -ieq 'Path'){ $P_path = $val } elseif($name -ieq 'SHA256'){ $P_sha = CleanHex $val } elseif($name -ieq 'Entries'){ $P_ent = To-IntSafe $val } elseif($name -ieq 'SizeBytes'){ $P_sz = To-IntSafe $val } } } } } $eqPath = ($P_path -ceq $pasteFile) $eqSha = ($P_sha -eq $psSha) $eqEnt = ($P_ent -eq $psEnt) $eqSize = ($P_sz -eq [int]$psSize) Write-Host ("PTR : path==paste={0} sha==paste={1} entries==paste={2} size==paste={3}" -f $eqPath,$eqSha,$eqEnt,$eqSize) if(-not ($eqPath -and $eqSha -and $eqEnt -and $eqSize)){ Write-Host "Pointer mismatch detected." if($FixPointer){ $post = Join-Path $scriptsDir 'post_bootpack_pointer_refresh_v1.0.4.ps1' if( (Test-Path -LiteralPath $post) ){ Write-Host "[FIX] running pointer refresh + acceptance..." $null = & powershell -NoProfile -ExecutionPolicy Bypass -File $post -Root $Root -NoPrompt -Verify } else { Write-Host ("[WARN] missing post-hook: {0}" -f $post) } } } else { Write-Host "[OK] pointer block matches paste." } if($RunAcceptance){ $acc = Join-Path $scriptsDir 'kb_acceptance_tests_v1.0.1.ps1' if(Test-Path -LiteralPath $acc){ $null = & powershell -NoProfile -ExecutionPolicy Bypass -File $acc $Root } else { Write-Host ("[WARN] acceptance script not found: {0}" -f $acc) } } if( ($eqPath -and $eqSha -and $eqEnt -and $eqSize) ){ Write-Host "Result: PASS"; exit 0 } else { Write-Host "Result: WARN"; exit 0 }