# gov_profile_launcher_v1.8.1.ps1 - PS 5.1-safe (fix variable $host -> $hostn) param() $UseAssertsOnly = $true $EnforceKBGate = $true 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 { '' } } $registry="\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $bootpack=Join-Path $registry 'bootpack\bootpack.txt' $kbfile =Join-Path $registry 'bootpack\bootpack_paste.txt' $logsDir =Join-Path $registry 'logs' $reports =Join-Path $registry 'reports' $outbox =Join-Path $registry 'ingest_outbox' $devRoot =Join-Path $registry 'dev_sessions' $lockFlg =Join-Path $registry 'DEV_LOCK.flag' Ensure-Dir $logsDir; Ensure-Dir $reports; Ensure-Dir $outbox; Ensure-Dir $devRoot function Discover-Profiles(){ $dir = Join-Path $registry '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 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(){ if(!(Test-Path $bootpack)){ Write-Host "[ERR] BootPack introuvable: $bootpack"; return } $raw = Get-Content -LiteralPath $bootpack -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-Validate([switch]$Export,[switch]$Suggest,[string]$Profile){ $validator=Join-Path $registry 'scripts\kb_guard_validator_v1.2.ps1' if(!(Test-Path $validator)){ Write-Host "[ERR] kb_guard_validator_v1.2.ps1 introuvable."; return $false } $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $log=Join-Path $logsDir ('run_kb_guard_'+$ts+'.txt') $args=@(); if($Export){ $args+='-Export' } ; if($Suggest){ $args+='-SuggestAsserts' } ; if($Profile){ $args+='-Profile'; $args+=$Profile } ; $args+='-AssertsOnly' & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $validator @args 2>&1 | Tee-Object -FilePath $log | Out-Null Get-Content -LiteralPath $log -Tail 100 'Log -> '+$log | Write-Host if($LASTEXITCODE -ne 0){ return $false } else { return $true } } function Export-IngestPack([string]$reason){ $exp = Join-Path $registry 'scripts\export_ingest_pack_v1.0.ps1' if(!(Test-Path $exp)){ Write-Host "[WARN] export_ingest_pack_v1.0.ps1 manquant."; return } & 'powershell.exe' -NoProfile -ExecutionPolicy Bypass -File $exp -Reason $reason 2>&1 } function Get-ActiveSessionPath(){ if(Test-Path $lockFlg){ $p=(Get-Content -LiteralPath $lockFlg -Raw -Encoding UTF8).Trim() if($p -and (Test-Path $p)){ return $p } } return $null } function Start-DevSession(){ if(Get-ActiveSessionPath){ Write-Host "[BLOCKING] Une session est déjà active. Finalise-la d'abord."; return } $feature='' while([string]::IsNullOrWhiteSpace($feature)){ $feature=Read-Host "Nom court de la fonctionnalité (ex: HUB-LL analyse prod - import X)" } $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $slug=$feature -replace '[^A-Za-z0-9\-_ ]','' $slug=$slug -replace '\s+','_' $sess=Join-Path $devRoot ($ts+'_'+$slug); Ensure-Dir $sess; Ensure-Dir (Join-Path $sess 'defects') $meta=Join-Path $sess 'session.meta.txt' $who=$env:USERNAME $hostn=$env:COMPUTERNAME # FIX: ne pas écraser $host réservé $lines=@(); $lines+="=== DEV SESSION META ==="; $lines+=("Start : {0}" -f (Get-Date -Format s)); $lines+=("User : {0}@{1}" -f $who,$hostn); $lines+=("Feature: {0}" -f $feature) [IO.File]::WriteAllLines($meta,$lines,[Text.UTF8Encoding]::new($false)) [IO.File]::WriteAllText($lockFlg,$sess,[Text.UTF8Encoding]::new($false)) Write-Host ("[LOCKED] Session démarrée -> {0}" -f $sess) Write-Host "Pendant le lock : commits (Write) interdits ; utilise Preview + Log Bug ; finalise quand c'est stable." } function Resume-DevSession(){ $p=Get-ActiveSessionPath; if($null -eq $p){ Write-Host "[INFO] Aucune session active." } else { Write-Host ("Session active -> {0}" -f $p) } } function Log-DevBug(){ $sess=Get-ActiveSessionPath; if($null -eq $sess){ Write-Host "[ERR] Aucune session active. Lance 'Start Dev Session'."; return } $defDir=Join-Path $sess 'defects'; Ensure-Dir $defDir $ts=Get-Date -Format 'yyyyMMdd_HHmmss'; $file=Join-Path $defDir ('bug_'+$ts+'.txt') $title=''; while([string]::IsNullOrWhiteSpace($title)){ $title = Read-Host "Résumé court (obligatoire)" } $area =''; while([string]::IsNullOrWhiteSpace($area )){ $area = Read-Host "Composant/zone (obligatoire)" } $desc =''; while([string]::IsNullOrWhiteSpace($desc )){ $desc = Read-Host "Détails (obligatoire)" } $tail=''; if(Test-Path $logsDir){ $last=(Get-ChildItem -LiteralPath $logsDir -Filter 'run_*' -File | Sort-Object LastWriteTime -Descending | Select -First 1); if($last){ $tail=(Get-Content -LiteralPath $last.FullName -Tail 30 | Out-String) } } $lines=@(); $lines+="=== DEV DEFECT ==="; $lines+=("When : {0}" -f (Get-Date -Format s)); $lines+=("Area : {0}" -f $area); $lines+=("Title: {0}" -f $title); $lines+=("Desc : {0}" -f $desc); $lines+=""; $lines+="--- Tail last run ---"; if($tail -ne ''){ $lines += $tail.TrimEnd() } else { $lines += "(no recent run log)" } [IO.File]::WriteAllLines($file,$lines,[Text.UTF8Encoding]::new($false)); Write-Host ("Bug loggé -> {0}" -f $file) } function Finalize-DevSession(){ $sess=Get-ActiveSessionPath; if($null -eq $sess){ Write-Host "[ERR] Aucune session active."; return } Write-Host "[FINALIZE] Analyse des blockers (asserts-only)." $ok=KB-Validate -Export -Suggest -Profile '' $lastRep=(Get-ChildItem -LiteralPath $reports -Filter 'kb_guard_report_*.txt' -File | Sort-Object LastWriteTime -Descending | Select -First 1) if($lastRep){ Copy-Item -LiteralPath $lastRep.FullName -Destination (Join-Path $sess 'ASSERTS_SUGGESTED.txt') -Force } Export-IngestPack -reason ("dev-session finalize: "+(Split-Path $sess -Leaf)) Write-Host "[ACTION] 1) Colle/ajoute les lignes proposées dans [KB_GUARD_ASSERTS] (via apply_kb_guard_asserts)." Write-Host " 2) Rebuild Preview puis Validate, puis Release." } function Release-DevLock(){ $sess=Get-ActiveSessionPath; if($null -eq $sess){ Write-Host "[INFO] Aucune session active."; return } Write-Host "[CHECK] Validation asserts-only avant release." $ok=KB-Validate -Profile '' if(-not $ok){ Write-Host "[BLOCKING] Des blockers non couverts subsistent."; return } $meta=Join-Path $sess 'session.meta.txt'; if(Test-Path $meta){ Add-Content -LiteralPath $meta -Value ("End : {0}" -f (Get-Date -Format s)) -Encoding UTF8 } Remove-Item -LiteralPath $lockFlg -Force Write-Host "[RELEASED] DEV-LOCK retiré. Les commits redeviennent possibles." } function Show-Pending(){ $ptr=Join-Path $outbox 'PENDING_LAST.txt' if(Test-Path $ptr){ $path=(Get-Content -LiteralPath $ptr -Raw -Encoding UTF8).Trim() if($path -and (Test-Path $path)){ if(Test-Path (Join-Path $path 'INGEST_PENDING.flag')){ Write-Host ("[PENDING] Pack d'ingestion en attente -> {0}" -f $path) } } } if(Test-Path $lockFlg){ $p=(Get-Content -LiteralPath $lockFlg -Raw -Encoding UTF8).Trim() if($p -and (Test-Path $p)){ Write-Host ("[DEV-LOCK] Session active -> {0}" -f $p) } } } function Run-Selected([string]$mode){ $sel = Choose-Profile; if($null -eq $sel){ return } $ts=Get-Date -Format 'yyyyMMdd_HHmmss'; $log=Join-Path $logsDir ('run_'+$sel.Label+'_'+$mode+'_'+$ts+'.txt') if(!(Test-Path $sel.Script)){ Write-Host ("[ERR] Script introuvable: {0}" -f $sel.Script); return } $preBP = FileSha $bootpack; $preKB = FileSha $kbfile if($mode -eq 'Write'){ if(Test-Path $lockFlg){ Write-Host "[GATE BLOCKED] DEV-LOCK actif : commits interdits."; return } if(-not (Ask-YesNo ("Confirmer l'écriture réelle pour "+$sel.Label))){ Write-Host "Annulé."; return } if($EnforceKBGate){ $ok=KB-Validate -Profile $sel.Label; if(-not $ok){ Write-Host "[GATE BLOCKED] KB blockers non couverts (asserts-only)."; 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" } if($mode -eq 'Write'){ $postBP = FileSha $bootpack; $postKB = FileSha $kbfile if(($preBP -ne $postBP) -or ($preKB -ne $postKB)){ Write-Host "[INFO] Changement détecté (BootPack/KB). Export du pack d'ingestion."; Export-IngestPack -reason "post-write change" } } } function Show-Menu(){ Clear-Host; Show-Pending Write-Host "=== GOV PROFILE LAUNCHER v1.8.1 - DEV-LOCK strict ===" Write-Host "1) Lancer profil - Preview" Write-Host "2) Lancer profil - Write (commit) [Gate asserts-only]" Write-Host "3) Audit MANIFEST (recalc vs fichier)" Write-Host "4) KB GUARDS - VALIDATE COVERAGE (asserts-only)" Write-Host "5) KB GUARDS - EXPORT REPORT" Write-Host "6) KB GUARDS - SUGGEST ASSERTS" Write-Host "----- Session de développement -----" Write-Host "F) Start Dev Session (DEV-LOCK)" Write-Host "R) Resume Dev Session" Write-Host "L) Log DEV BUG" Write-Host "X) Finalize Session" Write-Host "U) Release DevLock" 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' { KB-Validate -Profile '' | Out-Null ; Pause } '5' { KB-Validate -Export -Profile '' | Out-Null ; Pause } '6' { KB-Validate -Suggest -Profile '' | Out-Null ; Pause } 'F' { Start-DevSession ; Pause } 'R' { Resume-DevSession ; Pause } 'L' { Log-DevBug ; Pause } 'X' { Finalize-DevSession ; Pause } 'U' { Release-DevLock ; Pause } '0' { return } default { Write-Host "Sélection invalide." ; Start-Sleep -Seconds 1 } } }