# gov_profile_launcher_v1.5.ps1 - PS 5.1-safe param() $EnforceKBGate = $true # Gate bloquant avant tout Write (désactiver = $false) function Ensure-Dir([string]$p){ if($p -and !(Test-Path $p)){ New-Item -ItemType Directory -Force -Path $p | Out-Null } } function Ask-YesNo([string]$q){ while($true){ $a=Read-Host ($q+' (O/N)'); if($a -in @('O','o','Y','y')){return $true}; if($a -in @('N','n')){return $false} } } function Discover-Profiles(){ $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $dir = Join-Path $root 'scripts' if(!(Test-Path $dir)){ return @() } $files = Get-ChildItem -LiteralPath $dir -Filter 'rebuild_bootpack_full_*.ps1' -File $list = @() foreach($f in $files){ $n = $f.BaseName $name = $n -replace '^rebuild_bootpack_full_','' $name = ($name -split '_v')[0] if([string]::IsNullOrWhiteSpace($name)){ continue } $label = $name.ToUpper() $obj = New-Object psobject -Property @{ Label=$label; Script=$f.FullName } $list += $obj } $list | Sort-Object Label } function Choose-Profile(){ $profiles = Discover-Profiles if($profiles.Count -eq 0){ Write-Host "[ERR] Aucun profil trouvé."; return $null } Write-Host "=== Profils détectés ===" for($i=0; $i -lt $profiles.Count; $i++){ ($i+1).ToString().PadLeft(2,' ') + ") " + $profiles[$i].Label | Write-Host } while($true){ $c = Read-Host "Numéro de profil" $ok=[int]::TryParse($c,[ref]([int]$null)) if($ok){ $idx=[int]$c-1; if($idx -ge 0 -and $idx -lt $profiles.Count){ return $profiles[$idx] } } Write-Host "Sélection invalide." } } function Run-Selected([string]$mode){ $sel = Choose-Profile if($null -eq $sel){ return } $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $logs=Join-Path $root 'logs'; Ensure-Dir $logs $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $log=Join-Path $logs ('run_'+$sel.Label+'_'+$mode+'_'+$ts+'.txt') if(!(Test-Path $sel.Script)){ Write-Host "[ERR] Script introuvable: "+$sel.Script; return } if($mode -eq 'Write'){ if(-not (Ask-YesNo ("Confirmer l'écriture réelle pour "+$sel.Label))){ Write-Host "Annulé."; return } if($EnforceKBGate){ $validator=Join-Path $root 'scripts\kb_guard_validator_v1.0.ps1' if(!(Test-Path $validator)){ Write-Host "[BLOCKING] kb_guard_validator_v1.0.ps1 manquant."; return } $tmpLog = Join-Path $logs ('run_kb_guard_'+$ts+'.txt') & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $validator 2>&1 | Tee-Object -FilePath $tmpLog | Out-Null Get-Content -LiteralPath $tmpLog -Tail 40 if($LASTEXITCODE -ne 0){ Write-Host "[GATE BLOCKED] KB blockers non couverts. Corrige d'abord (asserts ou selftests)." return } } } $arg=@('-NoProfile','-ExecutionPolicy','Bypass','-File',$sel.Script); if($mode -eq 'Preview'){$arg+='-Preview'}else{$arg+='-Write'} & 'powershell.exe' @arg 2>&1 | Tee-Object -FilePath $log | Out-Null if(Test-Path $log){ Get-Content -LiteralPath $log -Tail 60; 'Log -> '+$log | Write-Host } else { Write-Host "[WARN] Log non trouvé: $log" } } function Recalc-ManifestShaObject([string]$raw){ $m = [regex]::Match($raw,'(?ms)^\[SYNC_MANIFEST\]\s*(.*?)(?=^\[|\Z)') if(-not $m.Success){ return @{'ok'=$false;'msg'='Section [SYNC_MANIFEST] absente.'} } $blk = $m.Groups[1].Value $infile = [regex]::Match($blk,'(?mi)^\s*MANIFEST_SHA256\s*:\s*([0-9a-f]+)\s*$').Groups[1].Value.ToLower() $coreLines=@(); foreach($ln in ($blk -split "`r?`n")){ if($ln -match '^\s*MANIFEST_SHA256\s*:'){ continue }; $coreLines += $ln.TrimEnd() } $core=[string]::Join("`r`n",$coreLines) $sha=[BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($core))).Replace('-','').ToLower() return @{'ok'=$true;'infile'=$infile;'recalc'=$sha} } function Audit-Manifest(){ $bp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt" if(!(Test-Path $bp)){ Write-Host "[ERR] BootPack introuvable: $bp"; return } $raw = Get-Content -LiteralPath $bp -Raw -Encoding UTF8 $res = Recalc-ManifestShaObject $raw if(-not $res.ok){ Write-Host "[ERR] "+$res.msg; return } 'InFile = '+$res.infile | Write-Host 'Recalc = '+$res.recalc | Write-Host 'STATUS = '+($(if($res.infile -eq $res.recalc){'MATCH'}else{'DIFF'})) | Write-Host } function KB-Guards-Validate([switch]$Export){ $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $validator=Join-Path $root 'scripts\kb_guard_validator_v1.0.ps1' if(!(Test-Path $validator)){ Write-Host "[ERR] kb_guard_validator_v1.0.ps1 introuvable."; return } $logs=Join-Path $root 'logs'; Ensure-Dir $logs $ts=Get-Date -Format 'yyyyMMdd_HHmmss'; $log=Join-Path $logs ('run_kb_guard_'+$ts+'.txt') $args=@() if($Export){ $args+='-Export' } & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $validator @args 2>&1 | Tee-Object -FilePath $log | Out-Null Get-Content -LiteralPath $log -Tail 60 'Log -> '+$log | Write-Host } function Log-DevBug(){ $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $droot=Join-Path $root 'logs\defects'; Ensure-Dir $droot $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $file=Join-Path $droot ('dev_bugs_'+$ts+'.txt') $title = Read-Host "Résumé court (ex: DIFF audit manifest v1.2)" $area = Read-Host "Composant/zone (ex: launcher, fix_manifest, build_hub)" $sev = Read-Host "Sévérité (S1/S2/S3)" $desc = Read-Host "Détails (court)" $who = $env:USERNAME $lines=@() $lines += "=== DEV BUG REPORT ===" $lines += ("When : {0}" -f (Get-Date -Format s)) $lines += ("Who : {0}" -f $who) $lines += ("Area : {0}" -f $area) $lines += ("Sev : {0}" -f $sev) $lines += ("Title : {0}" -f $title) $lines += ("Desc : {0}" -f $desc) $lines += "" $lines += "Action : à référencer dans prochaine SNAPSHOT et KB si nécessaire." [IO.File]::WriteAllLines($file,$lines,[Text.UTF8Encoding]::new($false)) "Bug loggé -> "+$file | Write-Host } function Show-Menu(){ Clear-Host Write-Host "=== GOV PROFILE LAUNCHER v1.5 ===" Write-Host "1) Lancer profil - Preview" Write-Host "2) Lancer profil - Write (commit) [GATE KB]" Write-Host "3) Audit MANIFEST (recalc vs fichier)" Write-Host "4) Corriger MANIFEST_SHA (SAFE-WRITE)" Write-Host "5) SYNC REPORT (20 derniers)" Write-Host "6) EXPORT SYNC REPORT (TXT)" Write-Host "7) AFFICHER EFFECTIVE POLICY (L0+profil)" Write-Host "8) AUDIT FIL - 8 CHECKS" Write-Host "9) KB GUARDS - VALIDATE COVERAGE" Write-Host "A) KB GUARDS - EXPORT REPORT" Write-Host "B) Log DEV BUG (TXT-ONLY)" Write-Host "0) Quitter" Write-Host "" } while($true){ Show-Menu $c=Read-Host "Choix" switch($c){ '1' { Run-Selected -mode 'Preview' ; Pause } '2' { Run-Selected -mode 'Write' ; Pause } '3' { Audit-Manifest ; Pause } '4' { $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry"; $fix=Join-Path $root 'scripts\fix_manifest_sha_v1.1.ps1'; if(Test-Path $fix){ & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $fix -Write 2>&1 } else { Write-Host "[ERR] fix_manifest_sha_v1.1.ps1 manquant." } ; Pause } '5' { $logp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\logs\registry_activity.log"; if(Test-Path $logp){ Write-Host "=== SYNC REPORT (20 derniers) ==="; Get-Content -LiteralPath $logp -Tail 20 } else { Write-Host "[INFO] Aucun log d'activité."; } ; Pause } '6' { $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry"; $logp=Join-Path $root 'logs\registry_activity.log'; if(Test-Path $logp){ $lines=Get-Content -LiteralPath $logp -Encoding UTF8; $rep=Join-Path $root 'reports'; Ensure-Dir $rep; $ts=Get-Date -Format 'yyyyMMdd_HHmmss'; $out=Join-Path $rep ('sync_report_'+$ts+'.txt'); "=== SYNC REPORT export - "+(Get-Date -Format s) | Out-File -LiteralPath $out -Encoding utf8; if($lines.Count -gt 20){ $lines[$lines.Count-20..($lines.Count-1)] | Out-File -LiteralPath $out -Encoding utf8 -Append } else { $lines | Out-File -LiteralPath $out -Encoding utf8 -Append }; 'Export -> '+$out | Write-Host } else { Write-Host "[INFO] Aucun log d'activité."; } ; Pause } '7' { $bp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt"; if(Test-Path $bp){ $raw=Get-Content -LiteralPath $bp -Raw -Encoding UTF8; $proj=[regex]::Match($raw,'(?mi)^\s*PROJECT:\s*(.+)$').Groups[1].Value.Trim(); $rootp=[regex]::Match($raw,'(?mi)^\s*ROOT:\s*(.+)$').Groups[1].Value.Trim(); $policy=[regex]::Match($raw,'(?ms)^\[POLICY\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value.Trim(); $profRef=[regex]::Match($raw,'(?ms)^\[PROJECT_PROFILE_REF\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value.Trim(); Write-Host "=== EFFECTIVE POLICY ==="; "PROJECT : $proj" | Write-Host; "ROOT : $rootp" | Write-Host; "POLICY (L0) :" | Write-Host; foreach($ln in ($policy -split "`r?`n")){ " - "+$ln.Trim() | Write-Host }; "PROFILE REF :" | Write-Host; foreach($ln in ($profRef -split "`r?`n")){ " "+$ln | Write-Host } } else { Write-Host "[ERR] BootPack introuvable." } ; Pause } '8' { & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File (Join-Path "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\scripts" 'gov_profile_launcher_v1.4.ps1') 2>$null 1>$null; # no-op to keep numbering stable; on garde l'implémentation d'audit déjà installée en v1.4 # Reprend l'AUDIT 8 CHECKS de la v1.4 via un appel direct ci-dessous (copie minimale) $bp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt"; if(!(Test-Path $bp)){ Write-Host "[ERR] BootPack introuvable."; Pause; break }; $raw=Get-Content -LiteralPath $bp -Raw -Encoding UTF8 function FileSha([string]$p){ if(Test-Path $p){ (Get-FileHash -Algorithm SHA256 -LiteralPath $p).Hash } else { '' } } $ok=@{}; $msg=@{} $ok.C1=$true; $msg.C1="BootPack présent" $pol=[regex]::Match($raw,'(?ms)^\[POLICY\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value $need=@('GOV_SCRIPT_GATE v1.4','SAFE-WRITE v1.1','TXT-ONLY v1.0','SYNC-MEM-ARCHIVE-RULE v1.0','SYNC-GUARD v1.1') $have=$pol -split "`r?`n" | % { $_.Trim() } | ? {$_ -ne ''}; $miss=@(); foreach($r in $need){ if(-not ($have -contains $r)){ $miss+=$r } } $ok.C2=($miss.Count -eq 0); $msg.C2=$(if($ok.C2){"OK"}else{"Manque: "+($miss -join ' ; ')}) $kbblk=[regex]::Match($raw,'(?ms)^\[BUG_KB_JSON_POINTER\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value; $kbPath=[regex]::Match($kbblk,'(?mi)^\s*Path:\s*(.+)$').Groups[1].Value.Trim(); $kbShaIn=[regex]::Match($kbblk,'(?mi)^\s*SHA256:\s*([0-9A-Fa-f]+)$').Groups[1].Value.Trim().ToLower(); $kbShaReal=(FileSha $kbPath).ToLower(); $ok.C3=($kbShaReal -ne '' -and $kbShaIn -eq $kbShaReal); $msg.C3=$(if($ok.C3){"OK"}else{"SHA pointer="+$kbShaIn+" vs real="+$kbShaReal}) $rawKB=''; if(Test-Path $kbPath){ $rawKB=Get-Content -LiteralPath $kbPath -Raw -Encoding UTF8 }; $i=$rawKB.LastIndexOf(']}'); if($i -gt 0){ $rawKB=$rawKB.Substring(0,$i+2) }; $kbEntriesIn=[regex]::Match($kbblk,'(?mi)^\s*Entries:\s*([0-9]+)$').Groups[1].Value; $kbCount=-1; try{ $kbCount=(($rawKB|ConvertFrom-Json).entries).Count } catch { $kbCount=([regex]::Matches($rawKB,'"id"\s*:')).Count }; $ok.C4=($kbCount -ge 0 -and [string]$kbCount -eq [string]$kbEntriesIn); $msg.C4=$(if($ok.C4){"OK"}else{"Entries pointer="+$kbEntriesIn+" vs real="+$kbCount}) $man=[regex]::Match($raw,'(?ms)^\[SYNC_MANIFEST\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value; $snapPath=[regex]::Match($man,'(?mi)^\s*SNAPSHOT_FILE\s*:\s*(.+)$').Groups[1].Value.Trim(); $snapShaIn=[regex]::Match($man,'(?mi)^\s*SNAPSHOT_SHA256\s*:\s*([0-9A-Fa-f]+|MISSING)$').Groups[1].Value.Trim().ToLower(); $snapRealExist=Test-Path $snapPath; $snapShaReal=$(if($snapRealExist){ (FileSha $snapPath).ToLower() } else { 'missing' }); $ok.C5=($(if($snapRealExist){ $snapShaReal -eq $snapShaIn.ToLower() } else { $snapShaIn -eq 'missing' })); $msg.C5=$(if($ok.C5){"OK"}else{"SNAPSHOT "+$(if($snapRealExist){"hash="+$snapShaReal}else{"absent"})+" vs manifest="+$snapShaIn}) $prf=[regex]::Match($raw,'(?ms)^\[PROJECT_PROFILE_REF\]\s*(.*?)(?=^\[|\Z)').Groups[1].Value; $pf=[regex]::Match($prf,'(?mi)^\s*PROFILE_FILE\s*:\s*(.+)$').Groups[1].Value.Trim(); $pfShaIn=[regex]::Match($prf,'(?mi)^\s*PROFILE_SHA256\s*:\s*([0-9A-Fa-f:]+)$').Groups[1].Value.Trim().ToLower(); $pfExist=Test-Path $pf; $pfShaReal=$(if($pfExist){ (FileSha $pf).ToLower() } else { 'missing' }); $ok.C6=($pfExist -and $pfShaIn -eq $pfShaReal); if(-not $pfExist -and ($pfShaIn -like 'planned:*')){$ok.C6=$true}; $msg.C6=$(if($ok.C6){"OK"}else{"PROFILE "+$(if($pfExist){"hash="+$pfShaReal}else{"absent"})+" vs ref="+$pfShaIn}) $res=Recalc-ManifestShaObject $raw; $ok.C7=($res.ok -and $res.infile -eq $res.recalc); $msg.C7=$(if($ok.C7){"OK"}else{"hash file="+$res.infile+" vs recalc="+$res.recalc}) $lines = New-Object System.Collections.ArrayList; foreach($ln in ($man -split "`r?`n")){ if($ln -match '^\s{2,}\\\\'){ [void]$lines.Add($ln.Trim()) } }; $mismatch=@(); $missing=@(); foreach($l in $lines){ $sp=$l.Split('='); if($sp.Count -lt 2){ continue }; $path=$sp[0].Trim(); $shaIn=$sp[1].Trim().ToLower(); $exists=Test-Path $path; if(-not $exists){ if($shaIn -ne 'missing'){ $missing+=$path }; continue }; $shaReal=(FileSha $path).ToLower(); if($shaIn -ne $shaReal){ $mismatch+=("$path -> $shaIn vs $shaReal") } }; $ok.C8=($mismatch.Count -eq 0 -and $missing.Count -eq 0); if($ok.C8){ $msg.C8="OK" } else { $m=""; if($mismatch.Count -gt 0){ $m += "Mismatch: "+($mismatch -join ' | ') }; if($missing.Count -gt 0){ if($m -ne ''){ $m+=' ; ' }; $m += "Missing: "+($missing -join ' | ') }; $msg.C8=$m } Write-Host "=== AUDIT FIL - 8 CHECKS ==="; foreach($k in 'C1','C2','C3','C4','C5','C6','C7','C8'){ ($k+' = '+($(if($ok.$k){'PASS'}else{'FAIL'}))+' - '+$msg.$k) | Write-Host } ; Pause } '9' { KB-Guards-Validate ; Pause } 'A' { KB-Guards-Validate -Export ; Pause } 'B' { Log-DevBug ; Pause } '0' { return } default { Write-Host "Choix invalide." ; Start-Sleep -Seconds 1 } } }