argleton 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. {argleton-0.4.0 → argleton-0.5.0}/.github/workflows/ci.yml +8 -5
  2. {argleton-0.4.0 → argleton-0.5.0}/.zenodo.json +1 -1
  3. argleton-0.5.0/CHANGELOG.md +111 -0
  4. {argleton-0.4.0 → argleton-0.5.0}/CITATION.cff +7 -2
  5. {argleton-0.4.0 → argleton-0.5.0}/PKG-INFO +72 -28
  6. {argleton-0.4.0 → argleton-0.5.0}/README.md +71 -27
  7. {argleton-0.4.0 → argleton-0.5.0}/adapters/engine_geopandas.py +27 -0
  8. {argleton-0.4.0 → argleton-0.5.0}/adapters/engine_naive.py +10 -0
  9. argleton-0.5.0/adapters/engine_qgis.py +181 -0
  10. argleton-0.5.0/adapters/gis_mcp.py +541 -0
  11. {argleton-0.4.0 → argleton-0.5.0}/adapters/mapsmith.py +9 -0
  12. argleton-0.5.0/adapters/qgis_agent_mcp.py +241 -0
  13. argleton-0.5.0/adapters/qgis_chains.py +631 -0
  14. argleton-0.5.0/adapters/qgis_mcp_plugin.py +151 -0
  15. argleton-0.5.0/argleton/__init__.py +38 -0
  16. {argleton-0.4.0 → argleton-0.5.0}/argleton/run.py +84 -19
  17. argleton-0.5.0/clean/c031-hole-wound-as-hole/README.md +16 -0
  18. argleton-0.5.0/clean/c031-hole-wound-as-hole/build.py +54 -0
  19. argleton-0.5.0/clean/c031-hole-wound-as-hole/probe.toml +44 -0
  20. {argleton-0.4.0 → argleton-0.5.0}/docs/FAMILIES.md +134 -142
  21. {argleton-0.4.0 → argleton-0.5.0}/docs/METHOD.md +21 -0
  22. {argleton-0.4.0 → argleton-0.5.0}/pyproject.toml +1 -1
  23. argleton-0.5.0/results/2026-09-02-ring-role/adapters-geopandas.json +767 -0
  24. argleton-0.5.0/results/2026-09-02-ring-role/adapters-mapsmith.json +767 -0
  25. argleton-0.5.0/results/2026-09-02-ring-role/adapters-naive.json +766 -0
  26. argleton-0.5.0/results/2026-09-02-ring-role/adapters-rasterio.json +767 -0
  27. argleton-0.5.0/results/2026-09-02-ring-role/adapters-whitebox.json +767 -0
  28. argleton-0.5.0/results/2026-09-02-thirty-traps/adapters-geopandas.json +743 -0
  29. argleton-0.5.0/results/2026-09-02-thirty-traps/adapters-mapsmith.json +743 -0
  30. argleton-0.5.0/results/2026-09-02-thirty-traps/adapters-naive.json +742 -0
  31. argleton-0.5.0/results/2026-09-02-thirty-traps/adapters-rasterio.json +743 -0
  32. argleton-0.5.0/results/2026-09-02-thirty-traps/adapters-whitebox.json +743 -0
  33. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-geopandas.json +767 -0
  34. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-gis-mcp.json +767 -0
  35. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-mapsmith.json +767 -0
  36. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-naive.json +766 -0
  37. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-rasterio.json +767 -0
  38. argleton-0.5.0/results/2026-09-10-gis-mcp/adapters-whitebox.json +767 -0
  39. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-geopandas.json +767 -0
  40. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-gis-mcp.json +767 -0
  41. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-mapsmith.json +767 -0
  42. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-naive.json +766 -0
  43. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-qgis-agent-mcp.json +768 -0
  44. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-qgis-mcp.json +768 -0
  45. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-qgis.json +767 -0
  46. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-rasterio.json +767 -0
  47. argleton-0.5.0/results/2026-09-15-qgis-three-ways/adapters-whitebox.json +767 -0
  48. argleton-0.5.0/results/LATEST +1 -0
  49. {argleton-0.4.0 → argleton-0.5.0}/results/README.md +335 -32
  50. {argleton-0.4.0 → argleton-0.5.0}/site/build.py +390 -246
  51. {argleton-0.4.0 → argleton-0.5.0}/site/index.template.html +383 -367
  52. argleton-0.5.0/tests/test_showcase.py +1271 -0
  53. argleton-0.5.0/traps/031-hole-wound-as-shell/README.md +70 -0
  54. argleton-0.5.0/traps/031-hole-wound-as-shell/build.py +99 -0
  55. argleton-0.5.0/traps/031-hole-wound-as-shell/probe.toml +108 -0
  56. argleton-0.4.0/adapters/gis_mcp.py +0 -220
  57. argleton-0.4.0/argleton/__init__.py +0 -3
  58. argleton-0.4.0/results/LATEST +0 -1
  59. argleton-0.4.0/tests/test_showcase.py +0 -642
  60. {argleton-0.4.0 → argleton-0.5.0}/.gitattributes +0 -0
  61. {argleton-0.4.0 → argleton-0.5.0}/.github/workflows/pages.yml +0 -0
  62. {argleton-0.4.0 → argleton-0.5.0}/.gitignore +0 -0
  63. {argleton-0.4.0 → argleton-0.5.0}/LICENSE +0 -0
  64. {argleton-0.4.0 → argleton-0.5.0}/adapters/__init__.py +0 -0
  65. {argleton-0.4.0 → argleton-0.5.0}/adapters/engine_rasterio.py +0 -0
  66. {argleton-0.4.0 → argleton-0.5.0}/adapters/engine_whitebox.py +0 -0
  67. {argleton-0.4.0 → argleton-0.5.0}/argleton/model.py +0 -0
  68. {argleton-0.4.0 → argleton-0.5.0}/argleton/published.py +0 -0
  69. {argleton-0.4.0 → argleton-0.5.0}/argleton/score.py +0 -0
  70. {argleton-0.4.0 → argleton-0.5.0}/clean/c001-raster-mean/build.py +0 -0
  71. {argleton-0.4.0 → argleton-0.5.0}/clean/c001-raster-mean/probe.toml +0 -0
  72. {argleton-0.4.0 → argleton-0.5.0}/clean/c002-projected-area/build.py +0 -0
  73. {argleton-0.4.0 → argleton-0.5.0}/clean/c002-projected-area/probe.toml +0 -0
  74. {argleton-0.4.0 → argleton-0.5.0}/clean/c003-raster-mean-nodata/build.py +0 -0
  75. {argleton-0.4.0 → argleton-0.5.0}/clean/c003-raster-mean-nodata/probe.toml +0 -0
  76. {argleton-0.4.0 → argleton-0.5.0}/clean/c004-points-in-polygon/build.py +0 -0
  77. {argleton-0.4.0 → argleton-0.5.0}/clean/c004-points-in-polygon/probe.toml +0 -0
  78. {argleton-0.4.0 → argleton-0.5.0}/clean/c005-polygon-area/build.py +0 -0
  79. {argleton-0.4.0 → argleton-0.5.0}/clean/c005-polygon-area/probe.toml +0 -0
  80. {argleton-0.4.0 → argleton-0.5.0}/clean/c006-named-layer/build.py +0 -0
  81. {argleton-0.4.0 → argleton-0.5.0}/clean/c006-named-layer/probe.toml +0 -0
  82. {argleton-0.4.0 → argleton-0.5.0}/clean/c007-distance-in-metres/build.py +0 -0
  83. {argleton-0.4.0 → argleton-0.5.0}/clean/c007-distance-in-metres/probe.toml +0 -0
  84. {argleton-0.4.0 → argleton-0.5.0}/clean/c008-equal-area-crs/build.py +0 -0
  85. {argleton-0.4.0 → argleton-0.5.0}/clean/c008-equal-area-crs/probe.toml +0 -0
  86. {argleton-0.4.0 → argleton-0.5.0}/clean/c009-native-resolution-classes/build.py +0 -0
  87. {argleton-0.4.0 → argleton-0.5.0}/clean/c009-native-resolution-classes/probe.toml +0 -0
  88. {argleton-0.4.0 → argleton-0.5.0}/clean/c010-physical-values/build.py +0 -0
  89. {argleton-0.4.0 → argleton-0.5.0}/clean/c010-physical-values/probe.toml +0 -0
  90. {argleton-0.4.0 → argleton-0.5.0}/clean/c011-solid-parcel/build.py +0 -0
  91. {argleton-0.4.0 → argleton-0.5.0}/clean/c011-solid-parcel/probe.toml +0 -0
  92. {argleton-0.4.0 → argleton-0.5.0}/clean/c012-disjoint-concessions/build.py +0 -0
  93. {argleton-0.4.0 → argleton-0.5.0}/clean/c012-disjoint-concessions/probe.toml +0 -0
  94. {argleton-0.4.0 → argleton-0.5.0}/clean/c013-flat-pipeline/build.py +0 -0
  95. {argleton-0.4.0 → argleton-0.5.0}/clean/c013-flat-pipeline/probe.toml +0 -0
  96. {argleton-0.4.0 → argleton-0.5.0}/clean/c014-convex-parcel/build.py +0 -0
  97. {argleton-0.4.0 → argleton-0.5.0}/clean/c014-convex-parcel/probe.toml +0 -0
  98. {argleton-0.4.0 → argleton-0.5.0}/clean/c015-wells-off-the-seam/build.py +0 -0
  99. {argleton-0.4.0 → argleton-0.5.0}/clean/c015-wells-off-the-seam/probe.toml +0 -0
  100. {argleton-0.4.0 → argleton-0.5.0}/clean/c016-decimal-degrees/build.py +0 -0
  101. {argleton-0.4.0 → argleton-0.5.0}/clean/c016-decimal-degrees/probe.toml +0 -0
  102. {argleton-0.4.0 → argleton-0.5.0}/clean/c017-equal-populations/build.py +0 -0
  103. {argleton-0.4.0 → argleton-0.5.0}/clean/c017-equal-populations/probe.toml +0 -0
  104. {argleton-0.4.0 → argleton-0.5.0}/clean/c018-plain-keys/build.py +0 -0
  105. {argleton-0.4.0 → argleton-0.5.0}/clean/c018-plain-keys/probe.toml +0 -0
  106. {argleton-0.4.0 → argleton-0.5.0}/clean/c019-fully-contained/build.py +0 -0
  107. {argleton-0.4.0 → argleton-0.5.0}/clean/c019-fully-contained/probe.toml +0 -0
  108. {argleton-0.4.0 → argleton-0.5.0}/clean/c020-one-owner-each/build.py +0 -0
  109. {argleton-0.4.0 → argleton-0.5.0}/clean/c020-one-owner-each/probe.toml +0 -0
  110. {argleton-0.4.0 → argleton-0.5.0}/clean/c021-greenwich-variant/build.py +0 -0
  111. {argleton-0.4.0 → argleton-0.5.0}/clean/c021-greenwich-variant/probe.toml +0 -0
  112. {argleton-0.4.0 → argleton-0.5.0}/clean/c022-two-gauges/build.py +0 -0
  113. {argleton-0.4.0 → argleton-0.5.0}/clean/c022-two-gauges/probe.toml +0 -0
  114. {argleton-0.4.0 → argleton-0.5.0}/clean/c023-longitude-first/build.py +0 -0
  115. {argleton-0.4.0 → argleton-0.5.0}/clean/c023-longitude-first/probe.toml +0 -0
  116. {argleton-0.4.0 → argleton-0.5.0}/clean/c024-pixel-is-area/build.py +0 -0
  117. {argleton-0.4.0 → argleton-0.5.0}/clean/c024-pixel-is-area/probe.toml +0 -0
  118. {argleton-0.4.0 → argleton-0.5.0}/clean/c025-single-hemisphere-zone/build.py +0 -0
  119. {argleton-0.4.0 → argleton-0.5.0}/clean/c025-single-hemisphere-zone/probe.toml +0 -0
  120. {argleton-0.4.0 → argleton-0.5.0}/clean/c026-north-up-grid/build.py +0 -0
  121. {argleton-0.4.0 → argleton-0.5.0}/clean/c026-north-up-grid/probe.toml +0 -0
  122. {argleton-0.4.0 → argleton-0.5.0}/clean/c027-lines-only/build.py +0 -0
  123. {argleton-0.4.0 → argleton-0.5.0}/clean/c027-lines-only/probe.toml +0 -0
  124. {argleton-0.4.0 → argleton-0.5.0}/clean/c028-projected-field/build.py +0 -0
  125. {argleton-0.4.0 → argleton-0.5.0}/clean/c028-projected-field/probe.toml +0 -0
  126. {argleton-0.4.0 → argleton-0.5.0}/clean/c029-coincident-boundaries/build.py +0 -0
  127. {argleton-0.4.0 → argleton-0.5.0}/clean/c029-coincident-boundaries/probe.toml +0 -0
  128. {argleton-0.4.0 → argleton-0.5.0}/clean/c030-single-georeferencing/build.py +0 -0
  129. {argleton-0.4.0 → argleton-0.5.0}/clean/c030-single-georeferencing/probe.toml +0 -0
  130. {argleton-0.4.0 → argleton-0.5.0}/docs/ADDING-A-TRAP.md +0 -0
  131. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-23-first-run/adapters-mapsmith.json +0 -0
  132. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-23-first-run/engine-geopandas.json +0 -0
  133. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-23-first-run/engine-naive.json +0 -0
  134. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-23-first-run/engine-rasterio.json +0 -0
  135. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-23-first-run/engine-whitebox.json +0 -0
  136. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-24-five-families/adapters-mapsmith.json +0 -0
  137. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-24-five-families/engine-geopandas.json +0 -0
  138. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-24-five-families/engine-naive.json +0 -0
  139. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-24-five-families/engine-rasterio.json +0 -0
  140. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-24-five-families/engine-whitebox.json +0 -0
  141. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-eight-families/adapters-mapsmith.json +0 -0
  142. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-eight-families/engine-geopandas.json +0 -0
  143. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-eight-families/engine-naive.json +0 -0
  144. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-eight-families/engine-rasterio.json +0 -0
  145. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-eight-families/engine-whitebox.json +0 -0
  146. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-six-families/adapters-mapsmith.json +0 -0
  147. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-six-families/engine-geopandas.json +0 -0
  148. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-six-families/engine-naive.json +0 -0
  149. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-six-families/engine-rasterio.json +0 -0
  150. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-25-six-families/engine-whitebox.json +0 -0
  151. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-eighteen-families/adapters-geopandas.json +0 -0
  152. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-eighteen-families/adapters-mapsmith.json +0 -0
  153. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-eighteen-families/adapters-naive.json +0 -0
  154. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-eighteen-families/adapters-rasterio.json +0 -0
  155. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-eighteen-families/adapters-whitebox.json +0 -0
  156. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nine-families/adapters-geopandas.json +0 -0
  157. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nine-families/adapters-mapsmith.json +0 -0
  158. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nine-families/adapters-naive.json +0 -0
  159. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nine-families/adapters-rasterio.json +0 -0
  160. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nine-families/adapters-whitebox.json +0 -0
  161. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nineteen-families/adapters-geopandas.json +0 -0
  162. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nineteen-families/adapters-mapsmith.json +0 -0
  163. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nineteen-families/adapters-naive.json +0 -0
  164. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nineteen-families/adapters-rasterio.json +0 -0
  165. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-nineteen-families/adapters-whitebox.json +0 -0
  166. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-ten-families/adapters-geopandas.json +0 -0
  167. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-ten-families/adapters-mapsmith.json +0 -0
  168. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-ten-families/adapters-naive.json +0 -0
  169. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-ten-families/adapters-rasterio.json +0 -0
  170. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-26-ten-families/adapters-whitebox.json +0 -0
  171. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-27-datum-shift-fixed/adapters-geopandas.json +0 -0
  172. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-27-datum-shift-fixed/adapters-mapsmith.json +0 -0
  173. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-27-datum-shift-fixed/adapters-naive.json +0 -0
  174. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-27-datum-shift-fixed/adapters-rasterio.json +0 -0
  175. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-27-datum-shift-fixed/adapters-whitebox.json +0 -0
  176. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-thiessen-pairing/adapters-geopandas.json +0 -0
  177. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-thiessen-pairing/adapters-mapsmith.json +0 -0
  178. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-thiessen-pairing/adapters-naive.json +0 -0
  179. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-thiessen-pairing/adapters-rasterio.json +0 -0
  180. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-thiessen-pairing/adapters-whitebox.json +0 -0
  181. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-timings/adapters-geopandas.json +0 -0
  182. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-timings/adapters-mapsmith.json +0 -0
  183. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-timings/adapters-naive.json +0 -0
  184. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-timings/adapters-rasterio.json +0 -0
  185. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-28-timings/adapters-whitebox.json +0 -0
  186. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-29-axis-order/adapters-geopandas.json +0 -0
  187. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-29-axis-order/adapters-mapsmith.json +0 -0
  188. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-29-axis-order/adapters-naive.json +0 -0
  189. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-29-axis-order/adapters-rasterio.json +0 -0
  190. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-29-axis-order/adapters-whitebox.json +0 -0
  191. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-all-families/adapters-geopandas.json +0 -0
  192. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-all-families/adapters-mapsmith.json +0 -0
  193. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-all-families/adapters-naive.json +0 -0
  194. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-all-families/adapters-rasterio.json +0 -0
  195. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-all-families/adapters-whitebox.json +0 -0
  196. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-antimeridian/adapters-geopandas.json +0 -0
  197. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-antimeridian/adapters-mapsmith.json +0 -0
  198. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-antimeridian/adapters-naive.json +0 -0
  199. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-antimeridian/adapters-rasterio.json +0 -0
  200. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-antimeridian/adapters-whitebox.json +0 -0
  201. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-grid-registration/adapters-geopandas.json +0 -0
  202. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-grid-registration/adapters-mapsmith.json +0 -0
  203. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-grid-registration/adapters-naive.json +0 -0
  204. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-grid-registration/adapters-rasterio.json +0 -0
  205. {argleton-0.4.0 → argleton-0.5.0}/results/2026-08-30-grid-registration/adapters-whitebox.json +0 -0
  206. {argleton-0.4.0 → argleton-0.5.0}/schema/probe.schema.json +0 -0
  207. {argleton-0.4.0 → argleton-0.5.0}/schema/result.schema.json +0 -0
  208. {argleton-0.4.0 → argleton-0.5.0}/tests/test_contracts.py +0 -0
  209. {argleton-0.4.0 → argleton-0.5.0}/traps/001-tiff-predictor/README.md +0 -0
  210. {argleton-0.4.0 → argleton-0.5.0}/traps/001-tiff-predictor/build.py +0 -0
  211. {argleton-0.4.0 → argleton-0.5.0}/traps/001-tiff-predictor/probe.toml +0 -0
  212. {argleton-0.4.0 → argleton-0.5.0}/traps/002-feet-as-metres/README.md +0 -0
  213. {argleton-0.4.0 → argleton-0.5.0}/traps/002-feet-as-metres/build.py +0 -0
  214. {argleton-0.4.0 → argleton-0.5.0}/traps/002-feet-as-metres/probe.toml +0 -0
  215. {argleton-0.4.0 → argleton-0.5.0}/traps/003-nodata-in-statistics/README.md +0 -0
  216. {argleton-0.4.0 → argleton-0.5.0}/traps/003-nodata-in-statistics/build.py +0 -0
  217. {argleton-0.4.0 → argleton-0.5.0}/traps/003-nodata-in-statistics/probe.toml +0 -0
  218. {argleton-0.4.0 → argleton-0.5.0}/traps/004-mismatched-crs-join/README.md +0 -0
  219. {argleton-0.4.0 → argleton-0.5.0}/traps/004-mismatched-crs-join/build.py +0 -0
  220. {argleton-0.4.0 → argleton-0.5.0}/traps/004-mismatched-crs-join/probe.toml +0 -0
  221. {argleton-0.4.0 → argleton-0.5.0}/traps/005-bowtie-area/README.md +0 -0
  222. {argleton-0.4.0 → argleton-0.5.0}/traps/005-bowtie-area/build.py +0 -0
  223. {argleton-0.4.0 → argleton-0.5.0}/traps/005-bowtie-area/probe.toml +0 -0
  224. {argleton-0.4.0 → argleton-0.5.0}/traps/006-default-layer/README.md +0 -0
  225. {argleton-0.4.0 → argleton-0.5.0}/traps/006-default-layer/build.py +0 -0
  226. {argleton-0.4.0 → argleton-0.5.0}/traps/006-default-layer/probe.toml +0 -0
  227. {argleton-0.4.0 → argleton-0.5.0}/traps/007-buffer-in-degrees/README.md +0 -0
  228. {argleton-0.4.0 → argleton-0.5.0}/traps/007-buffer-in-degrees/build.py +0 -0
  229. {argleton-0.4.0 → argleton-0.5.0}/traps/007-buffer-in-degrees/probe.toml +0 -0
  230. {argleton-0.4.0 → argleton-0.5.0}/traps/008-web-mercator-area/README.md +0 -0
  231. {argleton-0.4.0 → argleton-0.5.0}/traps/008-web-mercator-area/build.py +0 -0
  232. {argleton-0.4.0 → argleton-0.5.0}/traps/008-web-mercator-area/probe.toml +0 -0
  233. {argleton-0.4.0 → argleton-0.5.0}/traps/009-resampled-classes/README.md +0 -0
  234. {argleton-0.4.0 → argleton-0.5.0}/traps/009-resampled-classes/build.py +0 -0
  235. {argleton-0.4.0 → argleton-0.5.0}/traps/009-resampled-classes/probe.toml +0 -0
  236. {argleton-0.4.0 → argleton-0.5.0}/traps/010-scale-offset/README.md +0 -0
  237. {argleton-0.4.0 → argleton-0.5.0}/traps/010-scale-offset/build.py +0 -0
  238. {argleton-0.4.0 → argleton-0.5.0}/traps/010-scale-offset/probe.toml +0 -0
  239. {argleton-0.4.0 → argleton-0.5.0}/traps/011-polygon-holes/README.md +0 -0
  240. {argleton-0.4.0 → argleton-0.5.0}/traps/011-polygon-holes/build.py +0 -0
  241. {argleton-0.4.0 → argleton-0.5.0}/traps/011-polygon-holes/probe.toml +0 -0
  242. {argleton-0.4.0 → argleton-0.5.0}/traps/012-double-counting/README.md +0 -0
  243. {argleton-0.4.0 → argleton-0.5.0}/traps/012-double-counting/build.py +0 -0
  244. {argleton-0.4.0 → argleton-0.5.0}/traps/012-double-counting/probe.toml +0 -0
  245. {argleton-0.4.0 → argleton-0.5.0}/traps/013-z-dimension/README.md +0 -0
  246. {argleton-0.4.0 → argleton-0.5.0}/traps/013-z-dimension/build.py +0 -0
  247. {argleton-0.4.0 → argleton-0.5.0}/traps/013-z-dimension/probe.toml +0 -0
  248. {argleton-0.4.0 → argleton-0.5.0}/traps/014-centroid-outside/README.md +0 -0
  249. {argleton-0.4.0 → argleton-0.5.0}/traps/014-centroid-outside/build.py +0 -0
  250. {argleton-0.4.0 → argleton-0.5.0}/traps/014-centroid-outside/probe.toml +0 -0
  251. {argleton-0.4.0 → argleton-0.5.0}/traps/015-boundary-semantics/README.md +0 -0
  252. {argleton-0.4.0 → argleton-0.5.0}/traps/015-boundary-semantics/build.py +0 -0
  253. {argleton-0.4.0 → argleton-0.5.0}/traps/015-boundary-semantics/probe.toml +0 -0
  254. {argleton-0.4.0 → argleton-0.5.0}/traps/016-coordinate-parsing/README.md +0 -0
  255. {argleton-0.4.0 → argleton-0.5.0}/traps/016-coordinate-parsing/build.py +0 -0
  256. {argleton-0.4.0 → argleton-0.5.0}/traps/016-coordinate-parsing/probe.toml +0 -0
  257. {argleton-0.4.0 → argleton-0.5.0}/traps/017-aggregation-weighting/README.md +0 -0
  258. {argleton-0.4.0 → argleton-0.5.0}/traps/017-aggregation-weighting/build.py +0 -0
  259. {argleton-0.4.0 → argleton-0.5.0}/traps/017-aggregation-weighting/probe.toml +0 -0
  260. {argleton-0.4.0 → argleton-0.5.0}/traps/018-join-key-typing/README.md +0 -0
  261. {argleton-0.4.0 → argleton-0.5.0}/traps/018-join-key-typing/build.py +0 -0
  262. {argleton-0.4.0 → argleton-0.5.0}/traps/018-join-key-typing/probe.toml +0 -0
  263. {argleton-0.4.0 → argleton-0.5.0}/traps/019-partial-overlap/README.md +0 -0
  264. {argleton-0.4.0 → argleton-0.5.0}/traps/019-partial-overlap/build.py +0 -0
  265. {argleton-0.4.0 → argleton-0.5.0}/traps/019-partial-overlap/probe.toml +0 -0
  266. {argleton-0.4.0 → argleton-0.5.0}/traps/020-join-cardinality/README.md +0 -0
  267. {argleton-0.4.0 → argleton-0.5.0}/traps/020-join-cardinality/build.py +0 -0
  268. {argleton-0.4.0 → argleton-0.5.0}/traps/020-join-cardinality/probe.toml +0 -0
  269. {argleton-0.4.0 → argleton-0.5.0}/traps/021-ballpark-datum/README.md +0 -0
  270. {argleton-0.4.0 → argleton-0.5.0}/traps/021-ballpark-datum/build.py +0 -0
  271. {argleton-0.4.0 → argleton-0.5.0}/traps/021-ballpark-datum/probe.toml +0 -0
  272. {argleton-0.4.0 → argleton-0.5.0}/traps/022-thiessen-pairing/README.md +0 -0
  273. {argleton-0.4.0 → argleton-0.5.0}/traps/022-thiessen-pairing/build.py +0 -0
  274. {argleton-0.4.0 → argleton-0.5.0}/traps/022-thiessen-pairing/probe.toml +0 -0
  275. {argleton-0.4.0 → argleton-0.5.0}/traps/023-axis-order/README.md +0 -0
  276. {argleton-0.4.0 → argleton-0.5.0}/traps/023-axis-order/build.py +0 -0
  277. {argleton-0.4.0 → argleton-0.5.0}/traps/023-axis-order/probe.toml +0 -0
  278. {argleton-0.4.0 → argleton-0.5.0}/traps/024-pixel-is-point/README.md +0 -0
  279. {argleton-0.4.0 → argleton-0.5.0}/traps/024-pixel-is-point/build.py +0 -0
  280. {argleton-0.4.0 → argleton-0.5.0}/traps/024-pixel-is-point/probe.toml +0 -0
  281. {argleton-0.4.0 → argleton-0.5.0}/traps/025-antimeridian-zone/README.md +0 -0
  282. {argleton-0.4.0 → argleton-0.5.0}/traps/025-antimeridian-zone/build.py +0 -0
  283. {argleton-0.4.0 → argleton-0.5.0}/traps/025-antimeridian-zone/probe.toml +0 -0
  284. {argleton-0.4.0 → argleton-0.5.0}/traps/026-south-up-grid/README.md +0 -0
  285. {argleton-0.4.0 → argleton-0.5.0}/traps/026-south-up-grid/build.py +0 -0
  286. {argleton-0.4.0 → argleton-0.5.0}/traps/026-south-up-grid/probe.toml +0 -0
  287. {argleton-0.4.0 → argleton-0.5.0}/traps/027-mixed-geometry/README.md +0 -0
  288. {argleton-0.4.0 → argleton-0.5.0}/traps/027-mixed-geometry/build.py +0 -0
  289. {argleton-0.4.0 → argleton-0.5.0}/traps/027-mixed-geometry/probe.toml +0 -0
  290. {argleton-0.4.0 → argleton-0.5.0}/traps/028-degrees-as-metres/README.md +0 -0
  291. {argleton-0.4.0 → argleton-0.5.0}/traps/028-degrees-as-metres/build.py +0 -0
  292. {argleton-0.4.0 → argleton-0.5.0}/traps/028-degrees-as-metres/probe.toml +0 -0
  293. {argleton-0.4.0 → argleton-0.5.0}/traps/029-difference-order/README.md +0 -0
  294. {argleton-0.4.0 → argleton-0.5.0}/traps/029-difference-order/build.py +0 -0
  295. {argleton-0.4.0 → argleton-0.5.0}/traps/029-difference-order/probe.toml +0 -0
  296. {argleton-0.4.0 → argleton-0.5.0}/traps/030-sidecar-georeferencing/README.md +0 -0
  297. {argleton-0.4.0 → argleton-0.5.0}/traps/030-sidecar-georeferencing/build.py +0 -0
  298. {argleton-0.4.0 → argleton-0.5.0}/traps/030-sidecar-georeferencing/probe.toml +0 -0
