From 524c2d979ebc990b959a0fdc11c078a66cbabfb5 Mon Sep 17 00:00:00 2001 From: Jesper Schulz-Wedde Date: Fri, 11 Sep 2026 12:06:55 +0200 Subject: [PATCH] Fix bc-version overflow and pathless-row identity in bounded retrieval Catalog matching compared an Int32 -BCVersion against a bigint range bound. PowerShell coerces the right operand to the left operand's type, so a bound wider than Int32 threw a conversion error and failed the whole domain catalog rather than the single row. Metadata validation already accepts such bounds, so compare as bigint on both sides. The shared pager built its oversized-row message with $row.path, which throws under Set-StrictMode -Version Latest when a row carries no path, replacing the explicit bound failure with a property-lookup error. Resolve the path defensively for dictionary and object rows so the offset-based fallback is reachable. Both paths gain regression coverage that fails without these fixes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- tools/Bounded-Results.ps1 | 9 ++++++++- tools/Search-Knowledge.ps1 | 9 ++++++--- tools/Test-KnowledgeRetrieval.ps1 | 29 +++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/tools/Bounded-Results.ps1 b/tools/Bounded-Results.ps1 index 26f2078..6def944 100644 --- a/tools/Bounded-Results.ps1 +++ b/tools/Bounded-Results.ps1 @@ -98,7 +98,14 @@ function ConvertTo-BoundedPage { snapshot = $Header.snapshot } if ($page.returnedCount -eq 0) { - $identity = if ($row.path) { " at $($row.path)" } else { " at Offset=$Offset" } + $rowPath = $null + if ($row -is [Collections.IDictionary]) { + if ($row.Contains('path')) { $rowPath = $row['path'] } + } + elseif ($null -ne $row -and $row.PSObject.Properties['path']) { + $rowPath = $row.PSObject.Properties['path'].Value + } + $identity = if ($rowPath) { " at $rowPath" } else { " at Offset=$Offset" } throw "One complete $name row plus envelope exceeds MaxBytes=$MaxBytes$identity. No row was clipped." } return $json diff --git a/tools/Search-Knowledge.ps1 b/tools/Search-Knowledge.ps1 index bd53bbe..ade280e 100644 --- a/tools/Search-Knowledge.ps1 +++ b/tools/Search-Knowledge.ps1 @@ -142,12 +142,15 @@ foreach ($path in $paths) { $target = $context[$field] $matched = $false if ($field -eq 'bc-version') { + # Compare as bigint on both sides: metadata validation accepts bounds + # wider than Int32, and an int left operand would coerce them down. + $targetVersion = [bigint]$target if ($values.Count -eq 1 -and $values[0] -match '^(\d+)\.\.(\d+)?$') { - $matched = $target -ge [bigint]::Parse($Matches[1]) -and - (-not $Matches[2] -or $target -le [bigint]::Parse($Matches[2])) + $matched = $targetVersion -ge [bigint]::Parse($Matches[1]) -and + (-not $Matches[2] -or $targetVersion -le [bigint]::Parse($Matches[2])) } else { - $matched = @($values | Where-Object { [bigint]::Parse($_) -eq $target }).Count -gt 0 + $matched = @($values | Where-Object { [bigint]::Parse($_) -eq $targetVersion }).Count -gt 0 } } else { diff --git a/tools/Test-KnowledgeRetrieval.ps1 b/tools/Test-KnowledgeRetrieval.ps1 index abe9c81..ef65812 100644 --- a/tools/Test-KnowledgeRetrieval.ps1 +++ b/tools/Test-KnowledgeRetrieval.ps1 @@ -622,6 +622,21 @@ try { Assert-Sequence $versioned.countries @('dk') 'non-default country survives' Assert-Sequence $versioned.'application-area' @('finance') 'non-default application area survives' + # Metadata validation accepts range bounds wider than Int32, so version + # matching must compare as bigint rather than coercing the bound down. + $wideRoot = Join-Path $tmp 'wide-version' + New-NeutralArticle -FixtureRoot $wideRoot -Layer microsoft -Slug wide-closed -Version '"1..99999999999"' + New-NeutralArticle -FixtureRoot $wideRoot -Layer microsoft -Slug wide-open -Version '"99999999999.."' + $wideIndex = Join-Path $tmp 'wide-version-index.json' + & $generator -BCQualityRoot $wideRoot -IndexPath $wideIndex | Out-Null + $wide = Invoke-CatalogPages -Arguments @{ + BCQualityRoot = $wideRoot + IndexPath = $wideIndex + Domain = 'neutral' + BCVersion = 28 + } -MaxBytes 16000 + Assert-Sequence $wide.candidates.path @('microsoft/knowledge/neutral/wide-closed.md') 'bc-version bounds beyond Int32 compare without overflow' + $conditional = Invoke-CatalogPages -Arguments @{ BCQualityRoot = $fixtureRoot IndexPath = $fixtureIndex @@ -666,6 +681,20 @@ try { & $search -BCQualityRoot $largeRoot -IndexPath $largeIndex -Domain neutral -MaxBytes 1024 } 'One complete candidates row|Page envelope exceeds' 'oversized catalog row fails without clipping' + # The shared pager reports the oversized row's identity for any row shape; + # a row without a path must still reach its explicit offset-based failure. + . (Join-Path $Root 'tools/Bounded-Results.ps1') + $pagerHeader = [ordered]@{ version = 2; snapshot = ('0' * 64) } + foreach ($shape in @( + @{ name = 'dictionary'; row = [ordered]@{ blob = ('x' * 3000) } }, + @{ name = 'object'; row = [pscustomobject]@{ blob = ('x' * 3000) } } + )) { + Assert-Throws { + ConvertTo-BoundedPage -Header $pagerHeader ` + -Groups ([ordered]@{ rows = @($shape.row) }) -MaxBytes 1024 + } 'One complete rows row plus envelope exceeds MaxBytes=1024 at Offset=0' "oversized pathless $($shape.name) row fails with its offset identity" + } + $bodyRoot = Join-Path $tmp 'body-failures' New-NeutralArticle -FixtureRoot $bodyRoot -Layer microsoft -Slug huge-body -Description ('x' * 3000) New-NeutralArticle -FixtureRoot $bodyRoot -Layer microsoft -Slug broken-link