# requires -version 5.1 # gov_safe_deploy_from_zip_v1.0.ps1 # ONE-SHOT SAFE-CREATE from ZIP (Preview-first, end-summary, Y/N confirmation). # ASCII-only; UTF-8 BOM; no here-strings. param( [Parameter(Mandatory=$true)][string]$Root, # \\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry [Parameter(Mandatory=$true)][string]$ZipPath, # path to delivery ZIP [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" } # Map filename -> destination relative path (Full-File Only) function Map-Dest([string]$Name){ if($Name -match '^BUG_KB__canonical_.*_resolved\.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 -match '^BOOTPACK__docdigest_v2\.[0-9]+_FULL\.txt$'){ return 'bootpack\bootpack.txt' } return '' } # 0) Verify ZIP if(-not (Test-Path -LiteralPath $ZipPath)){ Write-Host "[FAIL] ZIP introuvable: $ZipPath"; exit 2 } $zipSha = Get-Sha256Hex -Path $ZipPath if($ExpectedZipSha -ne "" -and $zipSha -ne $ExpectedZipSha){ Write-Host "[FAIL] SHA ZIP mismatch. got=$zipSha expected=$ExpectedZipSha" exit 2 } # 1) Unpack (clean stage) Ensure-Dir -Path $StageUnpack Ensure-Dir -Path $StageInstall $unpack = Join-Path $StageUnpack ("_unpack_" + (NowTag())) Expand-Archive -LiteralPath $ZipPath -DestinationPath $unpack -Force # 2) Read 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) Build plan + verify each file SHA vs manifest $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 summary (end-of-preview, clear) Write-Host "================ PREVIEW SUMMARY ================" Write-Host ("ZIP : {0}" -f $ZipPath) Write-Host ("SHA : {0}" -f $zipSha) Write-Host ("ROOT : {0}" -f $Root) Write-Host ("FILES : {0}" -f ($plan.Count)) if($errors.Count -gt 0){ Write-Host "ERRORS :" foreach($e in $errors){ Write-Host (" - {0}" -f $e) } } else { Write-Host "ERRORS : none" } Write-Host "DEPLOY ORDER:" # deterministic order $priority = @( 'bug_kb\BUG_KB.json.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', 'bootpack\bootpack.txt' ) foreach($rel in $priority){ foreach($p in $plan){ if($p.DestRel -eq $rel){ Write-Host (" - {0} -> {1} | sha={2}" -f $p.Name, $p.DestRel, $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 } # 5) SAFE-CREATE deploy 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] {0}" -f $I.DestRel) } foreach($rel in $priority){ foreach($p in $plan){ if($p.DestRel -eq $rel){ Install-One -I $p }} } # 6) Acceptance (if present) $acc = Join-Path $Root "scripts\kb_bootpack_acceptance_v1.2.ps1" if(Test-Path -LiteralPath $acc){ powershell -NoProfile -ExecutionPolicy Bypass -File $acc -Root $Root } Write-Host "[OK] Script gov_safe_deploy_from_zip_v1.0.ps1 installé et permissions modifiées." # EOF