@@ -82,13 +82,16 @@ jobs:
82
82
  # point — a trap that stops catching its own naive adapter, or one
83
83
  # that starts catching a case it should not, has changed what it
84
84
  # measures, and that must be a decision rather than a surprise.
85
- # 0.9333 = 28 of 30 traps caught. TWO still pass, unchanged: 001,
85
+ # 0.9355 = 29 of 31 traps caught. TWO still pass, unchanged: 001,
86
86
  # where rasterio undoes the predictor on the naive adapter's behalf,
87
87
  # and 026, where `src.res` reports the cell size faithfully whichever
88
88
  # way the rows run — so a plain numpy gradient is more faithful to the
89
- # geotransform than a specialised terrain engine is. Moved from 0.931
90
- # on 2026-08-31 when trap 030 added family 28: the naive adapter falls
91
- # into it, so both numerator and denominator went up by one. Before
89
+ # geotransform than a specialised terrain engine is. Moved from
90
+ # 0.9333 on 2026-09-02 when trap 031 added family 29 — a shapefile
91
+ # whose inner ring is wound like its outer one, which the naive
92
+ # composition reads as two shells and adds — so both numerator and
93
+ # denominator went up by one. Before that 0.931 on 2026-08-31 when
94
+ # trap 030 added family 28, the same way. Before
92
95
  # that 0.96 when traps 026 to 029 closed the original family list, and
