# requires -version 5.1 # gov_safe_deploy_from_zip_v1.8.ps1 # One-shot SAFE-CREATE with self-check; preview summary + Y/N; acceptance-lite + v1.3 fallback. # ASCII-only; UTF-8 BOM; no here-strings; no ternary. param( [Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$ZipPath, [string]$StageUnpack = "C:\Temp_Gouvernance\_unpack", [string]$StageInstall = "C:\Temp_Gouvernance\install_docdigest", [string]$ExpectedZipSha = "", [switch]$Execute ) function Get-Sha256Hex([string]$Path) { (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash } function Ensure-Dir([string]$Path) { if(-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null } } function NowTag() { Get-Date -Format "yyyyMMdd_HHmmss" } # ---- Self-check (PS5.1) ---- $__scriptPath = $MyInvocation.MyCommand.Definition $__src = Get-Content -LiteralPath $__scriptPath -Raw $__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 } function Map-Dest([string]$Name){ if($Name -match '^BUG_KB__canonical_.*_(resolution|resolved|regression|addendum)\.json\.txt$'){ return 'bug_kb\BUG_KB.json.txt' } if($Name -match '^BUG_KB__canonical_.*\.json\.txt$'){ return 'bug_kb\BUG_KB.json.txt' } if($Name -eq 'KB_BLOCKING_INDEX__docdigest_latest.txt'){ return 'rules\KB_BLOCKING_INDEX__docdigest_latest.txt' } if($Name -eq 'PROVENANCE_POLICY__docdigest_latest.txt'){ return 'rules\PROVENANCE_POLICY__docdigest_latest.txt' } if($Name -eq 'PACKAGING_POLICY__docdigest_latest.txt'){ return 'rules\PACKAGING_POLICY__docdigest_latest.txt' } if($Name -eq 'GOV_SCRIPT_GATE__docdigest_latest.txt'){ return 'rules\GOV_SCRIPT_GATE__docdigest_latest.txt' } if($Name -eq 'SAFE_WRITE_RULE__docdigest_latest.txt'){ return 'rules\SAFE_WRITE_RULE__docdigest_latest.txt' } if($Name -eq 'DELIVERY_VERIFY_POLICY__docdigest_latest.txt'){ return 'rules\DELIVERY_VERIFY_POLICY__docdigest_latest.txt' } if($Name -eq 'SAFE_DEPLOY_POLICY__docdigest_latest.txt'){ return 'rules\SAFE_DEPLOY_POLICY__docdigest_latest.txt' } if($Name -eq 'RULES_INDEX__docdigest_latest.txt'){ return 'rules\RULES_INDEX__docdigest_latest.txt' } if($Name -eq 'NONREG_POLICY__docdigest_latest.txt'){ return 'rules\NONREG_POLICY__docdigest_latest.txt' } if($Name -match '^BOOTPACK__docdigest_v2\.[0-9]+_FULL\.txt$'){ return 'bootpack\bootpack.txt' } if($Name -match '^gov_safe_deploy_from_zip_v[0-9]+\.[0-9]+\.ps1$'){ return 'scripts\' + $Name } if($Name -match '^verify_.*\.ps1$'){ return 'scripts\' + $Name } if($Name -match '^kb_bootpack_acceptance_v[0-9]+\.[0-9]+\.ps1$'){ return 'scripts\' + $Name } return '' } # 0) Verify ZIP if(-not (Test-Path -LiteralPath $ZipPath)){ Write-Host "[FAIL] ZIP introuvable: $ZipPath"; exit 2 } $zipSha = Get-Sha256Hex -Path $ZipPath $zipOk = $true if($ExpectedZipSha -ne ""){ if($zipSha -ne $ExpectedZipSha){ $zipOk = $false } } # 1) Unpack Ensure-Dir -Path $StageUnpack Ensure-Dir -Path $StageInstall $unpack = Join-Path $StageUnpack ("_unpack_" + $(NowTag)) Expand-Archive -LiteralPath $ZipPath -DestinationPath $unpack -Force # 2) Manifest $mf = Join-Path $unpack 'ZIP_MANIFEST.json' if(-not (Test-Path -LiteralPath $mf)){ Write-Host "[FAIL] ZIP_MANIFEST.json introuvable"; exit 2 } $manifest = Get-Content -LiteralPath $mf -Raw | ConvertFrom-Json # 3) Plan $plan = @(); $errors = @() foreach($it in $manifest){ $name = [string]$it.file $shaM = [string]$it.sha256 $path = Join-Path $unpack $name if(-not (Test-Path -LiteralPath $path)){ $errors += "[MISSING IN ZIP] " + $name; continue } $shaF = Get-Sha256Hex -Path $path if($shaF -ne $shaM){ $errors += "[SHA MISMATCH] " + $name + " got=" + $shaF + " manifest=" + $shaM; continue } $destRel = Map-Dest -Name $name if($destRel -eq ''){ $errors += "[UNKNOWN DEST] " + $name; continue } $destAbs = Join-Path $Root $destRel $plan += @{ Name=$name; Src=$path; Sha=$shaF; DestRel=$destRel; DestAbs=$destAbs } } # 4) Preview Write-Host "================ PREVIEW SUMMARY ================" Write-Host ("ZIP : " + $ZipPath) Write-Host ("SHA : " + $zipSha) $zipOkStr = "NO"; if($zipOk){ $zipOkStr = "YES" } Write-Host ("ZIP-OK : " + $zipOkStr) Write-Host ("ROOT : " + $Root) Write-Host ("FILES : " + $plan.Count) if($errors.Count -gt 0){ Write-Host "ERRORS :"; foreach($e in $errors){ Write-Host (" - " + $e) } } else { Write-Host "ERRORS : none" } Write-Host "DEPLOY ORDER:" $priority = @( 'rules\NONREG_POLICY__docdigest_latest.txt', 'rules\KB_BLOCKING_INDEX__docdigest_latest.txt', 'rules\PROVENANCE_POLICY__docdigest_latest.txt', 'rules\PACKAGING_POLICY__docdigest_latest.txt', 'rules\GOV_SCRIPT_GATE__docdigest_latest.txt', 'rules\SAFE_WRITE_RULE__docdigest_latest.txt', 'rules\DELIVERY_VERIFY_POLICY__docdigest_latest.txt', 'rules\SAFE_DEPLOY_POLICY__docdigest_latest.txt', 'rules\RULES_INDEX__docdigest_latest.txt', 'scripts\verify_kb_nonregression_v1.0.ps1', 'bootpack\bootpack.txt', 'scripts\kb_bootpack_acceptance_v1.3.ps1', 'scripts\gov_safe_deploy_from_zip_v1.7.ps1', 'scripts\gov_safe_deploy_from_zip_v1.8.ps1' ) foreach($rel in $priority){ foreach($p in $plan){ if($p.DestRel -eq $rel){ Write-Host (" - " + $p.Name + " -> " + $p.DestRel + " | sha=" + $p.Sha) } } } Write-Host "=================================================" if(-not $Execute){ $ans = Read-Host "Proceed? (Y/N)" if($ans -ne "Y"){ Write-Host "Aborted by user."; exit 0 } } if($errors.Count -gt 0){ Write-Host "[FAIL] Aborting due to preview errors."; exit 2 } function Install-One([hashtable]$I){ $src = $I.Src; $dst = $I.DestAbs; $shaSrc = $I.Sha $dstDir = Split-Path -Parent $dst if(-not (Test-Path -LiteralPath $dstDir)){ New-Item -ItemType Directory -Path $dstDir | Out-Null } $tmp = "$dst.__tmp__" Copy-Item -LiteralPath $src -Destination $tmp -Force $shaTmp = Get-Sha256Hex -Path $tmp if($shaTmp -ne $shaSrc){ throw "SHA mismatch after tmp copy for $($I.Name)" } $bak = "$dst.bak_" + $(NowTag) if(Test-Path -LiteralPath $dst){ Copy-Item -LiteralPath $dst -Destination $bak -Force } Move-Item -LiteralPath $tmp -Destination $dst -Force $shaFinal = Get-Sha256Hex -Path $dst if($shaFinal -ne $shaSrc){ throw "SHA mismatch after final move for $($I.Name)" } Write-Host ("[OK] " + $I.DestRel) } foreach($rel in $priority){ foreach($p in $plan){ if($p.DestRel -eq $rel){ Install-One -I $p } } } # Acceptance-lite $kb = Join-Path $Root 'bug_kb\BUG_KB.json.txt' $bp = Join-Path $Root 'bootpack\bootpack.txt' if((Test-Path -LiteralPath $kb) -and (Test-Path -LiteralPath $bp)){ $kbSha = Get-Sha256Hex -Path $kb $ptrSha = "" $bpTxt = Get-Content -LiteralPath $bp -Raw $m = [regex]::Match($bpTxt, '^\s*SHA256\s*=\s*([0-9A-Fa-f]{64})', [System.Text.RegularExpressions.RegexOptions]::Multiline) if($m.Success){ $ptrSha = $m.Groups[1].Value.ToUpper() } $match = "False" if(($ptrSha -ne "") -and ($ptrSha -eq $kbSha.ToUpper())){ $match = "True" } Write-Host "----- ACCEPTANCE-LITE -----" Write-Host ("KB_SHA=" + $kbSha + " | BOOTPACK_PTR_SHA=" + $ptrSha + " | ptr_sha_match=" + $match) $cnt = 0; $blk = 0 Get-Content -LiteralPath $kb | ForEach-Object { $t = $_.Trim() if($t -match '^\{.*\}\s*$'){ $cnt = $cnt + 1 } if($t -match '"blocking"\s*:\s*true'){ $blk = $blk + 1 } } Write-Host ("entries=" + $cnt + " | blocking=" + $blk) Write-Host "---------------------------" } # Acceptance externe $acc13 = Join-Path $Root 'scripts\kb_bootpack_acceptance_v1.3.ps1' if(Test-Path -LiteralPath $acc13){ powershell -NoProfile -ExecutionPolicy Bypass -File $acc13 -Root $Root } Write-Host "[OK] Script gov_safe_deploy_from_zip_v1.8.ps1 installé et permissions modifiées."