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>
This commit is contained in:
Jesper Schulz-Wedde 2026-09-11 12:06:55 +02:00
parent 6a89831c5c
commit 524c2d979e
3 changed files with 43 additions and 4 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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