93
96
  # 0.9583, 0.9565 and 0.9545 as traps 025, 024 and 023 landed.
94
97
  # Each time the denominator changed and the numerator changed with
@@ -96,5 +99,5 @@ jobs:
96
99
  # mechanism working — the number moved because a trap was added, which
97
100
  # is a decision, and it has to be written down here rather than
98
101
  # discovered in a published table.
99
- sys.exit(0 if (r["silent_error_rate"], r["completion_rate"]) == (0.9333, 1.0) else 1)
102
+ sys.exit(0 if (r["silent_error_rate"], r["completion_rate"]) == (0.9355, 1.0) else 1)
100
103
  PY
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "upload_type": "software",
3
3
  "title": "Argleton: a correctness suite for geospatial systems",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "creators": [
6
6
  {"name": "The Argleton project"}
7
7
  ],
@@ -0,0 +1,111 @@
1
+ # Changelog
2
+
3
+ All notable changes to Argleton are documented here, in the format of
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project follows
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ This file starts at 0.5.0. Releases before it are described by their tags and
8
+ by the dated sections of [`results/README.md`](results/README.md), which is
9
+ where this project's findings have always been written — the omission was that
10
+ a reader had nowhere to see, in one place, what changed between two versions of
11
+ the suite itself.
12
+
13
+ ## [Unreleased]
14
+
15
+ Nothing yet.
16
+
17
+ ## [0.5.0] - 2026-09-21
18
+
19
+ Thirty-one commits since 0.4.0. The headline is that a third-party system was
20
+ measured against the engine it wraps, for the first time, and the result is
21
+ that the wrapper is not the thing to blame.
22
+
23
+ ### Added
24
+
25
+ - **Three QGIS rows, and they are the same QGIS.** The processing engine driven
26
+ headless through `qgis_process`, and the two MCP servers that run inside a
27
+ live QGIS and forward to it — `nkarasiak/qgis-mcp` 0.14.0 over its plugin
28
+ socket, QGIS Agent MCP 0.5.0 over its local bridge. All three answer **every
29
+ one of the thirty-one traps**, nothing unsupported, and all three come out at
30
+ the same silent-error rate of **0.3871**.
31
+
32
+ That equality is the finding, and it is only readable because the engine has
33
+ a row of its own: the wrappers inherit it, neither adding a correct answer
34
+ nor losing one. Without the engine's row, three equal numbers read as three
35
+ equally defective servers, and that reading would be wrong. The engine's row
36
+ was built first for exactly this reason.
37
+
38
+ The chains are shared by all three adapters in one file, so a difference
39
+ between the rows can only come from the system rather than from a chain that
40
+ drifted between copies.
41
+
42
+ - **`gis-mcp` 0.15.0**, the first system here whose defects are not ours to
43
+ fix, published on 2026-09-10 after its maintainer was told first.
44
+
45
+ - **Family 29, `ring-role-by-winding`.** A shapefile carries no nesting, so
46
+ which ring is a hole is decided by the direction it is wound and by nothing
47
+ else. An inner ring wound like its parent reads as a second shell and its
48
+ area is added: 31000 m² where the truth is 29000, +6.9% in the owner's
49
+ favour, with the right bounding box, the right CRS and no warning.
50
+
51
+ ### Changed
52
+
53
+ - **`argleton.__version__` is derived instead of written.** It answered
54
+ `0.1.0.dev0` while 0.4.0 was on PyPI, across four releases, because the
55
+ number lived in two places and only one was on a checklist. Nothing inside
56
+ the package read it, so nothing caught it. It now comes from the installed
57
+ distribution, or from `pyproject.toml` when running from a checkout, and a
58
+ test compares the two.
59
+
60
+ - **The naive baseline pin moves to 0.9355**, which is the pin doing its job
61
+ rather than a number being adjusted: the denominator grew when the suite
62
+ reached thirty-one traps.
63
+
64
+ - **A fixture build that dies with no output is retried on its signature, not
65
+ on a count.** One retry had been calibrated against 370 builds, where a
66
+ doubled failure is about one in a thousand. At nine adapters and 558 builds
67
+ the same doubled failure is about one in twenty. The constant was not wrong
68
+ when it was written; it went wrong when the table grew, and nothing was
69
+ watching it. The retry now keys on what the death looks like — both output
70
+ streams empty, because the process never ran — which is stricter than what it
71
+ replaces: a builder that fails while saying something now fails on the first
72
+ attempt.
73
+
74
+ ### Fixed
75
+
76
+ - **The site sorted both QGIS MCP servers below the naive baseline**, the row
77
+ that exists to be the worst line on the page, because neither matched a
78
+ prefix in the ordering list and an unmatched row sorts last. The comment
79
+ above that list had named their correct place two months earlier — beside
80
+ `gis-mcp`, with the engine they call directly underneath. A rule stated in a
81
+ comment and not executed by code is not a rule; a test covers it now.
82
+
83
+ - **The site said "every system here completes every clean probe"** directly
84
+ under a column showing two systems that do not.
85
+
86
+ - **Three surfaces said every number comes from calling code in the same
87
+ process rather than over a transport.** Two rows are a TCP socket and a local
88
+ bridge into a live QGIS, which is what their own labels say.
89
+
90
+ - **The results page said we had "found nothing of its own" about one wrapper**,
91
+ seven lines after describing a property of that wrapper. The true sentence is
92
+ "nothing that makes an answer wrong", and the notification rule is restated
93
+ around it.
94
+
95
+ - **"In opposite directions" described two rows with the same probe, the same
96
+ verdict and the same completion rate.** They differ in how they fail.
97
+
98
+ - **`FAMILIES.md` published four false sentences about its own count** while
99
+ two guards on counts were green, because they watched three other sentence
100
+ shapes. The numbers are derived now, and an unexplained spelled-out number in
101
+ that file is a failure rather than a gap.
102
+
103
+ - **The results index counts its own rows** instead of only checking the ones
104
+ that happen to be listed, and the tool count is asked of the server rather
105
+ than written down.
106
+
107
+ - **`publish_run.py`** — in the private workspace, but its effects are here —
108
+ refuses to publish an adapter whose failures all carry one message: that is a
109
+ system we could not reach, not a system that got things wrong. A run was
110
+ minutes from publishing `completion 0.2258` for a third-party server whose
111
+ 62 probes had all failed to connect.
@@ -5,10 +5,15 @@ type: software
5
5
  authors:
