# build_bootpack_and_manifest_hub_v1.0.ps1 # Objet : Mettre à jour bootpack.txt (POLICY=L0), injecter [SYNC_MANIFEST] et [PROJECT_PROFILE_REF] (HUB), recalculer SHA, garder TXT-ONLY (UTF-8 sans BOM), SAFE-WRITE (preview->confirm->write, .bak, SHA-chain, footer). # Profil : HUB_LYON_LUMIERE_ANALYSE_PROD (PS 5.1, Excel Desktop, pas d'admin). Encodage scripts .ps1 = UTF-8 BOM (ce fichier), fichiers .txt générés = UTF-8 sans BOM. # Règles actives visées : GOV_SCRIPT_GATE v1.4 ; SAFE-WRITE v1.1 ; TXT-ONLY v1.0 ; SYNC-MEM-ARCHIVE-RULE v1.0 ; SYNC-GUARD v1.1 param( [switch]$Preview, [switch]$Write ) function New-Nonce([int]$n=10){ $a=(48..57+65..90+97..122); -join (1..$n|%{[char]($a|Get-Random)}) } function Write-NoBOM([string]$Path,[string]$Text){ $enc=New-Object System.Text.UTF8Encoding($false); [IO.File]::WriteAllText($Path,$Text,$enc) } function Get-FileSHA256([string]$Path){ if(Test-Path $Path){ (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash } else { '' } } function Compute-TextSHA256([string]$Text){ $sha=[Security.Cryptography.SHA256]::Create(); $bytes=[Text.Encoding]::UTF8.GetBytes($Text); ($sha.ComputeHash($bytes)|%{ $_.ToString('x2') }) -join '' } function Ensure-Parent([string]$Path){ $p=Split-Path -Parent $Path; if($p -and !(Test-Path $p)){ New-Item -ItemType Directory -Force -Path $p|Out-Null } } function Safe-ReplaceFile([string]$Target,[string]$Text){ Ensure-Parent $Target $ts=Get-Date -Format 'yyyyMMdd_HHmmss' $tmp="$Target.$ts.tmp"; $bak="$Target.bak_$ts" Write-NoBOM $tmp $Text $shaTmp=(Get-FileHash -Algorithm SHA256 -LiteralPath $tmp).Hash if(Test-Path $Target){ Copy-Item -LiteralPath $Target -Destination $bak -Force } Move-Item -LiteralPath $tmp -Destination $Target -Force $shaFinal=(Get-FileHash -Algorithm SHA256 -LiteralPath $Target).Hash @{Bak=$bak;ShaTmp=$shaTmp;ShaFinal=$shaFinal} } function Replace-Section([string]$Content,[string]$Name,[string]$Body){ $pattern = "(?ms)^\[" + [regex]::Escape($Name) + "\]\s*.*?(?=^\[|\Z)" $block = "["+$Name+"]`r`n"+($Body.Trim())+"`r`n" if([regex]::IsMatch($Content,$pattern)){ return [regex]::Replace($Content,$pattern,$block) } else { return ($Content.TrimEnd()+"`r`n`r`n"+$block) } } function Read-FileText([string]$p){ if(Test-Path $p){ [IO.File]::ReadAllText($p,[Text.UTF8Encoding]::new($true)) } else { '' } } function Count-KBEntries([string]$kbPath){ $raw=Read-FileText $kbPath # Strip footer éventuel après la dernière ']}' $idx=$raw.LastIndexOf(']}'); if($idx -gt 0){ $raw=$raw.Substring(0,$idx+2) } try{ ($raw | ConvertFrom-Json).entries.Count }catch{ [regex]::Matches($raw,'"id"\s*:').Count } } # ---- Chemins & constantes $Root='\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry' $BootPack="$Root\bootpack\bootpack.txt" $KBPointerPath="$Root\bootpack\bootpack_paste.txt" $ProfileFile="$Root\profiles\project\HUB\PROJECT_PROFILE.txt" $Snapshot="$Root\snapshots\snapshot_{0}.json" -f (Get-Date -Format 'yyyy-MM-dd') $L0=@( 'GOV_SCRIPT_GATE v1.4', 'SAFE-WRITE v1.1', 'TXT-ONLY v1.0', 'SYNC-MEM-ARCHIVE-RULE v1.0', 'SYNC-GUARD v1.1' ) # ---- Lecture BootPack if(!(Test-Path $BootPack)){ throw "[BLOCKING] BootPack introuvable: $BootPack" } $bp=Read-FileText $BootPack # ---- POLICY -> L0 $policyBody=($L0 -join " ; ") $bp=Replace-Section $bp 'POLICY' $policyBody # ---- PROJECT_PROFILE_REF (HUB) $profileBody = "PROFILE_FILE: $ProfileFile`r`nPROFILE_SHA256: " $bp=Replace-Section $bp 'PROJECT_PROFILE_REF' $profileBody # ---- BUG_KB_JSON_POINTER (recalcul) $kbSha=Get-FileSHA256 $KBPointerPath $kbEntries=Count-KBEntries $KBPointerPath $kbSize=(Test-Path $KBPointerPath) ? (Get-Item $KBPointerPath).Length : 0 $kbUpdated=(Get-Item $KBPointerPath).LastWriteTimeUtc.ToString("yyyy-MM-ddTHH:mm:ssZ") $kbBody = @" Path: $KBPointerPath SHA256: $kbSha Entries: $kbEntries SizeBytes: $kbSize Updated: $kbUpdated "@ $bp=Replace-Section $bp 'BUG_KB_JSON_POINTER' $kbBody # ---- Snapshot (crée si absent) if(!(Test-Path $Snapshot)){ Ensure-Parent $Snapshot; Write-NoBOM $Snapshot "{}`r`n" } $snapSha=Get-FileSHA256 $Snapshot # ---- FILES_SHA256 (règles de référence) $files=@( "$Root\REGLES_GOUVERNANCE.txt", "$Root\SAFE-WRITE-RULE_v1.1.txt", "$Root\GOV_SCRIPT_GATE_v1.4.txt", "$Root\TXT-ONLY-POLICY_v1.0.txt" ) $lines=@() foreach($f in $files){ $h=Get-FileSHA256 $f; if(!$h){ $h='MISSING' } $lines += " $f = $h" } $filesShaBlock = ($lines -join "`r`n") # ---- SYNC_MANIFEST (avec MANIFEST_SHA256 calculé) $memId=(Get-Date).ToString('yyyy-MM-ddTHH:mm:ssK') $nonce=New-Nonce 10 $syncCore = @" MEM_SYNC_ID: $memId RULESET_ACTIVE: $($L0 -join ' ; ') SNAPSHOT_FILE: $Snapshot SNAPSHOT_SHA256: $snapSha FILES_SHA256: $filesShaBlock NONCE: $nonce "@.Trim() $manifestSha = Compute-TextSHA256 $syncCore $syncBody = "$syncCore`r`nMANIFEST_SHA256: $manifestSha" $bp=Replace-Section $bp 'SYNC_MANIFEST' $syncBody # ---- KB_GUARD_ASSERTS (stub utile) $kbAsserts=@" KB-AUTO-ps-5-1-operateur-ternaire-non-supporte-20251018_135633 = covered_by: RULE_PS51_NO_TERNARY v1.0 KB-AUTO-ps-5-1-select-string-sans-recurse-boucler-fichiers-20251018_160307 = covered_by: RULE_SEARCH_NO_RECURSE v1.0 KB-AUTO-join-path-ne-pas-passer-un-tableau-en-childpath-20251018_161141 = covered_by: RULE_SAFE_JOINPATH v1.0 KB-AUTO-safe-write-creer-repertoires-avant-tmp-20251018_160648 = covered_by: RULE_SAFE_WRITE_ENSURE_PARENT v1.0 KB-AUTO-ps-5-1-ps1-en-utf-8-avec-bom-ou-ascii-strict-20251018_162239 = covered_by: RULE_PS1_UTF8_BOM v1.0 KB-AUTO-executionpolicy-allsigned-sur-unc-utiliser-bypass-ou-wrapper-cmd-20251018_165000 = covered_by: RULE_CMD_WRAPPER_BYPASS v1.0 "@ $bp=Replace-Section $bp 'KB_GUARD_ASSERTS' $kbAsserts # ---- PROJECT_PROFILE.txt (HUB) + SHA $profileTxt=@" PROJECT=HUB_LYON_LUMIERE_ANALYSE_PROD RUNTIME=PS_5.1 ENCODING=.ps1=UTF8_BOM ; .txt=UTF8_noBOM CONSTRAINTS=NoAdmin ; UNC ; OneDriveProSeparate "@ Ensure-Parent $ProfileFile; Write-NoBOM $ProfileFile $profileTxt $profileSha=Get-FileSHA256 $ProfileFile $bp=Replace-Section $bp 'PROJECT_PROFILE_REF' ("PROFILE_FILE: {0}`r`nPROFILE_SHA256: {1}" -f $ProfileFile,$profileSha) # ---- Footer (doc-version-footer) $footer="; DOC-VERSION-FOOTER: build_bootpack_and_manifest_hub_v1.0.ps1 | "+(Get-Date -Format 'yyyy-MM-dd')+" | SHA="+(Compute-TextSHA256 $bp) # ---- Plan (Preview) $plan=@" == PREVIEW :: BUILD BOOTPACK (HUB) == Target BootPack : $BootPack Snapshot : $Snapshot (SHA=$snapSha) PROFILE_FILE : $ProfileFile (SHA=$profileSha) KB Pointer : $KBPointerPath (SHA=$kbSha, Entries=$kbEntries, Size=$kbSize, Updated=$kbUpdated) RULESET_ACTIVE : $($L0 -join ' ; ') MEM_SYNC_ID : $memId MANIFEST_SHA256 : $manifestSha "@ Write-Host $plan if(-not $Write){ if(-not $Preview){ $r=Read-Host "Confirmer l'écriture ? (O/N)"; if($r -notin @('O','o','Y','y')){ Write-Host "Abandon (preview only)."; exit 0 } } else { Write-Host "Mode PREVIEW : aucune écriture effectuée."; exit 0 } } # ---- Ecriture SAFE-WRITE $final = ($bp.TrimEnd()+"`r`n"+$footer+"`r`n") $result = Safe-ReplaceFile -Target $BootPack -Text $final Write-Host ("Backup : {0}" -f $result.Bak) Write-Host ("SHA tmp/final: {0} / {1}" -f $result.ShaTmp,$result.ShaFinal) Write-Host ("SYNC-COMMIT-OK - MEM_SYNC_ID={0}" -f $memId)