# rebuild_bootpack_full_hub_v1.1.ps1 # Objet : REGENERER COMPLETEMENT bootpack.txt (HUB). PREVIEW=no-touch. WRITE=SAFE-WRITE (.bak, SHA, footer, log). $(if(# Profil vis){ } else { HUB_LYON_LUMIERE_ANALYSE_PROD (PS 5.1). Rappel KB: scripts .ps1 en UTF-8 **avec BOM** }); .txt en UTF-8 **sans BOM**. # Invariants L0 : 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) # ==== Utilitaires s?rs (PS 5.1-safe) ==== function New-Nonce([int]$n=10){ $a=(48..57+65..90+97..122); $s=''; 1..$n|%{ $s+=[char]($a|Get-Random) }; $s } function Write-NoBOM([string]$Path,[string]$Text){ $enc=New-Object System.Text.UTF8Encoding($false); [IO.File]::WriteAllText($Path,$Text,$enc) } function Append-NoBOM([string]$Path,[string]$Text){ $enc=New-Object System.Text.UTF8Encoding($false); [IO.File]::AppendAllText($Path,$Text,$enc) } function Read-FileText([string]$p){ if(Test-Path $p){ [IO.File]::ReadAllText($p,[Text.UTF8Encoding]::new($true)) } else { '' } } 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 Count-KBEntries([string]$kbPath){ $raw=Read-FileText $kbPath $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 } } # ==== Constantes ==== $Root='\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry' # [PATCH] PTR HEALTHCHECK BEGIN (no write) function Read-Text([string]$p){ if(-not (Test-Path -LiteralPath $p)){ return '' } try { return Get-Content -LiteralPath $p -Raw -Encoding UTF8 } catch { return (Get-Content -LiteralPath $p -Raw) } } function Extract-PointerField([string]$raw,[string]$name){ $(if($(if($pat = "(){ ms)^\[BUG_KB_JSON_POINTER\].*){ ^\s*" + [regex] } else { } else { Escape($name) + "\s*:\s*(.+?)\s*$" }) }) $m = [regex]::Match($raw,$pat); if($m.Success){ return $m.Groups[1].Value.Trim() } else { return '' } } # [PATCH] PTR HEALTHCHECK END # [PATCH] PTR HEALTHCHECK CALL try{ $boot = Join-Path $Root 'bootpack\bootpack.txt' $raw = Read-Text $boot $ptrPath = Extract-PointerField $raw 'Path' if([string]::IsNullOrWhiteSpace($ptrPath)){ Write-Host '[WARN] PTR healthcheck: champ Path introuvable dans [BUG_KB_JSON_POINTER].' } else { if(-not (Test-Path -LiteralPath $ptrPath)){ Write-Host ("[WARN] PTR healthcheck: chemin pointe vers un fichier absent: {0}" -f $ptrPath) } } }catch{ Write-Host "[WARN] PTR healthcheck: exception mineure: $($_.Exception.Message)" } # [PATCH] PTR HEALTHCHECK CALL END $BootPack="$Root\bootpack\bootpack.txt" $KBPointerPath="$Root\bootpack\bootpack_paste.txt" $Snapshot="$Root\snapshots\snapshot_{0}.json" -f (Get-Date -Format 'yyyy-MM-dd') $ProfileFile="$Root\profiles\project\HUB\PROJECT_PROFILE.txt" $LogFile="$Root\logs\registry_activity.log" $L0=@('GOV_SCRIPT_GATE v1.4','SAFE-WRITE v1.1','TXT-ONLY v1.0','SYNC-MEM-ARCHIVE-RULE v1.0','SYNC-GUARD v1.1') # ==== Pr?-contr?les KB (bloquants si absent) ==== if(!(Test-Path $KBPointerPath)){ throw "[BLOCKING] bootpack_paste.txt introuvable : $KBPointerPath" } $kbSha=Get-FileSHA256 $KBPointerPath $kbEntries=Count-KBEntries $KBPointerPath $kbSize=0; if(Test-Path $KBPointerPath){ $kbSize=(Get-Item $KBPointerPath).Length } $kbUpdated=(Get-Item $KBPointerPath).LastWriteTimeUtc.ToString('yyyy-MM-ddTHH:mm:ssZ') # ==== Snapshot (NO-TOUCH en PREVIEW) ==== $hasSnap=Test-Path $Snapshot $snapSha= if($hasSnap){ Get-FileSHA256 $Snapshot } else { 'MISSING' } # ==== PROFILE (NO-TOUCH en PREVIEW) ==== $profileTxt=@" PROJECT=HUB_LYON_LUMIERE_ANALYSE_PROD RUNTIME=PS_5.1 ENCODING=.ps1=UTF8_BOM ; .txt=UTF8_noBOM CONSTRAINTS=NoAdmin ; UNC ; OneDriveProSeparate "@ $hasProfile = Test-Path $ProfileFile $profilePlannedSha = Compute-TextSHA256 $profileTxt $profileSha = if($hasProfile){ Get-FileSHA256 $ProfileFile } else { "PLANNED:$profilePlannedSha" } # ==== FILES_SHA256 des r?gles ==== $refFiles=@("$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 $refFiles){ $h=Get-FileSHA256 $f; if(!$h){ $h='MISSING' }; $lines += " $f = $h" } $filesShaBlock=($lines -join "`r`n") # ==== SYNC_MANIFEST (calcul en m?moire) ==== $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" # ==== Contenu bootpack (RECONSTRUCTION COMPLETE) ==== $bootpackText=@" [BOOT-PACK] PROJECT: HUB_LYON_LUMIERE_ANALYSE_PROD ROOT: $Root DOCS_FORMAT: TXT-ONLY ENCODING: UTF-8 (no BOM) [POLICY] $($L0 -join "`r`n") [PROJECT_PROFILE_REF] PROFILE_FILE: $ProfileFile PROFILE_SHA256: $profileSha [SYNC_MANIFEST] $syncBody [BUG_KB_JSON_POINTER] Path: $KBPointerPath SHA256: $kbSha Entries: $kbEntries SizeBytes: $kbSize Updated: $kbUpdated [KB_GUARD_ASSERTS] 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-20251018_165000 = covered_by: RULE_CMD_WRAPPER_BYPASS v1.0 "@.TrimEnd() # ==== PLAN (PREVIEW) ==== $plan=@" == PREVIEW :: REBUILD BOOTPACK (HUB) == Target BootPack : $BootPack PROFILE_FILE : $ProfileFile (SHA=$profileSha) KB Pointer : $KBPointerPath (SHA=$kbSha, Entries=$kbEntries, Size=$kbSize, Updated=$kbUpdated) SNAPSHOT_FILE : $Snapshot (SHA=$snapSha) RULESET_ACTIVE : $($L0 -join ' ; ') MEM_SYNC_ID : $memId MANIFEST_SHA256 : $manifestSha "@ Write-Host $plan # ==== Sortie si preview explicite ou wrapper sans -Write ==== if(-not $Write){ Write-Host "Mode PREVIEW : aucune ?criture effectu?e." exit 0 } # ==== Materialisation en WRITE uniquement ==== if(-not $hasSnap){ Ensure-Parent $Snapshot; Write-NoBOM $Snapshot "{}`r`n"; $snapSha=Get-FileSHA256 $Snapshot } if(-not $hasProfile){ Ensure-Parent $ProfileFile; Write-NoBOM $ProfileFile $profileTxt; $profileSha=Get-FileSHA256 $ProfileFile } # Recalcule le manifest avec SHA snapshot final + memId frais $memId=(Get-Date).ToString('yyyy-MM-ddTHH:mm:ssK') $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" # Recompose le fichier final + footer $footer="; DOC-VERSION-FOOTER: rebuild_bootpack_full_hub_v1.1.ps1 | "+(Get-Date -Format 'yyyy-MM-dd')+" | SHA="+(Compute-TextSHA256 $bootpackText) $final=($bootpackText.Replace("[SYNC_MANIFEST]", "[SYNC_MANIFEST]`r`n$syncBody").TrimEnd()+"`r`n"+$footer+"`r`n") # Ecriture SAFE-WRITE $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) # Journalisation (TXT-ONLY UTF-8 no BOM) Ensure-Parent $LogFile $logLine=("{0} | action=rebuild_bootpack | project=HUB | mem_sync_id={1} | manifest_sha={2} | bootpack_sha={3}" -f (Get-Date -Format s), $memId, $manifestSha, $result.ShaFinal) Append-NoBOM $LogFile ($logLine+"`r`n") Write-Host ("SYNC-COMMIT-OK - MEM_SYNC_ID={0}" -f $memId) # [PATCH] POST-HOOK :: pointer refresh + acceptance try{ $hook = Join-Path $PSScriptRoot 'post_bootpack_pointer_refresh_v1.0.2.ps1' }catch{ $hook = Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) 'post_bootpack_pointer_refresh_v1.0.2.ps1' } if(Test-Path -LiteralPath $hook){ & powershell -NoProfile -ExecutionPolicy Bypass -File $hook -Root $Root -NoPrompt -Verify }else{ Write-Host '[WARN] post_bootpack_pointer_refresh_v1.0.2.ps1 introuvable (skip).' } # [PATCH] POST-HOOK END