6
6
  - name: "The Argleton project"
7
7
  repository-code: "https://github.com/argleton/argleton"
8
+ doi: "10.5281/zenodo.22206349"
9
+ identifiers:
10
+ - type: doi
11
+ value: "10.5281/zenodo.22206349"
12
+ description: "Concept DOI — always resolves to the latest release"
8
13
  url: "https://argleton.org"
9
14
  license: Apache-2.0
10
- version: "0.4.0"
11
- date-released: "2026-08-31"
15
+ version: "0.5.0"
16
+ date-released: "2026-09-21"
12
17
  abstract: >-
13
18
  Probes whose right answer is known by construction — derived on paper, never
14
19
  measured from a reference implementation — and whose typical wrong answer
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: argleton
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Probes whose right answer is known by construction, and whose wrong answer looks fine
5
5
  Project-URL: Source, https://github.com/argleton/argleton
6
6
  Author: Argleton contributors
@@ -42,6 +42,13 @@ known by construction, and every trap has a *wrong* answer that looks fine.
42
42
  **[argleton.org](https://argleton.org)** — the current results, rendered by CI
43
43
  from this repository's own numbers. Nothing on that page is typed in by hand.
44
44
 
45
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22206349.svg)](https://doi.org/10.5281/zenodo.22206349)
46
+
47
+ Cite it as `10.5281/zenodo.22206349`, which always resolves to the current release. To cite the
48
+ exact version you measured against, take the version DOI from the
49
+ [Zenodo record](https://doi.org/10.5281/zenodo.22206349) — which is the same reason every run
50
+ here pins its `spec_commit`.
51
+
45
52
  That second half is the whole point. Existing benchmarks for geospatial agents
46
53
  score trajectories: did it pick the right tools, in the right order, and produce
47
54
  a file? A system can score full marks on all of that and hand you a number that
@@ -67,10 +74,13 @@ machine, so there is nothing to download and nothing to take on trust:
67
74
  pip install "argleton[fixtures]"
68
75
  ```
69
76
 
70
- The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
71
- 30 traps and 28 families the results below were produced from. When the checkout runs ahead
72
- of the release this paragraph says so, because a reader who cannot reproduce the table on
73
- this page has been told something untrue.
77
+ The probes ship with the runner, so that is the whole setup — release 0.5.0 carries the
78
+ 31 traps and 29 families the published table further down was run over, so installing it and
79
+ rerunning reproduces that table rather than a version of it. This paragraph says so whenever the
80
+ checkout runs ahead of the release, because a reader who cannot reproduce the table on this page
81
+ has been told something untrue: until this release it named thirty traps and twenty-eight
82
+ families, which gave a naive rate of 0.9333 against the 0.9355 on the page — the same systems,
83
+ a denominator one smaller.
74
84
 
75
85
  To read the probes, change them, or add one, take the checkout instead — the probes are the point of the
76
86
  repository and `probe.toml` is meant to be read:
@@ -83,19 +93,21 @@ pip install -e ".[fixtures]"
83
93
 
84
94
  ```
85
95
  $ argleton --adapter engine:rasterio
86
- ok clean c001-raster-mean correct 1093.0
87
- ok clean c003-raster-mean-nodata correct 1000.0
88
- ok clean c009-native-resolution-classes correct 900.0
89
- ok clean c010-physical-values correct 0.5000000093132254
90
- ok clean c024-pixel-is-area correct 412105.0
91
- ok clean c026-north-up-grid correct 5.710593137499643
92
- ok trap 001-tiff-predictor correct 1093.0
93
- ok trap 003-nodata-in-statistics correct 1000.0
94
- ok trap 009-resampled-classes correct 0.0
95
- ok trap 010-scale-offset correct 0.3333333333333334
96
- ok trap 024-pixel-is-point correct 412090.0
97
- ok trap 026-south-up-grid correct 5.710593137499643
98
- silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
96
+ ok clean c001-raster-mean correct 1093.0
97
+ ok clean c003-raster-mean-nodata correct 1000.0
98
+ ok clean c009-native-resolution-classes correct 900.0
99
+ ok clean c010-physical-values correct 0.5000000093132254
100
+ ok clean c024-pixel-is-area correct 412105.0
101
+ ok clean c026-north-up-grid correct 5.710593137499643
102
+ ok clean c030-single-georeferencing correct_with_warning read with internal georeferencing
103
+ ok trap 001-tiff-predictor correct 1093.0
104
+ ok trap 003-nodata-in-statistics correct 1000.0
105
+ ok trap 009-resampled-classes correct 0.0
106
+ ok trap 010-scale-offset correct 0.3333333333333334
107
+ ok trap 024-pixel-is-point correct 412090.0
108
+ ok trap 026-south-up-grid correct 5.710593137499643
109
+ ok trap 030-sidecar-georeferencing correct_with_warning read with internal georeferencing; terrain.tif.aux.x
110
+ silent_error_rate 0.0 over 7 traps | completion_rate 1.0 over 7 clean
99
111
 
100
112
  $ argleton --adapter engine:whitebox
101
113
  ok clean c001-raster-mean correct 1093.0
@@ -127,8 +139,8 @@ at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
127
139
  clean twins right.
128
140
 
129
141
  There is a third adapter, `engine:naive` — read the file, take the statistic,
130
- report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
131
- answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
142
+ report it — and it is the most useful one here. In the published run it scores **0.9355 / 1.0**:
143
+ it answers every clean probe correctly, falls into twenty-nine of the thirty-one traps, and
132
144
  **passes the other two**. 001, because rasterio undoes the predictor on its
133
145
  behalf; 026, because `src.res` reports the cell size faithfully whichever way the
134
146
  rows run, which on that probe makes a plain numpy gradient more faithful to the
@@ -154,7 +166,7 @@ Side by side, one glance tells you which you are looking at.
154
166
 
155
167
  ## What is covered
156
168
 
157
- Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
169
+ Twenty-nine families of thirty-three, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
158
170
  number from here can never be read as broader than it is. A low silent-error
159
171
  rate means a system did not fail silently *on these probes*.
160
172
 
@@ -188,19 +200,24 @@ rate means a system did not fail silently *on these probes*.
188
200
  | `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
189
201
  | `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
190
202
  | `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
203
+ | `ring-role-by-winding` | 31000 m² instead of 29000 | a shapefile carries no nesting, so which ring is a hole is decided by the direction it is wound and by nothing else; an inner ring wound like its parent reads as a second shell and its area is added, flattering the owner by 6.9% |
191
204
 
192
205
  ## Results
193
206
 
194
- Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
195
- numbers do not say](results/).
207
+ All twenty-nine families, `spec_commit` pinned, no agent in the loop — [every run,
208
+ and what the numbers do not say](results/).
196
209
 
197
210
  | system | silent error rate | completion rate | traps run | not applicable |
198
211
  |---|---|---|---|---|
199
- | MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
200
- | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
201
- | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
202
- | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
203
- | naive composition | 0.931 | 1.00 | 29 | 0 |
212
+ | MapSmith (main) | 0.00 | 1.00 | 31 | 0 |
213
+ | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 14 | 34 |
214
+ | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 7 | 48 |
215
+ | gis-mcp 0.15.0 | 0.20 | 1.00 | 20 | 22 |
216
+ | QGIS processing 3.44.12 (via qgis_process) | 0.3871 | 1.00 | 31 | 0 |
217
+ | nkarasiak/qgis-mcp 0.14.0 (plugin socket) | 0.3871 | 0.9677 | 31 | 0 |
218
+ | QGIS Agent MCP 0.5.0 (local bridge) | 0.3871 | 0.9677 | 31 | 0 |
219
+ | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 54 |
220
+ | naive composition | 0.9355 | 1.00 | 31 | 0 |
204
221
 
205
222
  The last two columns are not decoration. A rate over two traps and a rate over
206
223
  eight are different claims, and an adapter that could only be asked one question
@@ -209,6 +226,33 @@ before this one, three of MapSmith's probes were `unsupported` — it had no are
209
226
  operation at all, which is a gap in a catalog rather than a bug in code, and the
210
227
  suite is what named it.
211
228
 
229
+ **Three of these rows are the same QGIS, and reading them as three systems is
230
+ the mistake this table is arranged to prevent.** The processing engine driven
231
+ headless, and the two MCP servers that run inside a live QGIS and forward to it,
232
+ all come out at 0.3871 — the wrappers inherit the engine, neither adding a
233
+ correct answer nor losing one. That is only readable because the engine has a
234
+ row of its own, put there first for this reason: without it, three equal numbers
235
+ read as three equally defective servers, and that reading would be wrong. The
236
+ two completion rates below 1.00 are one clean probe that neither wrapper
237
+ answers, and [the section for that run](results/#2026-09-15--three-rows-that-are-the-same-qgis-and-why-that-is-the-finding)
238
+ says which probe, how the two differ in the way they fail, and why part of that
239
+ number is our own error handling rather than theirs.
240
+
241
+ **gis-mcp is the first row here that is not ours to fix, and it is written so a
242
+ reader can tell whose limit each number is.** The 0.20 is four wrong answers out
243
+ of twenty traps attempted, every one returned with `status: "success"`: 4 wells
244
+ where the file holds 31, an NDVI of 0.25 where the declared calibration gives
245
+ 0.3333, a latitude 74 m out, and a parcel four times its own area. The
246
+ twenty-two not applicable are eleven traps and their eleven clean twins, on
247
+ which our adapter found no composition of gis-mcp's tools that answers the
248
+ question — a limit of its tool surface as our adapter reads it, and our bug to
249
+ fix if a composition exists and we missed it. Its maintainer was told first, in
250
+ [an issue filed on 4 September](https://github.com/mahdin75/gis-mcp/issues/45)
251
+ and again before this was published, and neither message has been answered; the
252
+ denominator has moved four times without gis-mcp changing a line, lowering the
253
+ rate each time. [The full section](results/#2026-09-10--the-first-system-here-whose-defects-are-not-ours-to-fix)
254
+ gives every one of those numbers with its provenance.
255
+
212
256
  Three findings frame everything here, one per run. From the first run:
213
257
  **MapSmith scored 0.00 and its verification had nothing to do with it** — on
214
258
  trap 001 it wrote a manifest with seven passing checks, none of which looks at
@@ -6,6 +6,13 @@ known by construction, and every trap has a *wrong* answer that looks fine.
6
6
  **[argleton.org](https://argleton.org)** — the current results, rendered by CI
7
7
  from this repository's own numbers. Nothing on that page is typed in by hand.
8
8
 
9
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22206349.svg)](https://doi.org/10.5281/zenodo.22206349)
10
+
11
+ Cite it as `10.5281/zenodo.22206349`, which always resolves to the current release. To cite the
12
+ exact version you measured against, take the version DOI from the
13
+ [Zenodo record](https://doi.org/10.5281/zenodo.22206349) — which is the same reason every run
14
+ here pins its `spec_commit`.
15
+
9
16
  That second half is the whole point. Existing benchmarks for geospatial agents
10
17
  score trajectories: did it pick the right tools, in the right order, and produce
11
18
  a file? A system can score full marks on all of that and hand you a number that
@@ -31,10 +38,13 @@ machine, so there is nothing to download and nothing to take on trust:
31
38
  pip install "argleton[fixtures]"
32
39
  ```
33
40
 
34
- The probes ship with the runner, so that is the whole setup — release 0.4.0 carries the
35
- 30 traps and 28 families the results below were produced from. When the checkout runs ahead
36
- of the release this paragraph says so, because a reader who cannot reproduce the table on
37
- this page has been told something untrue.
41
+ The probes ship with the runner, so that is the whole setup — release 0.5.0 carries the
42
+ 31 traps and 29 families the published table further down was run over, so installing it and
43
+ rerunning reproduces that table rather than a version of it. This paragraph says so whenever the
44
+ checkout runs ahead of the release, because a reader who cannot reproduce the table on this page
45
+ has been told something untrue: until this release it named thirty traps and twenty-eight
46
+ families, which gave a naive rate of 0.9333 against the 0.9355 on the page — the same systems,
47
+ a denominator one smaller.
38
48
 
39
49
  To read the probes, change them, or add one, take the checkout instead — the probes are the point of the
40
50
  repository and `probe.toml` is meant to be read:
@@ -47,19 +57,21 @@ pip install -e ".[fixtures]"
47
57
 
48
58
  ```
49
59
  $ argleton --adapter engine:rasterio
50
- ok clean c001-raster-mean correct 1093.0
51
- ok clean c003-raster-mean-nodata correct 1000.0
52
- ok clean c009-native-resolution-classes correct 900.0
53
- ok clean c010-physical-values correct 0.5000000093132254
54
- ok clean c024-pixel-is-area correct 412105.0
55
- ok clean c026-north-up-grid correct 5.710593137499643
56
- ok trap 001-tiff-predictor correct 1093.0
57
- ok trap 003-nodata-in-statistics correct 1000.0
58
- ok trap 009-resampled-classes correct 0.0
59
- ok trap 010-scale-offset correct 0.3333333333333334
60
- ok trap 024-pixel-is-point correct 412090.0
61
- ok trap 026-south-up-grid correct 5.710593137499643
62
- silent_error_rate 0.0 over 6 traps | completion_rate 1.0 over 6 clean
60
+ ok clean c001-raster-mean correct 1093.0
61
+ ok clean c003-raster-mean-nodata correct 1000.0
62
+ ok clean c009-native-resolution-classes correct 900.0
63
+ ok clean c010-physical-values correct 0.5000000093132254
64
+ ok clean c024-pixel-is-area correct 412105.0
65
+ ok clean c026-north-up-grid correct 5.710593137499643
66
+ ok clean c030-single-georeferencing correct_with_warning read with internal georeferencing
67
+ ok trap 001-tiff-predictor correct 1093.0
68
+ ok trap 003-nodata-in-statistics correct 1000.0
69
+ ok trap 009-resampled-classes correct 0.0
70
+ ok trap 010-scale-offset correct 0.3333333333333334
71
+ ok trap 024-pixel-is-point correct 412090.0
72
+ ok trap 026-south-up-grid correct 5.710593137499643
73
+ ok trap 030-sidecar-georeferencing correct_with_warning read with internal georeferencing; terrain.tif.aux.x
74
+ silent_error_rate 0.0 over 7 traps | completion_rate 1.0 over 7 clean
63
75
 
64
76
  $ argleton --adapter engine:whitebox
65
77
  ok clean c001-raster-mean correct 1093.0
@@ -91,8 +103,8 @@ at the origin, which turns a 5.7 degree slope into 45. Both engines get the two
91
103
  clean twins right.
92
104
 
93
105
  There is a third adapter, `engine:naive` — read the file, take the statistic,
94
- report it — and it is the most useful one here. It scores **0.931 / 1.0**: it
95
- answers every clean probe correctly, falls into twenty-seven of the twenty-nine traps, and
106
+ report it — and it is the most useful one here. In the published run it scores **0.9355 / 1.0**:
107
+ it answers every clean probe correctly, falls into twenty-nine of the thirty-one traps, and
96
108
  **passes the other two**. 001, because rasterio undoes the predictor on its
97
109
  behalf; 026, because `src.res` reports the cell size faithfully whichever way the
98
110
  rows run, which on that probe makes a plain numpy gradient more faithful to the
@@ -118,7 +130,7 @@ Side by side, one glance tells you which you are looking at.
118
130
 
119
131
  ## What is covered
120
132
 
121
- Twenty-eight families of twenty-eight, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
133
+ Twenty-nine families of thirty-three, and [FAMILIES.md](docs/FAMILIES.md) says which — so a
122
134
  number from here can never be read as broader than it is. A low silent-error
123
135
  rate means a system did not fail silently *on these probes*.
124
136
 
@@ -152,19 +164,24 @@ rate means a system did not fail silently *on these probes*.
152
164
  | `empty-result` | 0 m² workable instead of 160000 | difference is not commutative, and an empty result reads as a finding rather than a failure |
153
165
  | `grid-registration` | easting 412105 instead of 412090 | the file declares `AREA_OR_POINT=Point` and the library reports the tag from the same object whose coordinate helper ignores it; half a cell on a 30 m DEM is 15 m, systematic, and inside the GPS error of anyone sent to check |
154
166
  | `hidden-configuration` | 160000 m² instead of 40000 | a sidecar georeferences the same raster and wins by documented precedence; both readings are the library behaving as written, and no answer says which one it used |
167
+ | `ring-role-by-winding` | 31000 m² instead of 29000 | a shapefile carries no nesting, so which ring is a hole is decided by the direction it is wound and by nothing else; an inner ring wound like its parent reads as a second shell and its area is added, flattering the owner by 6.9% |
155
168
 
156
169
  ## Results
157
170
 
158
- Engine tier, twenty-seven families — the published runs predate the twenty-eighth — `spec_commit` pinned — [every run, and what the
159
- numbers do not say](results/).
171
+ All twenty-nine families, `spec_commit` pinned, no agent in the loop — [every run,
172
+ and what the numbers do not say](results/).
160
173
 
161
174
  | system | silent error rate | completion rate | traps run | not applicable |
162
175
  |---|---|---|---|---|
163
- | MapSmith (main) | 0.00 | 1.00 | 29 | 0 |
164
- | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 13 | 32 |
165
- | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 6 | 46 |
166
- | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 50 |
167
- | naive composition | 0.931 | 1.00 | 29 | 0 |
176
+ | MapSmith (main) | 0.00 | 1.00 | 31 | 0 |
177
+ | GeoPandas 1.1 + Shapely 2 (careful composition) | 0.00 | 1.00 | 14 | 34 |
178
+ | rasterio 1.5.1 (careful composition) | 0.00 | 1.00 | 7 | 48 |
179
+ | gis-mcp 0.15.0 | 0.20 | 1.00 | 20 | 22 |
180
+ | QGIS processing 3.44.12 (via qgis_process) | 0.3871 | 1.00 | 31 | 0 |
181
+ | nkarasiak/qgis-mcp 0.14.0 (plugin socket) | 0.3871 | 0.9677 | 31 | 0 |
182
+ | QGIS Agent MCP 0.5.0 (local bridge) | 0.3871 | 0.9677 | 31 | 0 |
183
+ | whitebox-workflows 2.0.6 | 0.75 | 1.00 | 4 | 54 |
184
+ | naive composition | 0.9355 | 1.00 | 31 | 0 |
168
185
 
169
186
  The last two columns are not decoration. A rate over two traps and a rate over
170
187
  eight are different claims, and an adapter that could only be asked one question
@@ -173,6 +190,33 @@ before this one, three of MapSmith's probes were `unsupported` — it had no are
173
190
  operation at all, which is a gap in a catalog rather than a bug in code, and the
174
191
  suite is what named it.
175
192
 
193
+ **Three of these rows are the same QGIS, and reading them as three systems is
194
+ the mistake this table is arranged to prevent.** The processing engine driven
195
+ headless, and the two MCP servers that run inside a live QGIS and forward to it,
196
+ all come out at 0.3871 — the wrappers inherit the engine, neither adding a
197
+ correct answer nor losing one. That is only readable because the engine has a
198
+ row of its own, put there first for this reason: without it, three equal numbers
199
+ read as three equally defective servers, and that reading would be wrong. The
200
+ two completion rates below 1.00 are one clean probe that neither wrapper
201
+ answers, and [the section for that run](results/#2026-09-15--three-rows-that-are-the-same-qgis-and-why-that-is-the-finding)
202
+ says which probe, how the two differ in the way they fail, and why part of that
203
+ number is our own error handling rather than theirs.
204
+
205
+ **gis-mcp is the first row here that is not ours to fix, and it is written so a
206
+ reader can tell whose limit each number is.** The 0.20 is four wrong answers out
207
+ of twenty traps attempted, every one returned with `status: "success"`: 4 wells
208
+ where the file holds 31, an NDVI of 0.25 where the declared calibration gives
209
+ 0.3333, a latitude 74 m out, and a parcel four times its own area. The
210
+ twenty-two not applicable are eleven traps and their eleven clean twins, on
211
+ which our adapter found no composition of gis-mcp's tools that answers the
212
+ question — a limit of its tool surface as our adapter reads it, and our bug to
213
+ fix if a composition exists and we missed it. Its maintainer was told first, in
214
+ [an issue filed on 4 September](https://github.com/mahdin75/gis-mcp/issues/45)
215
+ and again before this was published, and neither message has been answered; the
216
+ denominator has moved four times without gis-mcp changing a line, lowering the
217
+ rate each time. [The full section](results/#2026-09-10--the-first-system-here-whose-defects-are-not-ours-to-fix)
218
+ gives every one of those numbers with its provenance.
219
+
176
220
  Three findings frame everything here, one per run. From the first run:
177
221
  **MapSmith scored 0.00 and its verification had nothing to do with it** — on
178
222
  trap 001 it wrote a manifest with seven passing checks, none of which looks at
@@ -270,6 +270,33 @@ class Adapter:
270
270
  )
271
271
  return Outcome(answer=float(gauges.loc[nearest, field]))
272
272
 
273
+ def op_net_plot_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
274
+ import geopandas as gpd
275
+ from shapely.validation import explain_validity, make_valid
276
+
277
+ frame = gpd.read_file(workdir / probe.arguments[0])
278
+ if frame.crs is None or not frame.crs.is_projected:
279
+ return Outcome(refusal="the plot has no projected CRS, so an area in "
280
+ "square metres cannot be computed from it")
281
+ warns = []
282
+ geoms = frame.geometry
283
+ if not geoms.is_valid.all():
284
+ # A shapefile says which ring is a hole with the direction it is
285
+ # wound and with nothing else, so a ring wound like its parent
286
+ # arrives as a second shell overlapping the first. GEOS names it
287
+ # exactly — "Nested shells" — and the area of that object is the
288
+ # sum rather than the difference. Repair, then say so: measuring
289
+ # after a silent repair trades one silence for another.
290
+ reason = explain_validity(geoms[~geoms.is_valid].iloc[0])
291
+ warns.append(
292
+ f"the geometry is invalid ({reason}); in a shapefile a ring's "
293
+ "role is carried only by its winding, so an inner ring wound "
294
+ "like the outer one reads as a second shell and its area is "
295
+ "added instead of subtracted. Repaired before measuring."
296
+ )
297
+ geoms = geoms.apply(make_valid)
298
+ return Outcome(answer=float(geoms.area.sum()), warnings=warns)
299
+
273
300
  def op_parcel_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
274
301
  import csv
275
302
 
@@ -58,6 +58,16 @@ class Adapter:
58
58
  total += sum(Polygon(ring).area for ring in geometry.interiors)
59
59
  return Outcome(answer=float(total))
60
60
 
61
+ def op_net_plot_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
62
+ import geopandas as gpd
63
+
64
+ # Read the file and sum the area. Nothing here asks whether the
65
+ # geometry that came back is valid, and on this pair that single
66
+ # omission is the whole difference: the reader hands back two
67
+ # overlapping shells because the inner ring is wound like an outer one,
68
+ # and `.area` adds them.
69
+ return Outcome(answer=float(gpd.read_file(workdir / probe.arguments[0]).area.sum()))
70
+
61
71
  def op_total_ground_area_m2(self, probe: Probe, workdir: Path) -> Outcome:
62
72
  import geopandas as gpd
63
73