# gov_profile_launcher_v1.2.ps1 # Menu HUB/SEEDBOX (Preview/Write), Audit MANIFEST, Fix MANIFEST, SYNC REPORT, EFFECTIVE POLICY, AUDIT 8 CHECKS param() 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 FileSha([string]$p){ if(Test-Path $p){ (Get-FileHash -Algorithm SHA256 -LiteralPath $p).Hash } else { '' } } function CountKBIds([string]$p){ if(!(Test-Path $p)){ return -1 } $raw = Get-Content -LiteralPath $p -Raw -Encoding UTF8 $i = $raw.LastIndexOf(']}'); if($i -gt 0){ $raw = $raw.Substring(0,$i+2) } try{ (($raw|ConvertFrom-Json).entries).Count } catch { ([regex]::Matches($raw,'"id"\s*:')).Count } } function RecalcManifestSha([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() $lines = $blk -split "`r?`n" $buf = New-Object System.Text.StringBuilder foreach($ln in $lines){ if($ln -match '^\s*MANIFEST_SHA256\s*:'){ continue }; [void]$buf.AppendLine($ln.TrimEnd()) } $core = $buf.ToString().TrimEnd("`r","`n") $sha = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash([Text.Encoding]::UTF8.GetBytes($core))).Replace('-','').ToLower() return @{'ok'=$true;'infile'=$infile;'recalc'=$sha} } function Run-Profile([string]$profile,[string]$mode){ $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_'+$profile+'_'+$mode+'_'+$ts+'.txt') if($profile -eq 'HUB'){ $script=Join-Path $root 'scripts\rebuild_bootpack_full_hub_v1.1.ps1' } elseif($profile -eq 'SEEDBOX'){ $script=Join-Path $root 'scripts\rebuild_bootpack_full_seedbox_v1.1.ps1' } else { Write-Host "[ERR] Profil inconnu: $profile"; return } if(!(Test-Path $script)){ Write-Host "[ERR] Script introuvable: $script"; return } if($mode -eq 'Write'){ if(-not (Ask-YesNo "Confirmer l'écriture réelle pour $profile")){ Write-Host "Annulé."; return } } $arg=@('-NoProfile','-ExecutionPolicy','Bypass','-File',$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 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 = RecalcManifestSha $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 Fix-Manifest(){ $root="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $fix =Join-Path $root 'scripts\fix_manifest_sha_v1.1.ps1' if(!(Test-Path $fix)){ Write-Host "[ERR] fix_manifest_sha_v1.1.ps1 introuvable."; return } $logs=Join-Path $root 'logs'; Ensure-Dir $logs $ts=Get-Date -Format 'yyyyMMdd_HHmmss'; $log=Join-Path $logs ('run_fix_manifest_'+$ts+'.txt') & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $fix -Write 2>&1 | Tee-Object -FilePath $log | Out-Null Get-Content -LiteralPath $log -Tail 30; 'Log -> '+$log | Write-Host } function Sync-Report(){ $logp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\logs\registry_activity.log" if(!(Test-Path $logp)){ Write-Host "[INFO] Aucun log d'activité encore."; return } $lines = Get-Content -LiteralPath $logp -Encoding UTF8 $last = New-Object System.Collections.ArrayList for($i=$lines.Count-1; $i -ge 0 -and $last.Count -lt 20; $i--){ [void]$last.Add($lines[$i]) } $arr=[System.Linq.Enumerable]::Reverse($last) Write-Host "=== SYNC REPORT (20 derniers) ===" foreach($l in $arr){ $l } } function Effective-Policy(){ $bp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt" if(!(Test-Path $bp)){ Write-Host "[ERR] BootPack introuvable."; return } $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 } } function Audit-8Checks(){ $bp="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt" if(!(Test-Path $bp)){ Write-Host "[ERR] BootPack introuvable."; return } $raw = Get-Content -LiteralPath $bp -Raw -Encoding UTF8 $ok=@{}; $msg=@{} # C1: BootPack présent (déjà ok) $ok.C1=$true; $msg.C1="BootPack présent" # C2: POLICY contient L0 requis $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 ' ; ')}) # C3: KB pointer: SHA du fichier = SHA inscrit $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}) # C4: KB Entries cohérents $kbEntriesIn=[regex]::Match($kbblk,'(?mi)^\s*Entries:\s*([0-9]+)$').Groups[1].Value $kbEntriesReal=CountKBIds $kbPath $ok.C4=($kbEntriesReal -ge 0 -and [string]$kbEntriesReal -eq [string]$kbEntriesIn); $msg.C4=$(if($ok.C4){"OK"}else{"Entries pointer="+$kbEntriesIn+" vs real="+$kbEntriesReal}) # C5: Snapshot: SHA inscrit = SHA réel (ou MISSING si absent) $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}) # C6: PROFILE_REF: fichier existe + SHA match $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}) # C7: MANIFEST_SHA256: match $res=RecalcManifestSha $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}) # C8: FILES_SHA256: chaque fichier référencé existe et match (tolère 'MISSING' inscrit) $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 } } function Show-Menu(){ Clear-Host Write-Host "=== GOV PROFILE LAUNCHER v1.2 ===" Write-Host "1) HUB - Preview" Write-Host "2) HUB - Write (commit)" Write-Host "3) SEEDBOX - Preview" Write-Host "4) SEEDBOX - Write (commit)" Write-Host "5) Audit MANIFEST (recalc vs fichier)" Write-Host "6) Corriger MANIFEST_SHA (SAFE-WRITE)" Write-Host "7) SYNC REPORT (20 derniers événements)" Write-Host "8) AFFICHER EFFECTIVE POLICY (L0+profil)" Write-Host "9) AUDIT FIL - 8 CHECKS" Write-Host "0) Quitter" Write-Host "" } while($true){ Show-Menu $c=Read-Host "Choix" switch($c){ '1' { Run-Profile -profile 'HUB' -mode 'Preview' ; Pause } '2' { Run-Profile -profile 'HUB' -mode 'Write' ; Pause } '3' { Run-Profile -profile 'SEEDBOX' -mode 'Preview' ; Pause } '4' { Run-Profile -profile 'SEEDBOX' -mode 'Write' ; Pause } '5' { Audit-Manifest ; Pause } '6' { Fix-Manifest ; Pause } '7' { Sync-Report ; Pause } '8' { Effective-Policy ; Pause } '9' { Audit-8Checks ; Pause } '0' { break } default { Write-Host "Choix invalide." ; Start-Sleep -Seconds 1 } } }