param([switch]$Preview=$true,[switch]$Execute) # === PARAMS === $Root = "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" $UpdateDir = Join-Path $Root "updates\2025\10\structure" $UpdatePath = Join-Path $UpdateDir "REGISTRY_STRUCTURE_UPDATE_2025-10-18.txt" $CoreDir = Join-Path $Root "__CORE__" $IndexRegles = Join-Path $CoreDir "INDEX_REGLES.txt" $Changelog = Join-Path $CoreDir "CHANGELOG_REGLES.txt" $LogsDir = Join-Path $Root "logs" $BootpackPath = Join-Path (Join-Path $Root "bootpack") "bootpack.txt" $Policy = "TXT-ONLY v1.0; SAFE-WRITE v1.1; GOV_SCRIPT_GATE v1.3" $IsoNow = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK" $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) $Stage = "C:\Temp_Gouvernance" # === Ensure dirs === foreach($d in @($UpdateDir,$CoreDir,$LogsDir,$Stage)){ if(!(Test-Path $d)){ New-Item -ItemType Directory -Force -Path $d | Out-Null } } # === Helpers === function Get-FileSHA256([string]$p){ $sha=[Security.Cryptography.SHA256]::Create() $fs=[IO.File]::Open($p,'Open','Read','ReadWrite') try{([BitConverter]::ToString($sha.ComputeHash($fs))).Replace('-','').ToLower()}finally{$fs.Dispose();$sha.Dispose()} } function Write-Safe([string]$Dest,[string[]]$Body,[string]$SourceTag){ $footer=@('','--- DOC-VERSION-FOOTER ---',"Generated: $IsoNow",'SHA-256: ',"Policy: $Policy","Source: $SourceTag") if([IO.Path]::GetExtension($Dest).ToLower() -eq '.ps1'){ $footer=$footer|%{ '# '+$_ } } $name=[IO.Path]::GetFileName($Dest) $stage=Join-Path $Stage $name [IO.File]::WriteAllText($stage,(($Body+$footer)-join "`r`n"),$Utf8NoBom) $h0=Get-FileSHA256 $stage [IO.File]::WriteAllText($stage,([IO.File]::ReadAllText($stage,$Utf8NoBom).Replace('SHA-256: ',"SHA-256: $h0")),$Utf8NoBom) $h1=Get-FileSHA256 $stage $tmp="$Dest.tmp" if(!(Test-Path (Split-Path $Dest))){ New-Item -ItemType Directory -Force -Path (Split-Path $Dest) | Out-Null } Copy-Item $stage $tmp -Force if((Get-FileSHA256 $tmp) -ne $h1){ throw "SAFE-WRITE: mismatch pour $Dest" } if($PSBoundParameters.ContainsKey('Preview') -and $Preview -and -not $Execute){ return @{Path=$Dest;Hash=$h1;Preview=$true} } if(Test-Path $Dest){ Copy-Item $Dest ($Dest+".bak_"+(Get-Date -Format 'yyyyMMdd_HHmmss')) -Force } Move-Item $tmp $Dest -Force return @{Path=$Dest;Hash=$h1;Preview=$false;Size=(Get-Item $Dest).Length} } function Read-Bootpack([string]$Path){ if(!(Test-Path $Path)){ throw "bootpack.txt introuvable: $Path" } $lines = Get-Content -LiteralPath $Path $sec=''; $map=@{} foreach($ln in $lines){ if($ln -match "^\s*\[(.+?)\]\s*$"){ $sec=$Matches[1]; $map[$sec]=@(); continue } if($sec){ $map[$sec]+=$ln } } $map } function Test-GovScriptGate([string]$BpPath){ $res=[ordered]@{ Status='OK'; Reasons=@() } $bp=Read-Bootpack -Path $BpPath foreach($req in @('BOOT-PACK','BUG_KB_JSON','POLICY')){ if(-not $bp.ContainsKey($req)){ $res.Status='BLOCKED'; $res.Reasons+="$req section manquante" } } if($bp.ContainsKey('POLICY')){ $p = ($bp['POLICY'] -join "`n") if($p -notmatch 'AllowedTextExt\s*=\s*txt'){ $res.Status='BLOCKED'; $res.Reasons+='POLICY: AllowedTextExt != txt' } if($p -notmatch 'DisallowedTextExt\s*=\s*md'){ $res.Reasons+='POLICY: DisallowedTextExt md absent' } } if($bp.ContainsKey('BUG_KB_JSON')){ $json = ($bp['BUG_KB_JSON'] -join "`n") | Where-Object {$_ -notmatch "^\s*$"} try{ $obj = $json | ConvertFrom-Json $entries = @() if($obj.entries){ $entries = $obj.entries } elseif($obj.issues){ $entries = $obj.issues } foreach($e in $entries){ if($e.blocking -eq $true){ $res.Status='BLOCKED'; $res.Reasons+="BUG_KB blocking: $($e.id)" } } } catch { $res.Status='BLOCKED'; $res.Reasons+='BUG_KB_JSON invalide' } } [pscustomobject]$res } # === Gate === $gate = Test-GovScriptGate $BootpackPath if($gate.Status -ne 'OK'){ Write-Host "[GATE] BLOQUÉ: " ($gate.Reasons -join '; ') exit 2 } # === Corps du document d'update (TXT-ONLY) === $UpdateBody = @( "REGISTRY_STRUCTURE_UPDATE_2025-10-18.txt", "Objet : Extension de la structure _registry pour l’exécution de la gouvernance (TXT-ONLY, SAFE-WRITE v1.1, GOV_SCRIPT_GATE v1.3)", ("Date : {0}" -f $IsoNow), "", "Motif :", "- Adoption TXT-ONLY (2025-10-17) pour tous documents de travail.", "- Introduction SAFE-WRITE v1.1 (écritures atomiques + SHA-256) et GOV_SCRIPT_GATE v1.3 (bootpack + BUG_KB).", "- Besoins d’exploitation : logs, audits, index, hashes, bootpack, bug_kb, archivage threads.", "", "Ajouts entérinés (top-level) :", "- rules/ : dépôt des règles unitaires (travail). __CORE__ demeure l’agrégateur “maître”.", "- scripts/ : automatisation (sous-dossiers : lib/, mem/).", "- logs/ : traces d’exécution.", "- audit_reports/ : rapports d’audit.", "- indices/ : index consolidés (forme TXT/TSV). [Remarque : remplace “index/” ; tolérance legacy.]", "- hashes/ : catalogues de hachages (SHA-256).", "- bootpack/ : source de vérité d’entrée (bootpack.txt).", "- bug_kb/ : base de connaissances BUG_KB (JSON TXT-compatible).", "- threads_archive/ : archives de fils (transcripts/artefacts). Complément à snapshots/.", "- memory_snapshots/ : (si présent) alias/documentation vers snapshots/.", "- modules/ : modules techniques (si utilisés par scripts).", "- update/ : legacy ; le répertoire canonique est “updates/”. Migration progressive sans suppression.", "", "Impacts :", "- Aucune suppression. Les éléments .md historiques restent en _old_versions/ ; leur équivalent .txt devient canonique.", "- Les index et journaux (indices/, hashes/, logs/) suivent TXT-ONLY.", "- Toute future évolution structurelle est à consigner dans updates/YYYY/MM/structure/.", "", "Actions liées :", "- Générer/mettre à jour __CORE__/INDEX_REGLES.txt et __CORE__/CHANGELOG_REGLES.txt pour référencer ces ajouts.", "- Aligner les scripts d’audit et SAFE-SYNC sur indices/ + hashes/ (TXT-ONLY)." ) # === Préparation des MAJ index/changelog (idempotence simple) === $IndexLine = ("{0} | REGISTRY_STRUCTURE_UPDATE_2025-10-18.txt | Extension structure (rules, scripts, logs, audit_reports, indices, hashes, bootpack, bug_kb, threads_archive, memory_snapshots, modules, update) | TXT-ONLY + SAFE-WRITE v1.1 + GOV_SCRIPT_GATE v1.3" -f ($IsoNow.Substring(0,10))) $ChangelogBlock = @( "### {0} - REGISTRY_STRUCTURE_UPDATE_2025-10-18" -f $IsoNow, "- Ajout dossiers techniques pour exécution gouvernance.", "- Respect TXT-ONLY ; introduction SAFE-WRITE v1.1 ; gate GOV_SCRIPT_GATE v1.3.", "- Aucun retrait ; documentation portée dans updates/2025/10/structure/." ) # === PREVIEW === "=== PREVIEW - apply_registry_structure_update_2025-10-18 ===" "Update file : $UpdatePath" "Index règles : $IndexRegles" "Changelog règles: $Changelog" "Log : $(Join-Path $LogsDir 'registry_activity.log')" if($Preview -and -not $Execute){ return } # === Écritures sûres === $rU = Write-Safe -Dest $UpdatePath -Body $UpdateBody -SourceTag 'REGISTRY_STRUCTURE_UPDATE' # INDEX_REGLES (append idempotent) $idxBody=@(); if(Test-Path $IndexRegles){ $idxBody += Get-Content -LiteralPath $IndexRegles -ErrorAction SilentlyContinue } if(($idxBody -join "`n") -notmatch 'REGISTRY_STRUCTURE_UPDATE_2025-10-18\.txt'){ $idxBody += $IndexLine } $rI = Write-Safe -Dest $IndexRegles -Body $idxBody -SourceTag 'REGISTRY_STRUCTURE_UPDATE' # CHANGELOG_REGLES (append block idempotent simple) $chgBody=@(); if(Test-Path $Changelog){ $chgBody += Get-Content -LiteralPath $Changelog -ErrorAction SilentlyContinue } if(($chgBody -join "`n") -notmatch 'REGISTRY_STRUCTURE_UPDATE_2025-10-18'){ $chgBody += @('') + $ChangelogBlock } $rC = Write-Safe -Dest $Changelog -Body $chgBody -SourceTag 'REGISTRY_STRUCTURE_UPDATE' # LOG $LogPath = Join-Path $LogsDir 'registry_activity.log' $logBody=@(); if(Test-Path $LogPath){ $logBody += Get-Content -LiteralPath $LogPath -ErrorAction SilentlyContinue } $logBody += @( "[{0}] write {1} sha256={2}" -f $IsoNow,$rU.Path,$rU.Hash, "[{0}] write {1} sha256={2}" -f $IsoNow,$rI.Path,$rI.Hash, "[{0}] write {1} sha256={2}" -f $IsoNow,$rC.Path,$rC.Hash ) $rL = Write-Safe -Dest $LogPath -Body $logBody -SourceTag 'REGISTRY_STRUCTURE_UPDATE' Write-Host "[OK] REGISTRY_STRUCTURE_UPDATE appliquée." Write-Host (" - Update : {0}" -f $rU.Path) Write-Host (" - Index : {0}" -f $rI.Path) Write-Host (" - Changelog: {0}" -f $rC.Path) Write-Host (" - Log : {0}" -f $rL.Path)