All findings · Lucene finding · LFV-782 · geo3d-polar-rectangle-outside-distance-misses-pole

Geo3D outside distance to a lat/lon box that reaches a pole ignores the pole, so newOutsideBoxSort sorts documents beyond the pole after nearer ones

Sorting with Geo3DDocValuesField.newOutsideBoxSort by a box that reaches a pole, say latitude 60..90 x longitude -10..10, gives a document across the pole at (80, 180) the distance to a far corner, 39.88 degrees, instead of 10, so it sorts after a document 25 degrees away. In every release since 5.3.0 (the sort since 6.1.0).

Summary

Priority
Low, rank 411Geo3D outside-distance sorts are wrong only for pole-reaching boxes narrower than 180 degrees in the little-used spatial3d sort, so it follows the similar outside-distance rows LFV-274 and LFV-735.
Impact
silent wrong results
Since
5.3.0 (LUCENE-6578, 2015); the sort since 6.1.0introduced 2015-07-01 · over 11 years
Affected
5.3.0 to 10.5.2 and main
Status
documented
Found by
a bounded-exhaustive comparison of geo3d outside distances against an exact reference

AI-assisted page: this finding was first signalled by a semi-formal check (an exact check of the real jar, or a modelling hypothesis that a tool then witnessed) and then checked with AI tools; the checks it went through, and what each found, are under Prior reports, history and checks. Not yet triaged by a human. Snapshot of 2026-11-06.

The finding in 30 seconds

Geo3D sorts documents by their distance from a lat/lon box with newOutsideBoxSort. For a box that touches a pole and spans under 180 degrees of longitude, Lucene builds a GeoNorthRectangle (or GeoSouthRectangle): a wedge whose two side edges meet at the pole. Its distance code checks the bottom edge, the sides and the bottom corners, but not the pole.

Summary card titled Geo3D outside distance to a lat/lon box that reaches a pole ignores the pole, for newOutsideBoxSort("loc", 60, 90, -10, 10, WGS84) with documents A (80, 180), B (35, 0) and P (90, 0). Left: a view from above the North Pole with the box as a grey wedge from the pole to latitude 60 between longitudes -10 and 10; A lies just past the pole, 10 degrees from it; blue lines of 39.88 degrees run from A to each far corner; B lies 25 degrees south of the box; a note says the pole is never measured. Right: four steps: maxLatitude 90 and 20 degrees wide gives a GeoNorthRectangle; both side edges end at the pole, which is in the box; outsideDistance is the minimum of 3 edges and 2 corners, with no pole term; A's side-edge points lie past the pole, so a far corner wins. Outside distance of A: expected 10.00 degrees to the pole, actual 39.88 degrees to a far corner; with maxLatitude 89.9999 it is 10.000098. Sort hits in meters: expected with the fix P 2.47, A 1,111,953.50, B 2,779,878.83; actual on main P 2.47, B 2,779,878.83, A 4,434,715.59. Notes: the fix adds the pole distance as GeoLongitudeSlice does; affected boxes reach latitude 90 or -90 and are under 180 degrees wide, in every release since 5.3.0; the hits never change, only the values and the order.
A view from above the North Pole, distances from the pole to scale: the box lat 60..90 x lon -10..10 as a wedge, A at (80, 180) 10 degrees past the pole, B at (35, 0) 25 degrees south of the box. Lucene measures A to a far corner, 39.88 degrees, and sorts it after B. Select the still to open it at full size. Generated with AI, checked against the code.
  1. You index documents with Geo3DDocValuesField (WGS84) at (80, 180), just across the North Pole, and at (35, 0), and sort them with newOutsideBoxSort("loc", 60, 90, -10, 10, WGS84): the box latitude 60..90 x longitude -10..10, which contains the pole.
  2. You expect (80, 180) first: the pole is inside the box and only 10 degrees (about 1,112 km) away, while (35, 0) is 25 degrees (about 2,780 km) south of the box.
  3. You get (35, 0) first: (80, 180) gets 4,434,716 m, 39.88 degrees, the distance to a bottom corner of the box, (60, 10) or (60, -10). No error; the hits are the same, only the sort values and the order are wrong.
  4. Why: for a box that reaches one pole and spans under 180 degrees of longitude (and is not a line or a point), Lucene builds a GeoNorthRectangle, whose outsideDistance takes the minimum over the bottom edge, the two side edges and the two bottom corners, never the pole where both side edges end. From (80, 180) neither side edge offers a point on the box, so a corner wins.
  5. Move the top to 89.9999 and the same point gets 10.0001 degrees (a plain rectangle, correct); at 89.999999 it is 39.88 again. Boxes that reach the South Pole (GeoSouthRectangle) have the mirror bug; boxes 180 degrees wide or more are fine.

Who is affected

Applications that sort with newOutsideBoxSort (or call computeOutsideDistance on a GeoBBoxFactory box) where the box reaches latitude 90 or -90 and spans under 180 degrees of longitude. Documents beyond the pole (26-37% of outside grid points in the boxes measured) get values too large, mostly by tens of degrees, and sort late or leave the top N.

Mechanism, terms and numbers929 words

Geo3DDocValuesField.newOutsideBoxSort orders documents "by ascending outside distance from the box". For a box whose top latitude is the North Pole (or whose bottom latitude is the South Pole) and whose longitude extent is under 180 degrees, GeoBBoxFactory builds a GeoNorthRectangle (GeoSouthRectangle): a wedge whose two side edges meet at the pole, which is inside the box. Its outsideDistance takes the minimum over the bottom parallel, the two side meridians and the two bottom corners, never the pole. For a point on the far side of the pole neither side meridian offers a point on the box, so the result is the distance to a corner: for the box latitude 60..90 x longitude -10..10 and the point (80, 180), 39.88 degrees instead of 10, and in the WGS84 sort 4,434,716 m, so the document sorts after one at (35, 0), 25 degrees south of the box (sort value 2,779,879 m). In the three boxes measured, 26% to 37% of the outside grid points get values that are too large, most by tens of degrees; the hit set never changes. The same box with a top latitude of 89.9999 is a plain rectangle and gives 10.0001 degrees. The bug dates from the first outside-distance code (LUCENE-6578, 5.3.0, 2015) and is in every release since (the sort since 6.1.0); adding the pole distance to the minimum, as GeoLongitudeSlice already does, fixes it, with all of lucene/spatial3d passing.

