# ============================================================================ # kb_bootpack_acceptance_v1.1.ps1 (comments-only header) PS 5.1 STRICT # Purpose # Read-only Acceptance Gate for BootPack. Prints exactly one METRICS line # and optionally one STATUS line per IO SPEC v1.0. # IMPORTANT: Acceptance reads ONLY the embedded snapshot [BUG_KB_JSON]. # It does NOT read the external KB file; the pointer is used only to # compute ptr_exists and ptr_sha_match and to echo ptr_entries_note. # # Inputs # -Root : Root folder of the SoT (expects bootpack\bootpack.txt). # Example: # -Root "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" # # Outputs (exact IO format, single-line, no spaces around '|' or '=' keys): # entries={int}|blocking={int}|ptr_exists={True|False}|ptr_sha_match={True|False}|ptr_entries_note={int}|json_entries={int} # Then optionally: # [PASS] BootPack acceptance OK. # or # [GATE-BLOCK] (e.g., missing or invalid snapshot) # # Semantics # - acceptance: reads the FIRST [BUG_KB_JSON] fenced block (``` ... ```), parses JSON. # - selftest : separate probe that reads external KB via [BUG_KB_JSON_POINTER] and prints diags. # - Normal states: # * After ingest but before rebuild: acceptance shows old Entries (E0); ptr_sha_match=False. # * After rebuild (BootPack up-to-date): acceptance entries=json_entries=E; ptr_sha_match=True. # # Non-functional constraints (governance) # - PowerShell 5.1 only. No PS7 tokens (?. ??), no ternary ?:, no here-strings. # - Encoding: this file must be saved as UTF-8 with BOM; ASCII-safe characters only. # - Read-only: no write operations to user files. # # Reference docs (SoT: _registry\docs) # - PIPE_DEV_SAFETY_CONTRACT_v1.0.txt # - PRE-ACCEPTANCE_LINTER_CHECKLIST_v1.0.txt # - TRUTH_TABLE_acceptance_selftest_v1.0.txt # - GLOSSAIRE_metrics_v1.0.txt # - IO_SPEC_acceptance_selftest_v1.0.txt # # Change scope in this variant: COMMENTS ONLY. No logic changes. # Timestamp: 2025-11-01T23:50:08Z # ============================================================================ # kb_bootpack_acceptance_v1.1.ps1 # PS 5.1 only. UTF-8 with BOM. ASCII-safe. # Read-only acceptance: robust ptr_exists, same outputs as v1.0. # Usage: # powershell -NoProfile -ExecutionPolicy Bypass -File .\kb_bootpack_acceptance_v1.1.ps1 -Root "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry" param( [Parameter(Mandatory=$true)] [string]$Root ) function Read-All { param([string]$Path) $raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8 if($null -eq $raw){ return @() } $lines = $raw -split "(`r`n|`n|`r)" if($lines.Length -gt 0){ $first = $lines[0] if($null -ne $first -and $first.Length -gt 0){ $bom = [char]0xFEFF if($first[0] -eq $bom){ $lines[0] = $first.Substring(1) } } } return ,$lines } function Parse-Pointer { param([string[]]$Lines) $p=''; $sha=''; $ent=-1; $in=$false for($i=0;$i -lt $Lines.Length;$i++){ $line=$Lines[$i]; if($null -eq $line){ $line='' } if($line -match '^\s*\[BUG_KB_JSON_POINTER\]\s*$'){ $in=$true; continue } if($in){ $probe = $line.Trim() if($probe -match '^\s*\['){ break } if($line -match '^\s*Path\s*[:=]\s*(.+?)\s*$'){ $p=$Matches[1] } elseif($line -match '^\s*SHA256\s*[:=]\s*([0-9A-Fa-f]{64})\s*$'){ $sha=$Matches[1].ToUpperInvariant() } elseif($line -match '^\s*Entries\s*[:=]\s*(\d+)\s*$'){ $ent=[int]$Matches[1] } } } return @{ Path=$p; SHA256=$sha; Entries=$ent } } function Extract-JsonFromBoot { param([string[]]$Lines) $idxStart=-1; $idxFenceStart=-1; $idxFenceEnd=-1 for($i=0;$i -lt $Lines.Length;$i++){ $line=$Lines[$i] if($idxStart -lt 0){ if($line -match '^\s*\[BUG_KB_JSON\]\s*$'){ $idxStart=$i; continue } } else { if($idxFenceStart -lt 0){ if($line -match '^\s*```'){ $idxFenceStart=$i+1; continue } } else { if($line -match '^\s*```'){ $idxFenceEnd=$i-1; break } } } } if($idxStart -lt 0 -or $idxFenceStart -lt 0 -or $idxFenceEnd -lt 0 -or $idxFenceEnd -lt $idxFenceStart){ throw "[BUG_KB_JSON] missing or not fenced." } $jsonLines = @() for($j=$idxFenceStart;$j -le $idxFenceEnd;$j++){ $jsonLines+=,$Lines[$j] } $json = [string]::Join("`n", $jsonLines) return $json } function Test-PathDeterministic { param([string]$PathRaw) $res = @{ Exists=$false; Used=$null } $pRaw = $PathRaw if($null -ne $pRaw){ $pDeq = $pRaw.Trim() if(($pDeq.StartsWith('"')) -and ($pDeq.EndsWith('"'))){ $pDeq = $pDeq.Substring(1, $pDeq.Length-2) } else { $pDeq = $pRaw } if(Test-Path -LiteralPath $pRaw){ $res.Exists=$true; $res.Used=$pRaw } elseif(Test-Path -LiteralPath $pDeq){ $res.Exists=$true; $res.Used=$pDeq } elseif(($pDeq.StartsWith('\')) -and ($pDeq.Length -ge 3)){ $rest = $pDeq.Substring(2) $pLong = '\\?\UNC\' + $rest if(Test-Path -LiteralPath $pLong){ $res.Exists=$true; $res.Used=$pLong } } } return $res } function Get-JsonObjectSafe { param([string]$JsonText) $cut = $JsonText $lastBrace = $cut.LastIndexOf('}') if($lastBrace -gt 0){ $cut = $cut.Substring(0, $lastBrace+1) } $obj = ConvertFrom-Json -InputObject $cut return $obj } # MAIN $boot = Join-Path $Root 'bootpack\bootpack.txt' if(-not (Test-Path -LiteralPath $boot)){ Write-Host "[GATE-BLOCK] bootpack.txt not found." -ForegroundColor Red Write-Host "entries=0|blocking=0|ptr_exists=False|ptr_sha_match=False|ptr_entries_note=0|json_entries=0" exit 1 } $lines = Read-All -Path $boot # Parse embedded KB JSON (for counts and blocking) $kbJson = $null; $kbObj = $null try{ $kbJson = Extract-JsonFromBoot -Lines $lines $kbObj = Get-JsonObjectSafe -JsonText $kbJson }catch{ Write-Host "[GATE-BLOCK] [BUG_KB_JSON] missing or invalid." -ForegroundColor Red Write-Host "entries=0|blocking=0|ptr_exists=False|ptr_sha_match=False|ptr_entries_note=0|json_entries=0" exit 2 } # Parse pointer $ptr = Parse-Pointer -Lines $lines $ptrPath = $ptr['Path']; $ptrSha = $ptr['SHA256']; $ptrNote = $ptr['Entries'] # Existence and SHA (deterministic, UNC long path fallback) $ptrExists=$false; $used=$null; $ptrShaReal='' if(-not [string]::IsNullOrWhiteSpace($ptrPath)){ $tp = Test-PathDeterministic -PathRaw $ptrPath if($tp.Exists){ $ptrExists = $true $used = $tp.Used try{ $ptrShaReal = (Get-FileHash -Algorithm SHA256 -LiteralPath $used).Hash.ToUpperInvariant() }catch{ $ptrShaReal='' } } } # Counts $entriesCount=0; $blockingCount=0 if($kbObj -and $kbObj.entries){ $entriesCount = @($kbObj.entries).Count $blockingCount = @($kbObj.entries | Where-Object { $_.blocking -eq $true }).Count } # SHA match $ptrShaMatch = $false if(($ptrSha -ne $null) -and ($ptrSha.Trim().Length -gt 0) -and ($ptrShaReal.Length -gt 0)){ $ptrShaMatch = ($ptrShaReal -ieq $ptrSha.Trim()) } # Summary (same order as v1.0) Write-Host ("entries={0}|blocking={1}|ptr_exists={2}|ptr_sha_match={3}|ptr_entries_note={4}|json_entries={5}" -f ` $entriesCount, $blockingCount, $ptrExists, $ptrShaMatch, ($ptrNote -as [int]), $entriesCount) # Gate decision (parity with v1.0 behavior) if(-not $ptrExists){ Write-Host ("[GATE-BLOCK] KB pointer path not found: " + $ptrPath) -ForegroundColor Red exit 8 } if($blockingCount -gt 0){ $ids = ($kbObj.entries | Where-Object { $_.blocking -eq $true } | ForEach-Object { $_.id }) -join "," Write-Host ("[GATE-BLOCK] blocking ids: " + $ids) -ForegroundColor Red exit 9 } Write-Host "[PASS] BootPack acceptance OK." -ForegroundColor Green exit 0