# ============================================================================ # acceptance_selftest_v1.0.ps1 (comments-only header) PS 5.1 STRICT # Purpose # Read-only probe (selftest) for BootPack. Prints the standard METRICS line # and diagnostic lines (diag: ...) per IO SPEC v1.0. # IMPORTANT: Selftest reads the EXTERNAL KB via [BUG_KB_JSON_POINTER]. # It does NOT use the embedded snapshot for metrics (that's acceptance's job). # # Inputs # -BootPackPath : Full path to bootpack.txt (or bootpack_paste.txt). # Example: "\\DS-918\chatgpt\ChatGPT-Gouvernance-Projets\_registry\bootpack\bootpack.txt" # # 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 0..N diagnostic lines starting with "diag:" (First3, PtrPathRaw, TestPathRaw/Dequoted/Long, # OpenReadOK, KbSha, PtrSha, PtrEntriesNote). No PASS line in selftest. # # Semantics (vs acceptance) # - acceptance: reads the FIRST [BUG_KB_JSON] fenced block (embedded snapshot). # - selftest : reads the external KB via pointer to validate path/sha/entries and I/O health. # - Normal states: # * After ingest, before rebuild: selftest shows E1 (KB), ptr_sha_match=False; acceptance shows E0 (snapshot). # * After rebuild: both show E and 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-02T00:09:07Z # ============================================================================ # acceptance_selftest_v1.0.ps1 # PowerShell 5.1 only, UTF-8 with BOM, ASCII-safe comments. # Read-only self test for BootPack acceptance pointer robustness. # Prints the same metrics as the acceptance checker plus diagnostics. # Usage: # powershell -NoProfile -ExecutionPolicy Bypass -File .\acceptance_selftest_v1.0.ps1 -BootPackPath "\\DS-918\...\bootpack\bootpack.txt" param( [Parameter(Mandatory=$true)] [string]$BootPackPath ) function Normalize-Lines { param([string]$Text) if($null -eq $Text){ return @() } # Split on CRLF / LF / CR $lines = $Text -split "(`r`n|`n|`r)" if($lines.Length -gt 0){ # Strip BOM from very first line only if($lines[0].Length -gt 0){ $first = $lines[0] $bom = [char]0xFEFF if($first[0] -eq $bom){ $lines[0] = $first.Substring(1) } } } return ,$lines } function Find-Pointer { param([string[]]$Lines) $idx = -1 for($i=0; $i -lt $Lines.Length; $i++){ $t = $Lines[$i] if($null -ne $t){ $t = $t.Trim() } else { $t = "" } # case-insensitive compare for header if($t.ToUpper() -eq "[BUG_KB_JSON_POINTER]"){ $idx = $i; break } } if($idx -lt 0){ return $null } $path = $null $sha = $null $entriesNote = $null for($j=$idx+1; $j -lt $Lines.Length; $j++){ $line = $Lines[$j] if($null -eq $line){ $line = "" } $probe = $line.Trim() if($probe -like "[[]*[]]*"){ break } # next section starts # Accept "Key=Value" with optional spaces around '=' $eq = $line.IndexOf("=") if($eq -gt 0){ $k = $line.Substring(0, $eq).Trim() $v = $line.Substring($eq+1) # keep internal spaces # Also trim surrounding quotes if they wrap the whole string (deferred to existence tests as well) if($k -ieq "Path"){ $path = $v } elseif($k -ieq "SHA256"){ $sha = $v.Trim() } elseif($k -ieq "Entries"){ $v2 = $v.Trim() [int]$tmp = 0 if([int]::TryParse($v2, [ref]$tmp)){ $entriesNote = $tmp } } } } $h = @{ Path = $path SHA256 = $sha Entries = $entriesNote } return $h } function Test-PathVariants { param([string]$RawPath) $result = @{ Raw = $false Dequoted = $false Long = $false Used = $null } $pRaw = $RawPath $pDeq = $RawPath if($null -ne $pDeq){ $pDeq = $pDeq.Trim() if(($pDeq.StartsWith('"')) -and ($pDeq.EndsWith('"'))){ $pDeq = $pDeq.Substring(1, $pDeq.Length-2) } } if($null -ne $pRaw){ if(Test-Path -LiteralPath $pRaw){ $result.Raw = $true; if($null -eq $result.Used){ $result.Used = $pRaw } } } if(!$result.Raw -and $null -ne $pDeq){ if(Test-Path -LiteralPath $pDeq){ $result.Dequoted = $true; if($null -eq $result.Used){ $result.Used = $pDeq } } } $pLong = $null if(($null -ne $pDeq) -and ($pDeq.StartsWith("\\")) -and ($pDeq.Length -ge 3)){ # Build \\?\UNC\server\share\... $rest = $pDeq.Substring(2) $pLong = "\\?\UNC\" + $rest if(Test-Path -LiteralPath $pLong){ $result.Long = $true; if($null -eq $result.Used){ $result.Used = $pLong } } } return $result } function Read-KbObject { param([string]$KbPath) # Read entire file as raw text (could contain a footer after JSON) $raw = Get-Content -LiteralPath $KbPath -Raw if($null -eq $raw){ throw "KB read returned null" } # If footer marker exists, cut before it. Else, cut at the last '}' character. $cut = $raw $footerMarker = "--- DOC-VERSION-FOOTER ---" $idx = $raw.IndexOf($footerMarker) if($idx -ge 0){ $cut = $raw.Substring(0, $idx) } else { $lastBrace = $raw.LastIndexOf("}") if($lastBrace -gt 0){ $cut = $raw.Substring(0, $lastBrace+1) } } $obj = $null try { $obj = ConvertFrom-Json -InputObject $cut } catch { throw "ConvertFrom-Json failed: $($_.Exception.Message)" } if($null -eq $obj){ throw "KB JSON parse resulted in null" } return $obj } # ---- MAIN ---- # Diagnostics: first 3 bytes and path length $first3 = "NA" $pathLen = 0 $openOK = $false try { $bytes = [System.IO.File]::ReadAllBytes($BootPackPath) $pathLen = $BootPackPath.Length if($bytes.Length -ge 3){ $first3 = ("{0:X2}-{1:X2}-{2:X2}" -f $bytes[0], $bytes[1], $bytes[2]) } elseif($bytes.Length -gt 0){ $arr = @() for($i=0; $i -lt [Math]::Min(3,$bytes.Length); $i++){ $arr += ("{0:X2}" -f $bytes[$i]) } $first3 = ($arr -join "-") } else { $first3 = "EMPTY" } $fs = [System.IO.File]::OpenRead($BootPackPath) $fs.Close() $openOK = $true } catch { $openOK = $false } # Parse BootPack $bootText = Get-Content -LiteralPath $BootPackPath -Raw $lines = Normalize-Lines -Text $bootText $ptr = Find-Pointer -Lines $lines $ptrPath = $null; $ptrSha = $null; $ptrEntriesNote = $null if($null -ne $ptr){ $ptrPath = $ptr.Path $ptrSha = $ptr.SHA256 $ptrEntriesNote = $ptr.Entries } $tp = Test-PathVariants -RawPath $ptrPath $ptrExists = $false $kbPathUsed = $null if($tp.Raw -or $tp.Dequoted -or $tp.Long){ $ptrExists = $true $kbPathUsed = $tp.Used } # Compute KB SHA and parse JSON $kbSha = "" $jsonEntries = 0 $blocking = 0 if($ptrExists){ try { $h = Get-FileHash -LiteralPath $kbPathUsed -Algorithm SHA256 $kbSha = $h.Hash.ToUpperInvariant() } catch { $kbSha = "" } try { $kb = Read-KbObject -KbPath $kbPathUsed if($null -ne $kb.entries){ $jsonEntries = @($kb.entries).Count $blocking = @($kb.entries | Where-Object { $_.blocking -eq $true }).Count } } catch { # leave zeros } } $ptrShaMatch = $false if(($null -ne $ptrSha) -and ($ptrSha.Trim().Length -gt 0) -and ($kbSha.Length -gt 0)){ $ptrShaMatch = ($kbSha -ieq $ptrSha.Trim()) } # Emit metrics (same format as acceptance checker) # entries = jsonEntries to match historical output Write-Host ("entries={0}|blocking={1}|ptr_exists={2}|ptr_sha_match={3}|ptr_entries_note={4}|json_entries={5}" -f $jsonEntries, $blocking, $ptrExists, $ptrShaMatch, ($ptrEntriesNote -as [int]), $jsonEntries) # Emit diagnostics $lenUsed = 0; if($null -ne $kbPathUsed){ $lenUsed = $kbPathUsed.Length } $tpRaw = [string]$tp.Raw $tpDeq = [string]$tp.Dequoted $tpLong = [string]$tp.Long Write-Host ("diag: First3={0} BootPackPathLength={1} OpenReadOK={2}" -f $first3, $pathLen, $openOK) Write-Host ("diag: PtrPathRaw=`"{0}`"" -f ($ptrPath)) Write-Host ("diag: TestPathRaw={0} TestPathDequoted={1} TestPathLong={2} UsedLen={3}" -f $tpRaw, $tpDeq, $tpLong, $lenUsed) if($kbSha -ne ""){ Write-Host ("diag: KbSha={0}" -f $kbSha) } if($null -ne $ptrSha){ Write-Host ("diag: PtrSha={0}" -f ($ptrSha.Trim())) } if($null -ne $ptrEntriesNote){ Write-Host ("diag: PtrEntriesNote={0}" -f $ptrEntriesNote) }