Terms

  • Outside distance: the arc distance from a point to the nearest point of a shape, 0 inside it. Geo3DDocValuesField.newOutsideBoxSort sorts by it and reports it in meters (the arc distance times the planet's mean radius).
  • Polar rectangle: GeoBBoxFactory.makeGeoBBox builds a GeoNorthRectangle when the top latitude is the pole, the bottom latitude is not the other pole, the box has nonzero width and height, and its longitude extent is under 180 degrees (strictly, under pi minus pi x 1e-12 rad), a GeoSouthRectangle for the South Pole (GeoBBoxFactory.java:58-131). Its boundary is the bottom (top) parallel between two corners and two meridian segments from the corners to the pole.
  • Pole tolerance: the factory treats a latitude as the pole when it is within pi x 1e-12 rad of it or its sine is within 1e-12 of 1 (GeoBBoxFactory.java:137-150). The sine test reaches about 0.00008 degrees (9 m) from the pole, so 89.999999 and 89.99992 build a GeoNorthRectangle and 89.9999 a plain GeoRectangle. The sine tolerance dates from LUCENE-10673 (first released in 9.4.0); in older releases only latitudes essentially at the pole count.

The bug (main a9779ef3c193)

  1. newOutsideBoxSort (Geo3DDocValuesField.java:281-291) calls Geo3DUtil.fromBox (Geo3DUtil.java:173-189), which checks the latitudes against +/-90 and calls GeoBBoxFactory.makeGeoBBox; the factory returns GeoNorthRectangle / GeoSouthRectangle for a pole-reaching box narrower than GeoWideRectangle.MIN_WIDE_EXTENT (GeoBBoxFactory.java:101, :128-131; GeoWideRectangle.java:31).
  2. The sort's comparator calls computeOutsideDistance(DistanceStyle.ARC, x, y, z) on each decoded doc value (Geo3DPointOutsideDistanceComparator.java:101, :143), which returns 0.0 inside the box and outsideDistance otherwise (GeoBaseMembershipShape.java:47-53).
  3. GeoNorthRectangle.outsideDistance (GeoNorthRectangle.java:283-298) returns min(bottom, left, right, LRHC, LLHC); GeoSouthRectangle.outsideDistance (GeoSouthRectangle.java:254-269) is the mirror. The pole is never a candidate.
  4. Each side term measures only to the perpendicular foot on the meridian plane and its antipode and drops those outside the box (Plane.java:600-636). For (80, 180) and the side at longitude -10 the foot is (80.1489, 170), on the far half of the meridian, and the antipode (-80.1489, -10), below the box: the term is +infinity, and the same for the other side.
  5. What is left: the bottom parallel's point (60, 0) at 40 degrees and the corners (60, +-10) at 39.882280 degrees. The pole, 10 degrees away, is never asked.
  6. The class itself lists the pole as the end point of both side edges (leftPlanePoints, rightPlanePoints, edgePoints, GeoNorthRectangle.java:156-159) and adds it to its bounds (:279). GeoLongitudeSlice.outsideDistance adds both poles (GeoLongitudeSlice.java:196-207); the wide polar rectangles search the whole side meridians, so the pole lies on their arc (GeoWideNorthRectangle.java:258-274).

The numbers (executed, assertions on and off give the same values)

boxpointLucenecorrect
lat 60..90 x lon -10..10, SPHERE and WGS84(80, 180)39.882280 degrees (a corner)10 degrees (the pole)
lat 60..89.9999 x lon -10..10 (control: GeoRectangle)(80, 180)10.000098 degrees10.000098 degrees
lat 60..89.999999 x lon -10..10 (inside the pole tolerance)(80, 180)39.882280 degrees10 degrees
lat 60..90 x lon -10..10(89, 180)30.985249 degrees1 degree
lat 60..90 x lon -100..100 (wide, control)(80, 180)9.846552 degrees9.846552 degrees
lat -90..-60 x lon -10..10 (GeoSouthRectangle)(-80, 180)39.882280 degrees10 degrees
lat 45..90 x lon -20..30 (SPHERE)(75, 170)57.1244 degrees15 degrees
lat -90..-40 x lon 100..160 (SPHERE)(-70, -60)66.2143 degrees20 degrees
WGS84 sort, lat 60..90 x lon -10..10(80, 180) vs (35, 0)4,434,715.59 m, sorted after 2,779,878.83 mabout 1,111,954 m, sorted first

In grid sweeps against an independent spherical-trigonometry reference, 226 of 801 outside points (north box lat 45..90 x lon -20..30) and 205 of 790 (south box lat -90..-40 x lon 100..160) were wrong; the worst points were (81.5, -177.5) with 52.65 degrees instead of 8.50 and (-86.5, -45.5) with 52.91 instead of 3.50. For the box lat 60..90 x lon -10..10 a 1-degree grid in the fact check found 37% of the outside points wrong, by up to 30 degrees. The points need not be polar: for the tall box lat -89.5..90 x lon -10..10 the finder's sweep has wrong values near the equator, about 1.2 degrees too large.

Exposure and likelihood464 words
  • Who. Users of the optional lucene/spatial3d module who sort with Geo3DDocValuesField.newOutsideBoxSort, or call computeOutsideDistance on a GeoBBoxFactory box (alone or inside a composite shape), where the factory builds a GeoNorthRectangle or GeoSouthRectangle: a box of nonzero size that reaches latitude 90 or -90 (or lies within the factory's pole tolerance, such as 89.999999), not both, and spans under 180 degrees of longitude, for example a polar sector such as lat 60..90 x lon -10..10, and the index holds documents on the far side of the pole.
  • Effect. Those documents get sort values that are too large (meters in the FieldDoc), mostly by tens of degrees, so they sort after farther documents or fall out of the top N. Every returned hit still matches the query. No exception, with assertions on or off.
  • Default vs opt-in. An optional module and an apparently niche sort: no in-tree caller or test uses it and no matching public report was found (there is no usage data). The caller must pass a PlanetModel; the repro passes WGS84, with new IndexWriterConfig() and the codec the test framework picks (Asserting(Lucene104) in the logged run). The bug is in the shape geometry and does not depend on the codec or the writer settings.
  • Scale. None: one document triggers it, deterministically, at any index size. The share depends on the box: in the independent reproducer's grid sweeps 28% (north box) and 26% (south box) of the outside points were wrong, by up to about 44 and 49 degrees; for the headline box lat 60..90 x lon -10..10 a 1-degree grid in the fact check found 37% wrong, by up to 30 degrees. On the fact check's grid, 81-87% of the wrong values in these three boxes were off by 10 degrees or more.
  • Not affected. Boxes whose top (bottom) latitude is outside the factory's pole tolerance, about 0.00008 degrees from the pole (89.9999 is already correct; 89.99992 and 89.999999 are not), boxes spanning 180 degrees or more, boxes away from the poles, and the hit set of any query.
  • Workaround. Until a fix ships, a top latitude just outside the tolerance, such as 89.9999, gives correct values; it leaves out a cap of about 11 m around the pole, so points in that cap get a small positive distance instead of 0.
  • How likely. Rare: it needs both a pole-reaching narrow box and documents beyond the pole. No public report was found in the 11 years since 5.3.0.
  • Related findings (separate reports, other fix sites): LFV-737 (GeoExactCircle outside distance skips the slice end points) is the same missing-end-point pattern; LFV-735 (GeoStandardPath measures its end caps to the end point instead of the cap edge) is a related outside-distance error at the ends of a shape.

Minimal repro

The test shows that the Geo3D outside distance from the point (80, 180) to the box lat 60..90 x lon -10..10 (a GeoNorthRectangle, which contains the north pole) ignores the pole.

TestPolarRectangleOutsideDistance.java (JUnit test)36 lines
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.lucene.spatial3d.geom;

import org.apache.lucene.tests.util.LuceneTestCase;

public class TestPolarRectangleOutsideDistance extends LuceneTestCase {

  public void testPointBeyondNorthPole() {
    // box lat 60..90 x lon -10..10 contains the north pole, so (80, 180) is 10 degrees from it
    GeoBBox box =
        GeoBBoxFactory.makeGeoBBox(
            PlanetModel.SPHERE,
            Math.toRadians(90),
            Math.toRadians(60),
            Math.toRadians(-10),
            Math.toRadians(10));
    GeoPoint point = new GeoPoint(PlanetModel.SPHERE, Math.toRadians(80), Math.toRadians(180));
    double distance = box.computeOutsideDistance(DistanceStyle.ARC, point);
    assertEquals("outside distance in degrees", 10.0, Math.toDegrees(distance), 1e-9);
  }
}
Command
# in a copy of this finding's repro folder (repro/LFV-782/, which holds src/)
# Lucene checkout at the tested commit, built with: ./gradlew --no-daemon assemble   (JDK 25)
LUCENE=/path/to/lucene; V=12.0.0-SNAPSHOT
CP=$LUCENE/lucene/core/build/libs/lucene-core-$V.jar
CP=$CP:$LUCENE/lucene/codecs/build/libs/lucene-codecs-$V.jar
CP=$CP:$LUCENE/lucene/test-framework/build/libs/lucene-test-framework-$V.jar
CP=$CP:$LUCENE/lucene/spatial3d/build/libs/lucene-spatial3d-$V.jar
CP=$CP:randomizedtesting-runner-2.9.2.jar:junit-4.13.2.jar:hamcrest-3.0.jar   # test-framework dependencies (Maven Central)
CP=$CP:junit-platform-console-standalone-6.1.3.jar   # JUnit Platform console launcher (Maven Central)

javac -d classes -cp "$CP" $(find src -name '*.java')

# run 1: assertions on, seed DEADBEEF
java -ea -esa --add-modules jdk.incubator.vector --enable-native-access=ALL-UNNAMED -Dtests.seed=DEADBEEF \
     -cp "classes:$CP" org.junit.platform.console.ConsoleLauncher \
     execute --select-class org.apache.lucene.spatial3d.geom.TestPolarRectangleOutsideDistance
Expected vs actual
Expected: 10 degrees (the distance to the pole, which is in the box); actual (-ea and -da): 39.882280145867114 degrees (a far corner), which is the value Geo3DDocValuesField.newOutsideBoxSort sorts by.

All files of this repro: repro/LFV-782/

More files, environment, full output37 words

Environment

luceneLucene main e1faeba08bde (2026-11-02)
jdk25.0.3+9-LTS

Observed output

# Lucene main 645d4fe14a20, assertions on (-ea), seed DEADBEEF: 1 test(s) run, 1 failed
    => java.lang.AssertionError: outside distance in degrees expected:<10.0> but was:<39.882280145867114>
       org.junit.Assert.fail(Assert.java:89)
       org.junit.Assert.failNotEquals(Assert.java:835)
       org.junit.Assert.assertEquals(Assert.java:555)
       org.apache.lucene.spatial3d.geom.TestPolarRectangleOutsideDistance.testPointBeyondNorthPole(TestPolarRectangleOutsideDistance.java:34)

Why it looks unintended

  • newOutsideBoxSort promises "ascending outside distance from the box" for any latitude within +/-90; the pole is inside the box.
  • The class lists the pole as an end point of both side edges and measures the other end points, the corners; GeoLongitudeSlice, from the same commit, includes the poles.
  • Not an approximation: top 89.9999 gives 10.0001 degrees, 90 gives 39.88.
The full argument253 words
  • The contract. newOutsideBoxSort: "This sort orders documents by ascending outside distance from the box. Points within the box have distance 0.0." Latitude bounds "must be within standard +/-90 coordinate bounds" (Geo3DDocValuesField.java:260-280). GeoOutsideDistance: shapes "that can compute the distance from a point to the closest outside edge" (GeoOutsideDistance.java:19-21).
  • The stated intent. LUCENE-6578 (now #7636), which introduced outside distances, asked for the arc distance "from a GeoPoint to the nearest edge/point of a GeoShape".
  • The class's own geometry. The pole is inside the box (the repro asserts isWithin(NORTH_POLE)) and is the end point of both side edges (GeoNorthRectangle.java:156-159); the method already measures the other end points, the corners.
  • The same commit did it right elsewhere. 688c67f8ff2 gave GeoLongitudeSlice.outsideDistance its NORTH_POLE / SOUTH_POLE terms in the same diff.
  • Not an approximation. On the same point the value jumps from 10.000098 degrees (top 89.9999) to 39.882280 (top 90): about 3,300 km for an 11 m change of the box. spatial3d's documented accuracy caveats are orders of magnitude smaller: the doc-value precision loss (4.19e-8 for the latitude component and 8.38e-8 for longitude, Geo3DDocValuesField.java:45-47), "up to 0.5% error" for the WGS84 distance query (Geo3DPoint.java:112-113) and the accuracy setting of exact circles (GeoCircleFactory.java:50-57).
  • Tests and history. No upstream test calls newOutsideBoxSort; the only box outside-distance assertions use a plain rectangle (lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/TestGeoBBox.java:75-77). The two methods are unchanged since 2015 except for a reformat (LUCENE-9570), and no CHANGES entry or issue documents a polar limitation.
The devil's-advocate check228 words

The strongest case for "intended" (the intended-behaviour checker built it; cross-model reviews rated it weak): GeoNorthRectangle and GeoSouthRectangle are @lucene.internal, and GeoOutsideDistance, GeoBBox and GeoBBoxFactory are @lucene.experimental, as are PlanetModel, which the sort requires, and Geo3DPoint; the Geo3DDocValuesField class javadoc still lists its factory methods as "TBD" and warns about precision loss; "the closest outside edge" does not say that edge end points count; LUCENE-7212 (#8267), which added the sort, is still open and its last maintainer comment lists tests as still needed; and a fix changes existing sort values and top-N results.

Why it fails: newOutsideBoxSort itself carries no stability tag and promises the ascending outside distance for any latitude in +/-90; "edge" must include its end points, because the method already measures the corners; neither an open feature issue nor a precision warning in the 1e-8 range documents that values may be off by tens of degrees; and the jump between 89.9999 and 90 shows a missing case, not a design. Upstream has treated geo3d distance errors as bugs before (for example LUCENE-7139, "Fix bugs in geo3d's Vincenty surface distance"). The compatibility effect is real; it bears on how the fix is announced (a CHANGES entry), not on whether the current values are correct. Expect a maintainer to say the sort is rarely used: that bears on priority, not on validity.

Suggested fix

Recommended Add the pole distance to the minimum

Add the pole to the minimum in GeoNorthRectangle.outsideDistance and GeoSouthRectangle.outsideDistance, as GeoLongitudeSlice does: two added lines and one changed line per class. The repro passes with assertions on and off, and all of lucene/spatial3d passes (185 tests including the repro).

--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java
@@ -291,9 +291,11 @@ class GeoNorthRectangle extends GeoBaseBBox {
     final double LRHCDistance = distanceStyle.computeDistance(LRHC, x, y, z);
     final double LLHCDistance = distanceStyle.computeDistance(LLHC, x, y, z);
+    // the left and right edges end at the pole, which is part of the rectangle
+    final double northDistance = distanceStyle.computeDistance(planetModel.NORTH_POLE, x, y, z);
 
     return Math.min(
-        bottomDistance,
+        Math.min(bottomDistance, northDistance),
         Math.min(Math.min(leftDistance, rightDistance), Math.min(LRHCDistance, LLHCDistance)));
   }
(GeoSouthRectangle: the same with SOUTH_POLE)
All options and trade-offs3 options ยท 696 words

Option A was applied in a scratch worktree at a9779ef3c193 and run through gradle (seed DEADBEEF); B and C were not run. Until a fix ships, a top latitude just outside the pole tolerance, such as 89.9999, avoids the bug; it leaves out a cap of about 11 m around the pole.

Add the pole distance to the minimum (validated by the checks) (recommended)

In GeoNorthRectangle.outsideDistance (:283-298) add distanceStyle.computeDistance(planetModel.NORTH_POLE, x, y, z) to the minimum; in GeoSouthRectangle.outsideDistance (:254-269) the same with SOUTH_POLE. The boundary is the bottom (top) parallel and two meridian segments ending at the corners and the pole; the existing terms cover the parallel, the segments' interiors and the corners, so the pole is the only missing end point. For a point whose perpendicular feet fall off both segments, the nearest point of each segment is an end point, so this completes the minimum.

--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java
@@ -291,9 +291,11 @@ class GeoNorthRectangle extends GeoBaseBBox {
     final double LRHCDistance = distanceStyle.computeDistance(LRHC, x, y, z);
     final double LLHCDistance = distanceStyle.computeDistance(LLHC, x, y, z);
+    // the left and right edges end at the pole, which is part of the rectangle
+    final double northDistance = distanceStyle.computeDistance(planetModel.NORTH_POLE, x, y, z);
 
     return Math.min(
-        bottomDistance,
+        Math.min(bottomDistance, northDistance),
         Math.min(Math.min(leftDistance, rightDistance), Math.min(LRHCDistance, LLHCDistance)));
   }
(GeoSouthRectangle: the same with SOUTH_POLE)

Trade-offs

  • Cost: one extra point distance per call, only in the two narrow polar classes. No format, API or index change.
  • Compatibility: existing users of the sort get smaller values for documents beyond the pole, a different order and possibly a different top N; worth stating in the CHANGES entry.
  • Distance styles: the pole is a point of the box for every DistanceStyle, and the term uses the same computeDistance(GeoPoint, x, y, z) as the corners; only ARC, the style the sort uses, was validated.
  • With the PR: add the repro (or a polar case in TestGeoBBox plus a first newOutsideBoxSort test in TestGeo3DDocValues) and a CHANGES entry.

What the checks validated

  • Stock main a9779ef3c193 (gradle, seed DEADBEEF): the repro fails 3 of 4 with assertions on and off; TestGeoBBox (11 tests) and TestGeo3DDocValues (1) pass.
  • With the fix: the repro passes 4 of 4 with assertions on and off; TestGeoBBox and TestGeo3DDocValues pass; all of :lucene:spatial3d:test with the repro class added passes (20 classes, 185 tests, 0 failures, 3 skipped); ecjLintMain, ecjLintTest and checkGoogleJavaFormat pass for spatial3d; fix.diff applies cleanly to a clean tree.
  • With the fix shadowed onto the main jars: the repro 4 of 4, the hunt's JUnit 3 of 3, the independent reproducer's JUnit 7 of 7 and the cross-model reviewer's own test 5 of 5 pass.
  • In the hunt: the upstream spatial3d suite gives the same result with and without the change (181 tests: 178 pass, 3 assumption-aborted, on ea57360912ed), and the bounded sweep drops from 81,742 to 0 violations in 17,068,396 checks (not re-run independently).

Document the limitation (a compatibility choice)

Say in the newOutsideBoxSort javadoc that boxes reaching a pole give too large values beyond the pole, and keep the current values. Users can work around it with a top latitude such as 89.9999.

Trade-offs

Keeps existing sort values and top-N results stable and is cheaper to review, but leaves errors of tens of degrees (up to about 49 in the boxes measured) for a legal input when a two-line fix exists, and the factory's pole tolerance (89.999999 counts as 90) makes the trigger hard to describe.

Not a fix: search the whole side meridians, as the wide classes do

GeoWideNorthRectangle bounds its side planes only by the latitude plane, which is safe because, for an extent of 180 degrees or more, the far half of each side meridian lies inside the box (GeoWideNorthRectangle.java:262-267).

Trade-offs

For a narrow box the far half of each side meridian is outside the box, so the same change would make distances too small: for (80, 180) the left-side candidate (80.1489, 170), 1.727941 degrees away, is rejected only by the right side plane, so dropping that bound would return 1.73 degrees instead of 10 (the diagram probe trace; the change itself was not run).

Downstream note

Downstream relevance: an application that takes the first hit of this sort as the document nearest to a polar box can get a farther one; hit sets and filters are not affected.

How it was found

A bounded-exhaustive comparison of the real jar against an exact reference: 540 lat/lon boxes and about 600 polygons, each checked at about 3,700 to 5,400 points on the sphere and on WGS84, 17.07 million checks. All 81,742 mismatches were in GeoNorthRectangle and GeoSouthRectangle, always too large; the cause was then read off the code and reduced to a JUnit.

The method in full234 words

Method: bounded-exhaustive translation validation on the stock jar (semi-formal tier), in a hunt unit on the geo3d distance sort fields.

  • The harness built 540 GeoBBoxFactory boxes (9 latitudes including +-90 and +-89.5 x 12 longitude pairs including dateline-crossing, full, wide and degenerate boxes) and 600 simple star polygons (599 built on WGS84), and compared each one's outside distance at about 3,700 to 5,400 decoded doc-value points with an independent exact reference of the arc distance to the boundary. Geo3D parallels are horizontal planes and its meridians planes through the origin, so the same direction-based reference is exact on SPHERE and WGS84 for Lucene's ARC distance (the angle between the points' direction vectors, not an ellipsoidal surface distance). Only ARC was checked, and 27,398 checks within 3e-6 rad of an edge were skipped.
  • Of 17,068,396 checks, 81,742 failed, all in two classes: GeoNorthRectangle (20,963 per planet model) and GeoSouthRectangle (19,908 per planet model), always with Lucene's value too large. With the pole term added, 0.
  • The unit's plan targeted the sort comparators; it did not name this lead. The cause was read off the two outsideDistance methods and reduced to a public-API JUnit on the stock main jars.
  • Found 2026-10-27 on main a9779ef3c193; then checked by an independent reproducer, an intended-behaviour devil's advocate, reviews by a model from another family (OpenAI GPT-5.6), an adjudication, a history, prior-report and security follow-up, and a credibility audit.

Prior reports, history and checks

Prior reports

No prior report found (2026-10-27) in two runs of the hunt's prior-report tool (63 and 65 queries), manual GitHub, JIRA, list and web searches, and the changed files of all 409 open PRs. The closest items introduced outside distances and the sort without discussing poles.

Searches4 searches

Verdict new: these searches found no report of this bug by anyone. All 26 logged hits were judged not this bug. Keyword search cannot prove absence: list results were judged from subjects and snippets, web results are capped at about 10-25 per query, and private reports are not visible.

What was searched, when, and what turned up
WhereWhenHowFound
apache/lucene issues, PRs and commits; Elasticsearch, OpenSearch, Solr; dev@, java-user@, issues@, commits@; the JIRA archive; the web2026-10-27two runs of the hunt's prior-report tool (63 and 65 queries), plus the finder's 51 and the devil's advocate's 45 queries26 logged hits, none the same bug; context only: #7636 (LUCENE-6578, introduced outside distances, never mentions poles), #8267 (LUCENE-7212, open, added the sort fields)
apache/lucene, apache/lucene-solr, apache/solr and global GitHub2026-10-2713 manual issue/PR searches, one code search, the commits APIGeoNorthRectangle membership, bounds and construction fixes #8256, #8283, #8345, #9503, #9014, #11544, all leaving outsideDistance unchanged
JIRA LUCENE and SOLR; mailing lists; the web2026-10-2712 JIRA queries, 11 list queries, 16 web searchesSOLR-8636 (Won't Fix, another code path); nothing on point
all 409 open apache/lucene PRs2026-10-27changed-file scannone touches the five files, except that one, #16911 (a branch_10x deletion PR), was too large to list fully; #14775 (adds inverseSort()) is the only open PR on the outside-sort code and leaves the geometry alone

History

There from the start: GeoNorthRectangle and GeoSouthRectangle.outsideDistance were first written without a pole term in LUCENE-6578 (688c67f8ff2, 2015-07-01, released in 5.3.0); the sort came in 6.1.0 (LUCENE-7212). Executed on 14 releases from 5.3.0 to 10.5.2 and main; not fixed upstream.

Versions tested7 rows

Introduced by LUCENE-6578 (now #7636, "Geo3d: arcDistanceToShape() method may be useful"): trunk 688c67f8ff2 (svn r1688545, 2015-07-01, "LUCENE-6578: Geo3D: compute the distance from a point to a shape.", committed by @dsmiley; CHANGES credits Karl Wright and David Smiley; JIRA patches, no PR) and the branch_5x backport 780e4040238, first released in 5.3.0. The same diff gave GeoLongitudeSlice.outsideDistance its pole terms.

Later changes to the two methods: only the LUCENE-9570 reformat (2cbf261032d, 2021); the files moved with LUCENE-6607 and LUCENE-7056. The sort newOutsideBoxSort came with LUCENE-7212 (cc263413457, branch_6x aa339a2f075, 2016-05-18, #8267, still open) and had the bug from its first release, 6.1.0. Release-level bracketing only; no commit-level build.

The near-pole trigger is younger: latitudes such as 89.999999 count as the pole only through the sine tolerance of LUCENE-10673 (bd0718f0716, branch_9x 17e8a42e1aa, 2022-08-04, first released in 9.4.0); before that, the pole test was within pi x 1e-12 rad (in 9.2-9.3 also a sine of exactly 1), so older releases are affected only at the exact pole latitudes.

Versions and branches executed, with results
Version or commitVerdictResult
main a9779ef3c193 affectedthe repro fails 3 of 4 through gradle (assertions on and off) and the classpath runner (-ea, -da); passes with the fix
10.4.0, 10.5.2 affectedthe four-test repro fails 3 of 4 (10.5.2 with -ea and -da, 10.4.0 with -da), the same values as on main
6.1.0, 6.6.6, 7.7.3, 8.5.0, 8.11.4, 9.0.0, 9.12.3, 10.5.2 affectedthe sort test fails 3 of 3: the far-side document sorts last (4,434,715.59 m against 2,779,878.83 m)
5.3.0, 5.3.2, 5.5.5, 6.0.0, 6.0.1 shape APIthe shape test fails 3 of 3 (39.882280145867114 degrees instead of 10, north and south); no sort API yet
5.3.0 with the pole term added controlpasses 3 of 3, so the 5.3.0 failure is this defect
5.2.1 no APIno outside-distance API (the shape test does not compile)
688c67f8ff2 / 780e4040238 (LUCENE-6578) introducingin all 11 release tags 5.3.0 to 5.5.5 (backport) and the 83 release tags 6.0.0 to 10.5.2 in the history clone, which has no tags for 8.8.2 and 8.9.0-8.11.4 (8.11.4 was executed and fails)

Independent checks

An independent reproducer and an intended-behaviour devil's advocate reproduced it with their own tests and controls; a cross-model reviewer (OpenAI GPT-5.6) reproduced it with its own test, and a second one rated the intended-behaviour case weak; an adjudication and an audit re-ran it with new seeds, and this doc's gradle runs reproduce it on a9779ef3c193.

Checks and verdicts453 words

Every executing check wrote or re-ran its own repro on stock main; every control passed and every fix check passed. Not independently re-run: the finder's bounded-sweep counts and the commit-level introduction (release bracketing only).

Each independent check and its verdict
CheckVerdictWhat it found
finder candidateBounded-exhaustive sweep (81,742 violations, only these two classes; 0 with the fix), then a 3-test public-API JUnit: 3 of 3 fail on main (-ea, -da) and on 6.6.6 through 10.5.2; pass with the fix.
cross-model review, refute lens (OpenAI GPT-5.6) real, highIts own test (different boxes and points, a south mirror and a wide-box control) fails 3 of 5 with -ea and -da; passes 5 of 5 with the fix; TestGeoBBox and TestGeo3DDocValues still pass with it; intended-behaviour argument weak.
independent reproducer realOwn JUnit with a spherical-trigonometry reference: 5 of 7 fail on main (-ea, -da), 10.5.2 and 9.12.3, both controls pass; 7 of 7 with the fix.
cross-model review, intended lens (the cross-model review, OpenAI GPT-5.6, reading only) real, mediumRoot cause agreed, inputs in contract, intended-behaviour argument weak; a separate run re-ran the finder's JUnit (3 of 3 fail).
intended-behaviour devil's advocate realBuilt the strongest case for intended behaviour and rejected it; its probe shows the jump from 10.000098 degrees (top 89.9999) to 39.882280 (90 and 89.999999), and the wide box correct.
adjudication confirmed, high, priority LowRe-ran the independent reproduction's JUnit with a new seed (5 of 7 fail with -ea and -da, identical values) and with the fix (7 of 7 pass).
Registration check and credibility audit keepRe-ran the finder's JUnit (3 of 3 fail, -ea and -da, new seeds) and the independent reproduction's (5 of 7), the fix (3 of 3 pass), re-read the cited lines and checked the expected value by hand.
fact checks of this doc (an independent checker and a cross-model reviewer, OpenAI GPT-5.6) bug, mechanism and numbers confirmed; wording errors fixedThe independent checker re-ran the gradle repro on a clean extraction of a9779ef3c193 (3 of 4 fail with assertions on and off; 4 of 4 with the fix), the independent reproducer's JUnit (5 of 7) and the repro with a new seed on main, ea57360912ed and 10.5.2, and recomputed every number with its own probe; the cross-model reviewer re-checked the claims against the code, logs and history (read-only). Their corrections (scale figures, the pole tolerance, older-release compatibility of the repro, wording) are applied.
this doc reproduced; fix validatedGradle in a clean worktree at a9779ef3c193: the repro fails 3 of 4 (control passes) with assertions on and off; with the fix 4 of 4 pass and all of lucene/spatial3d passes (185 tests).

Diagram file

The diagram as a still image, generated with AI and checked against the code. It is under 10 MB, the limit for a GitHub attachment.

Sources and provenance

Finding record, Lucene and upstream sources, repro files and diagram notes1,434 words

Finding record

LFV-782, snapshot of 2026-11-06

Registry title
Geo3D outside distance to a lat/lon box that reaches a pole (GeoNorthRectangle / GeoSouthRectangle) ignores the pole, so Geo3DDocValuesField.newOutsideBoxSort gives documents beyond the pole the distance to a far corner (39.88 instead of 10 degrees for the box lat 60..90 x lon -10..10 and the point (80, 180)) and sorts them after nearer documents
Area
spatial3d / geom polar rectangles outside distance (GeoNorthRectangle, GeoSouthRectangle) and the Geo3DDocValuesField outside-box sort
Priority
Low, rank 411: Geo3D outside-distance sorts are wrong only for pole-reaching boxes narrower than 180 degrees in the little-used spatial3d sort, so it follows the similar outside-distance rows LFV-274 and LFV-735.
Impact class
silent wrong results
Impact note
Applications that sort by Geo3DDocValuesField.newOutsideBoxSort (or call computeOutsideDistance on a GeoBBoxFactory box) with a box whose maxLatitude is 90 or minLatitude -90 (also latitudes within the factory's pole tolerance, such as 89.999999) and whose longitude extent is under 180 degrees get distances tens of degrees too large for documents on the far side of the pole, so those documents sort after farther ones or drop out of the top N, with no error, on the default planet model, codec and config and with -ea and -da alike; the hit set never changes, and boxes that stop short of the pole (89.9999 already gives the right value) or span 180 degrees or more of longitude are not affected.
Summary
You sort documents by their outside distance from a lat/lon box that reaches the North Pole, for example latitude 60..90 x longitude -10..10, with Geo3DDocValuesField.newOutsideBoxSort. A document at (80, 180) is only 10 degrees (about 1,112 km) from that box, because the box contains the pole, so it should sort before a document at (35, 0), which is 25 degrees south of the box. Instead GeoNorthRectangle.outsideDistance never considers the pole, where both side edges of the box end, and returns the distance to a far corner (39.88 degrees on the sphere; 4,434,716 m against 2,779,879 m in the WGS84 sort), so the nearer document sorts last; GeoSouthRectangle has the mirror bug at the South Pole.
Introduced
688c67f8ff22b72b9df4ed2f92d26b909563b2b1 (trunk, svn r1688545, 2015-07-01, 'LUCENE-6578: Geo3D: compute the distance from a point to a shape.', committed by GitHub dsmiley; issue LUCENE-6578, now apache/lucene#7636; SVN/JIRA era, no PR), released through the branch_5x backport 780e4040238713c2ff85c4b981cbc6b80aa4094f (svn r1688546, in 5.3.0) [read: git log -L on outsideDistance in both classes in the history clone: the methods were first written in this commit without a pole term, while the same diff gave GeoLongitudeSlice.outsideDistance its NORTH_POLE / SOUTH_POLE terms; the only later change is the 2cbf261032d reformat (LUCENE-9570)] [exec: release bracketing, 5.2.1 has no outside-distance API and 5.3.0 fails; no commit-level build]. Not a regression. The user-facing sort came later with the bug from its first release: Geo3DDocValuesField.newOutsideBoxSort, LUCENE-7212 (trunk cc263413457, branch_6x aa339a2f075, 2016-05-18, apache/lucene#8267) (2015-07-01 (trunk 688c67f8ff2 and branch_5x backport 780e4040238); the outside-box sort API on 2016-05-18 (LUCENE-7212))
First affected version
5.3.0 [exec] for the geom API (GeoBBoxFactory.makeGeoBBox(...).computeOutsideDistance); 5.2.1, the release before, has no outside-distance API [exec: API probe passes, shape test does not compile]. 6.1.0 [exec] for Geo3DDocValuesField.newOutsideBoxSort (6.0.1 has no Geo3DDocValuesField [exec: compile error])
Affected versions
Every release from 5.3.0 to 10.5.2 and main a9779ef3c193; not fixed upstream. [exec] the shape test (SPHERE 39.882280145867114 degrees instead of 10, north and south) fails 3 of 3 on 5.3.0, 5.3.2, 5.5.5, 6.0.0, 6.0.1, 6.1.0, 9.12.3, 10.5.2 and main; the sort test fails 3 of 3 on 6.1.0, 6.6.6, 7.7.3, 8.5.0, 8.11.4, 9.0.0, 9.12.3, 10.5.2 and main (-ea and -da); with the pole term shadowed in, 5.3.0 and main pass; the independent reproduction's own JUnit fails 5 of 7 (its 2 controls pass) on main (-ea, -da), 10.5.2 and 9.12.3. [read] git tag --contains: 780e4040238 is in all 11 releases 5.3.0-5.5.5 and 688c67f8ff2 in the 83 release tags 6.0.0-10.5.2; GeoNorthRectangle.java and GeoSouthRectangle.java at a9779ef3c193 are byte-identical to the study base 7cecf795236, and lucene/spatial3d is unchanged between ea57360912ed and a9779ef3c193 (git diff --stat empty). All runs on JDK 25.0.3
Status
documented (brief, checks and a per-finding doc exist)
Verification
Confirmed by AI-run checks: an independent repro, a cross-model review that agreed, an intended-behaviour check, a prior-report search and a skeptical audit; not yet triaged by a person.
Found by
semi-formal: exhaustive check
Upstream and prior reports
none found (searched 2026-10-27, read-only; the prior-report record, verdict new): two scripted prior-report searches (63 and 65 queries over GitHub issues, PRs and commits of apache/lucene, downstream Elasticsearch, OpenSearch and Solr, the dev@, java-user@, issues@ and commits@ lists, the JIRA archive and the web), the finder's 51-query and the intended-behaviour check's 45-query runs, manual GitHub searches (13 issue/PR searches in apache/lucene, lucene-solr, solr and globally, one code search, the commits API), JIRA LUCENE and SOLR (12 queries), 11 mailing-list queries, 16 web searches, the changed files of all 409 open apache/lucene PRs and the full git history. Context, not reports: LUCENE-6578 / #7636 (closed, 5.3; introduced outside distances with the stated intent 'distance from a GeoPoint to the nearest edge/point of a GeoShape'; no discussion of poles), LUCENE-7212 / #8267 (open; added the outside-distance sort fields; its last maintainer comment lists tests as still needed), GeoNorthRectangle test failures LUCENE-7201 / #8256, LUCENE-7228 / #8283, LUCENE-7290 / #8345 and LUCENE-8457 / #9503 and the pole fixes LUCENE-7965 / #9014 and LUCENE-10508 / #11544 (membership, bounds and construction; outsideDistance unchanged), SOLR-8636 (Won't Fix; another code path). The only open PR touching the outside-sort code, #14775, adds inverseSort() and leaves the geometry alone. Not fixed: outsideDistance in both classes is unchanged in substance since 688c67f8ff2 on main (last file change dfd536186fe, 2026-04-01) and branch_10x (de3b294be44, 2023-10-11); CHANGES.txt has no entry. Gaps: keyword searches only, list results judged from subjects and snippets, web results capped at about 10-25 per query, private reports not visible
Lucene commit tested
main a9779ef3c193 (commit of 2026-10-26): the repro JUnit through gradle in a clean worktree, stock and with the fix, assertions on and off, plus TestGeoBBox, TestGeo3DDocValues and all of lucene/spatial3d (20 classes, 185 tests) with the fix (2026-10-28); through the hunt's classpath runner, the same JUnit on main (-ea, -da, fix shadowed), on 10.5.2 (-ea, -da) and on 10.4.0 (-da). Hunt and history runs: the shape test on 5.3.0, 5.3.2, 5.5.5, 6.0.0, 6.0.1, 6.1.0, 9.12.3, 10.5.2 and main, the sort test on 6.1.0, 6.6.6, 7.7.3, 8.5.0, 8.11.4, 9.0.0, 9.12.3, 10.5.2 and main; some hunt reviews on main ea57360912ed (identical spatial3d code). OpenJDK 25.0.3, Linux aarch64.

Lucene and upstream sources

Finding record
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java:22-28 (at a9779ef3c193) (class javadoc 'including the north pole', @lucene.internal), :156-159 (leftPlanePoints / rightPlanePoints = {NORTH_POLE, corner}, edgePoints = {NORTH_POLE}), :279 (getBounds addPoint(NORTH_POLE)), :283-298 (outsideDistance = min(bottom, left, right, LRHC, LLHC), no pole term)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoSouthRectangle.java:148-151 (at a9779ef3c193) (side-edge points and edgePoints contain SOUTH_POLE), :250 (getBounds addPoint(SOUTH_POLE)), :252-269 (outsideDistance = min(top, left, right, ULHC, URHC), no pole term)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLongitudeSlice.java:196-207 (at a9779ef3c193) and geom/GeoDegenerateLongitudeSlice.java:157-158 (sibling outsideDistance methods include the NORTH_POLE and SOUTH_POLE distances)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java:72, (at a9779ef3c193) :112, :129, :131 (latitude zone for full longitude, wide rectangle for extents of PI or more, GeoNorthRectangle / GeoSouthRectangle for a pole-reaching box narrower than PI)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java:260-290 (at a9779ef3c193) (newOutsideBoxSort: 'This sort orders documents by ascending outside distance from the box. Points within the box have distance 0.0.'; latitude bounds 'within standard +/-90'; no stability tag) and Geo3DUtil.java fromBox (GeoUtils.checkLatitude / checkLongitude, then GeoBBoxFactory.makeGeoBBox)
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoOutsideDistance.java:17-23 (at a9779ef3c193) ('compute the distance from a point to the closest outside edge', @lucene.experimental) and :25-55; Geo3DPointOutsideDistanceComparator.java:101, :143 (the sort value is computeOutsideDistance(DistanceStyle.ARC, ...) of the decoded point)
  • lucene/spatial3d/src/test: (at a9779ef3c193) no test calls newOutsideBoxSort (grep); TestGeoBBox.java:75-77 asserts outside distance only for a plain rectangle
The finding in 30 seconds
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java:260-291; Geo3DUtil.java:173-189; Geo3DPointOutsideDistanceComparator.java:101, 125, 143
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBBoxFactory.java:58, 83, 94, 101, 119, 128-131, 137-150; geom/Vector.java:26, 29; geom/GeoWideRectangle.java:31; geom/GeoBaseMembershipShape.java:47-53
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoNorthRectangle.java:22-28, 156-159, 279, 283-298; geom/GeoSouthRectangle.java:148-151, 250, 254-269
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/Plane.java:600-636; geom/GeoLongitudeSlice.java:196-207; geom/GeoDegenerateLongitudeSlice.java:152-161; geom/GeoWideNorthRectangle.java:258-274
Who is affected
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoBaseCompositeMembershipShape.java:55-69 (a composite's outside distance is the minimum over its members); geom/GeoCompositeMembershipShape.java:27, geom/GeoCompositeAreaShape.java:28 (public)
  • lucene/spatial3d/src/test (grep: no call of newOutsideBoxSort)
Why it looks unintended
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/Geo3DDocValuesField.java:40 (TBD), 45-47 (precision warning), 260-280; geom/GeoOutsideDistance.java:19-21; geom/GeoNorthRectangle.java:22-28 (@lucene.internal), 156-159; geom/PlanetModel.java:26 and Geo3DPoint.java:42 (@lucene.experimental); Geo3DPoint.java:112-113; geom/GeoCircleFactory.java:50-57
  • lucene/spatial3d/src/test/org/apache/lucene/spatial3d/geom/TestGeoBBox.java:75-77
Suggested fix
  • lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoLongitudeSlice.java:196-207; geom/GeoWideNorthRectangle.java:258-274

Other statements trace to the hunt's working files (models, logs, reviews and briefs), which are not published.

Repro files

About the diagrams

Made in parallel with this doc; its values come from a tracing probe on main a9779ef3c193 (assertions on and off, and with the fix), and its code lines from a9779ef3c193.

Built 2026-11-06 from the hunt's record of this finding and its doc file. Lucene main under test in this hunt: ea57360912ed. Every listed finding was re-run on Lucene main e1faeba08bde on 2026-11-02.